initial code repo
[stor4nfv.git] / src / ceph / src / ceph-volume / ceph_volume / devices / lvm / prepare.py
diff --git a/src/ceph/src/ceph-volume/ceph_volume/devices/lvm/prepare.py b/src/ceph/src/ceph-volume/ceph_volume/devices/lvm/prepare.py
new file mode 100644 (file)
index 0000000..32c0e5e
--- /dev/null
@@ -0,0 +1,293 @@
+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)