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