]>
Commit | Line | Data |
---|---|---|
d2e6a577 FG |
1 | from __future__ import print_function |
2 | import json | |
b32b8144 | 3 | import logging |
d2e6a577 FG |
4 | from textwrap import dedent |
5 | from ceph_volume.util import prepare as prepare_utils | |
b32b8144 | 6 | from ceph_volume.util import encryption as encryption_utils |
181888fb | 7 | from ceph_volume.util import system, disk |
3a9019d9 | 8 | from ceph_volume.util.arg_validators import exclude_group_options |
181888fb | 9 | from ceph_volume import conf, decorators, terminal |
3efd9988 | 10 | from ceph_volume.api import lvm as api |
b32b8144 | 11 | from .common import prepare_parser, rollback_osd |
d2e6a577 FG |
12 | |
13 | ||
b32b8144 FG |
14 | logger = logging.getLogger(__name__) |
15 | ||
16 | ||
17 | def prepare_dmcrypt(key, device, device_type, tags): | |
18 | """ | |
19 | Helper for devices that are encrypted. The operations needed for | |
05a536ef | 20 | block, db, wal devices are all the same |
b32b8144 FG |
21 | """ |
22 | if not device: | |
23 | return '' | |
24 | tag_name = 'ceph.%s_uuid' % device_type | |
25 | uuid = tags[tag_name] | |
26 | # format data device | |
27 | encryption_utils.luks_format( | |
28 | key, | |
29 | device | |
30 | ) | |
31 | encryption_utils.luks_open( | |
32 | key, | |
33 | device, | |
34 | uuid | |
35 | ) | |
36 | ||
37 | return '/dev/mapper/%s' % uuid | |
38 | ||
39 | ||
b32b8144 | 40 | def prepare_bluestore(block, wal, db, secrets, tags, osd_id, fsid): |
3efd9988 FG |
41 | """ |
42 | :param block: The name of the logical volume for the bluestore data | |
43 | :param wal: a regular/plain disk or logical volume, to be used for block.wal | |
44 | :param db: a regular/plain disk or logical volume, to be used for block.db | |
45 | :param secrets: A dict with the secrets needed to create the osd (e.g. cephx) | |
46 | :param id_: The OSD id | |
47 | :param fsid: The OSD fsid, also known as the OSD UUID | |
48 | """ | |
49 | cephx_secret = secrets.get('cephx_secret', prepare_utils.create_key()) | |
b32b8144 FG |
50 | # encryption-only operations |
51 | if secrets.get('dmcrypt_key'): | |
52 | # If encrypted, there is no need to create the lockbox keyring file because | |
53 | # bluestore re-creates the files and does not have support for other files | |
54 | # like the custom lockbox one. This will need to be done on activation. | |
55 | # format and open ('decrypt' devices) and re-assign the device and journal | |
56 | # variables so that the rest of the process can use the mapper paths | |
57 | key = secrets['dmcrypt_key'] | |
58 | block = prepare_dmcrypt(key, block, 'block', tags) | |
59 | wal = prepare_dmcrypt(key, wal, 'wal', tags) | |
60 | db = prepare_dmcrypt(key, db, 'db', tags) | |
3efd9988 | 61 | |
3efd9988 FG |
62 | # create the directory |
63 | prepare_utils.create_osd_path(osd_id, tmpfs=True) | |
64 | # symlink the block | |
65 | prepare_utils.link_block(block, osd_id) | |
66 | # get the latest monmap | |
67 | prepare_utils.get_monmap(osd_id) | |
68 | # write the OSD keyring if it doesn't exist already | |
69 | prepare_utils.write_keyring(osd_id, cephx_secret) | |
70 | # prepare the osd filesystem | |
71 | prepare_utils.osd_mkfs_bluestore( | |
72 | osd_id, fsid, | |
73 | keyring=cephx_secret, | |
74 | wal=wal, | |
75 | db=db | |
76 | ) | |
d2e6a577 FG |
77 | |
78 | ||
79 | class Prepare(object): | |
80 | ||
81 | help = 'Format an LVM device and associate it with an OSD' | |
82 | ||
83 | def __init__(self, argv): | |
84 | self.argv = argv | |
b32b8144 | 85 | self.osd_id = None |
d2e6a577 | 86 | |
3efd9988 | 87 | def get_ptuuid(self, argument): |
181888fb FG |
88 | uuid = disk.get_partuuid(argument) |
89 | if not uuid: | |
90 | terminal.error('blkid could not detect a PARTUUID for device: %s' % argument) | |
3efd9988 | 91 | raise RuntimeError('unable to use device') |
181888fb FG |
92 | return uuid |
93 | ||
f91f0fd5 | 94 | def setup_device(self, device_type, device_name, tags, size, slots): |
3efd9988 FG |
95 | """ |
96 | Check if ``device`` is an lv, if so, set the tags, making sure to | |
97 | update the tags with the lv_uuid and lv_path which the incoming tags | |
98 | will not have. | |
99 | ||
100 | If the device is not a logical volume, then retrieve the partition UUID | |
101 | by querying ``blkid`` | |
102 | """ | |
103 | if device_name is None: | |
104 | return '', '', tags | |
105 | tags['ceph.type'] = device_type | |
94b18763 | 106 | tags['ceph.vdo'] = api.is_vdo(device_name) |
f6b5b4d7 TL |
107 | |
108 | try: | |
109 | vg_name, lv_name = device_name.split('/') | |
a4b75251 TL |
110 | lv = api.get_single_lv(filters={'lv_name': lv_name, |
111 | 'vg_name': vg_name}) | |
f6b5b4d7 TL |
112 | except ValueError: |
113 | lv = None | |
114 | ||
3efd9988 | 115 | if lv: |
f91f0fd5 | 116 | lv_uuid = lv.lv_uuid |
3efd9988 | 117 | path = lv.lv_path |
f91f0fd5 | 118 | tags['ceph.%s_uuid' % device_type] = lv_uuid |
3efd9988 FG |
119 | tags['ceph.%s_device' % device_type] = path |
120 | lv.set_tags(tags) | |
92f5a8d4 TL |
121 | elif disk.is_device(device_name): |
122 | # We got a disk, create an lv | |
123 | lv_type = "osd-{}".format(device_type) | |
f91f0fd5 | 124 | name_uuid = system.generate_uuid() |
92f5a8d4 TL |
125 | kwargs = { |
126 | 'device': device_name, | |
127 | 'tags': tags, | |
f91f0fd5 | 128 | 'slots': slots |
92f5a8d4 | 129 | } |
f91f0fd5 TL |
130 | #TODO use get_block_db_size and co here to get configured size in |
131 | #conf file | |
92f5a8d4 | 132 | if size != 0: |
f91f0fd5 | 133 | kwargs['size'] = size |
92f5a8d4 TL |
134 | lv = api.create_lv( |
135 | lv_type, | |
f91f0fd5 | 136 | name_uuid, |
92f5a8d4 TL |
137 | **kwargs) |
138 | path = lv.lv_path | |
139 | tags['ceph.{}_device'.format(device_type)] = path | |
f91f0fd5 TL |
140 | tags['ceph.{}_uuid'.format(device_type)] = lv.lv_uuid |
141 | lv_uuid = lv.lv_uuid | |
92f5a8d4 | 142 | lv.set_tags(tags) |
3efd9988 FG |
143 | else: |
144 | # otherwise assume this is a regular disk partition | |
f91f0fd5 | 145 | name_uuid = self.get_ptuuid(device_name) |
3efd9988 | 146 | path = device_name |
f91f0fd5 | 147 | tags['ceph.%s_uuid' % device_type] = name_uuid |
3efd9988 | 148 | tags['ceph.%s_device' % device_type] = path |
f91f0fd5 TL |
149 | lv_uuid = name_uuid |
150 | return path, lv_uuid, tags | |
3efd9988 | 151 | |
92f5a8d4 | 152 | def prepare_data_device(self, device_type, osd_uuid): |
3efd9988 FG |
153 | """ |
154 | Check if ``arg`` is a device or partition to create an LV out of it | |
155 | with a distinct volume group name, assigning LV tags on it and | |
156 | ultimately, returning the logical volume object. Failing to detect | |
157 | a device or partition will result in error. | |
158 | ||
159 | :param arg: The value of ``--data`` when parsing args | |
05a536ef | 160 | :param device_type: Usually ``block`` |
92f5a8d4 | 161 | :param osd_uuid: The OSD uuid |
3efd9988 | 162 | """ |
92f5a8d4 TL |
163 | device = self.args.data |
164 | if disk.is_partition(device) or disk.is_device(device): | |
3efd9988 | 165 | # we must create a vg, and then a single lv |
92f5a8d4 TL |
166 | lv_name_prefix = "osd-{}".format(device_type) |
167 | kwargs = {'device': device, | |
168 | 'tags': {'ceph.type': device_type}, | |
f91f0fd5 | 169 | 'slots': self.args.data_slots, |
92f5a8d4 TL |
170 | } |
171 | logger.debug('data device size: {}'.format(self.args.data_size)) | |
172 | if self.args.data_size != 0: | |
f91f0fd5 | 173 | kwargs['size'] = self.args.data_size |
3efd9988 | 174 | return api.create_lv( |
92f5a8d4 TL |
175 | lv_name_prefix, |
176 | osd_uuid, | |
177 | **kwargs) | |
3efd9988 FG |
178 | else: |
179 | error = [ | |
92f5a8d4 | 180 | 'Cannot use device ({}).'.format(device), |
b32b8144 | 181 | 'A vg/lv path or an existing device is needed'] |
3efd9988 FG |
182 | raise RuntimeError(' '.join(error)) |
183 | ||
92f5a8d4 | 184 | raise RuntimeError('no data logical volume found with: {}'.format(device)) |
3efd9988 | 185 | |
91327a77 | 186 | def safe_prepare(self, args=None): |
b32b8144 FG |
187 | """ |
188 | An intermediate step between `main()` and `prepare()` so that we can | |
189 | capture the `self.osd_id` in case we need to rollback | |
91327a77 AA |
190 | |
191 | :param args: Injected args, usually from `lvm create` which compounds | |
192 | both `prepare` and `create` | |
b32b8144 | 193 | """ |
91327a77 AA |
194 | if args is not None: |
195 | self.args = args | |
f6b5b4d7 TL |
196 | |
197 | try: | |
198 | vgname, lvname = self.args.data.split('/') | |
a4b75251 TL |
199 | lv = api.get_single_lv(filters={'lv_name': lvname, |
200 | 'vg_name': vgname}) | |
f6b5b4d7 TL |
201 | except ValueError: |
202 | lv = None | |
203 | ||
204 | if api.is_ceph_device(lv): | |
92f5a8d4 TL |
205 | logger.info("device {} is already used".format(self.args.data)) |
206 | raise RuntimeError("skipping {}, it is already prepared".format(self.args.data)) | |
b32b8144 | 207 | try: |
91327a77 | 208 | self.prepare() |
b32b8144 | 209 | except Exception: |
28e407b8 | 210 | logger.exception('lvm prepare was unable to complete') |
b32b8144 | 211 | logger.info('will rollback OSD ID creation') |
91327a77 | 212 | rollback_osd(self.args, self.osd_id) |
b32b8144 | 213 | raise |
91327a77 AA |
214 | terminal.success("ceph-volume lvm prepare successful for: %s" % self.args.data) |
215 | ||
216 | def get_cluster_fsid(self): | |
217 | """ | |
218 | Allows using --cluster-fsid as an argument, but can fallback to reading | |
219 | from ceph.conf if that is unset (the default behavior). | |
220 | """ | |
221 | if self.args.cluster_fsid: | |
222 | return self.args.cluster_fsid | |
223 | else: | |
224 | return conf.ceph.get('global', 'fsid') | |
b32b8144 | 225 | |
d2e6a577 | 226 | @decorators.needs_root |
91327a77 | 227 | def prepare(self): |
d2e6a577 FG |
228 | # FIXME we don't allow re-using a keyring, we always generate one for the |
229 | # OSD, this needs to be fixed. This could either be a file (!) or a string | |
230 | # (!!) or some flags that we would need to compound into a dict so that we | |
231 | # can convert to JSON (!!!) | |
232 | secrets = {'cephx_secret': prepare_utils.create_key()} | |
b32b8144 | 233 | cephx_lockbox_secret = '' |
91327a77 | 234 | encrypted = 1 if self.args.dmcrypt else 0 |
b32b8144 FG |
235 | cephx_lockbox_secret = '' if not encrypted else prepare_utils.create_key() |
236 | ||
237 | if encrypted: | |
238 | secrets['dmcrypt_key'] = encryption_utils.create_dmcrypt_key() | |
239 | secrets['cephx_lockbox_secret'] = cephx_lockbox_secret | |
d2e6a577 | 240 | |
91327a77 AA |
241 | cluster_fsid = self.get_cluster_fsid() |
242 | ||
243 | osd_fsid = self.args.osd_fsid or system.generate_uuid() | |
244 | crush_device_class = self.args.crush_device_class | |
b32b8144 FG |
245 | if crush_device_class: |
246 | secrets['crush_device_class'] = crush_device_class | |
247 | # reuse a given ID if it exists, otherwise create a new ID | |
91327a77 | 248 | self.osd_id = prepare_utils.create_id(osd_fsid, json.dumps(secrets), osd_id=self.args.osd_id) |
b32b8144 FG |
249 | tags = { |
250 | 'ceph.osd_fsid': osd_fsid, | |
251 | 'ceph.osd_id': self.osd_id, | |
252 | 'ceph.cluster_fsid': cluster_fsid, | |
253 | 'ceph.cluster_name': conf.cluster, | |
254 | 'ceph.crush_device_class': crush_device_class, | |
e306af50 | 255 | 'ceph.osdspec_affinity': prepare_utils.get_osdspec_affinity() |
b32b8144 | 256 | } |
05a536ef | 257 | if self.args.bluestore: |
f6b5b4d7 TL |
258 | try: |
259 | vg_name, lv_name = self.args.data.split('/') | |
a4b75251 TL |
260 | block_lv = api.get_single_lv(filters={'lv_name': lv_name, |
261 | 'vg_name': vg_name}) | |
f6b5b4d7 TL |
262 | except ValueError: |
263 | block_lv = None | |
264 | ||
3efd9988 | 265 | if not block_lv: |
92f5a8d4 | 266 | block_lv = self.prepare_data_device('block', osd_fsid) |
3efd9988 | 267 | |
b32b8144 FG |
268 | tags['ceph.block_device'] = block_lv.lv_path |
269 | tags['ceph.block_uuid'] = block_lv.lv_uuid | |
270 | tags['ceph.cephx_lockbox_secret'] = cephx_lockbox_secret | |
271 | tags['ceph.encrypted'] = encrypted | |
94b18763 | 272 | tags['ceph.vdo'] = api.is_vdo(block_lv.lv_path) |
3efd9988 | 273 | |
92f5a8d4 | 274 | wal_device, wal_uuid, tags = self.setup_device( |
f91f0fd5 TL |
275 | 'wal', |
276 | self.args.block_wal, | |
277 | tags, | |
278 | self.args.block_wal_size, | |
279 | self.args.block_wal_slots) | |
92f5a8d4 | 280 | db_device, db_uuid, tags = self.setup_device( |
f91f0fd5 TL |
281 | 'db', |
282 | self.args.block_db, | |
283 | tags, | |
284 | self.args.block_db_size, | |
285 | self.args.block_db_slots) | |
3efd9988 FG |
286 | |
287 | tags['ceph.type'] = 'block' | |
288 | block_lv.set_tags(tags) | |
289 | ||
290 | prepare_bluestore( | |
291 | block_lv.lv_path, | |
292 | wal_device, | |
293 | db_device, | |
294 | secrets, | |
b32b8144 FG |
295 | tags, |
296 | self.osd_id, | |
297 | osd_fsid, | |
3efd9988 | 298 | ) |
d2e6a577 FG |
299 | |
300 | def main(self): | |
301 | sub_command_help = dedent(""" | |
302 | Prepare an OSD by assigning an ID and FSID, registering them with the | |
303 | cluster with an ID and FSID, formatting and mounting the volume, and | |
304 | finally by adding all the metadata to the logical volumes using LVM | |
305 | tags, so that it can later be discovered. | |
306 | ||
307 | Once the OSD is ready, an ad-hoc systemd unit will be enabled so that | |
308 | it can later get activated and the OSD daemon can get started. | |
309 | ||
b32b8144 | 310 | Encryption is supported via dmcrypt and the --dmcrypt flag. |
d2e6a577 | 311 | |
91327a77 | 312 | Existing logical volume (lv): |
3efd9988 | 313 | |
91327a77 | 314 | ceph-volume lvm prepare --data {vg/lv} |
3efd9988 | 315 | |
92f5a8d4 | 316 | Existing block device (a logical volume will be created): |
d2e6a577 | 317 | |
91327a77 | 318 | ceph-volume lvm prepare --data /path/to/device |
d2e6a577 | 319 | |
92f5a8d4 TL |
320 | Optionally, can consume db and wal devices, partitions or logical |
321 | volumes. A device will get a logical volume, partitions and existing | |
322 | logical volumes will be used as is: | |
d2e6a577 | 323 | |
92f5a8d4 | 324 | ceph-volume lvm prepare --data {vg/lv} --block.wal {partition} --block.db {/path/to/device} |
d2e6a577 FG |
325 | """) |
326 | parser = prepare_parser( | |
327 | prog='ceph-volume lvm prepare', | |
328 | description=sub_command_help, | |
329 | ) | |
330 | if len(self.argv) == 0: | |
331 | print(sub_command_help) | |
332 | return | |
05a536ef | 333 | exclude_group_options(parser, argv=self.argv, groups=['bluestore']) |
91327a77 | 334 | self.args = parser.parse_args(self.argv) |
3efd9988 FG |
335 | # Default to bluestore here since defaulting it in add_argument may |
336 | # cause both to be True | |
05a536ef | 337 | if not self.args.bluestore: |
91327a77 | 338 | self.args.bluestore = True |
92f5a8d4 | 339 | self.safe_prepare() |