X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=ceph%2Fsrc%2Fceph-volume%2Fceph_volume%2Futil%2Fprepare.py;h=d1cddf073a6978c22989c4d3b20d58043e4c677a;hb=28e407b858acd3bddc89f68583571f771bb42e46;hp=eefa0adc2041526ee1358c50762ae5146e295c77;hpb=d2e6a577eb19928d58b31d1b6e096ca0f03c4052;p=ceph.git diff --git a/ceph/src/ceph-volume/ceph_volume/util/prepare.py b/ceph/src/ceph-volume/ceph_volume/util/prepare.py index eefa0adc2..d1cddf073 100644 --- a/ceph/src/ceph-volume/ceph_volume/util/prepare.py +++ b/ceph/src/ceph-volume/ceph_volume/util/prepare.py @@ -6,6 +6,7 @@ the single-call helper """ import os import logging +import json from ceph_volume import process, conf from ceph_volume.util import system, constants @@ -13,33 +14,77 @@ logger = logging.getLogger(__name__) def create_key(): - stdout, stderr, returncode = process.call(['ceph-authtool', '--gen-print-key']) + stdout, stderr, returncode = process.call( + ['ceph-authtool', '--gen-print-key'], + show_command=True) if returncode != 0: raise RuntimeError('Unable to generate a new auth key') return ' '.join(stdout).strip() -def write_keyring(osd_id, secret): - # FIXME this only works for cephx, but there will be other types of secrets - # later - osd_keyring = '/var/lib/ceph/osd/%s-%s/keyring' % (conf.cluster, osd_id) +def write_keyring(osd_id, secret, keyring_name='keyring', name=None): + """ + Create a keyring file with the ``ceph-authtool`` utility. Constructs the + path over well-known conventions for the OSD, and allows any other custom + ``name`` to be set. + + :param osd_id: The ID for the OSD to be used + :param secret: The key to be added as (as a string) + :param name: Defaults to 'osd.{ID}' but can be used to add other client + names, specifically for 'lockbox' type of keys + :param keyring_name: Alternative keyring name, for supporting other + types of keys like for lockbox + """ + osd_keyring = '/var/lib/ceph/osd/%s-%s/%s' % (conf.cluster, osd_id, keyring_name) + name = name or 'osd.%s' % str(osd_id) process.run( [ 'ceph-authtool', osd_keyring, '--create-keyring', - '--name', 'osd.%s' % str(osd_id), + '--name', name, '--add-key', secret ]) system.chown(osd_keyring) - # TODO: do the restorecon dance on the osd_keyring path -def create_id(fsid, json_secrets): +def create_id(fsid, json_secrets, osd_id=None): """ :param fsid: The osd fsid to create, always required :param json_secrets: a json-ready object with whatever secrets are wanted to be passed to the monitor + :param osd_id: Reuse an existing ID from an OSD that's been destroyed, if the + id does not exist in the cluster a new ID will be created + """ + bootstrap_keyring = '/var/lib/ceph/bootstrap-osd/%s.keyring' % conf.cluster + cmd = [ + 'ceph', + '--cluster', conf.cluster, + '--name', 'client.bootstrap-osd', + '--keyring', bootstrap_keyring, + '-i', '-', + 'osd', 'new', fsid + ] + if check_id(osd_id): + cmd.append(osd_id) + stdout, stderr, returncode = process.call( + cmd, + stdin=json_secrets, + show_command=True + ) + if returncode != 0: + raise RuntimeError('Unable to create a new OSD id') + return ' '.join(stdout).strip() + + +def check_id(osd_id): """ + Checks to see if an osd ID exists or not. Returns True + if it does exist, False if it doesn't. + + :param osd_id: The osd ID to check + """ + if osd_id is None: + return False bootstrap_keyring = '/var/lib/ceph/bootstrap-osd/%s.keyring' % conf.cluster stdout, stderr, returncode = process.call( [ @@ -47,23 +92,39 @@ def create_id(fsid, json_secrets): '--cluster', conf.cluster, '--name', 'client.bootstrap-osd', '--keyring', bootstrap_keyring, - '-i', '-', - 'osd', 'new', fsid + 'osd', + 'tree', + '-f', 'json', ], - stdin=json_secrets + show_command=True ) if returncode != 0: - raise RuntimeError('Unable to create a new OSD id') - return ' '.join(stdout).strip() + raise RuntimeError('Unable check if OSD id exists: %s' % osd_id) + + output = json.loads(''.join(stdout).strip()) + osds = output['nodes'] + return any([str(osd['id']) == str(osd_id) for osd in osds]) -def create_path(osd_id): +def mount_tmpfs(path): + process.run([ + 'mount', + '-t', + 'tmpfs', 'tmpfs', + path + ]) + + +def create_osd_path(osd_id, tmpfs=False): + path = '/var/lib/ceph/osd/%s-%s' % (conf.cluster, osd_id) system.mkdir_p('/var/lib/ceph/osd/%s-%s' % (conf.cluster, osd_id)) + if tmpfs: + mount_tmpfs(path) def format_device(device): # only supports xfs - command = ['sudo', 'mkfs', '-t', 'xfs'] + command = ['mkfs', '-t', 'xfs'] # get the mkfs options if any for xfs, # fallback to the default options defined in constants.mkfs @@ -83,30 +144,109 @@ def format_device(device): process.run(command) -def mount_osd(device, osd_id): +def _normalize_mount_flags(flags, extras=None): + """ + Mount flag options have to be a single string, separated by a comma. If the + flags are separated by spaces, or with commas and spaces in ceph.conf, the + mount options will be passed incorrectly. + + This will help when parsing ceph.conf values return something like:: + + ["rw,", "exec,"] + + Or:: + + [" rw ,", "exec"] + + :param flags: A list of flags, or a single string of mount flags + :param extras: Extra set of mount flags, useful when custom devices like VDO need + ad-hoc mount configurations + """ + # Instead of using set(), we append to this new list here, because set() + # will create an arbitrary order on the items that is made worst when + # testing with tools like tox that includes a randomizer seed. By + # controlling the order, it is easier to correctly assert the expectation + unique_flags = [] + if isinstance(flags, list): + if extras: + flags.extend(extras) + + # ensure that spaces and commas are removed so that they can join + # correctly, remove duplicates + for f in flags: + if f and f not in unique_flags: + unique_flags.append(f.strip().strip(',')) + return ','.join(unique_flags) + + # split them, clean them, and join them back again + flags = flags.strip().split(' ') + if extras: + flags.extend(extras) + + # remove possible duplicates + for f in flags: + if f and f not in unique_flags: + unique_flags.append(f.strip().strip(',')) + flags = ','.join(unique_flags) + # Before returning, split them again, since strings can be mashed up + # together, preventing removal of duplicate entries + return ','.join(set(flags.split(','))) + + +def mount_osd(device, osd_id, **kw): + extras = [] + is_vdo = kw.get('is_vdo', '0') + if is_vdo == '1': + extras = ['discard'] destination = '/var/lib/ceph/osd/%s-%s' % (conf.cluster, osd_id) - command = ['sudo', 'mount', '-t', 'xfs', '-o'] + command = ['mount', '-t', 'xfs', '-o'] flags = conf.ceph.get_list( 'osd', 'osd_mount_options_xfs', default=constants.mount.get('xfs'), split=' ', ) - command.append(flags) + command.append( + _normalize_mount_flags(flags, extras=extras) + ) command.append(device) command.append(destination) process.run(command) -def link_journal(journal_device, osd_id): - journal_path = '/var/lib/ceph/osd/%s-%s/journal' % ( +def _link_device(device, device_type, osd_id): + """ + Allow linking any device type in an OSD directory. ``device`` must the be + source, with an absolute path and ``device_type`` will be the destination + name, like 'journal', or 'block' + """ + device_path = '/var/lib/ceph/osd/%s-%s/%s' % ( conf.cluster, - osd_id + osd_id, + device_type ) - command = ['sudo', 'ln', '-s', journal_device, journal_path] + command = ['ln', '-s', device, device_path] + system.chown(device) + process.run(command) +def link_journal(journal_device, osd_id): + _link_device(journal_device, 'journal', osd_id) + + +def link_block(block_device, osd_id): + _link_device(block_device, 'block', osd_id) + + +def link_wal(wal_device, osd_id): + _link_device(wal_device, 'block.wal', osd_id) + + +def link_db(db_device, osd_id): + _link_device(db_device, 'block.db', osd_id) + + def get_monmap(osd_id): """ Before creating the OSD files, a monmap needs to be retrieved so that it @@ -121,7 +261,6 @@ def get_monmap(osd_id): monmap_destination = os.path.join(path, 'activate.monmap') process.run([ - 'sudo', 'ceph', '--cluster', conf.cluster, '--name', 'client.bootstrap-osd', @@ -130,7 +269,65 @@ def get_monmap(osd_id): ]) -def osd_mkfs(osd_id, fsid): +def osd_mkfs_bluestore(osd_id, fsid, keyring=None, wal=False, db=False): + """ + Create the files for the OSD to function. A normal call will look like: + + ceph-osd --cluster ceph --mkfs --mkkey -i 0 \ + --monmap /var/lib/ceph/osd/ceph-0/activate.monmap \ + --osd-data /var/lib/ceph/osd/ceph-0 \ + --osd-uuid 8d208665-89ae-4733-8888-5d3bfbeeec6c \ + --keyring /var/lib/ceph/osd/ceph-0/keyring \ + --setuser ceph --setgroup ceph + + In some cases it is required to use the keyring, when it is passed in as + a keywork argument it is used as part of the ceph-osd command + """ + path = '/var/lib/ceph/osd/%s-%s/' % (conf.cluster, osd_id) + monmap = os.path.join(path, 'activate.monmap') + + system.chown(path) + + base_command = [ + 'ceph-osd', + '--cluster', conf.cluster, + # undocumented flag, sets the `type` file to contain 'bluestore' + '--osd-objectstore', 'bluestore', + '--mkfs', + '-i', osd_id, + '--monmap', monmap, + ] + + supplementary_command = [ + '--osd-data', path, + '--osd-uuid', fsid, + '--setuser', 'ceph', + '--setgroup', 'ceph' + ] + + if keyring is not None: + base_command.extend(['--keyfile', '-']) + + if wal: + base_command.extend( + ['--bluestore-block-wal-path', wal] + ) + system.chown(wal) + + if db: + base_command.extend( + ['--bluestore-block-db-path', db] + ) + system.chown(db) + + command = base_command + supplementary_command + + _, _, returncode = process.call(command, stdin=keyring, show_command=True) + if returncode != 0: + raise RuntimeError('Command failed with exit code %s: %s' % (returncode, ' '.join(command))) + + +def osd_mkfs_filestore(osd_id, fsid): """ Create the files for the OSD to function. A normal call will look like: @@ -151,9 +348,10 @@ def osd_mkfs(osd_id, fsid): system.chown(path) process.run([ - 'sudo', 'ceph-osd', '--cluster', conf.cluster, + # undocumented flag, sets the `type` file to contain 'filestore' + '--osd-objectstore', 'filestore', '--mkfs', '-i', osd_id, '--monmap', monmap,