]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/devices/lvm/prepare.py
update sources to v12.2.3
[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
FG
8from ceph_volume.util import system, disk
9from ceph_volume import conf, decorators, terminal
3efd9988 10from ceph_volume.api import lvm as api
b32b8144 11from .common import prepare_parser, rollback_osd
d2e6a577
FG
12
13
b32b8144
FG
14logger = logging.getLogger(__name__)
15
16
17def prepare_dmcrypt(key, device, device_type, tags):
18 """
19 Helper for devices that are encrypted. The operations needed for
20 block, db, wal, or data/journal devices are all the same
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
40def prepare_filestore(device, journal, secrets, tags, osd_id, fsid):
d2e6a577 41 """
3efd9988 42 :param device: The name of the logical volume to work with
d2e6a577
FG
43 :param journal: similar to device but can also be a regular/plain disk
44 :param secrets: A dict with the secrets needed to create the osd (e.g. cephx)
45 :param id_: The OSD id
46 :param fsid: The OSD fsid, also known as the OSD UUID
47 """
48 cephx_secret = secrets.get('cephx_secret', prepare_utils.create_key())
d2e6a577 49
b32b8144
FG
50 # encryption-only operations
51 if secrets.get('dmcrypt_key'):
52 # format and open ('decrypt' devices) and re-assign the device and journal
53 # variables so that the rest of the process can use the mapper paths
54 key = secrets['dmcrypt_key']
55 device = prepare_dmcrypt(key, device, 'data', tags)
56 journal = prepare_dmcrypt(key, journal, 'journal', tags)
57
d2e6a577 58 # create the directory
3efd9988 59 prepare_utils.create_osd_path(osd_id)
d2e6a577
FG
60 # format the device
61 prepare_utils.format_device(device)
62 # mount the data device
63 prepare_utils.mount_osd(device, osd_id)
64 # symlink the journal
65 prepare_utils.link_journal(journal, osd_id)
66 # get the latest monmap
67 prepare_utils.get_monmap(osd_id)
68 # prepare the osd filesystem
3efd9988 69 prepare_utils.osd_mkfs_filestore(osd_id, fsid)
d2e6a577
FG
70 # write the OSD keyring if it doesn't exist already
71 prepare_utils.write_keyring(osd_id, cephx_secret)
b32b8144
FG
72 if secrets.get('dmcrypt_key'):
73 # if the device is going to get activated right away, this can be done
74 # here, otherwise it will be recreated
75 encryption_utils.write_lockbox_keyring(
76 osd_id,
77 fsid,
78 tags['ceph.cephx_lockbox_secret']
79 )
d2e6a577
FG
80
81
b32b8144 82def prepare_bluestore(block, wal, db, secrets, tags, osd_id, fsid):
3efd9988
FG
83 """
84 :param block: The name of the logical volume for the bluestore data
85 :param wal: a regular/plain disk or logical volume, to be used for block.wal
86 :param db: a regular/plain disk or logical volume, to be used for block.db
87 :param secrets: A dict with the secrets needed to create the osd (e.g. cephx)
88 :param id_: The OSD id
89 :param fsid: The OSD fsid, also known as the OSD UUID
90 """
91 cephx_secret = secrets.get('cephx_secret', prepare_utils.create_key())
b32b8144
FG
92 # encryption-only operations
93 if secrets.get('dmcrypt_key'):
94 # If encrypted, there is no need to create the lockbox keyring file because
95 # bluestore re-creates the files and does not have support for other files
96 # like the custom lockbox one. This will need to be done on activation.
97 # format and open ('decrypt' devices) and re-assign the device and journal
98 # variables so that the rest of the process can use the mapper paths
99 key = secrets['dmcrypt_key']
100 block = prepare_dmcrypt(key, block, 'block', tags)
101 wal = prepare_dmcrypt(key, wal, 'wal', tags)
102 db = prepare_dmcrypt(key, db, 'db', tags)
3efd9988 103
3efd9988
FG
104 # create the directory
105 prepare_utils.create_osd_path(osd_id, tmpfs=True)
106 # symlink the block
107 prepare_utils.link_block(block, osd_id)
108 # get the latest monmap
109 prepare_utils.get_monmap(osd_id)
110 # write the OSD keyring if it doesn't exist already
111 prepare_utils.write_keyring(osd_id, cephx_secret)
112 # prepare the osd filesystem
113 prepare_utils.osd_mkfs_bluestore(
114 osd_id, fsid,
115 keyring=cephx_secret,
116 wal=wal,
117 db=db
118 )
d2e6a577
FG
119
120
121class Prepare(object):
122
123 help = 'Format an LVM device and associate it with an OSD'
124
125 def __init__(self, argv):
126 self.argv = argv
b32b8144 127 self.osd_id = None
d2e6a577 128
3efd9988 129 def get_ptuuid(self, argument):
181888fb
FG
130 uuid = disk.get_partuuid(argument)
131 if not uuid:
132 terminal.error('blkid could not detect a PARTUUID for device: %s' % argument)
3efd9988 133 raise RuntimeError('unable to use device')
181888fb
FG
134 return uuid
135
3efd9988 136 def get_lv(self, argument):
b5b8bbf5 137 """
3efd9988
FG
138 Perform some parsing of the command-line value so that the process
139 can determine correctly if it got a device path or an lv.
140
141 :param argument: The command-line value that will need to be split to
142 retrieve the actual lv
b5b8bbf5
FG
143 """
144 try:
145 vg_name, lv_name = argument.split('/')
146 except (ValueError, AttributeError):
147 return None
148 return api.get_lv(lv_name=lv_name, vg_name=vg_name)
149
3efd9988
FG
150 def setup_device(self, device_type, device_name, tags):
151 """
152 Check if ``device`` is an lv, if so, set the tags, making sure to
153 update the tags with the lv_uuid and lv_path which the incoming tags
154 will not have.
155
156 If the device is not a logical volume, then retrieve the partition UUID
157 by querying ``blkid``
158 """
159 if device_name is None:
160 return '', '', tags
161 tags['ceph.type'] = device_type
162 lv = self.get_lv(device_name)
163 if lv:
164 uuid = lv.lv_uuid
165 path = lv.lv_path
166 tags['ceph.%s_uuid' % device_type] = uuid
167 tags['ceph.%s_device' % device_type] = path
168 lv.set_tags(tags)
169 else:
170 # otherwise assume this is a regular disk partition
171 uuid = self.get_ptuuid(device_name)
172 path = device_name
173 tags['ceph.%s_uuid' % device_type] = uuid
174 tags['ceph.%s_device' % device_type] = path
175 return path, uuid, tags
176
177 def prepare_device(self, arg, device_type, cluster_fsid, osd_fsid):
178 """
179 Check if ``arg`` is a device or partition to create an LV out of it
180 with a distinct volume group name, assigning LV tags on it and
181 ultimately, returning the logical volume object. Failing to detect
182 a device or partition will result in error.
183
184 :param arg: The value of ``--data`` when parsing args
185 :param device_type: Usually, either ``data`` or ``block`` (filestore vs. bluestore)
186 :param cluster_fsid: The cluster fsid/uuid
187 :param osd_fsid: The OSD fsid/uuid
188 """
189 if disk.is_partition(arg) or disk.is_device(arg):
190 # we must create a vg, and then a single lv
191 vg_name = "ceph-%s" % cluster_fsid
192 if api.get_vg(vg_name=vg_name):
193 # means we already have a group for this, make a different one
194 # XXX this could end up being annoying for an operator, maybe?
195 vg_name = "ceph-%s" % str(uuid.uuid4())
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:
218 logger.error('lvm prepare was unable to complete')
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
3efd9988
FG
265
266 journal_device, journal_uuid, tags = self.setup_device('journal', args.journal, tags)
267
268 tags['ceph.type'] = 'data'
269 data_lv.set_tags(tags)
d2e6a577
FG
270
271 prepare_filestore(
272 data_lv.lv_path,
273 journal_device,
274 secrets,
b32b8144
FG
275 tags,
276 self.osd_id,
277 osd_fsid,
d2e6a577
FG
278 )
279 elif args.bluestore:
3efd9988
FG
280 block_lv = self.get_lv(args.data)
281 if not block_lv:
282 block_lv = self.prepare_device(args.data, 'block', cluster_fsid, osd_fsid)
283
b32b8144
FG
284 tags['ceph.block_device'] = block_lv.lv_path
285 tags['ceph.block_uuid'] = block_lv.lv_uuid
286 tags['ceph.cephx_lockbox_secret'] = cephx_lockbox_secret
287 tags['ceph.encrypted'] = encrypted
3efd9988
FG
288
289 wal_device, wal_uuid, tags = self.setup_device('wal', args.block_wal, tags)
290 db_device, db_uuid, tags = self.setup_device('db', args.block_db, tags)
291
292 tags['ceph.type'] = 'block'
293 block_lv.set_tags(tags)
294
295 prepare_bluestore(
296 block_lv.lv_path,
297 wal_device,
298 db_device,
299 secrets,
b32b8144
FG
300 tags,
301 self.osd_id,
302 osd_fsid,
3efd9988 303 )
d2e6a577
FG
304
305 def main(self):
306 sub_command_help = dedent("""
307 Prepare an OSD by assigning an ID and FSID, registering them with the
308 cluster with an ID and FSID, formatting and mounting the volume, and
309 finally by adding all the metadata to the logical volumes using LVM
310 tags, so that it can later be discovered.
311
312 Once the OSD is ready, an ad-hoc systemd unit will be enabled so that
313 it can later get activated and the OSD daemon can get started.
314
315 Most basic Usage looks like (journal will be collocated from the same volume group):
316
317 ceph-volume lvm prepare --data {volume group name}
318
b32b8144 319 Encryption is supported via dmcrypt and the --dmcrypt flag.
d2e6a577
FG
320
321 Example calls for supported scenarios:
322
323 Dedicated volume group for Journal(s)
324 -------------------------------------
325
326 Existing logical volume (lv) or device:
327
3efd9988 328 ceph-volume lvm prepare --filestore --data {vg/lv} --journal /path/to/device
d2e6a577
FG
329
330 Or:
331
3efd9988
FG
332 ceph-volume lvm prepare --filestore --data {vg/lv} --journal {vg/lv}
333
334 Existing block device, that will be made a group and logical volume:
335
336 ceph-volume lvm prepare --filestore --data /path/to/device --journal {vg/lv}
337
338 Bluestore
339 ---------
340
341 Existing logical volume (lv):
342
343 ceph-volume lvm prepare --bluestore --data {vg/lv}
344
345 Existing block device, that will be made a group and logical volume:
d2e6a577 346
3efd9988 347 ceph-volume lvm prepare --bluestore --data /path/to/device
d2e6a577 348
3efd9988 349 Optionally, can consume db and wal devices or logical volumes:
d2e6a577 350
3efd9988 351 ceph-volume lvm prepare --bluestore --data {vg/lv} --block.wal {device} --block-db {vg/lv}
d2e6a577
FG
352 """)
353 parser = prepare_parser(
354 prog='ceph-volume lvm prepare',
355 description=sub_command_help,
356 )
357 if len(self.argv) == 0:
358 print(sub_command_help)
359 return
360 args = parser.parse_args(self.argv)
3efd9988
FG
361 # Default to bluestore here since defaulting it in add_argument may
362 # cause both to be True
b32b8144 363 if not args.bluestore and not args.filestore:
3efd9988 364 args.bluestore = True
b32b8144 365 self.safe_prepare(args)