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