from __future__ import print_function
import json
+import logging
import uuid
from textwrap import dedent
from ceph_volume.util import prepare as prepare_utils
+from ceph_volume.util import encryption as encryption_utils
from ceph_volume.util import system, disk
from ceph_volume import conf, decorators, terminal
from ceph_volume.api import lvm as api
-from .common import prepare_parser
+from .common import prepare_parser, rollback_osd
-def prepare_filestore(device, journal, secrets, id_=None, fsid=None):
+logger = logging.getLogger(__name__)
+
+
+def prepare_dmcrypt(key, device, device_type, tags):
+ """
+ Helper for devices that are encrypted. The operations needed for
+ block, db, wal, or data/journal devices are all the same
+ """
+ if not device:
+ return ''
+ tag_name = 'ceph.%s_uuid' % device_type
+ uuid = tags[tag_name]
+ # format data device
+ encryption_utils.luks_format(
+ key,
+ device
+ )
+ encryption_utils.luks_open(
+ key,
+ device,
+ uuid
+ )
+
+ return '/dev/mapper/%s' % uuid
+
+
+def prepare_filestore(device, journal, secrets, tags, osd_id, fsid):
"""
:param device: The name of the logical volume to work with
:param journal: similar to device but can also be a regular/plain disk
:param fsid: The OSD fsid, also known as the OSD UUID
"""
cephx_secret = secrets.get('cephx_secret', prepare_utils.create_key())
- json_secrets = json.dumps(secrets)
- # allow re-using an existing fsid, in case prepare failed
- fsid = fsid or system.generate_uuid()
- # allow re-using an id, in case a prepare failed
- osd_id = id_ or prepare_utils.create_id(fsid, json_secrets)
+ # encryption-only operations
+ if secrets.get('dmcrypt_key'):
+ # format and open ('decrypt' devices) and re-assign the device and journal
+ # variables so that the rest of the process can use the mapper paths
+ key = secrets['dmcrypt_key']
+ device = prepare_dmcrypt(key, device, 'data', tags)
+ journal = prepare_dmcrypt(key, journal, 'journal', tags)
+
# create the directory
prepare_utils.create_osd_path(osd_id)
# format the device
prepare_utils.osd_mkfs_filestore(osd_id, fsid)
# write the OSD keyring if it doesn't exist already
prepare_utils.write_keyring(osd_id, cephx_secret)
+ if secrets.get('dmcrypt_key'):
+ # if the device is going to get activated right away, this can be done
+ # here, otherwise it will be recreated
+ encryption_utils.write_lockbox_keyring(
+ osd_id,
+ fsid,
+ tags['ceph.cephx_lockbox_secret']
+ )
-def prepare_bluestore(block, wal, db, secrets, id_=None, fsid=None):
+def prepare_bluestore(block, wal, db, secrets, tags, osd_id, fsid):
"""
:param block: The name of the logical volume for the bluestore data
:param wal: a regular/plain disk or logical volume, to be used for block.wal
:param fsid: The OSD fsid, also known as the OSD UUID
"""
cephx_secret = secrets.get('cephx_secret', prepare_utils.create_key())
- json_secrets = json.dumps(secrets)
+ # encryption-only operations
+ if secrets.get('dmcrypt_key'):
+ # If encrypted, there is no need to create the lockbox keyring file because
+ # bluestore re-creates the files and does not have support for other files
+ # like the custom lockbox one. This will need to be done on activation.
+ # format and open ('decrypt' devices) and re-assign the device and journal
+ # variables so that the rest of the process can use the mapper paths
+ key = secrets['dmcrypt_key']
+ block = prepare_dmcrypt(key, block, 'block', tags)
+ wal = prepare_dmcrypt(key, wal, 'wal', tags)
+ db = prepare_dmcrypt(key, db, 'db', tags)
- # allow re-using an existing fsid, in case prepare failed
- fsid = fsid or system.generate_uuid()
- # allow re-using an id, in case a prepare failed
- osd_id = id_ or prepare_utils.create_id(fsid, json_secrets)
# create the directory
prepare_utils.create_osd_path(osd_id, tmpfs=True)
# symlink the block
def __init__(self, argv):
self.argv = argv
+ self.osd_id = None
def get_ptuuid(self, argument):
uuid = disk.get_partuuid(argument)
tags={'ceph.type': device_type})
else:
error = [
- 'Cannot use device (%s).',
- 'A vg/lv path or an existing device is needed' % arg]
+ 'Cannot use device (%s).' % arg,
+ 'A vg/lv path or an existing device is needed']
raise RuntimeError(' '.join(error))
raise RuntimeError('no data logical volume found with: %s' % arg)
+ def safe_prepare(self, args):
+ """
+ An intermediate step between `main()` and `prepare()` so that we can
+ capture the `self.osd_id` in case we need to rollback
+ """
+ try:
+ self.prepare(args)
+ except Exception:
+ logger.error('lvm prepare was unable to complete')
+ logger.info('will rollback OSD ID creation')
+ rollback_osd(args, self.osd_id)
+ raise
+ terminal.success("ceph-volume lvm prepare successful for: %s" % args.data)
+
@decorators.needs_root
def prepare(self, args):
# FIXME we don't allow re-using a keyring, we always generate one for the
# (!!) or some flags that we would need to compound into a dict so that we
# can convert to JSON (!!!)
secrets = {'cephx_secret': prepare_utils.create_key()}
+ cephx_lockbox_secret = ''
+ encrypted = 1 if args.dmcrypt else 0
+ cephx_lockbox_secret = '' if not encrypted else prepare_utils.create_key()
+
+ if encrypted:
+ secrets['dmcrypt_key'] = encryption_utils.create_dmcrypt_key()
+ secrets['cephx_lockbox_secret'] = cephx_lockbox_secret
cluster_fsid = conf.ceph.get('global', 'fsid')
osd_fsid = args.osd_fsid or system.generate_uuid()
- # allow re-using an id, in case a prepare failed
- osd_id = args.osd_id or prepare_utils.create_id(osd_fsid, json.dumps(secrets))
+ crush_device_class = args.crush_device_class
+ if crush_device_class:
+ secrets['crush_device_class'] = crush_device_class
+ # reuse a given ID if it exists, otherwise create a new ID
+ self.osd_id = prepare_utils.create_id(osd_fsid, json.dumps(secrets), osd_id=args.osd_id)
+ tags = {
+ 'ceph.osd_fsid': osd_fsid,
+ 'ceph.osd_id': self.osd_id,
+ 'ceph.cluster_fsid': cluster_fsid,
+ 'ceph.cluster_name': conf.cluster,
+ 'ceph.crush_device_class': crush_device_class,
+ }
if args.filestore:
if not args.journal:
raise RuntimeError('--journal is required when using --filestore')
if not data_lv:
data_lv = self.prepare_device(args.data, 'data', cluster_fsid, osd_fsid)
- tags = {
- 'ceph.osd_fsid': osd_fsid,
- 'ceph.osd_id': osd_id,
- 'ceph.cluster_fsid': cluster_fsid,
- 'ceph.cluster_name': conf.cluster,
- 'ceph.data_device': data_lv.lv_path,
- 'ceph.data_uuid': data_lv.lv_uuid,
- }
+ tags['ceph.data_device'] = data_lv.lv_path
+ tags['ceph.data_uuid'] = data_lv.lv_uuid
+ tags['ceph.cephx_lockbox_secret'] = cephx_lockbox_secret
+ tags['ceph.encrypted'] = encrypted
journal_device, journal_uuid, tags = self.setup_device('journal', args.journal, tags)
data_lv.lv_path,
journal_device,
secrets,
- id_=osd_id,
- fsid=osd_fsid,
+ tags,
+ self.osd_id,
+ osd_fsid,
)
elif args.bluestore:
block_lv = self.get_lv(args.data)
if not block_lv:
block_lv = self.prepare_device(args.data, 'block', cluster_fsid, osd_fsid)
- tags = {
- 'ceph.osd_fsid': osd_fsid,
- 'ceph.osd_id': osd_id,
- 'ceph.cluster_fsid': cluster_fsid,
- 'ceph.cluster_name': conf.cluster,
- 'ceph.block_device': block_lv.lv_path,
- 'ceph.block_uuid': block_lv.lv_uuid,
- }
+ tags['ceph.block_device'] = block_lv.lv_path
+ tags['ceph.block_uuid'] = block_lv.lv_uuid
+ tags['ceph.cephx_lockbox_secret'] = cephx_lockbox_secret
+ tags['ceph.encrypted'] = encrypted
wal_device, wal_uuid, tags = self.setup_device('wal', args.block_wal, tags)
db_device, db_uuid, tags = self.setup_device('db', args.block_db, tags)
wal_device,
db_device,
secrets,
- id_=osd_id,
- fsid=osd_fsid,
+ tags,
+ self.osd_id,
+ osd_fsid,
)
def main(self):
ceph-volume lvm prepare --data {volume group name}
+ Encryption is supported via dmcrypt and the --dmcrypt flag.
Example calls for supported scenarios:
args = parser.parse_args(self.argv)
# Default to bluestore here since defaulting it in add_argument may
# cause both to be True
- if args.bluestore is None and args.filestore is None:
+ if not args.bluestore and not args.filestore:
args.bluestore = True
- self.prepare(args)
+ self.safe_prepare(args)