]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/devices/lvm/batch.py
bump version to 12.2.12-pve1
[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):
1adf2230 142 # remove devices with partitions
f64942e4
AA
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))
1adf2230
AA
147
148 def print_help(self):
149 return self._help.format(
150 detected_devices=self.get_devices(),
151 )
152
153 def report(self, args):
91327a77 154 strategy = self._get_strategy(args)
1adf2230
AA
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):
91327a77 163 strategy = self._get_strategy(args)
1adf2230
AA
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)'):
91327a77
AA
168 devices = ','.join([device.abspath for device in args.devices])
169 terminal.error('aborting OSD provisioning for %s' % devices)
1adf2230
AA
170 raise SystemExit(0)
171
172 strategy.execute()
173
91327a77
AA
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
1adf2230
AA
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 )
91327a77
AA
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 )
1adf2230
AA
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)