from __future__ import print_function
import argparse
+import base64
import json
import logging
import os
from textwrap import dedent
-from ceph_volume import process, decorators, terminal
+from ceph_volume import process, decorators, terminal, conf
from ceph_volume.util import system, disk
+from ceph_volume.util import encryption as encryption_utils
from ceph_volume.systemd import systemctl
logger = logging.getLogger(__name__)
+mlogger = terminal.MultiLogger(__name__)
class Activate(object):
self.argv = argv
self.systemd = systemd
+ def validate_devices(self, json_config):
+ """
+ ``json_config`` is the loaded dictionary coming from the JSON file. It is usually mixed with
+ other non-device items, but for sakes of comparison it doesn't really matter. This method is
+ just making sure that the keys needed exist
+ """
+ devices = json_config.keys()
+ try:
+ objectstore = json_config['type']
+ except KeyError:
+ logger.warning('"type" was not defined, will assume "bluestore"')
+ objectstore = 'bluestore'
+
+ # Go through all the device combinations that are absolutely required,
+ # raise an error describing what was expected and what was found
+ # otherwise.
+ if objectstore == 'filestore':
+ if {'data', 'journal'}.issubset(set(devices)):
+ return True
+ else:
+ found = [i for i in devices if i in ['data', 'journal']]
+ mlogger.error("Required devices (data, and journal) not present for filestore")
+ mlogger.error('filestore devices found: %s', found)
+ raise RuntimeError('Unable to activate filestore OSD due to missing devices')
+ else:
+ # This is a bit tricky, with newer bluestore we don't need data, older implementations
+ # do (e.g. with ceph-disk). ceph-volume just uses a tmpfs that doesn't require data.
+ if {'block', 'data'}.issubset(set(devices)):
+ return True
+ else:
+ bluestore_devices = ['block.db', 'block.wal', 'block', 'data']
+ found = [i for i in devices if i in bluestore_devices]
+ mlogger.error("Required devices (block and data) not present for bluestore")
+ mlogger.error('bluestore devices found: %s', found)
+ raise RuntimeError('Unable to activate bluestore OSD due to missing devices')
+
+ def get_device(self, uuid):
+ """
+ If a device is encrypted, it will decrypt/open and return the mapper
+ path, if it isn't encrypted it will just return the device found that
+ is mapped to the uuid. This will make it easier for the caller to
+ avoid if/else to check if devices need decrypting
+
+ :param uuid: The partition uuid of the device (PARTUUID)
+ """
+ device = disk.get_device_from_partuuid(uuid)
+
+ # If device is not found, it is fine to return an empty string from the
+ # helper that finds `device`. If it finds anything and it is not
+ # encrypted, just return what was found
+ if not self.is_encrypted or not device:
+ return device
+
+ if self.encryption_type == 'luks':
+ encryption_utils.luks_open(self.dmcrypt_secret, device, uuid)
+ else:
+ encryption_utils.plain_open(self.dmcrypt_secret, device, uuid)
+
+ return '/dev/mapper/%s' % uuid
+
@decorators.needs_root
def activate(self, args):
with open(args.json_config, 'r') as fp:
osd_metadata = json.load(fp)
+ # Make sure that required devices are configured
+ self.validate_devices(osd_metadata)
+
osd_id = osd_metadata.get('whoami', args.osd_id)
osd_fsid = osd_metadata.get('fsid', args.osd_fsid)
-
- cluster_name = osd_metadata.get('cluster_name', 'ceph')
- osd_dir = '/var/lib/ceph/osd/%s-%s' % (cluster_name, osd_id)
data_uuid = osd_metadata.get('data', {}).get('uuid')
+ conf.cluster = osd_metadata.get('cluster_name', 'ceph')
if not data_uuid:
raise RuntimeError(
'Unable to activate OSD %s - no "uuid" key found for data' % args.osd_id
)
- data_device = disk.get_device_from_partuuid(data_uuid)
- journal_device = disk.get_device_from_partuuid(osd_metadata.get('journal', {}).get('uuid'))
- block_device = disk.get_device_from_partuuid(osd_metadata.get('block', {}).get('uuid'))
- block_db_device = disk.get_device_from_partuuid(osd_metadata.get('block.db', {}).get('uuid'))
- block_wal_device = disk.get_device_from_partuuid(
- osd_metadata.get('block.wal', {}).get('uuid')
- )
+
+ # Encryption detection, and capturing of the keys to decrypt
+ self.is_encrypted = osd_metadata.get('encrypted', False)
+ self.encryption_type = osd_metadata.get('encryption_type')
+ if self.is_encrypted:
+ lockbox_secret = osd_metadata.get('lockbox.keyring')
+ # write the keyring always so that we can unlock
+ encryption_utils.write_lockbox_keyring(osd_id, osd_fsid, lockbox_secret)
+ # Store the secret around so that the decrypt method can reuse
+ raw_dmcrypt_secret = encryption_utils.get_dmcrypt_key(osd_id, osd_fsid)
+ # Note how both these calls need b64decode. For some reason, the
+ # way ceph-disk creates these keys, it stores them in the monitor
+ # *undecoded*, requiring this decode call again. The lvm side of
+ # encryption doesn't need it, so we are assuming here that anything
+ # that `simple` scans, will come from ceph-disk and will need this
+ # extra decode call here
+ self.dmcrypt_secret = base64.b64decode(raw_dmcrypt_secret)
+
+ cluster_name = osd_metadata.get('cluster_name', 'ceph')
+ osd_dir = '/var/lib/ceph/osd/%s-%s' % (cluster_name, osd_id)
+
+ # XXX there is no support for LVM here
+ data_device = self.get_device(data_uuid)
+ journal_device = self.get_device(osd_metadata.get('journal', {}).get('uuid'))
+ block_device = self.get_device(osd_metadata.get('block', {}).get('uuid'))
+ block_db_device = self.get_device(osd_metadata.get('block.db', {}).get('uuid'))
+ block_wal_device = self.get_device(osd_metadata.get('block.wal', {}).get('uuid'))
if not system.device_is_mounted(data_device, destination=osd_dir):
- process.run(['sudo', 'mount', '-v', data_device, osd_dir])
+ process.run(['mount', '-v', data_device, osd_dir])
device_map = {
'journal': journal_device,
# always re-do the symlink regardless if it exists, so that the journal
# device path that may have changed can be mapped correctly every time
destination = os.path.join(osd_dir, name)
- process.run(['sudo', 'ln', '-snf', device, destination])
+ process.run(['ln', '-snf', device, destination])
# make sure that the journal has proper permissions
system.chown(device)
# start the OSD
systemctl.start_osd(osd_id)
- if not self.systemd:
- terminal.success('Successfully activated OSD %s with FSID %s' % (osd_id, osd_fsid))
- terminal.warning(
- ('All ceph-disk systemd units have been disabled to '
- 'prevent OSDs getting triggered by UDEV events')
- )
+ terminal.success('Successfully activated OSD %s with FSID %s' % (osd_id, osd_fsid))
+ terminal.warning(
+ ('All ceph-disk systemd units have been disabled to '
+ 'prevent OSDs getting triggered by UDEV events')
+ )
def main(self):
sub_command_help = dedent("""