]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
ceph-volume: add raw mode
authorSage Weil <sage@redhat.com>
Fri, 6 Dec 2019 20:08:12 +0000 (14:08 -0600)
committerJan Fajerski <jfajerski@suse.com>
Fri, 28 Feb 2020 09:34:30 +0000 (10:34 +0100)
list, prepare, activate, create (prepare+activate) basic bluestore OSDs
on a single device.

No support (or plans to ever support) dmcrypt.

I think support for db/wal could be added later without much trouble.

For now, we require --no-systemd and --bluestore flags because we haven't
implemented support for anything else but suspect we will in the future,
and would like to ensure users are being explicit about what they're
asking for.

Signed-off-by: Sage Weil <sage@redhat.com>
(cherry picked from commit 1e9de77add67fa94204c450c52d22fc3a33d7b22)

src/ceph-volume/ceph_volume/devices/__init__.py
src/ceph-volume/ceph_volume/devices/raw/__init__.py [new file with mode: 0644]
src/ceph-volume/ceph_volume/devices/raw/activate.py [new file with mode: 0644]
src/ceph-volume/ceph_volume/devices/raw/common.py [new file with mode: 0644]
src/ceph-volume/ceph_volume/devices/raw/create.py [new file with mode: 0644]
src/ceph-volume/ceph_volume/devices/raw/list.py [new file with mode: 0644]
src/ceph-volume/ceph_volume/devices/raw/main.py [new file with mode: 0644]
src/ceph-volume/ceph_volume/devices/raw/prepare.py [new file with mode: 0644]
src/ceph-volume/ceph_volume/devices/raw/scan.py [new file with mode: 0644]
src/ceph-volume/ceph_volume/main.py

index 8af5d1e74e0196b16c80f43daddd7988ffd3e890..2b017d671fd729c862637bdd023f8f34b1141800 100644 (file)
@@ -1 +1 @@
-from . import lvm, simple # noqa
+from . import lvm, simple, raw # noqa
diff --git a/src/ceph-volume/ceph_volume/devices/raw/__init__.py b/src/ceph-volume/ceph_volume/devices/raw/__init__.py
new file mode 100644 (file)
index 0000000..dd0a653
--- /dev/null
@@ -0,0 +1 @@
+from .main import Raw # noqa
diff --git a/src/ceph-volume/ceph_volume/devices/raw/activate.py b/src/ceph-volume/ceph_volume/devices/raw/activate.py
new file mode 100644 (file)
index 0000000..743df16
--- /dev/null
@@ -0,0 +1,151 @@
+from __future__ import print_function
+import argparse
+import logging
+import os
+from textwrap import dedent
+from ceph_volume import process, conf, decorators, terminal, __release__, configuration
+from ceph_volume.util import system, disk
+from ceph_volume.util import prepare as prepare_utils
+from ceph_volume.util import encryption as encryption_utils
+from ceph_volume.systemd import systemctl
+from ceph_volume.api import lvm as api
+from .list import direct_report
+
+
+logger = logging.getLogger(__name__)
+
+def activate_bluestore(meta, tmpfs, systemd):
+    # find the osd
+    osd_id = meta['osd_id']
+    osd_fsid = meta['osd_uuid']
+
+    # mount on tmpfs the osd directory
+    osd_path = '/var/lib/ceph/osd/%s-%s' % (conf.cluster, osd_id)
+    if not system.path_is_mounted(osd_path):
+        # mkdir -p and mount as tmpfs
+        prepare_utils.create_osd_path(osd_id, tmpfs=tmpfs)
+
+    # XXX This needs to be removed once ceph-bluestore-tool can deal with
+    # symlinks that exist in the osd dir
+    for link_name in ['block', 'block.db', 'block.wal']:
+        link_path = os.path.join(osd_path, link_name)
+        if os.path.exists(link_path):
+            os.unlink(os.path.join(osd_path, link_name))
+
+    # Once symlinks are removed, the osd dir can be 'primed again. chown first,
+    # regardless of what currently exists so that ``prime-osd-dir`` can succeed
+    # even if permissions are somehow messed up
+    system.chown(osd_path)
+    prime_command = [
+        'ceph-bluestore-tool',
+        'prime-osd-dir', '--dev', meta['device'],
+        '--path', osd_path,
+        '--no-mon-config']
+    process.run(prime_command)
+
+    # always re-do the symlink regardless if it exists, so that the block,
+    # block.wal, and block.db devices that may have changed can be mapped
+    # correctly every time
+    process.run(['ln', '-snf', meta['device'], os.path.join(osd_path, 'block')])
+    system.chown(os.path.join(osd_path, 'block'))
+    system.chown(osd_path)
+
+#    if systemd:
+        # write me
+        # enable the OSD
+        #systemctl.enable_osd(osd_id)
+
+        # start the OSD
+        #systemctl.start_osd(osd_id)
+
+    terminal.success("ceph-volume raw activate successful for osd ID: %s" % osd_id)
+
+
+class Activate(object):
+
+    help = 'Discover and prepare a data directory for a (BlueStore) OSD on a raw device'
+
+    def __init__(self, argv):
+        self.argv = argv
+        self.args = None
+
+    @decorators.needs_root
+    def activate(self, devices=None, only_osd_id=None, only_osd_fsid=None):
+        """
+        :param args: The parsed arguments coming from the CLI
+        """
+        if devices:
+            found = direct_report(devices)
+        else:
+            found = direct_report(None)
+
+        for osd_id, meta in found.items():
+            if only_osd_id and only_osd_fsid and \
+               (only_osd_id != osd_id or only_osd_fsid != meta['osd_uuid']):
+                logger.debug('ignoring osd.%d uuid %s cluster %s' % (
+                    osd_id, meta['osd_uuid'], meta['ceph_fsid']))
+                continue
+
+            logger.info('Activating osd.%s uuid %s cluster %s' % (
+                    osd_id, meta['osd_uuid'], meta['ceph_fsid']))
+            activate_bluestore(meta,
+                               tmpfs=not self.args.no_tmpfs,
+                               systemd=not self.args.no_systemd)
+
+    def main(self):
+        sub_command_help = dedent("""
+        Activate (BlueStore) OSD on a raw block device based on the 
+        device label (normally the first block of the device).
+
+            ceph-volume raw activate --device /dev/sdb
+            ceph-volume raw activate --osd-id 1 --osd-uuid f0327efd-c28e-40bb-9199-f2e61e54c12a
+
+        The device(s) associated with the OSD needs to have been prepared
+        previously, so that all needed tags and metadata exist.
+        """)
+        parser = argparse.ArgumentParser(
+            prog='ceph-volume raw activate',
+            formatter_class=argparse.RawDescriptionHelpFormatter,
+            description=sub_command_help,
+        )
+
+        parser.add_argument(
+            '--osd-id',
+            help='The ID of the OSD, usually an integer, like 0'
+        )
+        parser.add_argument(
+            '--osd-uuid',
+            help='The UUID of the OSD'
+        )
+        parser.add_argument(
+            '--device',
+            nargs='*',
+            help='The device(s) for the OSD to start')
+        parser.add_argument(
+            '--all',
+            dest='activate_all',
+            action='store_true',
+            help='Activate all OSDs found in the system',
+        )
+        parser.add_argument(
+            '--no-systemd',
+            dest='no_systemd',
+            action='store_true',
+            help='Skip creating and enabling systemd units and starting OSD services',
+        )
+        parser.add_argument(
+            '--no-tmpfs',
+            action='store_true',
+            help='Do not use a tmpfs mount for OSD data dir')
+        if len(self.argv) == 0:
+            print(sub_command_help)
+            return
+        args = parser.parse_args(self.argv)
+        self.args = args
+        if not args.device and not args.activate_all and not (args.osd_id and args.osd_uuid):
+            terminal.error('must specify one of --device, --activate-all, or --osd-id and --osd-uuid')
+            raise SystemExit(1)
+        if not args.no_systemd:
+            terminal.error('systemd support not yet implemented')
+            raise SystemExit(1)
+        self.activate(args.device, args.osd_id, args.osd_fsid, not args.no_tmpfs)
diff --git a/src/ceph-volume/ceph_volume/devices/raw/common.py b/src/ceph-volume/ceph_volume/devices/raw/common.py
new file mode 100644 (file)
index 0000000..ca7f861
--- /dev/null
@@ -0,0 +1,45 @@
+import argparse
+from ceph_volume.util import arg_validators
+
+def create_parser(prog, description):
+    """
+    Both prepare and create share the same parser, those are defined here to
+    avoid duplication
+    """
+    parser = argparse.ArgumentParser(
+        prog=prog,
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description=description,
+    )
+    parser.add_argument(
+        '--data',
+        required=True,
+    type=arg_validators.ValidDevice(as_string=True),
+        help='a raw device to use for the OSD',
+    )
+    parser.add_argument(
+        '--bluestore',
+        action='store_true',
+        help='Use BlueStore backend')
+    parser.add_argument(
+        '--crush-device-class',
+        dest='crush_device_class',
+        help='Crush device class to assign this OSD to',
+    )
+    parser.add_argument(
+        '--osd-id',
+        help='Reuse an existing OSD id',
+    )
+    parser.add_argument(
+        '--osd-uuid',
+        help='Reuse an existing OSD UUID',
+    )
+    parser.add_argument(
+        '--cluster-fsid',
+        help='Specify the cluster fsid, useful when no ceph.conf is available',
+    )
+    parser.add_argument(
+        '--no-tmpfs',
+        action='store_true',
+        help='Do not use a tmpfs mount for OSD data dir')
+    return parser
diff --git a/src/ceph-volume/ceph_volume/devices/raw/create.py b/src/ceph-volume/ceph_volume/devices/raw/create.py
new file mode 100644 (file)
index 0000000..3d64e81
--- /dev/null
@@ -0,0 +1,80 @@
+from __future__ import print_function
+from textwrap import dedent
+import logging
+from ceph_volume.util import system
+from ceph_volume.util.arg_validators import exclude_group_options
+from ceph_volume import decorators, terminal
+from .prepare import Prepare
+from .activate import Activate
+from .common import create_parser
+from ceph_volume.devices.lvm.common import rollback_osd
+
+logger = logging.getLogger(__name__)
+
+
+class Create(object):
+
+    help = 'Create a new (BlueStore) OSD on a raw device'
+
+    def __init__(self, argv):
+        self.argv = argv
+        self.args = None
+
+    @decorators.needs_root
+    def create(self, args):
+        if not args.osd_fsid:
+            args.osd_fsid = system.generate_uuid()
+        prepare_step = Prepare([])
+        prepare_step.safe_prepare(args)
+        osd_id = prepare_step.osd_id
+        try:
+            # we try this for activate only when 'creating' an OSD,
+            # because a rollback should not happen when doing normal
+            # activation. For example when starting an OSD, systemd
+            # will call activate, which would never need to be rolled
+            # back.
+            a = Activate([])
+            a.args = self.args
+            a.activate([args.data])
+        except Exception:
+            logger.exception('raw activate was unable to complete, while creating the OSD')
+            logger.info('will rollback OSD ID creation')
+            rollback_osd(args, osd_id)
+            raise
+        terminal.success("ceph-volume raw create successful for: %s" % args.data)
+
+    def main(self):
+        sub_command_help = dedent("""
+        Create an OSD by assigning an ID and FSID, registering them with the
+        cluster with an ID and FSID, formatting and mounting the volume, and
+        starting the OSD daemon. This is a convinience command that combines
+        the prepare and activate steps.
+
+        Encryption is not supported.
+
+        Separate DB and WAL devices are not supported.
+
+            ceph-volume raw create --data /dev/sdb
+
+        """)
+        parser = create_parser(
+            prog='ceph-volume raw create',
+            description=sub_command_help,
+        )
+        parser.add_argument(
+            '--no-systemd',
+            dest='no_systemd',
+            action='store_true',
+            help='Skip creating and enabling systemd units and starting OSD services',
+        )
+        if len(self.argv) == 0:
+            print(sub_command_help)
+            return
+        self.args = parser.parse_args(self.argv)
+        if not self.args.bluestore:
+            terminal.error('must specify --bluestore (currently the only supported backend)')
+            raise SystemExit(1)
+        if not args.no_systemd:
+            terminal.error('systemd support not yet implemented')
+            raise SystemExit(1)
+        self.create(self.args)
diff --git a/src/ceph-volume/ceph_volume/devices/raw/list.py b/src/ceph-volume/ceph_volume/devices/raw/list.py
new file mode 100644 (file)
index 0000000..7e526d3
--- /dev/null
@@ -0,0 +1,118 @@
+from __future__ import print_function
+import argparse
+import base64
+import json
+import logging
+import os
+from textwrap import dedent
+from ceph_volume import decorators, terminal, conf, process
+from ceph_volume.api import lvm
+from ceph_volume.systemd import systemctl
+from ceph_volume.util import arg_validators, system, disk, encryption
+from ceph_volume.util.device import Device
+
+
+logger = logging.getLogger(__name__)
+
+def direct_report(devices):
+    """
+    Other non-cli consumers of listing information will want to consume the
+    report without the need to parse arguments or other flags. This helper
+    bypasses the need to deal with the class interface which is meant for cli
+    handling.
+    """
+    _list = List([])
+    return _list.generate(devices)
+
+
+class List(object):
+
+    help = 'list BlueStore OSDs on raw devices'
+
+    def __init__(self, argv):
+        self.argv = argv
+
+    def generate(self, devs=None):
+        if not devs:
+            logger.debug('Listing block devices via lsblk...')
+            devs = []
+            out, err, ret = process.call([
+                'lsblk', '--paths', '--nodeps', '--output=NAME', '--noheadings'
+            ])
+            assert not ret
+            r = json.loads(''.join(out))
+            for dev in r.get('blockdevices', []):
+                devs.append('/dev/' + dev['name'])
+        result = {}
+        for dev in devs:
+            logger.debug('Examining %s' % dev)
+            # bluestore?
+            out, err, ret = process.call([
+                'ceph-bluestore-tool', 'show-label',
+                '--dev', dev])
+            if ret:
+                logger.debug('No label on %s' % dev)
+                continue
+            oj = json.loads(''.join(out))
+            if dev not in oj:
+                continue
+            if oj[dev]['description'] != 'main':
+                # ignore non-main devices, for now
+                continue
+            whoami = oj[dev]['whoami']
+            result[whoami] = {
+                'type': 'bluestore',
+                'osd_id': int(whoami),
+            }
+            for f in ['osd_uuid', 'ceph_fsid']:
+                result[whoami][f] = oj[dev][f]
+            result[whoami]['device'] = dev
+        return result
+
+    @decorators.needs_root
+    def list(self, args):
+        report = self.generate(args.device)
+        if args.format == 'json':
+            print(json.dumps(report, indent=4, sort_keys=True))
+        else:
+            if not report:
+                raise SystemExit('No valid Ceph devices found')
+            raise RuntimeError('not implemented yet')
+
+    def main(self):
+        sub_command_help = dedent("""
+        List OSDs on raw devices with raw device labels (usually the first
+        block of the device).
+
+        Full listing of all identifiable (currently, BlueStore) OSDs
+        on raw devices:
+
+            ceph-volume raw list
+
+        List a particular device, reporting all metadata about it::
+
+            ceph-volume raw list /dev/sda1
+
+        """)
+        parser = argparse.ArgumentParser(
+            prog='ceph-volume raw list',
+            formatter_class=argparse.RawDescriptionHelpFormatter,
+            description=sub_command_help,
+        )
+
+        parser.add_argument(
+            'device',
+            metavar='DEVICE',
+            nargs='*',
+            help='Path to a device like /dev/sda1'
+        )
+
+        parser.add_argument(
+            '--format',
+            help='output format, defaults to "pretty"',
+            default='json',
+            choices=['json', 'pretty'],
+        )
+
+        args = parser.parse_args(self.argv)
+        self.list(args)
diff --git a/src/ceph-volume/ceph_volume/devices/raw/main.py b/src/ceph-volume/ceph_volume/devices/raw/main.py
new file mode 100644 (file)
index 0000000..61286ac
--- /dev/null
@@ -0,0 +1,42 @@
+import argparse
+from textwrap import dedent
+from ceph_volume import terminal
+from . import list
+from . import create
+from . import prepare
+from . import activate
+
+class Raw(object):
+
+    help = 'Manage single-device OSDs on raw block devices'
+
+    _help = dedent("""
+    Manage a single-device OSD on a raw block device.  Rely on
+    the existing device labels to store any needed metadata.
+
+    {sub_help}
+    """)
+
+    mapper = {
+        'list': list.List,
+        'create': create.Create,
+        'prepare': prepare.Prepare,
+        'activate': activate.Activate,
+    }
+
+    def __init__(self, argv):
+        self.argv = argv
+
+    def print_help(self, sub_help):
+        return self._help.format(sub_help=sub_help)
+
+    def main(self):
+        terminal.dispatch(self.mapper, self.argv)
+        parser = argparse.ArgumentParser(
+            prog='ceph-volume raw',
+            formatter_class=argparse.RawDescriptionHelpFormatter,
+            description=self.print_help(terminal.subhelp(self.mapper)),
+        )
+        parser.parse_args(self.argv)
+        if len(self.argv) <= 1:
+            return parser.print_help()
diff --git a/src/ceph-volume/ceph_volume/devices/raw/prepare.py b/src/ceph-volume/ceph_volume/devices/raw/prepare.py
new file mode 100644 (file)
index 0000000..ff59b6f
--- /dev/null
@@ -0,0 +1,126 @@
+from __future__ import print_function
+import argparse
+import json
+import logging
+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.devices.lvm.common import rollback_osd
+from .common import create_parser
+
+logger = logging.getLogger(__name__)
+
+
+def prepare_bluestore(block, secrets, osd_id, fsid, tmpfs):
+    """
+    :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())
+
+    # create the directory
+    prepare_utils.create_osd_path(osd_id, tmpfs=tmpfs)
+    # 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,
+    )
+
+
+class Prepare(object):
+
+    help = 'Format a raw device and associate it with a (BlueStore) OSD'
+
+    def __init__(self, argv):
+        self.argv = argv
+        self.osd_id = None
+
+    def safe_prepare(self, args=None):
+        """
+        An intermediate step between `main()` and `prepare()` so that we can
+        capture the `self.osd_id` in case we need to rollback
+
+        :param args: Injected args, usually from `raw create` which compounds
+                     both `prepare` and `create`
+        """
+        if args is not None:
+            self.args = args
+        try:
+            self.prepare()
+        except Exception:
+            logger.exception('raw prepare was unable to complete')
+            logger.info('will rollback OSD ID creation')
+            rollback_osd(self.args, self.osd_id)
+            raise
+        terminal.success("ceph-volume raw prepare successful for: %s" % self.args.data)
+
+    def get_cluster_fsid(self):
+        """
+        Allows using --cluster-fsid as an argument, but can fallback to reading
+        from ceph.conf if that is unset (the default behavior).
+        """
+        if self.args.cluster_fsid:
+            return self.args.cluster_fsid
+        else:
+            return conf.ceph.get('global', 'fsid')
+
+    @decorators.needs_root
+    def prepare(self):
+        secrets = {'cephx_secret': prepare_utils.create_key()}
+        cluster_fsid = self.get_cluster_fsid()
+        osd_fsid = self.args.osd_fsid or system.generate_uuid()
+        crush_device_class = self.args.crush_device_class
+        if crush_device_class:
+            secrets['crush_device_class'] = crush_device_class
+        tmpfs = not self.args.no_tmpfs
+        # 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=self.args.osd_id)
+        prepare_bluestore(
+            self.args.data,
+            secrets,
+            self.osd_id,
+            osd_fsid,
+            tmpfs,
+        )
+
+    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 the volume.
+
+        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.
+
+        Encryption is not supported.
+
+        DB and WAL devices are not supported.
+
+            ceph-volume raw prepare --bluestore --data {device}
+
+        """)
+        parser = create_parser(
+            prog='ceph-volume raw prepare',
+            description=sub_command_help,
+        )
+        if len(self.argv) == 0:
+            print(sub_command_help)
+            return
+        self.args = parser.parse_args(self.argv)
+        if not self.args.bluestore:
+            terminal.error('must specify --bluestore (currently the only supported backend)')
+            raise SystemExit(1)
+
+        self.safe_prepare(self.args)
diff --git a/src/ceph-volume/ceph_volume/devices/raw/scan.py b/src/ceph-volume/ceph_volume/devices/raw/scan.py
new file mode 100644 (file)
index 0000000..604e11c
--- /dev/null
@@ -0,0 +1,23 @@
+from __future__ import print_function
+import argparse
+import base64
+import json
+import logging
+import os
+from textwrap import dedent
+from ceph_volume import decorators, terminal, conf
+from ceph_volume.api import lvm
+from ceph_volume.systemd import systemctl
+from ceph_volume.util import arg_validators, system, disk, encryption
+from ceph_volume.util.device import Device
+
+
+logger = logging.getLogger(__name__)
+
+class Scan(object):
+
+    help = 'Capture metadata from all running ceph-disk OSDs, OSD data partition or directory'
+
+    def __init__(self, argv):
+        self.argv = argv
+        self._etc_path = '/etc/ceph/osd/'
index f396daf002546048689a08762548c6811e25c131..8c5801c5db59e394d8795d6a70421bc692b14f50 100644 (file)
@@ -27,6 +27,7 @@ Ceph Conf: {ceph_path}
         self.mapper = {
             'lvm': devices.lvm.LVM,
             'simple': devices.simple.Simple,
+            'raw': devices.raw.Raw,
             'inventory': inventory.Inventory,
         }
         self.plugin_help = "No plugins found/loaded"