]> git.proxmox.com Git - ceph.git/blame - 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
CommitLineData
1adf2230 1import argparse
91327a77 2import logging
1adf2230
AA
3from textwrap import dedent
4from ceph_volume import terminal, decorators
5from ceph_volume.util import disk, prompt_bool
6from ceph_volume.util import arg_validators
7from . import strategies
8
91327a77
AA
9mlogger = terminal.MultiLogger(__name__)
10logger = logging.getLogger(__name__)
11
1adf2230
AA
12
13device_list_template = """
14 * {path: <25} {size: <10} {state}"""
15
16
17def 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
29def 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
39def 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
49def 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
59def 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
91327a77 69def get_strategy(args, devices):
1adf2230
AA
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:
91327a77 92 backend = strategy(devices)
1adf2230 93 if backend:
91327a77
AA
94 return backend
95
96
97def 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
1adf2230
AA
117
118
119class 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):
91327a77 157 strategy = self._get_strategy(args)
1adf2230
AA
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):
91327a77 166 strategy = self._get_strategy(args)
1adf2230
AA
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)'):
91327a77
AA
171 devices = ','.join([device.abspath for device in args.devices])
172 terminal.error('aborting OSD provisioning for %s' % devices)
1adf2230
AA
173 raise SystemExit(0)
174
175 strategy.execute()
176
91327a77
AA
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
1adf2230
AA
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 )
91327a77
AA
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 )
1adf2230
AA
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)