]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/devices/lvm/batch.py
update source to 12.2.11
[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 # remove devices with partitions
143 devices = [(device, details) for device, details in
144 disk.get_devices().items() if details.get('partitions') == {}]
145 size_sort = lambda x: (x[0], x[1]['size'])
146 return device_formatter(sorted(devices, key=size_sort))
147
148 def print_help(self):
149 return self._help.format(
150 detected_devices=self.get_devices(),
151 )
152
153 def report(self, args):
154 strategy = self._get_strategy(args)
155 if args.format == 'pretty':
156 strategy.report_pretty()
157 elif args.format == 'json':
158 strategy.report_json()
159 else:
160 raise RuntimeError('report format must be "pretty" or "json"')
161
162 def execute(self, args):
163 strategy = self._get_strategy(args)
164 if not args.yes:
165 strategy.report_pretty()
166 terminal.info('The above OSDs would be created if the operation continues')
167 if not prompt_bool('do you want to proceed? (yes/no)'):
168 devices = ','.join([device.abspath for device in args.devices])
169 terminal.error('aborting OSD provisioning for %s' % devices)
170 raise SystemExit(0)
171
172 strategy.execute()
173
174 def _get_strategy(self, args):
175 strategy = get_strategy(args, args.devices)
176 unused_devices = filter_devices(args)
177 if not unused_devices and not args.format == 'json':
178 # report nothing changed
179 mlogger.info("All devices are already used by ceph. No OSDs will be created.")
180 raise SystemExit(0)
181 else:
182 new_strategy = get_strategy(args, unused_devices)
183 if new_strategy and strategy != new_strategy:
184 mlogger.error("Aborting because strategy changed from %s to %s after filtering" % (strategy.type(), new_strategy.type()))
185 raise SystemExit(1)
186
187 return strategy(unused_devices, args)
188
189 @decorators.needs_root
190 def main(self):
191 parser = argparse.ArgumentParser(
192 prog='ceph-volume lvm batch',
193 formatter_class=argparse.RawDescriptionHelpFormatter,
194 description=self.print_help(),
195 )
196
197 parser.add_argument(
198 'devices',
199 metavar='DEVICES',
200 nargs='*',
201 type=arg_validators.ValidDevice(),
202 default=[],
203 help='Devices to provision OSDs',
204 )
205 parser.add_argument(
206 '--bluestore',
207 action='store_true',
208 help='bluestore objectstore (default)',
209 )
210 parser.add_argument(
211 '--filestore',
212 action='store_true',
213 help='filestore objectstore',
214 )
215 parser.add_argument(
216 '--report',
217 action='store_true',
218 help='Autodetect the objectstore by inspecting the OSD',
219 )
220 parser.add_argument(
221 '--yes',
222 action='store_true',
223 help='Avoid prompting for confirmation when provisioning',
224 )
225 parser.add_argument(
226 '--format',
227 help='output format, defaults to "pretty"',
228 default='pretty',
229 choices=['json', 'pretty'],
230 )
231 parser.add_argument(
232 '--dmcrypt',
233 action='store_true',
234 help='Enable device encryption via dm-crypt',
235 )
236 parser.add_argument(
237 '--crush-device-class',
238 dest='crush_device_class',
239 help='Crush device class to assign this OSD to',
240 )
241 parser.add_argument(
242 '--no-systemd',
243 dest='no_systemd',
244 action='store_true',
245 help='Skip creating and enabling systemd units and starting OSD services',
246 )
247 parser.add_argument(
248 '--osds-per-device',
249 type=int,
250 default=1,
251 help='Provision more than 1 (the default) OSD per device',
252 )
253 parser.add_argument(
254 '--block-db-size',
255 type=int,
256 help='Set (or override) the "bluestore_block_db_size" value, in bytes'
257 )
258 parser.add_argument(
259 '--journal-size',
260 type=int,
261 help='Override the "osd_journal_size" value, in megabytes'
262 )
263 parser.add_argument(
264 '--prepare',
265 action='store_true',
266 help='Only prepare all OSDs, do not activate',
267 )
268 args = parser.parse_args(self.argv)
269
270 if not args.devices:
271 return parser.print_help()
272
273 # Default to bluestore here since defaulting it in add_argument may
274 # cause both to be True
275 if not args.bluestore and not args.filestore:
276 args.bluestore = True
277
278 if args.report:
279 self.report(args)
280 else:
281 self.execute(args)