]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/devices/lvm/prepare.py
update ceph source to reef 18.2.0
[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
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 40def 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
79class 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()