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
9 mlogger
= terminal
.MultiLogger(__name__
)
10 logger
= logging
.getLogger(__name__
)
13 device_list_template
= """
14 * {path: <25} {size: <10} {state}"""
17 def device_formatter(devices
):
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')
28 # Scenario filtering/detection
29 def bluestore_single_type(device_facts
):
31 Detect devices that are just HDDs or solid state so that a 1:1
32 device-to-osd provisioning can be done
34 types
= [device
.sys_api
['rotational'] for device
in device_facts
]
35 if len(set(types
)) == 1:
36 return strategies
.bluestore
.SingleType
39 def bluestore_mixed_type(device_facts
):
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.
44 types
= [device
.sys_api
['rotational'] for device
in device_facts
]
45 if len(set(types
)) > 1:
46 return strategies
.bluestore
.MixedType
49 def filestore_single_type(device_facts
):
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
54 types
= [device
.sys_api
['rotational'] for device
in device_facts
]
55 if len(set(types
)) == 1:
56 return strategies
.filestore
.SingleType
59 def filestore_mixed_type(device_facts
):
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.
64 types
= [device
.sys_api
['rotational'] for device
in device_facts
]
65 if len(set(types
)) > 1:
66 return strategies
.filestore
.MixedType
69 def get_strategy(args
, devices
):
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
75 * Single device type on Bluestore
76 * Mixed device types on Bluestore
77 * Single device type on Filestore
78 * Mixed device types on Filestore
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
84 bluestore_strategies
= [bluestore_mixed_type
, bluestore_single_type
]
85 filestore_strategies
= [filestore_mixed_type
, filestore_single_type
]
87 strategies
= bluestore_strategies
89 strategies
= filestore_strategies
91 for strategy
in strategies
:
92 backend
= strategy(devices
)
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 filtered_devices
= {}
103 for device
in used_devices
:
104 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"),
112 filtered_devices
[last_device
.abspath
] = {"reasons": [reason
]}
113 logger
.info(reason
+ ": %s" % last_device
.abspath
)
116 return unused_devices
, filtered_devices
121 help = 'Automatically size devices for multi-OSD provisioning with minimal interaction'
124 Automatically size devices ready for OSD provisioning based on default strategies.
131 ceph-volume lvm batch [DEVICE...]
133 Optional reporting on possible outcomes is enabled with --report
135 ceph-volume lvm batch --report [DEVICE...]
138 def __init__(self
, argv
):
139 parser
= argparse
.ArgumentParser(
140 prog
='ceph-volume lvm batch',
141 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
142 description
=self
.print_help(),
149 type=arg_validators
.ValidDevice(),
151 help='Devices to provision OSDs',
156 type=arg_validators
.ValidDevice(),
158 help='Devices to provision OSDs db volumes',
163 type=arg_validators
.ValidDevice(),
165 help='Devices to provision OSDs wal volumes',
170 type=arg_validators
.ValidDevice(),
172 help='Devices to provision OSDs journal volumes',
177 help=('deploy standalone OSDs if rotational and non-rotational drives '
178 'are passed in DEVICES'),
183 help='bluestore objectstore (default)',
188 help='filestore objectstore',
193 help='Autodetect the objectstore by inspecting the OSD',
198 help='Avoid prompting for confirmation when provisioning',
202 help='output format, defaults to "pretty"',
204 choices
=['json', 'pretty'],
209 help='Enable device encryption via dm-crypt',
212 '--crush-device-class',
213 dest
='crush_device_class',
214 help='Crush device class to assign this OSD to',
220 help='Skip creating and enabling systemd units and starting OSD services',
226 help='Provision more than 1 (the default) OSD per device',
231 help='Set (or override) the "bluestore_block_db_size" value, in bytes'
236 help='Set (or override) the "bluestore_block_wal_size" value, in bytes'
241 help='Override the "osd_journal_size" value, in megabytes'
246 help='Only prepare all OSDs, do not activate',
252 help='Reuse existing OSD ids',
254 self
.args
= parser
.parse_args(argv
)
256 for dev_list
in ['', 'db_', 'wal_', 'journal_']:
257 setattr(self
, '{}usable'.format(dev_list
), [])
259 def get_devices(self
):
260 # remove devices with partitions
261 devices
= [(device
, details
) for device
, details
in
262 disk
.get_devices().items() if details
.get('partitions') == {}]
263 size_sort
= lambda x
: (x
[0], x
[1]['size'])
264 return device_formatter(sorted(devices
, key
=size_sort
))
266 def print_help(self
):
267 return self
._help
.format(
268 detected_devices
=self
.get_devices(),
272 if self
.args
.format
== 'pretty':
273 self
.strategy
.report_pretty(self
.filtered_devices
)
274 elif self
.args
.format
== 'json':
275 self
.strategy
.report_json(self
.filtered_devices
)
277 raise RuntimeError('report format must be "pretty" or "json"')
280 if not self
.args
.yes
:
281 self
.strategy
.report_pretty(self
.filtered_devices
)
282 terminal
.info('The above OSDs would be created if the operation continues')
283 if not prompt_bool('do you want to proceed? (yes/no)'):
284 devices
= ','.join([device
.abspath
for device
in self
.args
.devices
])
285 terminal
.error('aborting OSD provisioning for %s' % devices
)
288 self
.strategy
.execute()
290 def _get_strategy(self
):
291 strategy
= get_strategy(self
.args
, self
.args
.devices
)
292 unused_devices
, self
.filtered_devices
= filter_devices(self
.args
)
293 if not unused_devices
and not self
.args
.format
== 'json':
294 # report nothing changed
295 mlogger
.info("All devices are already used by ceph. No OSDs will be created.")
298 new_strategy
= get_strategy(self
.args
, unused_devices
)
299 if new_strategy
and strategy
!= new_strategy
:
300 mlogger
.error("Aborting because strategy changed from %s to %s after filtering" % (strategy
.type(), new_strategy
.type()))
303 self
.strategy
= strategy
.with_auto_devices(self
.args
, unused_devices
)
305 @decorators.needs_root
307 if not self
.args
.devices
:
308 return self
.parser
.print_help()
310 # Default to bluestore here since defaulting it in add_argument may
311 # cause both to be True
312 if not self
.args
.bluestore
and not self
.args
.filestore
:
313 self
.args
.bluestore
= True
315 if (self
.args
.no_auto
or self
.args
.db_devices
or
316 self
.args
.journal_devices
or
317 self
.args
.wal_devices
):
318 self
._get
_explicit
_strategy
()
327 def _get_explicit_strategy(self
):
328 # TODO assert that none of the device lists overlap?
329 self
._filter
_devices
()
330 if self
.args
.bluestore
:
331 if self
.db_usable
or self
.wal_usable
:
332 self
.strategy
= strategies
.bluestore
.MixedType(
338 self
.strategy
= strategies
.bluestore
.SingleType(
342 if self
.journal_usable
:
343 self
.strategy
= strategies
.filestore
.MixedType(
348 self
.strategy
= strategies
.filestore
.SingleType(
353 def _filter_devices(self
):
354 # filter devices by their available property.
355 # TODO: Some devices are rejected in the argparser already. maybe it
356 # makes sense to unifiy this
357 used_reason
= {"reasons": ["Used by ceph as a data device already"]}
358 self
.filtered_devices
= {}
359 for dev_list
in ['', 'db_', 'wal_', 'journal_']:
360 dev_list_prop
= '{}devices'.format(dev_list
)
361 if hasattr(self
.args
, dev_list_prop
):
362 usable_dev_list_prop
= '{}usable'.format(dev_list
)
363 usable
= [d
for d
in getattr(self
.args
, dev_list_prop
) if d
.available
]
364 setattr(self
, usable_dev_list_prop
, usable
)
365 self
.filtered_devices
.update({d
: used_reason
for d
in
366 getattr(self
.args
, dev_list_prop
)