--- /dev/null
+from __future__ import print_function
+import json
+import uuid
+from textwrap import dedent
+from ceph_volume.util import prepare as prepare_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
+
+
+def prepare_filestore(device, journal, secrets, id_=None, fsid=None):
+ """
+ :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 secrets: A dict with the secrets needed to create the osd (e.g. cephx)
+ :param id_: The OSD id
+ :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)
+ # create the directory
+ prepare_utils.create_osd_path(osd_id)
+ # format the device
+ prepare_utils.format_device(device)
+ # mount the data device
+ prepare_utils.mount_osd(device, osd_id)
+ # symlink the journal
+ prepare_utils.link_journal(journal, osd_id)
+ # get the latest monmap
+ prepare_utils.get_monmap(osd_id)
+ # prepare the osd filesystem
+ 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)
+
+
+def prepare_bluestore(block, wal, db, secrets, id_=None, fsid=None):
+ """
+ :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 db: a regular/plain disk or logical volume, to be used for block.db
+ :param secrets: A dict with the secrets needed to create the osd (e.g. cephx)
+ :param id_: The OSD id
+ :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)
+ # create the directory
+ prepare_utils.create_osd_path(osd_id, tmpfs=True)
+ # symlink the block
+ prepare_utils.link_block(block, osd_id)
+ # get the latest monmap
+ prepare_utils.get_monmap(osd_id)
+ # write the OSD keyring if it doesn't exist already
+ prepare_utils.write_keyring(osd_id, cephx_secret)
+ # prepare the osd filesystem
+ prepare_utils.osd_mkfs_bluestore(
+ osd_id, fsid,
+ keyring=cephx_secret,
+ wal=wal,
+ db=db
+ )
+
+
+class Prepare(object):
+
+ help = 'Format an LVM device and associate it with an OSD'
+
+ def __init__(self, argv):
+ self.argv = argv
+
+ def get_ptuuid(self, argument):
+ uuid = disk.get_partuuid(argument)
+ if not uuid:
+ terminal.error('blkid could not detect a PARTUUID for device: %s' % argument)
+ raise RuntimeError('unable to use device')
+ return uuid
+
+ def get_lv(self, argument):
+ """
+ Perform some parsing of the command-line value so that the process
+ can determine correctly if it got a device path or an lv.
+
+ :param argument: The command-line value that will need to be split to
+ retrieve the actual lv
+ """
+ try:
+ vg_name, lv_name = argument.split('/')
+ except (ValueError, AttributeError):
+ return None
+ return api.get_lv(lv_name=lv_name, vg_name=vg_name)
+
+ def setup_device(self, device_type, device_name, tags):
+ """
+ Check if ``device`` is an lv, if so, set the tags, making sure to
+ update the tags with the lv_uuid and lv_path which the incoming tags
+ will not have.
+
+ If the device is not a logical volume, then retrieve the partition UUID
+ by querying ``blkid``
+ """
+ if device_name is None:
+ return '', '', tags
+ tags['ceph.type'] = device_type
+ lv = self.get_lv(device_name)
+ if lv:
+ uuid = lv.lv_uuid
+ path = lv.lv_path
+ tags['ceph.%s_uuid' % device_type] = uuid
+ tags['ceph.%s_device' % device_type] = path
+ lv.set_tags(tags)
+ else:
+ # otherwise assume this is a regular disk partition
+ uuid = self.get_ptuuid(device_name)
+ path = device_name
+ tags['ceph.%s_uuid' % device_type] = uuid
+ tags['ceph.%s_device' % device_type] = path
+ return path, uuid, tags
+
+ def prepare_device(self, arg, device_type, cluster_fsid, osd_fsid):
+ """
+ Check if ``arg`` is a device or partition to create an LV out of it
+ with a distinct volume group name, assigning LV tags on it and
+ ultimately, returning the logical volume object. Failing to detect
+ a device or partition will result in error.
+
+ :param arg: The value of ``--data`` when parsing args
+ :param device_type: Usually, either ``data`` or ``block`` (filestore vs. bluestore)
+ :param cluster_fsid: The cluster fsid/uuid
+ :param osd_fsid: The OSD fsid/uuid
+ """
+ if disk.is_partition(arg) or disk.is_device(arg):
+ # we must create a vg, and then a single lv
+ vg_name = "ceph-%s" % cluster_fsid
+ if api.get_vg(vg_name=vg_name):
+ # means we already have a group for this, make a different one
+ # XXX this could end up being annoying for an operator, maybe?
+ vg_name = "ceph-%s" % str(uuid.uuid4())
+ api.create_vg(vg_name, arg)
+ lv_name = "osd-%s-%s" % (device_type, osd_fsid)
+ return api.create_lv(
+ lv_name,
+ vg_name, # the volume group
+ tags={'ceph.type': device_type})
+ else:
+ error = [
+ '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)
+
+ @decorators.needs_root
+ def prepare(self, args):
+ # FIXME we don't allow re-using a keyring, we always generate one for the
+ # OSD, this needs to be fixed. This could either be a file (!) or a string
+ # (!!) 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()}
+
+ 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))
+ if args.filestore:
+ if not args.journal:
+ raise RuntimeError('--journal is required when using --filestore')
+
+ data_lv = self.get_lv(args.data)
+ 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,
+ }
+
+ journal_device, journal_uuid, tags = self.setup_device('journal', args.journal, tags)
+
+ tags['ceph.type'] = 'data'
+ data_lv.set_tags(tags)
+
+ prepare_filestore(
+ data_lv.lv_path,
+ journal_device,
+ secrets,
+ id_=osd_id,
+ fsid=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,
+ }
+
+ 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)
+
+ tags['ceph.type'] = 'block'
+ block_lv.set_tags(tags)
+
+ prepare_bluestore(
+ block_lv.lv_path,
+ wal_device,
+ db_device,
+ secrets,
+ id_=osd_id,
+ fsid=osd_fsid,
+ )
+
+ def main(self):
+ sub_command_help = dedent("""
+ Prepare an OSD by assigning an ID and FSID, registering them with the
+ cluster with an ID and FSID, formatting and mounting the volume, and
+ finally by adding all the metadata to the logical volumes using LVM
+ tags, so that it can later be discovered.
+
+ Once the OSD is ready, an ad-hoc systemd unit will be enabled so that
+ it can later get activated and the OSD daemon can get started.
+
+ Most basic Usage looks like (journal will be collocated from the same volume group):
+
+ ceph-volume lvm prepare --data {volume group name}
+
+
+ Example calls for supported scenarios:
+
+ Dedicated volume group for Journal(s)
+ -------------------------------------
+
+ Existing logical volume (lv) or device:
+
+ ceph-volume lvm prepare --filestore --data {vg/lv} --journal /path/to/device
+
+ Or:
+
+ ceph-volume lvm prepare --filestore --data {vg/lv} --journal {vg/lv}
+
+ Existing block device, that will be made a group and logical volume:
+
+ ceph-volume lvm prepare --filestore --data /path/to/device --journal {vg/lv}
+
+ Bluestore
+ ---------
+
+ Existing logical volume (lv):
+
+ ceph-volume lvm prepare --bluestore --data {vg/lv}
+
+ Existing block device, that will be made a group and logical volume:
+
+ ceph-volume lvm prepare --bluestore --data /path/to/device
+
+ Optionally, can consume db and wal devices or logical volumes:
+
+ ceph-volume lvm prepare --bluestore --data {vg/lv} --block.wal {device} --block-db {vg/lv}
+ """)
+ parser = prepare_parser(
+ prog='ceph-volume lvm prepare',
+ description=sub_command_help,
+ )
+ if len(self.argv) == 0:
+ print(sub_command_help)
+ return
+ 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:
+ args.bluestore = True
+ self.prepare(args)