]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/devices/lvm/batch.py
update sources to 12.2.10
[ceph.git] / ceph / src / ceph-volume / ceph_volume / devices / lvm / batch.py
1 import argparse
2 import logging
3 from textwrap import dedent
4 from ceph_volume import terminal, decorators
5 from ceph_volume.util import disk, prompt_bool
6 from ceph_volume.util import arg_validators
7 from . import strategies
8
9 mlogger = terminal.MultiLogger(__name__)
10 logger = logging.getLogger(__name__)
11
12
13 device_list_template = """
14 * {path: <25} {size: <10} {state}"""
15
16
17 def device_formatter(devices):
18 lines = []
19 for path, details in devices:
20 lines.append(device_list_template.format(
21 path=path, size=details['human_readable_size'],
22 state='solid' if details['rotational'] == '0' else 'rotational')
23 )
24
25 return ''.join(lines)
26
27
28 # Scenario filtering/detection
29 def bluestore_single_type(device_facts):
30 """
31 Detect devices that are just HDDs or solid state so that a 1:1
32 device-to-osd provisioning can be done
33 """
34 types = [device.sys_api['rotational'] for device in device_facts]
35 if len(set(types)) == 1:
36 return strategies.bluestore.SingleType
37
38
39 def bluestore_mixed_type(device_facts):
40 """
41 Detect if devices are HDDs as well as solid state so that block.db can be
42 placed in solid devices while data is kept in the spinning drives.
43 """
44 types = [device.sys_api['rotational'] for device in device_facts]
45 if len(set(types)) > 1:
46 return strategies.bluestore.MixedType
47
48
49 def filestore_single_type(device_facts):
50 """
51 Detect devices that are just HDDs or solid state so that a 1:1
52 device-to-osd provisioning can be done, keeping the journal on the OSD
53 """
54 types = [device.sys_api['rotational'] for device in device_facts]
55 if len(set(types)) == 1:
56 return strategies.filestore.SingleType
57
58
59 def filestore_mixed_type(device_facts):
60 """
61 Detect if devices are HDDs as well as solid state so that the journal can be
62 placed in solid devices while data is kept in the spinning drives.
63 """
64 types = [device.sys_api['rotational'] for device in device_facts]
65 if len(set(types)) > 1:
66 return strategies.filestore.MixedType
67
68
69 def get_strategy(args, devices):
70 """
71 Given a set of devices as input, go through the different detection
72 mechanisms to narrow down on a strategy to use. The strategies are 4 in
73 total:
74
75 * Single device type on Bluestore
76 * Mixed device types on Bluestore
77 * Single device type on Filestore
78 * Mixed device types on Filestore
79
80 When the function matches to a scenario it returns the strategy class. This
81 allows for dynamic loading of the conditions needed for each scenario, with
82 normalized classes
83 """
84 bluestore_strategies = [bluestore_mixed_type, bluestore_single_type]
85 filestore_strategies = [filestore_mixed_type, filestore_single_type]
86 if args.bluestore:
87 strategies = bluestore_strategies
88 else:
89 strategies = filestore_strategies
90
91 for strategy in strategies:
92 backend = strategy(devices)
93 if backend:
94 return backend
95
96
97 def filter_devices(args):
98 unused_devices = [device for device in args.devices if not device.used_by_ceph]
99 # only data devices, journals can be reused
100 used_devices = [device.abspath for device in args.devices if device.used_by_ceph]
101 args.filtered_devices = {}
102 if used_devices:
103 for device in used_devices:
104 args.filtered_devices[device] = {"reasons": ["Used by ceph as a data device already"]}
105 logger.info("Ignoring devices already used by ceph: %s" % ", ".join(used_devices))
106 if len(unused_devices) == 1:
107 last_device = unused_devices[0]
108 if not last_device.rotational and last_device.is_lvm_member:
109 reason = "Used by ceph as a %s already and there are no devices left for data/block" % (
110 last_device.lvs[0].tags.get("ceph.type"),
111 )
112 args.filtered_devices[last_device.abspath] = {"reasons": [reason]}
113 logger.info(reason + ": %s" % last_device.abspath)
114 unused_devices = []
115
116 return unused_devices
117
118
119 class Batch(object):
120
121 help = 'Automatically size devices for multi-OSD provisioning with minimal interaction'
122
123 _help = dedent("""
124 Automatically size devices ready for OSD provisioning based on default strategies.
125
126 Detected devices:
127 {detected_devices}
128
129 Usage:
130
131 ceph-volume lvm batch [DEVICE...]
132
133 Optional reporting on possible outcomes is enabled with --report
134
135 ceph-volume lvm batch --report [DEVICE...]
136 """)
137
138 def __init__(self, argv):
139 self.argv = argv
140
141 def get_devices(self):
142 all_devices = disk.get_devices()
143 # remove devices with partitions
144 # XXX Should be optional when getting device info
145 for device, detail in all_devices.items():
146 if detail.get('partitions') != {}:
147 del all_devices[device]
148 devices = sorted(all_devices.items(), key=lambda x: (x[0], x[1]['size']))
149 return device_formatter(devices)
150
151 def print_help(self):
152 return self._help.format(
153 detected_devices=self.get_devices(),
154 )
155
156 def report(self, args):
157 strategy = self._get_strategy(args)
158 if args.format == 'pretty':
159 strategy.report_pretty()
160 elif args.format == 'json':
161 strategy.report_json()
162 else:
163 raise RuntimeError('report format must be "pretty" or "json"')
164
165 def execute(self, args):
166 strategy = self._get_strategy(args)
167 if not args.yes:
168 strategy.report_pretty()
169 terminal.info('The above OSDs would be created if the operation continues')
170 if not prompt_bool('do you want to proceed? (yes/no)'):
171 devices = ','.join([device.abspath for device in args.devices])
172 terminal.error('aborting OSD provisioning for %s' % devices)
173 raise SystemExit(0)
174
175 strategy.execute()
176
177 def _get_strategy(self, args):
178 strategy = get_strategy(args, args.devices)
179 unused_devices = filter_devices(args)
180 if not unused_devices and not args.format == 'json':
181 # report nothing changed
182 mlogger.info("All devices are already used by ceph. No OSDs will be created.")
183 raise SystemExit(0)
184 else:
185 new_strategy = get_strategy(args, unused_devices)
186 if new_strategy and strategy != new_strategy:
187 mlogger.error("Aborting because strategy changed from %s to %s after filtering" % (strategy.type(), new_strategy.type()))
188 raise SystemExit(1)
189
190 return strategy(unused_devices, args)
191
192 @decorators.needs_root
193 def main(self):
194 parser = argparse.ArgumentParser(
195 prog='ceph-volume lvm batch',
196 formatter_class=argparse.RawDescriptionHelpFormatter,
197 description=self.print_help(),
198 )
199
200 parser.add_argument(
201 'devices',
202 metavar='DEVICES',
203 nargs='*',
204 type=arg_validators.ValidDevice(),
205 default=[],
206 help='Devices to provision OSDs',
207 )
208 parser.add_argument(
209 '--bluestore',
210 action='store_true',
211 help='bluestore objectstore (default)',
212 )
213 parser.add_argument(
214 '--filestore',
215 action='store_true',
216 help='filestore objectstore',
217 )
218 parser.add_argument(
219 '--report',
220 action='store_true',
221 help='Autodetect the objectstore by inspecting the OSD',
222 )
223 parser.add_argument(
224 '--yes',
225 action='store_true',
226 help='Avoid prompting for confirmation when provisioning',
227 )
228 parser.add_argument(
229 '--format',
230 help='output format, defaults to "pretty"',
231 default='pretty',
232 choices=['json', 'pretty'],
233 )
234 parser.add_argument(
235 '--dmcrypt',
236 action='store_true',
237 help='Enable device encryption via dm-crypt',
238 )
239 parser.add_argument(
240 '--crush-device-class',
241 dest='crush_device_class',
242 help='Crush device class to assign this OSD to',
243 )
244 parser.add_argument(
245 '--no-systemd',
246 dest='no_systemd',
247 action='store_true',
248 help='Skip creating and enabling systemd units and starting OSD services',
249 )
250 parser.add_argument(
251 '--osds-per-device',
252 type=int,
253 default=1,
254 help='Provision more than 1 (the default) OSD per device',
255 )
256 parser.add_argument(
257 '--block-db-size',
258 type=int,
259 help='Set (or override) the "bluestore_block_db_size" value, in bytes'
260 )
261 parser.add_argument(
262 '--journal-size',
263 type=int,
264 help='Override the "osd_journal_size" value, in megabytes'
265 )
266 parser.add_argument(
267 '--prepare',
268 action='store_true',
269 help='Only prepare all OSDs, do not activate',
270 )
271 args = parser.parse_args(self.argv)
272
273 if not args.devices:
274 return parser.print_help()
275
276 # Default to bluestore here since defaulting it in add_argument may
277 # cause both to be True
278 if not args.bluestore and not args.filestore:
279 args.bluestore = True
280
281 if args.report:
282 self.report(args)
283 else:
284 self.execute(args)