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