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 args
.filtered_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"),
112 args
.filtered_devices
[last_device
.abspath
] = {"reasons": [reason
]}
113 logger
.info(reason
+ ": %s" % last_device
.abspath
)
116 return unused_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
):
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
))
148 def print_help(self
):
149 return self
._help
.format(
150 detected_devices
=self
.get_devices(),
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()
160 raise RuntimeError('report format must be "pretty" or "json"')
162 def execute(self
, args
):
163 strategy
= self
._get
_strategy
(args
)
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
)
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.")
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()))
187 return strategy(unused_devices
, args
)
189 @decorators.needs_root
191 parser
= argparse
.ArgumentParser(
192 prog
='ceph-volume lvm batch',
193 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
194 description
=self
.print_help(),
201 type=arg_validators
.ValidDevice(),
203 help='Devices to provision OSDs',
208 help='bluestore objectstore (default)',
213 help='filestore objectstore',
218 help='Autodetect the objectstore by inspecting the OSD',
223 help='Avoid prompting for confirmation when provisioning',
227 help='output format, defaults to "pretty"',
229 choices
=['json', 'pretty'],
234 help='Enable device encryption via dm-crypt',
237 '--crush-device-class',
238 dest
='crush_device_class',
239 help='Crush device class to assign this OSD to',
245 help='Skip creating and enabling systemd units and starting OSD services',
251 help='Provision more than 1 (the default) OSD per device',
256 help='Set (or override) the "bluestore_block_db_size" value, in bytes'
261 help='Override the "osd_journal_size" value, in megabytes'
266 help='Only prepare all OSDs, do not activate',
268 args
= parser
.parse_args(self
.argv
)
271 return parser
.print_help()
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