--- /dev/null
+#!/usr/bin/env python
+#
+# Copyright (C) 2015, 2016, 2017 Red Hat <contact@redhat.com>
+# Copyright (C) 2014 Inktank <info@inktank.com>
+# Copyright (C) 2014 Cloudwatt <libre.licensing@cloudwatt.com>
+# Copyright (C) 2014 Catalyst.net Ltd
+#
+# Author: Loic Dachary <loic@dachary.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Library Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library Public License for more details.
+#
+
+from __future__ import print_function
+
+import argparse
+import base64
+import errno
+import fcntl
+import functools
+import json
+import logging
+import os
+import platform
+import re
+import subprocess
+import stat
+import sys
+import tempfile
+import uuid
+import time
+import shlex
+import shutil
+import pwd
+import grp
+import textwrap
+import glob
+
+CEPH_OSD_ONDISK_MAGIC = 'ceph osd volume v026'
+CEPH_LOCKBOX_ONDISK_MAGIC = 'ceph lockbox volume v001'
+
+KEY_MANAGEMENT_MODE_V1 = 'ceph-mon v1'
+
+PTYPE = {
+ 'regular': {
+ 'journal': {
+ # identical because creating a journal is atomic
+ 'ready': '45b0969e-9b03-4f30-b4c6-b4b80ceff106',
+ 'tobe': '45b0969e-9b03-4f30-b4c6-b4b80ceff106',
+ },
+ 'block': {
+ # identical because creating a block is atomic
+ 'ready': 'cafecafe-9b03-4f30-b4c6-b4b80ceff106',
+ 'tobe': 'cafecafe-9b03-4f30-b4c6-b4b80ceff106',
+ },
+ 'block.db': {
+ # identical because creating a block is atomic
+ 'ready': '30cd0809-c2b2-499c-8879-2d6b78529876',
+ 'tobe': '30cd0809-c2b2-499c-8879-2d6b785292be',
+ },
+ 'block.wal': {
+ # identical because creating a block is atomic
+ 'ready': '5ce17fce-4087-4169-b7ff-056cc58473f9',
+ 'tobe': '5ce17fce-4087-4169-b7ff-056cc58472be',
+ },
+ 'osd': {
+ 'ready': '4fbd7e29-9d25-41b8-afd0-062c0ceff05d',
+ 'tobe': '89c57f98-2fe5-4dc0-89c1-f3ad0ceff2be',
+ },
+ 'lockbox': {
+ 'ready': 'fb3aabf9-d25f-47cc-bf5e-721d1816496b',
+ 'tobe': 'fb3aabf9-d25f-47cc-bf5e-721d181642be',
+ },
+ },
+ 'luks': {
+ 'journal': {
+ 'ready': '45b0969e-9b03-4f30-b4c6-35865ceff106',
+ 'tobe': '89c57f98-2fe5-4dc0-89c1-35865ceff2be',
+ },
+ 'block': {
+ 'ready': 'cafecafe-9b03-4f30-b4c6-35865ceff106',
+ 'tobe': '89c57f98-2fe5-4dc0-89c1-35865ceff2be',
+ },
+ 'block.db': {
+ 'ready': '166418da-c469-4022-adf4-b30afd37f176',
+ 'tobe': '7521c784-4626-4260-bc8d-ba77a0f5f2be',
+ },
+ 'block.wal': {
+ 'ready': '86a32090-3647-40b9-bbbd-38d8c573aa86',
+ 'tobe': '92dad30f-175b-4d40-a5b0-5c0a258b42be',
+ },
+ 'osd': {
+ 'ready': '4fbd7e29-9d25-41b8-afd0-35865ceff05d',
+ 'tobe': '89c57f98-2fe5-4dc0-89c1-5ec00ceff2be',
+ },
+ },
+ 'plain': {
+ 'journal': {
+ 'ready': '45b0969e-9b03-4f30-b4c6-5ec00ceff106',
+ 'tobe': '89c57f98-2fe5-4dc0-89c1-35865ceff2be',
+ },
+ 'block': {
+ 'ready': 'cafecafe-9b03-4f30-b4c6-5ec00ceff106',
+ 'tobe': '89c57f98-2fe5-4dc0-89c1-35865ceff2be',
+ },
+ 'block.db': {
+ 'ready': '93b0052d-02d9-4d8a-a43b-33a3ee4dfbc3',
+ 'tobe': '69d17c68-3e58-4399-aff0-b68265f2e2be',
+ },
+ 'block.wal': {
+ 'ready': '306e8683-4fe2-4330-b7c0-00a917c16966',
+ 'tobe': 'f2d89683-a621-4063-964a-eb1f7863a2be',
+ },
+ 'osd': {
+ 'ready': '4fbd7e29-9d25-41b8-afd0-5ec00ceff05d',
+ 'tobe': '89c57f98-2fe5-4dc0-89c1-5ec00ceff2be',
+ },
+ },
+ 'mpath': {
+ 'journal': {
+ 'ready': '45b0969e-8ae0-4982-bf9d-5a8d867af560',
+ 'tobe': '45b0969e-8ae0-4982-bf9d-5a8d867af560',
+ },
+ 'block': {
+ 'ready': 'cafecafe-8ae0-4982-bf9d-5a8d867af560',
+ 'tobe': 'cafecafe-8ae0-4982-bf9d-5a8d867af560',
+ },
+ 'block.db': {
+ 'ready': 'ec6d6385-e346-45dc-be91-da2a7c8b3261',
+ 'tobe': 'ec6d6385-e346-45dc-be91-da2a7c8b32be',
+ },
+ 'block.wal': {
+ 'ready': '01b41e1b-002a-453c-9f17-88793989ff8f',
+ 'tobe': '01b41e1b-002a-453c-9f17-88793989f2be',
+ },
+ 'osd': {
+ 'ready': '4fbd7e29-8ae0-4982-bf9d-5a8d867af560',
+ 'tobe': '89c57f98-8ae0-4982-bf9d-5a8d867af560',
+ },
+ 'lockbox': {
+ 'ready': '7f4a666a-16f3-47a2-8445-152ef4d03f6c',
+ 'tobe': '7f4a666a-16f3-47a2-8445-152ef4d032be',
+ },
+ },
+}
+
+try:
+ # see https://bugs.python.org/issue23098
+ os.major(0x80002b00)
+except OverflowError:
+ os.major = lambda devid: ((devid >> 8) & 0xfff) | ((devid >> 32) & ~0xfff)
+ os.minor = lambda devid: (devid & 0xff) | ((devid >> 12) & ~0xff)
+
+
+class Ptype(object):
+
+ @staticmethod
+ def get_ready_by_type(what):
+ return [x['ready'] for x in PTYPE[what].values()]
+
+ @staticmethod
+ def get_ready_by_name(name):
+ return [x[name]['ready'] for x in PTYPE.values() if name in x]
+
+ @staticmethod
+ def is_regular_space(ptype):
+ return Ptype.is_what_space('regular', ptype)
+
+ @staticmethod
+ def is_mpath_space(ptype):
+ return Ptype.is_what_space('mpath', ptype)
+
+ @staticmethod
+ def is_plain_space(ptype):
+ return Ptype.is_what_space('plain', ptype)
+
+ @staticmethod
+ def is_luks_space(ptype):
+ return Ptype.is_what_space('luks', ptype)
+
+ @staticmethod
+ def is_what_space(what, ptype):
+ for name in Space.NAMES:
+ if ptype == PTYPE[what][name]['ready']:
+ return True
+ return False
+
+ @staticmethod
+ def space_ptype_to_name(ptype):
+ for what in PTYPE.values():
+ for name in Space.NAMES:
+ if ptype == what[name]['ready']:
+ return name
+ raise ValueError('ptype ' + ptype + ' not found')
+
+ @staticmethod
+ def is_dmcrypt_space(ptype):
+ for name in Space.NAMES:
+ if Ptype.is_dmcrypt(ptype, name):
+ return True
+ return False
+
+ @staticmethod
+ def is_dmcrypt(ptype, name):
+ for what in ('plain', 'luks'):
+ if ptype == PTYPE[what][name]['ready']:
+ return True
+ return False
+
+
+SYSFS = '/sys'
+
+if platform.system() == 'FreeBSD':
+ FREEBSD = True
+ DEFAULT_FS_TYPE = 'zfs'
+ PROCDIR = '/compat/linux/proc'
+ # FreeBSD does not have blockdevices any more
+ BLOCKDIR = '/dev'
+ ROOTGROUP = 'wheel'
+else:
+ FREEBSD = False
+ DEFAULT_FS_TYPE = 'xfs'
+ PROCDIR = '/proc'
+ BLOCKDIR = '/sys/block'
+ ROOTGROUP = 'root'
+
+"""
+OSD STATUS Definition
+"""
+OSD_STATUS_OUT_DOWN = 0
+OSD_STATUS_OUT_UP = 1
+OSD_STATUS_IN_DOWN = 2
+OSD_STATUS_IN_UP = 3
+
+MOUNT_OPTIONS = dict(
+ btrfs='noatime,user_subvol_rm_allowed',
+ # user_xattr is default ever since linux 2.6.39 / 3.0, but we'll
+ # delay a moment before removing it fully because we did have some
+ # issues with ext4 before the xatts-in-leveldb work, and it seemed
+ # that user_xattr helped
+ ext4='noatime,user_xattr',
+ xfs='noatime,inode64',
+)
+
+MKFS_ARGS = dict(
+ btrfs=[
+ # btrfs requires -f, for the same reason as xfs (see comment below)
+ '-f',
+ '-m', 'single',
+ '-l', '32768',
+ '-n', '32768',
+ ],
+ xfs=[
+ # xfs insists on not overwriting previous fs; even if we wipe
+ # partition table, we often recreate it exactly the same way,
+ # so we'll see ghosts of filesystems past
+ '-f',
+ '-i', 'size=2048',
+ ],
+ zfs=[
+ '-o', 'atime=off'
+ ],
+)
+
+INIT_SYSTEMS = [
+ 'upstart',
+ 'sysvinit',
+ 'systemd',
+ 'openrc',
+ 'bsdrc',
+ 'auto',
+ 'none',
+]
+
+STATEDIR = '/var/lib/ceph'
+
+SYSCONFDIR = '/etc/ceph'
+
+prepare_lock = None
+activate_lock = None
+SUPPRESS_PREFIX = None
+
+# only warn once about some things
+warned_about = {}
+
+# Nuke the TERM variable to avoid confusing any subprocesses we call.
+# For example, libreadline will print weird control sequences for some
+# TERM values.
+if 'TERM' in os.environ:
+ del os.environ['TERM']
+
+LOG_NAME = __name__
+if LOG_NAME == '__main__':
+ LOG_NAME = os.path.basename(sys.argv[0])
+LOG = logging.getLogger(LOG_NAME)
+
+# Allow user-preferred values for subprocess user and group
+CEPH_PREF_USER = None
+CEPH_PREF_GROUP = None
+
+
+class FileLock(object):
+ def __init__(self, fn):
+ self.fn = fn
+ self.fd = None
+
+ def __enter__(self):
+ assert not self.fd
+ self.fd = os.open(self.fn, os.O_WRONLY | os.O_CREAT)
+ fcntl.lockf(self.fd, fcntl.LOCK_EX)
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ assert self.fd
+ fcntl.lockf(self.fd, fcntl.LOCK_UN)
+ os.close(self.fd)
+ self.fd = None
+
+
+class Error(Exception):
+ """
+ Error
+ """
+
+ def __str__(self):
+ doc = _bytes2str(self.__doc__.strip())
+ try:
+ str_type = basestring
+ except NameError:
+ str_type = str
+ args = [a if isinstance(a, str_type) else str(a) for a in self.args]
+ return ': '.join([doc] + [_bytes2str(a) for a in args])
+
+
+class MountError(Error):
+ """
+ Mounting filesystem failed
+ """
+
+
+class UnmountError(Error):
+ """
+ Unmounting filesystem failed
+ """
+
+
+class BadMagicError(Error):
+ """
+ Does not look like a Ceph OSD, or incompatible version
+ """
+
+
+class TruncatedLineError(Error):
+ """
+ Line is truncated
+ """
+
+
+class TooManyLinesError(Error):
+ """
+ Too many lines
+ """
+
+
+class FilesystemTypeError(Error):
+ """
+ Cannot discover filesystem type
+ """
+
+
+class CephDiskException(Exception):
+ """
+ A base exception for ceph-disk to provide custom (ad-hoc) messages that
+ will be caught and dealt with when main() is executed
+ """
+ pass
+
+
+class ExecutableNotFound(CephDiskException):
+ """
+ Exception to report on executables not available in PATH
+ """
+ pass
+
+
+def is_systemd():
+ """
+ Detect whether systemd is running
+ """
+ with open(PROCDIR + '/1/comm', 'r') as f:
+ return 'systemd' in f.read()
+
+
+def is_upstart():
+ """
+ Detect whether upstart is running
+ """
+ (out, err, _) = command(['init', '--version'])
+ return 'upstart' in out
+
+
+def maybe_mkdir(*a, **kw):
+ """
+ Creates a new directory if it doesn't exist, removes
+ existing symlink before creating the directory.
+ """
+ # remove any symlink, if it is there..
+ if os.path.exists(*a) and stat.S_ISLNK(os.lstat(*a).st_mode):
+ LOG.debug('Removing old symlink at %s', *a)
+ os.unlink(*a)
+ try:
+ os.mkdir(*a, **kw)
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ pass
+ else:
+ raise
+
+
+def which(executable):
+ """find the location of an executable"""
+ envpath = os.environ.get('PATH') or os.defpath
+ PATH = envpath.split(os.pathsep)
+
+ locations = PATH + [
+ '/usr/local/bin',
+ '/bin',
+ '/usr/bin',
+ '/usr/local/sbin',
+ '/usr/sbin',
+ '/sbin',
+ ]
+
+ for location in locations:
+ executable_path = os.path.join(location, executable)
+ if (os.path.isfile(executable_path) and
+ os.access(executable_path, os.X_OK)):
+ return executable_path
+
+
+def _get_command_executable(arguments):
+ """
+ Return the full path for an executable, raise if the executable is not
+ found. If the executable has already a full path do not perform any checks.
+ """
+ if os.path.isabs(arguments[0]): # an absolute path
+ return arguments
+ executable = which(arguments[0])
+ if not executable:
+ command_msg = 'Could not run command: %s' % ' '.join(arguments)
+ executable_msg = '%s not in path.' % arguments[0]
+ raise ExecutableNotFound('%s %s' % (executable_msg, command_msg))
+
+ # swap the old executable for the new one
+ arguments[0] = executable
+ return arguments
+
+
+def command(arguments, **kwargs):
+ """
+ Safely execute a ``subprocess.Popen`` call making sure that the
+ executable exists and raising a helpful error message
+ if it does not.
+
+ .. note:: This should be the preferred way of calling ``subprocess.Popen``
+ since it provides the caller with the safety net of making sure that
+ executables *will* be found and will error nicely otherwise.
+
+ This returns the output of the command and the return code of the
+ process in a tuple: (stdout, stderr, returncode).
+ """
+
+ arguments = list(map(_bytes2str, _get_command_executable(arguments)))
+
+ LOG.info('Running command: %s' % ' '.join(arguments))
+ process = subprocess.Popen(
+ arguments,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ **kwargs)
+ out, err = process.communicate()
+
+ return _bytes2str(out), _bytes2str(err), process.returncode
+
+
+def command_with_stdin(arguments, stdin):
+ LOG.info("Running command with stdin: " + " ".join(arguments))
+ process = subprocess.Popen(
+ arguments,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = process.communicate(stdin)
+ LOG.debug(out)
+ if process.returncode != 0:
+ LOG.error(err)
+ raise SystemExit(
+ "'{cmd}' failed with status code {returncode}".format(
+ cmd=arguments,
+ returncode=process.returncode,
+ )
+ )
+ return out
+
+
+def _bytes2str(string):
+ return string.decode('utf-8') if isinstance(string, bytes) else string
+
+
+def command_init(arguments, **kwargs):
+ """
+ Safely execute a non-blocking ``subprocess.Popen`` call
+ making sure that the executable exists and raising a helpful
+ error message if it does not.
+
+ .. note:: This should be the preferred way of calling ``subprocess.Popen``
+ since it provides the caller with the safety net of making sure that
+ executables *will* be found and will error nicely otherwise.
+
+ This returns the process.
+ """
+
+ arguments = list(map(_bytes2str, _get_command_executable(arguments)))
+
+ LOG.info('Running command: %s' % ' '.join(arguments))
+ process = subprocess.Popen(
+ arguments,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ **kwargs)
+ return process
+
+
+def command_wait(process):
+ """
+ Wait for the process finish and parse its output.
+ """
+
+ out, err = process.communicate()
+
+ return _bytes2str(out), _bytes2str(err), process.returncode
+
+
+def command_check_call(arguments, exit=False):
+ """
+ Safely execute a ``subprocess.check_call`` call making sure that the
+ executable exists and raising a helpful error message if it does not.
+
+ When ``exit`` is set to ``True`` this helper will do a clean (sans
+ traceback) system exit.
+ .. note:: This should be the preferred way of calling
+ ``subprocess.check_call`` since it provides the caller with the safety net
+ of making sure that executables *will* be found and will error nicely
+ otherwise.
+ """
+ arguments = _get_command_executable(arguments)
+ command = ' '.join(arguments)
+ LOG.info('Running command: %s', command)
+ try:
+ return subprocess.check_call(arguments)
+ except subprocess.CalledProcessError as error:
+ if exit:
+ if error.output:
+ LOG.error(error.output)
+ raise SystemExit(
+ "'{cmd}' failed with status code {returncode}".format(
+ cmd=command,
+ returncode=error.returncode,
+ )
+ )
+ raise
+
+
+#
+# An alternative block_path implementation would be
+#
+# name = basename(dev)
+# return /sys/devices/virtual/block/$name
+#
+# It is however more fragile because it relies on the fact
+# that the basename of the device the user will use always
+# matches the one the driver will use. On Ubuntu 14.04, for
+# instance, when multipath creates a partition table on
+#
+# /dev/mapper/353333330000007d0 -> ../dm-0
+#
+# it will create partition devices named
+#
+# /dev/mapper/353333330000007d0-part1
+#
+# which is the same device as /dev/dm-1 but not a symbolic
+# link to it:
+#
+# ubuntu@other:~$ ls -l /dev/mapper /dev/dm-1
+# brw-rw---- 1 root disk 252, 1 Aug 15 17:52 /dev/dm-1
+# lrwxrwxrwx 1 root root 7 Aug 15 17:52 353333330000007d0 -> ../dm-0
+# brw-rw---- 1 root disk 252, 1 Aug 15 17:52 353333330000007d0-part1
+#
+# Using the basename in this case fails.
+#
+
+
+def block_path(dev):
+ if FREEBSD:
+ return dev
+ path = os.path.realpath(dev)
+ rdev = os.stat(path).st_rdev
+ (M, m) = (os.major(rdev), os.minor(rdev))
+ return "{sysfs}/dev/block/{M}:{m}".format(sysfs=SYSFS, M=M, m=m)
+
+
+def get_dm_uuid(dev):
+ uuid_path = os.path.join(block_path(dev), 'dm', 'uuid')
+ LOG.debug("get_dm_uuid " + dev + " uuid path is " + uuid_path)
+ if not os.path.exists(uuid_path):
+ return False
+ uuid = open(uuid_path, 'r').read()
+ LOG.debug("get_dm_uuid " + dev + " uuid is " + uuid)
+ return uuid
+
+
+def is_mpath(dev):
+ """
+ True if the path is managed by multipath
+ """
+ if FREEBSD:
+ return False
+ uuid = get_dm_uuid(dev)
+ return (uuid and
+ (re.match('part\d+-mpath-', uuid) or
+ re.match('mpath-', uuid)))
+
+
+def get_dev_name(path):
+ """
+ get device name from path. e.g.::
+
+ /dev/sda -> sda, /dev/cciss/c0d1 -> cciss!c0d1
+
+ a device "name" is something like::
+
+ sdb
+ cciss!c0d1
+
+ """
+ assert path.startswith('/dev/')
+ base = path[5:]
+ return base.replace('/', '!')
+
+
+def get_dev_path(name):
+ """
+ get a path (/dev/...) from a name (cciss!c0d1)
+ a device "path" is something like::
+
+ /dev/sdb
+ /dev/cciss/c0d1
+
+ """
+ return '/dev/' + name.replace('!', '/')
+
+
+def get_dev_relpath(name):
+ """
+ get a relative path to /dev from a name (cciss!c0d1)
+ """
+ return name.replace('!', '/')
+
+
+def get_dev_size(dev, size='megabytes'):
+ """
+ Attempt to get the size of a device so that we can prevent errors
+ from actions to devices that are smaller, and improve error reporting.
+
+ Because we want to avoid breakage in case this approach is not robust, we
+ will issue a warning if we failed to get the size.
+
+ :param size: bytes or megabytes
+ :param dev: the device to calculate the size
+ """
+ fd = os.open(dev, os.O_RDONLY)
+ dividers = {'bytes': 1, 'megabytes': 1024 * 1024}
+ try:
+ device_size = os.lseek(fd, 0, os.SEEK_END)
+ divider = dividers.get(size, 1024 * 1024) # default to megabytes
+ return device_size // divider
+ except Exception as error:
+ LOG.warning('failed to get size of %s: %s' % (dev, str(error)))
+ finally:
+ os.close(fd)
+
+
+def stmode_is_diskdevice(dmode):
+ if stat.S_ISBLK(dmode):
+ return True
+ else:
+ # FreeBSD does not have block devices
+ # All disks are character devices
+ return FREEBSD and stat.S_ISCHR(dmode)
+
+
+def dev_is_diskdevice(dev):
+ dmode = os.stat(dev).st_mode
+ return stmode_is_diskdevice(dmode)
+
+
+def ldev_is_diskdevice(dev):
+ dmode = os.lstat(dev).st_mode
+ return stmode_is_diskdevice(dmode)
+
+
+def path_is_diskdevice(path):
+ dev = os.path.realpath(path)
+ return dev_is_diskdevice(dev)
+
+
+def get_partition_mpath(dev, pnum):
+ part_re = "part{pnum}-mpath-".format(pnum=pnum)
+ partitions = list_partitions_mpath(dev, part_re)
+ if partitions:
+ return partitions[0]
+ else:
+ return None
+
+
+def retry(on_error=Exception, max_tries=10, wait=0.2, backoff=0):
+ def wrapper(func):
+ @functools.wraps(func)
+ def repeat(*args, **kwargs):
+ for tries in range(max_tries - 1):
+ try:
+ return func(*args, **kwargs)
+ except on_error:
+ time.sleep(wait + backoff * tries)
+ return func(*args, **kwargs)
+ return repeat
+ return wrapper
+
+
+@retry(Error)
+def get_partition_dev(dev, pnum):
+ """
+ get the device name for a partition
+
+ assume that partitions are named like the base dev,
+ with a number, and optionally
+ some intervening characters (like 'p'). e.g.,
+
+ sda 1 -> sda1
+ cciss/c0d1 1 -> cciss!c0d1p1
+ """
+ partname = None
+ error_msg = ""
+ if is_mpath(dev):
+ partname = get_partition_mpath(dev, pnum)
+ else:
+ name = get_dev_name(os.path.realpath(dev))
+ sys_entry = os.path.join(BLOCKDIR, name)
+ error_msg = " in %s" % sys_entry
+ for f in os.listdir(sys_entry):
+ if f.startswith(name) and f.endswith(str(pnum)):
+ # we want the shortest name that starts with the base name
+ # and ends with the partition number
+ if not partname or len(f) < len(partname):
+ partname = f
+ if partname:
+ return get_dev_path(partname)
+ else:
+ raise Error('partition %d for %s does not appear to exist%s' %
+ (pnum, dev, error_msg))
+
+
+def list_all_partitions():
+ """
+ Return a list of devices and partitions
+ """
+ if not FREEBSD:
+ names = os.listdir(BLOCKDIR)
+ dev_part_list = {}
+ for name in names:
+ # /dev/fd0 may hang http://tracker.ceph.com/issues/6827
+ if re.match(r'^fd\d$', name):
+ continue
+ dev_part_list[name] = list_partitions(get_dev_path(name))
+ else:
+ with open(os.path.join(PROCDIR, "partitions")) as partitions:
+ for line in partitions:
+ columns = line.split()
+ if len(columns) >= 4:
+ name = columns[3]
+ dev_part_list[name] = list_partitions(get_dev_path(name))
+ return dev_part_list
+
+
+def list_partitions(dev):
+ dev = os.path.realpath(dev)
+ if is_mpath(dev):
+ return list_partitions_mpath(dev)
+ else:
+ return list_partitions_device(dev)
+
+
+def list_partitions_mpath(dev, part_re="part\d+-mpath-"):
+ p = block_path(dev)
+ partitions = []
+ holders = os.path.join(p, 'holders')
+ for holder in os.listdir(holders):
+ uuid_path = os.path.join(holders, holder, 'dm', 'uuid')
+ uuid = open(uuid_path, 'r').read()
+ LOG.debug("list_partitions_mpath: " + uuid_path + " uuid = " + uuid)
+ if re.match(part_re, uuid):
+ partitions.append(holder)
+ return partitions
+
+
+def list_partitions_device(dev):
+ """
+ Return a list of partitions on the given device name
+ """
+ partitions = []
+ basename = get_dev_name(dev)
+ for name in os.listdir(block_path(dev)):
+ if name.startswith(basename):
+ partitions.append(name)
+ return partitions
+
+
+def get_partition_base(dev):
+ """
+ Get the base device for a partition
+ """
+ dev = os.path.realpath(dev)
+ if not ldev_is_diskdevice(dev):
+ raise Error('not a block device', dev)
+
+ name = get_dev_name(dev)
+ if os.path.exists(os.path.join('/sys/block', name)):
+ raise Error('not a partition', dev)
+
+ # find the base
+ for basename in os.listdir('/sys/block'):
+ if os.path.exists(os.path.join('/sys/block', basename, name)):
+ return get_dev_path(basename)
+ raise Error('no parent device for partition', dev)
+
+
+def is_partition_mpath(dev):
+ uuid = get_dm_uuid(dev)
+ return bool(re.match('part\d+-mpath-', uuid))
+
+
+def partnum_mpath(dev):
+ uuid = get_dm_uuid(dev)
+ return re.findall('part(\d+)-mpath-', uuid)[0]
+
+
+def get_partition_base_mpath(dev):
+ slave_path = os.path.join(block_path(dev), 'slaves')
+ slaves = os.listdir(slave_path)
+ assert slaves
+ name_path = os.path.join(slave_path, slaves[0], 'dm', 'name')
+ name = open(name_path, 'r').read().strip()
+ return os.path.join('/dev/mapper', name)
+
+
+def is_partition(dev):
+ """
+ Check whether a given device path is a partition or a full disk.
+ """
+ if is_mpath(dev):
+ return is_partition_mpath(dev)
+
+ dev = os.path.realpath(dev)
+ st = os.lstat(dev)
+ if not stmode_is_diskdevice(st.st_mode):
+ raise Error('not a block device', dev)
+
+ name = get_dev_name(dev)
+ if os.path.exists(os.path.join(BLOCKDIR, name)):
+ return False
+
+ # make sure it is a partition of something else
+ major = os.major(st.st_rdev)
+ minor = os.minor(st.st_rdev)
+ if os.path.exists('/sys/dev/block/%d:%d/partition' % (major, minor)):
+ return True
+
+ raise Error('not a disk or partition', dev)
+
+
+def is_mounted(dev):
+ """
+ Check if the given device is mounted.
+ """
+ dev = os.path.realpath(dev)
+ with open(PROCDIR + '/mounts', 'rb') as proc_mounts:
+ for line in proc_mounts:
+ fields = line.split()
+ if len(fields) < 3:
+ continue
+ mounts_dev = fields[0]
+ path = fields[1]
+ if os.path.isabs(mounts_dev) and os.path.exists(mounts_dev):
+ mounts_dev = os.path.realpath(mounts_dev)
+ if mounts_dev == dev:
+ return _bytes2str(path)
+ return None
+
+
+def is_held(dev):
+ """
+ Check if a device is held by another device (e.g., a dm-crypt mapping)
+ """
+ assert os.path.exists(dev)
+ if is_mpath(dev):
+ return []
+
+ dev = os.path.realpath(dev)
+ base = get_dev_name(dev)
+
+ # full disk?
+ directory = '/sys/block/{base}/holders'.format(base=base)
+ if os.path.exists(directory):
+ return os.listdir(directory)
+
+ # partition?
+ part = base
+ while len(base):
+ directory = '/sys/block/{base}/{part}/holders'.format(
+ part=part, base=base)
+ if os.path.exists(directory):
+ return os.listdir(directory)
+ base = base[:-1]
+ return []
+
+
+def verify_not_in_use(dev, check_partitions=False):
+ """
+ Verify if a given device (path) is in use (e.g. mounted or
+ in use by device-mapper).
+
+ :raises: Error if device is in use.
+ """
+ assert os.path.exists(dev)
+ if is_mounted(dev):
+ raise Error('Device is mounted', dev)
+ holders = is_held(dev)
+ if holders:
+ raise Error('Device %s is in use by a device-mapper '
+ 'mapping (dm-crypt?)' % dev, ','.join(holders))
+
+ if check_partitions and not is_partition(dev):
+ for partname in list_partitions(dev):
+ partition = get_dev_path(partname)
+ if is_mounted(partition):
+ raise Error('Device is mounted', partition)
+ holders = is_held(partition)
+ if holders:
+ raise Error('Device %s is in use by a device-mapper '
+ 'mapping (dm-crypt?)'
+ % partition, ','.join(holders))
+
+
+def must_be_one_line(line):
+ """
+ Checks if given line is really one single line.
+
+ :raises: TruncatedLineError or TooManyLinesError
+ :return: Content of the line, or None if line isn't valid.
+ """
+ line = _bytes2str(line)
+
+ if line[-1:] != '\n':
+ raise TruncatedLineError(line)
+ line = line[:-1]
+ if '\n' in line:
+ raise TooManyLinesError(line)
+ return line
+
+
+def read_one_line(parent, name):
+ """
+ Read a file whose sole contents are a single line.
+
+ Strips the newline.
+
+ :return: Contents of the line, or None if file did not exist.
+ """
+ path = os.path.join(parent, name)
+ try:
+ line = open(path, 'rb').read()
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ return None
+ else:
+ raise
+
+ try:
+ line = must_be_one_line(line)
+ except (TruncatedLineError, TooManyLinesError) as e:
+ raise Error(
+ 'File is corrupt: {path}: {msg}'.format(
+ path=path,
+ msg=e,
+ )
+ )
+ return line
+
+
+def write_one_line(parent, name, text):
+ """
+ Write a file whose sole contents are a single line.
+
+ Adds a newline.
+ """
+ path = os.path.join(parent, name)
+ tmp = '{path}.{pid}.tmp'.format(path=path, pid=os.getpid())
+ with open(tmp, 'wb') as tmp_file:
+ tmp_file.write(text.encode('utf-8') + b'\n')
+ os.fsync(tmp_file.fileno())
+ path_set_context(tmp)
+ os.rename(tmp, path)
+
+
+def init_get():
+ """
+ Get a init system using 'ceph-detect-init'
+ """
+ init = _check_output(
+ args=[
+ 'ceph-detect-init',
+ '--default', 'sysvinit',
+ ],
+ )
+ init = must_be_one_line(init)
+ return init
+
+
+def check_osd_magic(path):
+ """
+ Check that this path has the Ceph OSD magic.
+
+ :raises: BadMagicError if this does not look like a Ceph OSD data
+ dir.
+ """
+ magic = read_one_line(path, 'magic')
+ if magic is None:
+ # probably not mkfs'ed yet
+ raise BadMagicError(path)
+ if magic != CEPH_OSD_ONDISK_MAGIC:
+ raise BadMagicError(path)
+
+
+def check_osd_id(osd_id):
+ """
+ Ensures osd id is numeric.
+ """
+ if not re.match(r'^[0-9]+$', osd_id):
+ raise Error('osd id is not numeric', osd_id)
+
+
+def allocate_osd_id(
+ cluster,
+ fsid,
+ keyring,
+ path,
+):
+ """
+ Allocates an OSD id on the given cluster.
+
+ :raises: Error if the call to allocate the OSD id fails.
+ :return: The allocated OSD id.
+ """
+ lockbox_path = os.path.join(STATEDIR, 'osd-lockbox', fsid)
+ lockbox_osd_id = read_one_line(lockbox_path, 'whoami')
+ osd_keyring = os.path.join(path, 'keyring')
+ if lockbox_osd_id:
+ LOG.debug('Getting OSD id from Lockbox...')
+ osd_id = lockbox_osd_id
+ shutil.move(os.path.join(lockbox_path, 'osd_keyring'),
+ osd_keyring)
+ path_set_context(osd_keyring)
+ os.unlink(os.path.join(lockbox_path, 'whoami'))
+ return osd_id
+
+ LOG.debug('Allocating OSD id...')
+ secrets = Secrets()
+ try:
+ wanttobe = read_one_line(path, 'wanttobe')
+ if os.path.exists(os.path.join(path, 'wanttobe')):
+ os.unlink(os.path.join(path, 'wanttobe'))
+ id_arg = wanttobe and [wanttobe] or []
+ osd_id = command_with_stdin(
+ [
+ 'ceph',
+ '--cluster', cluster,
+ '--name', 'client.bootstrap-osd',
+ '--keyring', keyring,
+ '-i', '-',
+ 'osd', 'new',
+ fsid,
+ ] + id_arg,
+ secrets.get_json()
+ )
+ except subprocess.CalledProcessError as e:
+ raise Error('ceph osd create failed', e, e.output)
+ osd_id = must_be_one_line(osd_id)
+ check_osd_id(osd_id)
+ secrets.write_osd_keyring(osd_keyring, osd_id)
+ return osd_id
+
+
+def get_osd_id(path):
+ """
+ Gets the OSD id of the OSD at the given path.
+ """
+ osd_id = read_one_line(path, 'whoami')
+ if osd_id is not None:
+ check_osd_id(osd_id)
+ return osd_id
+
+
+def get_ceph_user():
+ global CEPH_PREF_USER
+
+ if CEPH_PREF_USER is not None:
+ try:
+ pwd.getpwnam(CEPH_PREF_USER)
+ return CEPH_PREF_USER
+ except KeyError:
+ print("No such user:", CEPH_PREF_USER)
+ sys.exit(2)
+ else:
+ try:
+ pwd.getpwnam('ceph')
+ return 'ceph'
+ except KeyError:
+ return 'root'
+
+
+def get_ceph_group():
+ global CEPH_PREF_GROUP
+
+ if CEPH_PREF_GROUP is not None:
+ try:
+ grp.getgrnam(CEPH_PREF_GROUP)
+ return CEPH_PREF_GROUP
+ except KeyError:
+ print("No such group:", CEPH_PREF_GROUP)
+ sys.exit(2)
+ else:
+ try:
+ grp.getgrnam('ceph')
+ return 'ceph'
+ except KeyError:
+ return 'root'
+
+
+def path_set_context(path):
+ # restore selinux context to default policy values
+ if which('restorecon'):
+ command(['restorecon', '-R', path])
+
+ # if ceph user exists, set owner to ceph
+ if get_ceph_user() == 'ceph':
+ command(['chown', '-R', 'ceph:ceph', path])
+
+
+def _check_output(args=None, **kwargs):
+ out, err, ret = command(args, **kwargs)
+ if ret:
+ cmd = args[0]
+ error = subprocess.CalledProcessError(ret, cmd)
+ error.output = out + err
+ raise error
+ return _bytes2str(out)
+
+
+def get_conf(cluster, variable):
+ """
+ Get the value of the given configuration variable from the
+ cluster.
+
+ :raises: Error if call to ceph-conf fails.
+ :return: The variable value or None.
+ """
+ try:
+ out, err, ret = command(
+ [
+ 'ceph-conf',
+ '--cluster={cluster}'.format(
+ cluster=cluster,
+ ),
+ '--name=osd.',
+ '--lookup',
+ variable,
+ ],
+ close_fds=True,
+ )
+ except OSError as e:
+ raise Error('error executing ceph-conf', e, err)
+ if ret == 1:
+ # config entry not found
+ return None
+ elif ret != 0:
+ raise Error('getting variable from configuration failed')
+ value = out.split('\n', 1)[0]
+ # don't differentiate between "var=" and no var set
+ if not value:
+ return None
+ return value
+
+
+def get_conf_with_default(cluster, variable):
+ """
+ Get a config value that is known to the C++ code.
+
+ This will fail if called on variables that are not defined in
+ common config options.
+ """
+ try:
+ out = _check_output(
+ args=[
+ 'ceph-osd',
+ '--cluster={cluster}'.format(
+ cluster=cluster,
+ ),
+ '--show-config-value={variable}'.format(
+ variable=variable,
+ ),
+ ],
+ close_fds=True,
+ )
+ except subprocess.CalledProcessError as e:
+ raise Error(
+ 'getting variable from configuration failed',
+ e,
+ )
+
+ value = str(out).split('\n', 1)[0]
+ return value
+
+
+def get_fsid(cluster):
+ """
+ Get the fsid of the cluster.
+
+ :return: The fsid or raises Error.
+ """
+ fsid = get_conf_with_default(cluster=cluster, variable='fsid')
+ if fsid is None:
+ raise Error('getting cluster uuid from configuration failed')
+ return fsid.lower()
+
+
+def get_dmcrypt_key_path(
+ _uuid,
+ key_dir,
+ luks
+):
+ """
+ Get path to dmcrypt key file.
+
+ :return: Path to the dmcrypt key file, callers should check for existence.
+ """
+ if luks:
+ path = os.path.join(key_dir, _uuid + ".luks.key")
+ else:
+ path = os.path.join(key_dir, _uuid)
+
+ return path
+
+
+def get_dmcrypt_key(
+ _uuid,
+ key_dir,
+ luks
+):
+ legacy_path = get_dmcrypt_key_path(_uuid, key_dir, luks)
+ if os.path.exists(legacy_path):
+ return (legacy_path,)
+ path = os.path.join(STATEDIR, 'osd-lockbox', _uuid)
+ if os.path.exists(path):
+ mode = get_oneliner(path, 'key-management-mode')
+ osd_uuid = get_oneliner(path, 'osd-uuid')
+ ceph_fsid = read_one_line(path, 'ceph_fsid')
+ if ceph_fsid is None:
+ LOG.warning("no `ceph_fsid` found falling back to 'ceph' "
+ "for cluster name")
+ cluster = 'ceph'
+ else:
+ cluster = find_cluster_by_uuid(ceph_fsid)
+ if cluster is None:
+ raise Error('No cluster conf found in ' + SYSCONFDIR +
+ ' with fsid %s' % ceph_fsid)
+
+ if mode == KEY_MANAGEMENT_MODE_V1:
+ key, stderr, ret = command(
+ [
+ 'ceph',
+ '--cluster', cluster,
+ '--name',
+ 'client.osd-lockbox.' + osd_uuid,
+ '--keyring',
+ os.path.join(path, 'keyring'),
+ 'config-key',
+ 'get',
+ 'dm-crypt/osd/' + osd_uuid + '/luks',
+ ],
+ )
+ LOG.debug("stderr " + stderr)
+ assert ret == 0
+ return base64.b64decode(key)
+ else:
+ raise Error('unknown key-management-mode ' + str(mode))
+ raise Error('unable to read dm-crypt key', path, legacy_path)
+
+
+def _dmcrypt_map(
+ rawdev,
+ key,
+ _uuid,
+ cryptsetup_parameters,
+ luks,
+ format_dev=False,
+):
+ dev = dmcrypt_is_mapped(_uuid)
+ if dev:
+ return dev
+
+ if isinstance(key, tuple):
+ # legacy, before lockbox
+ assert os.path.exists(key[0])
+ keypath = key[0]
+ key = None
+ else:
+ keypath = '-'
+ dev = '/dev/mapper/' + _uuid
+ luksFormat_args = [
+ 'cryptsetup',
+ '--batch-mode',
+ '--key-file',
+ keypath,
+ 'luksFormat',
+ rawdev,
+ ] + cryptsetup_parameters
+
+ luksOpen_args = [
+ 'cryptsetup',
+ '--key-file',
+ keypath,
+ 'luksOpen',
+ rawdev,
+ _uuid,
+ ]
+
+ create_args = [
+ 'cryptsetup',
+ '--key-file',
+ keypath,
+ 'create',
+ _uuid,
+ rawdev,
+ ] + cryptsetup_parameters
+
+ try:
+ if luks:
+ if format_dev:
+ command_with_stdin(luksFormat_args, key)
+ command_with_stdin(luksOpen_args, key)
+ else:
+ # Plain mode has no format function, nor any validation
+ # that the key is correct.
+ command_with_stdin(create_args, key)
+ # set proper ownership of mapped device
+ command_check_call(['chown', 'ceph:ceph', dev])
+ return dev
+
+ except subprocess.CalledProcessError as e:
+ raise Error('unable to map device', rawdev, e)
+
+
+@retry(Error, max_tries=10, wait=0.5, backoff=1.0)
+def dmcrypt_unmap(_uuid):
+ if not os.path.exists('/dev/mapper/' + _uuid):
+ return
+ try:
+ command_check_call(['cryptsetup', 'remove', _uuid])
+ except subprocess.CalledProcessError as e:
+ raise Error('unable to unmap device', _uuid, e)
+
+
+def mount(
+ dev,
+ fstype,
+ options,
+):
+ """
+ Mounts a device with given filessystem type and
+ mount options to a tempfile path under /var/lib/ceph/tmp.
+ """
+ # sanity check: none of the arguments are None
+ if dev is None:
+ raise ValueError('dev may not be None')
+ if fstype is None:
+ raise ValueError('fstype may not be None')
+
+ # pick best-of-breed mount options based on fs type
+ if options is None:
+ options = MOUNT_OPTIONS.get(fstype, '')
+
+ myTemp = STATEDIR + '/tmp'
+ # mkdtemp expect 'dir' to be existing on the system
+ # Let's be sure it's always the case
+ if not os.path.exists(myTemp):
+ os.makedirs(myTemp)
+
+ # mount
+ path = tempfile.mkdtemp(
+ prefix='mnt.',
+ dir=myTemp,
+ )
+ try:
+ LOG.debug('Mounting %s on %s with options %s', dev, path, options)
+ command_check_call(
+ [
+ 'mount',
+ '-t', fstype,
+ '-o', options,
+ '--',
+ dev,
+ path,
+ ],
+ )
+ if which('restorecon'):
+ command(
+ [
+ 'restorecon',
+ path,
+ ],
+ )
+ except subprocess.CalledProcessError as e:
+ try:
+ os.rmdir(path)
+ except (OSError, IOError):
+ pass
+ raise MountError(e)
+
+ return path
+
+
+@retry(UnmountError, max_tries=3, wait=0.5, backoff=1.0)
+def unmount(
+ path,
+ do_rm=True,
+):
+ """
+ Unmount and removes the given mount point.
+ """
+ try:
+ LOG.debug('Unmounting %s', path)
+ command_check_call(
+ [
+ '/bin/umount',
+ '--',
+ path,
+ ],
+ )
+ except subprocess.CalledProcessError as e:
+ raise UnmountError(e)
+ if not do_rm:
+ return
+ os.rmdir(path)
+
+
+###########################################
+
+def extract_parted_partition_numbers(partitions):
+ numbers_as_strings = re.findall('^\d+', partitions, re.MULTILINE)
+ return map(int, numbers_as_strings)
+
+
+def get_free_partition_index(dev):
+ """
+ Get the next free partition index on a given device.
+
+ :return: Index number (> 1 if there is already a partition on the device)
+ or 1 if there is no partition table.
+ """
+ try:
+ lines = _check_output(
+ args=[
+ 'parted',
+ '--machine',
+ '--',
+ dev,
+ 'print',
+ ],
+ )
+ except subprocess.CalledProcessError as e:
+ LOG.info('cannot read partition index; assume it '
+ 'isn\'t present\n (Error: %s)' % e)
+ return 1
+
+ if not lines:
+ raise Error('parted failed to output anything')
+ LOG.debug('get_free_partition_index: analyzing ' + lines)
+ if ('CHS;' not in lines and
+ 'CYL;' not in lines and
+ 'BYT;' not in lines):
+ raise Error('parted output expected to contain one of ' +
+ 'CHH; CYL; or BYT; : ' + lines)
+ if os.path.realpath(dev) not in lines:
+ raise Error('parted output expected to contain ' + dev + ': ' + lines)
+ _, partitions = lines.split(os.path.realpath(dev))
+ partition_numbers = extract_parted_partition_numbers(partitions)
+ if partition_numbers:
+ return max(partition_numbers) + 1
+ else:
+ return 1
+
+
+def check_journal_reqs(args):
+ _, _, allows_journal = command([
+ 'ceph-osd', '--check-allows-journal',
+ '-i', '0',
+ '--log-file', '$run_dir/$cluster-osd-check.log',
+ '--cluster', args.cluster,
+ '--setuser', get_ceph_user(),
+ '--setgroup', get_ceph_group(),
+ ])
+ _, _, wants_journal = command([
+ 'ceph-osd', '--check-wants-journal',
+ '-i', '0',
+ '--log-file', '$run_dir/$cluster-osd-check.log',
+ '--cluster', args.cluster,
+ '--setuser', get_ceph_user(),
+ '--setgroup', get_ceph_group(),
+ ])
+ _, _, needs_journal = command([
+ 'ceph-osd', '--check-needs-journal',
+ '-i', '0',
+ '--log-file', '$run_dir/$cluster-osd-check.log',
+ '--cluster', args.cluster,
+ '--setuser', get_ceph_user(),
+ '--setgroup', get_ceph_group(),
+ ])
+ return (not allows_journal, not wants_journal, not needs_journal)
+
+
+def update_partition(dev, description):
+ """
+ Must be called after modifying a partition table so the kernel
+ know about the change and fire udev events accordingly. A side
+ effect of partprobe is to remove partitions and add them again.
+ The first udevadm settle waits for ongoing udev events to
+ complete, just in case one of them rely on an existing partition
+ on dev. The second udevadm settle guarantees to the caller that
+ all udev events related to the partition table change have been
+ processed, i.e. the 95-ceph-osd.rules actions and mode changes,
+ group changes etc. are complete.
+ """
+ LOG.debug('Calling partprobe on %s device %s', description, dev)
+ partprobe_ok = False
+ error = 'unknown error'
+ partprobe = _get_command_executable(['partprobe'])[0]
+ for i in range(5):
+ command_check_call(['udevadm', 'settle', '--timeout=600'])
+ try:
+ _check_output(['flock', '-s', dev, partprobe, dev])
+ partprobe_ok = True
+ break
+ except subprocess.CalledProcessError as e:
+ error = e.output
+ if ('unable to inform the kernel' not in error and
+ 'Device or resource busy' not in error):
+ raise
+ LOG.debug('partprobe %s failed : %s (ignored, waiting 60s)'
+ % (dev, error))
+ time.sleep(60)
+ if not partprobe_ok:
+ raise Error('partprobe %s failed : %s' % (dev, error))
+ command_check_call(['udevadm', 'settle', '--timeout=600'])
+
+
+def zap_linux(dev):
+ try:
+ # Thoroughly wipe all partitions of any traces of
+ # Filesystems or OSD Journals
+ #
+ # In addition we need to write 10M of data to each partition
+ # to make sure that after re-creating the same partition
+ # there is no trace left of any previous Filesystem or OSD
+ # Journal
+
+ LOG.debug('Writing zeros to existing partitions on %s', dev)
+
+ for partname in list_partitions(dev):
+ partition = get_dev_path(partname)
+ command_check_call(
+ [
+ 'wipefs',
+ '--all',
+ partition,
+ ],
+ )
+
+ command_check_call(
+ [
+ 'dd',
+ 'if=/dev/zero',
+ 'of={path}'.format(path=partition),
+ 'bs=1M',
+ 'count=10',
+ ],
+ )
+
+ LOG.debug('Zapping partition table on %s', dev)
+
+ # try to wipe out any GPT partition table backups. sgdisk
+ # isn't too thorough.
+ lba_size = 4096
+ size = 33 * lba_size
+ with open(dev, 'wb') as dev_file:
+ dev_file.seek(-size, os.SEEK_END)
+ dev_file.write(size * b'\0')
+
+ command_check_call(
+ [
+ 'sgdisk',
+ '--zap-all',
+ '--',
+ dev,
+ ],
+ )
+ command_check_call(
+ [
+ 'sgdisk',
+ '--clear',
+ '--mbrtogpt',
+ '--',
+ dev,
+ ],
+ )
+ update_partition(dev, 'zapped')
+
+ except subprocess.CalledProcessError as e:
+ raise Error(e)
+
+
+def zap_freebsd(dev):
+ try:
+ # For FreeBSD we just need to zap the partition.
+ command_check_call(
+ [
+ 'gpart',
+ 'destroy',
+ '-F',
+ dev,
+ ],
+ )
+
+ except subprocess.CalledProcessError as e:
+ raise Error(e)
+
+
+def zap(dev):
+ """
+ Destroy the partition table and content of a given disk.
+ """
+ dev = os.path.realpath(dev)
+ dmode = os.stat(dev).st_mode
+ if not stat.S_ISBLK(dmode) or is_partition(dev):
+ raise Error('not full block device; cannot zap', dev)
+ if FREEBSD:
+ zap_freebsd(dev)
+ else:
+ zap_linux(dev)
+
+
+def adjust_symlink(target, path):
+ create = True
+ if os.path.lexists(path):
+ try:
+ mode = os.lstat(path).st_mode
+ if stat.S_ISREG(mode):
+ LOG.debug('Removing old file %s', path)
+ os.unlink(path)
+ elif stat.S_ISLNK(mode):
+ old = os.readlink(path)
+ if old != target:
+ LOG.debug('Removing old symlink %s -> %s', path, old)
+ os.unlink(path)
+ else:
+ create = False
+ except:
+ raise Error('unable to remove (or adjust) old file (symlink)',
+ path)
+ if create:
+ LOG.debug('Creating symlink %s -> %s', path, target)
+ try:
+ os.symlink(target, path)
+ except:
+ raise Error('unable to create symlink %s -> %s' % (path, target))
+
+
+def get_mount_options(cluster, fs_type):
+ mount_options = get_conf(
+ cluster,
+ variable='osd_mount_options_{fstype}'.format(
+ fstype=fs_type,
+ ),
+ )
+ if mount_options is None:
+ mount_options = get_conf(
+ cluster,
+ variable='osd_fs_mount_options_{fstype}'.format(
+ fstype=fs_type,
+ ),
+ )
+ else:
+ # remove whitespaces
+ mount_options = "".join(mount_options.split())
+ return mount_options
+
+
+class Device(object):
+
+ def __init__(self, path, args):
+ self.args = args
+ self.path = path
+ self.dev_size = None
+ self.partitions = {}
+ self.ptype_map = None
+ assert not is_partition(self.path)
+
+ def create_partition(self, uuid, name, size=0, num=0):
+ ptype = self.ptype_tobe_for_name(name)
+ if num == 0:
+ num = get_free_partition_index(dev=self.path)
+ if size > 0:
+ new = '--new={num}:0:+{size}M'.format(num=num, size=size)
+ if size > self.get_dev_size():
+ LOG.error('refusing to create %s on %s' % (name, self.path))
+ LOG.error('%s size (%sM) is bigger than device (%sM)'
+ % (name, size, self.get_dev_size()))
+ raise Error('%s device size (%sM) is not big enough for %s'
+ % (self.path, self.get_dev_size(), name))
+ else:
+ new = '--largest-new={num}'.format(num=num)
+
+ LOG.debug('Creating %s partition num %d size %d on %s',
+ name, num, size, self.path)
+ command_check_call(
+ [
+ 'sgdisk',
+ new,
+ '--change-name={num}:ceph {name}'.format(num=num, name=name),
+ '--partition-guid={num}:{uuid}'.format(num=num, uuid=uuid),
+ '--typecode={num}:{uuid}'.format(num=num, uuid=ptype),
+ '--mbrtogpt',
+ '--',
+ self.path,
+ ],
+ exit=True
+ )
+ update_partition(self.path, 'created')
+ return num
+
+ def ptype_tobe_for_name(self, name):
+ LOG.debug("name = " + name)
+ if name == 'data':
+ name = 'osd'
+ if name == 'lockbox':
+ if is_mpath(self.path):
+ return PTYPE['mpath']['lockbox']['tobe']
+ else:
+ return PTYPE['regular']['lockbox']['tobe']
+ if self.ptype_map is None:
+ partition = DevicePartition.factory(
+ path=self.path, dev=None, args=self.args)
+ self.ptype_map = partition.ptype_map
+ return self.ptype_map[name]['tobe']
+
+ def get_partition(self, num):
+ if num not in self.partitions:
+ dev = get_partition_dev(self.path, num)
+ partition = DevicePartition.factory(
+ path=self.path, dev=dev, args=self.args)
+ partition.set_partition_number(num)
+ self.partitions[num] = partition
+ return self.partitions[num]
+
+ def get_dev_size(self):
+ if self.dev_size is None:
+ self.dev_size = get_dev_size(self.path)
+ return self.dev_size
+
+ @staticmethod
+ def factory(path, args):
+ return Device(path, args)
+
+
+class DevicePartition(object):
+
+ def __init__(self, args):
+ self.args = args
+ self.num = None
+ self.rawdev = None
+ self.dev = None
+ self.uuid = None
+ self.ptype_map = None
+ self.ptype = None
+ self.set_variables_ptype()
+
+ def get_uuid(self):
+ if self.uuid is None:
+ self.uuid = get_partition_uuid(self.rawdev)
+ return self.uuid
+
+ def get_ptype(self):
+ if self.ptype is None:
+ self.ptype = get_partition_type(self.rawdev)
+ return self.ptype
+
+ def set_partition_number(self, num):
+ self.num = num
+
+ def get_partition_number(self):
+ return self.num
+
+ def set_dev(self, dev):
+ self.dev = dev
+ self.rawdev = dev
+
+ def get_dev(self):
+ return self.dev
+
+ def get_rawdev(self):
+ return self.rawdev
+
+ def set_variables_ptype(self):
+ self.ptype_map = PTYPE['regular']
+
+ def ptype_for_name(self, name):
+ return self.ptype_map[name]['ready']
+
+ @staticmethod
+ @retry(OSError)
+ def factory(path, dev, args):
+ dmcrypt_type = CryptHelpers.get_dmcrypt_type(args)
+ if ((path is not None and is_mpath(path)) or
+ (dev is not None and is_mpath(dev))):
+ partition = DevicePartitionMultipath(args)
+ elif dmcrypt_type == 'luks':
+ partition = DevicePartitionCryptLuks(args)
+ elif dmcrypt_type == 'plain':
+ partition = DevicePartitionCryptPlain(args)
+ else:
+ partition = DevicePartition(args)
+ partition.set_dev(dev)
+ return partition
+
+
+class DevicePartitionMultipath(DevicePartition):
+
+ def set_variables_ptype(self):
+ self.ptype_map = PTYPE['mpath']
+
+
+class DevicePartitionCrypt(DevicePartition):
+
+ def __init__(self, args):
+ super(DevicePartitionCrypt, self).__init__(args)
+ self.osd_dm_key = None
+ self.cryptsetup_parameters = CryptHelpers.get_cryptsetup_parameters(
+ self.args)
+ self.dmcrypt_type = CryptHelpers.get_dmcrypt_type(self.args)
+ self.dmcrypt_keysize = CryptHelpers.get_dmcrypt_keysize(self.args)
+
+ def setup_crypt(self):
+ pass
+
+ def map(self):
+ self.setup_crypt()
+ self.dev = _dmcrypt_map(
+ rawdev=self.rawdev,
+ key=self.osd_dm_key,
+ _uuid=self.get_uuid(),
+ cryptsetup_parameters=self.cryptsetup_parameters,
+ luks=self.luks(),
+ format_dev=True,
+ )
+
+ def unmap(self):
+ self.setup_crypt()
+ dmcrypt_unmap(self.get_uuid())
+ self.dev = self.rawdev
+
+ def format(self):
+ self.setup_crypt()
+ self.map()
+
+
+class DevicePartitionCryptPlain(DevicePartitionCrypt):
+
+ def luks(self):
+ return False
+
+ def setup_crypt(self):
+ if self.osd_dm_key is not None:
+ return
+
+ self.cryptsetup_parameters += ['--key-size', str(self.dmcrypt_keysize)]
+
+ self.osd_dm_key = get_dmcrypt_key(
+ self.get_uuid(), self.args.dmcrypt_key_dir,
+ False)
+
+ def set_variables_ptype(self):
+ self.ptype_map = PTYPE['plain']
+
+
+class DevicePartitionCryptLuks(DevicePartitionCrypt):
+
+ def luks(self):
+ return True
+
+ def setup_crypt(self):
+ if self.osd_dm_key is not None:
+ return
+
+ if self.dmcrypt_keysize == 1024:
+ # We don't force this into the cryptsetup_parameters,
+ # as we want the cryptsetup defaults
+ # to prevail for the actual LUKS key lengths.
+ pass
+ else:
+ self.cryptsetup_parameters += ['--key-size',
+ str(self.dmcrypt_keysize)]
+
+ self.osd_dm_key = get_dmcrypt_key(
+ self.get_uuid(), self.args.dmcrypt_key_dir,
+ True)
+
+ def set_variables_ptype(self):
+ self.ptype_map = PTYPE['luks']
+
+
+class Prepare(object):
+
+ def __init__(self, args):
+ self.args = args
+
+ @staticmethod
+ def parser():
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument(
+ '--cluster',
+ metavar='NAME',
+ default='ceph',
+ help='cluster name to assign this disk to',
+ )
+ parser.add_argument(
+ '--cluster-uuid',
+ metavar='UUID',
+ help='cluster uuid to assign this disk to',
+ )
+ parser.add_argument(
+ '--osd-uuid',
+ metavar='UUID',
+ help='unique OSD uuid to assign this disk to',
+ )
+ parser.add_argument(
+ '--osd-id',
+ metavar='ID',
+ help='unique OSD id to assign this disk to',
+ )
+ parser.add_argument(
+ '--crush-device-class',
+ help='crush device class to assign this disk to',
+ )
+ parser.add_argument(
+ '--dmcrypt',
+ action='store_true', default=None,
+ help='encrypt DATA and/or JOURNAL devices with dm-crypt',
+ )
+ parser.add_argument(
+ '--dmcrypt-key-dir',
+ metavar='KEYDIR',
+ default='/etc/ceph/dmcrypt-keys',
+ help='directory where dm-crypt keys are stored',
+ )
+ parser.add_argument(
+ '--prepare-key',
+ metavar='PATH',
+ help='bootstrap-osd keyring path template (%(default)s)',
+ default='{statedir}/bootstrap-osd/{cluster}.keyring',
+ dest='prepare_key_template',
+ )
+ parser.add_argument(
+ '--no-locking',
+ action='store_true', default=None,
+ help='let many prepare\'s run in parallel',
+ )
+ return parser
+
+ @staticmethod
+ def set_subparser(subparsers):
+ parents = [
+ Prepare.parser(),
+ PrepareData.parser(),
+ Lockbox.parser(),
+ ]
+ parents.extend(PrepareFilestore.parent_parsers())
+ parents.extend(PrepareBluestore.parent_parsers())
+ parser = subparsers.add_parser(
+ 'prepare',
+ parents=parents,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ If the --bluestore argument is given, a bluestore objectstore
+ will be created. If --filestore is provided, a legacy FileStore
+ objectstore will be created. If neither is specified, we default
+ to BlueStore.
+
+ When an entire device is prepared for bluestore, two
+ partitions are created. The first partition is for metadata,
+ the second partition is for blocks that contain data.
+
+ Unless explicitly specified with --block.db or
+ --block.wal, the bluestore DB and WAL data is stored on
+ the main block device. For instance:
+
+ ceph-disk prepare --bluestore /dev/sdc
+
+ Will create
+
+ /dev/sdc1 for osd metadata
+ /dev/sdc2 for block, db, and wal data (the rest of the disk)
+
+
+ If either --block.db or --block.wal are specified to be
+ the same whole device, they will be created as partition
+ three and four respectively. For instance:
+
+ ceph-disk prepare --bluestore \\
+ --block.db /dev/sdc \\
+ --block.wal /dev/sdc \\
+ /dev/sdc
+
+ Will create
+
+ /dev/sdc1 for osd metadata
+ /dev/sdc2 for block (the rest of the disk)
+ /dev/sdc3 for db
+ /dev/sdc4 for wal
+
+ """)),
+ help='Prepare a directory or disk for a Ceph OSD',
+ )
+ parser.set_defaults(
+ func=Prepare.main,
+ )
+ return parser
+
+ def prepare(self):
+ if self.args.no_locking:
+ self._prepare()
+ else:
+ with prepare_lock:
+ self._prepare()
+
+ @staticmethod
+ def factory(args):
+ if args.bluestore:
+ return PrepareBluestore(args)
+ else:
+ return PrepareFilestore(args)
+
+ @staticmethod
+ def main(args):
+ Prepare.factory(args).prepare()
+
+
+class PrepareFilestore(Prepare):
+
+ def __init__(self, args):
+ super(PrepareFilestore, self).__init__(args)
+ if args.dmcrypt:
+ self.lockbox = Lockbox(args)
+ self.data = PrepareFilestoreData(args)
+ self.journal = PrepareJournal(args)
+
+ @staticmethod
+ def parent_parsers():
+ return [
+ PrepareJournal.parser(),
+ ]
+
+ def _prepare(self):
+ if self.data.args.dmcrypt:
+ self.lockbox.prepare()
+ self.data.prepare(self.journal)
+
+
+class PrepareBluestore(Prepare):
+
+ def __init__(self, args):
+ super(PrepareBluestore, self).__init__(args)
+ if args.dmcrypt:
+ self.lockbox = Lockbox(args)
+ self.data = PrepareBluestoreData(args)
+ self.block = PrepareBluestoreBlock(args)
+ self.blockdb = PrepareBluestoreBlockDB(args)
+ self.blockwal = PrepareBluestoreBlockWAL(args)
+
+ @staticmethod
+ def parser():
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument(
+ '--bluestore',
+ dest='bluestore',
+ action='store_true', default=True,
+ help='bluestore objectstore',
+ )
+ parser.add_argument(
+ '--filestore',
+ dest='bluestore',
+ action='store_false',
+ help='filestore objectstore',
+ )
+ return parser
+
+ @staticmethod
+ def parent_parsers():
+ return [
+ PrepareBluestore.parser(),
+ PrepareBluestoreBlock.parser(),
+ PrepareBluestoreBlockDB.parser(),
+ PrepareBluestoreBlockWAL.parser(),
+ ]
+
+ def _prepare(self):
+ if self.data.args.dmcrypt:
+ self.lockbox.prepare()
+ to_prepare_list = []
+ if getattr(self.data.args, 'block.db'):
+ to_prepare_list.append(self.blockdb)
+ if getattr(self.data.args, 'block.wal'):
+ to_prepare_list.append(self.blockwal)
+ to_prepare_list.append(self.block)
+ self.data.prepare(*to_prepare_list)
+
+
+class Space(object):
+
+ NAMES = ('block', 'journal', 'block.db', 'block.wal')
+
+
+class PrepareSpace(object):
+
+ NONE = 0
+ FILE = 1
+ DEVICE = 2
+
+ def __init__(self, args):
+ self.args = args
+ self.set_type()
+ self.space_size = self.get_space_size()
+ if getattr(self.args, self.name + '_uuid') is None:
+ setattr(self.args, self.name + '_uuid', str(uuid.uuid4()))
+ self.space_symlink = None
+ self.space_dmcrypt = None
+
+ def set_type(self):
+ name = self.name
+ args = self.args
+ if (self.wants_space() and
+ dev_is_diskdevice(args.data) and
+ not is_partition(args.data) and
+ getattr(args, name) is None and
+ getattr(args, name + '_file') is None):
+ LOG.info('Will colocate %s with data on %s',
+ name, args.data)
+ setattr(args, name, args.data)
+
+ if getattr(args, name) is None:
+ if getattr(args, name + '_dev'):
+ raise Error('%s is unspecified; not a block device' %
+ name.capitalize(), getattr(args, name))
+ self.type = self.NONE
+ return
+
+ if not os.path.exists(getattr(args, name)):
+ if getattr(args, name + '_dev'):
+ raise Error('%s does not exist; not a block device' %
+ name.capitalize(), getattr(args, name))
+ self.type = self.FILE
+ return
+
+ mode = os.stat(getattr(args, name)).st_mode
+ if stmode_is_diskdevice(mode):
+ if getattr(args, name + '_file'):
+ raise Error('%s is not a regular file' % name.capitalize,
+ getattr(args, name))
+ self.type = self.DEVICE
+ return
+
+ if stat.S_ISREG(mode):
+ if getattr(args, name + '_dev'):
+ raise Error('%s is not a block device' % name.capitalize,
+ getattr(args, name))
+ self.type = self.FILE
+ return
+
+ raise Error('%s %s is neither a block device nor regular file' %
+ (name.capitalize, getattr(args, name)))
+
+ def is_none(self):
+ return self.type == self.NONE
+
+ def is_file(self):
+ return self.type == self.FILE
+
+ def is_device(self):
+ return self.type == self.DEVICE
+
+ @staticmethod
+ def parser(name, positional=True):
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument(
+ '--%s-uuid' % name,
+ metavar='UUID',
+ help='unique uuid to assign to the %s' % name,
+ )
+ parser.add_argument(
+ '--%s-file' % name,
+ action='store_true', default=None,
+ help='verify that %s is a file' % name.upper(),
+ )
+ parser.add_argument(
+ '--%s-dev' % name,
+ action='store_true', default=None,
+ help='verify that %s is a block device' % name.upper(),
+ )
+
+ if positional:
+ parser.add_argument(
+ name,
+ metavar=name.upper(),
+ nargs='?',
+ help=('path to OSD %s disk block device;' % name +
+ ' leave out to store %s in file' % name),
+ )
+ return parser
+
+ def wants_space(self):
+ return True
+
+ def populate_data_path(self, path):
+ if self.type == self.DEVICE:
+ self.populate_data_path_device(path)
+ elif self.type == self.FILE:
+ self.populate_data_path_file(path)
+ elif self.type == self.NONE:
+ pass
+ else:
+ raise Error('unexpected type ', self.type)
+
+ def populate_data_path_file(self, path):
+ space_uuid = self.name + '_uuid'
+ if getattr(self.args, space_uuid) is not None:
+ write_one_line(path, space_uuid,
+ getattr(self.args, space_uuid))
+ if self.space_symlink is not None:
+ adjust_symlink(self.space_symlink,
+ os.path.join(path, self.name))
+
+ def populate_data_path_device(self, path):
+ self.populate_data_path_file(path)
+
+ if self.space_dmcrypt is not None:
+ adjust_symlink(self.space_dmcrypt,
+ os.path.join(path, self.name + '_dmcrypt'))
+ else:
+ try:
+ os.unlink(os.path.join(path, self.name + '_dmcrypt'))
+ except OSError:
+ pass
+
+ def prepare(self):
+ if self.type == self.DEVICE:
+ self.prepare_device()
+ elif self.type == self.FILE:
+ self.prepare_file()
+ elif self.type == self.NONE:
+ pass
+ else:
+ raise Error('unexpected type ', self.type)
+
+ def prepare_file(self):
+ space_filename = getattr(self.args, self.name)
+ if not os.path.exists(space_filename):
+ LOG.debug('Creating %s file %s with size 0'
+ ' (ceph-osd will resize and allocate)',
+ self.name,
+ space_filename)
+ space_file = open(space_filename, 'wb')
+ space_file.close()
+ path_set_context(space_filename)
+
+ LOG.debug('%s is file %s',
+ self.name.capitalize(),
+ space_filename)
+ LOG.warning('OSD will not be hot-swappable if %s is '
+ 'not the same device as the osd data' %
+ self.name)
+ self.space_symlink = space_filename
+
+ def prepare_device(self):
+ reusing_partition = False
+
+ if is_partition(getattr(self.args, self.name)):
+ LOG.debug('%s %s is a partition',
+ self.name.capitalize(), getattr(self.args, self.name))
+ partition = DevicePartition.factory(
+ path=None, dev=getattr(self.args, self.name), args=self.args)
+ if isinstance(partition, DevicePartitionCrypt):
+ raise Error(getattr(self.args, self.name) +
+ ' partition already exists'
+ ' and --dmcrypt specified')
+ LOG.warning('OSD will not be hot-swappable' +
+ ' if ' + self.name + ' is not' +
+ ' the same device as the osd data')
+ if partition.get_ptype() == partition.ptype_for_name(self.name):
+ LOG.debug('%s %s was previously prepared with '
+ 'ceph-disk. Reusing it.',
+ self.name.capitalize(),
+ getattr(self.args, self.name))
+ reusing_partition = True
+ # Read and reuse the partition uuid from this journal's
+ # previous life. We reuse the uuid instead of changing it
+ # because udev does not reliably notice changes to an
+ # existing partition's GUID. See
+ # http://tracker.ceph.com/issues/10146
+ setattr(self.args, self.name + '_uuid', partition.get_uuid())
+ LOG.debug('Reusing %s with uuid %s',
+ self.name,
+ getattr(self.args, self.name + '_uuid'))
+ else:
+ LOG.warning('%s %s was not prepared with '
+ 'ceph-disk. Symlinking directly.',
+ self.name.capitalize(),
+ getattr(self.args, self.name))
+ self.space_symlink = getattr(self.args, self.name)
+ return
+
+ self.space_symlink = '/dev/disk/by-partuuid/{uuid}'.format(
+ uuid=getattr(self.args, self.name + '_uuid'))
+
+ if self.args.dmcrypt:
+ self.space_dmcrypt = self.space_symlink
+ self.space_symlink = '/dev/mapper/{uuid}'.format(
+ uuid=getattr(self.args, self.name + '_uuid'))
+
+ if reusing_partition:
+ # confirm that the space_symlink exists. It should since
+ # this was an active space
+ # in the past. Continuing otherwise would be futile.
+ assert os.path.exists(self.space_symlink)
+ return
+
+ num = self.desired_partition_number()
+
+ if num == 0:
+ LOG.warning('OSD will not be hot-swappable if %s '
+ 'is not the same device as the osd data',
+ self.name)
+
+ device = Device.factory(getattr(self.args, self.name), self.args)
+ num = device.create_partition(
+ uuid=getattr(self.args, self.name + '_uuid'),
+ name=self.name,
+ size=self.space_size,
+ num=num)
+
+ partition = device.get_partition(num)
+
+ LOG.debug('%s is GPT partition %s',
+ self.name.capitalize(),
+ self.space_symlink)
+
+ if isinstance(partition, DevicePartitionCrypt):
+ partition.format()
+ partition.map()
+
+ command_check_call(
+ [
+ 'sgdisk',
+ '--typecode={num}:{uuid}'.format(
+ num=num,
+ uuid=partition.ptype_for_name(self.name),
+ ),
+ '--',
+ getattr(self.args, self.name),
+ ],
+ )
+ update_partition(getattr(self.args, self.name), 'prepared')
+
+ LOG.debug('%s is GPT partition %s',
+ self.name.capitalize(),
+ self.space_symlink)
+
+
+class PrepareJournal(PrepareSpace):
+
+ def __init__(self, args):
+ self.name = 'journal'
+ (self.allows_journal,
+ self.wants_journal,
+ self.needs_journal) = check_journal_reqs(args)
+
+ if args.journal and not self.allows_journal:
+ raise Error('journal specified but not allowed by osd backend')
+
+ super(PrepareJournal, self).__init__(args)
+
+ def wants_space(self):
+ return self.wants_journal
+
+ def get_space_size(self):
+ return int(get_conf_with_default(
+ cluster=self.args.cluster,
+ variable='osd_journal_size',
+ ))
+
+ def desired_partition_number(self):
+ if self.args.journal == self.args.data:
+ # we're sharing the disk between osd data and journal;
+ # make journal be partition number 2
+ num = 2
+ else:
+ num = 0
+ return num
+
+ @staticmethod
+ def parser():
+ return PrepareSpace.parser('journal')
+
+
+class PrepareBluestoreBlock(PrepareSpace):
+
+ def __init__(self, args):
+ self.name = 'block'
+ super(PrepareBluestoreBlock, self).__init__(args)
+
+ def get_space_size(self):
+ block_size = get_conf(
+ cluster=self.args.cluster,
+ variable='bluestore_block_size',
+ )
+
+ if block_size is None:
+ return 0 # get as much space as possible
+ else:
+ return int(block_size) / 1048576 # MB
+
+ def desired_partition_number(self):
+ if self.args.block == self.args.data:
+ num = 2
+ else:
+ num = 0
+ return num
+
+ @staticmethod
+ def parser():
+ return PrepareSpace.parser('block')
+
+
+class PrepareBluestoreBlockDB(PrepareSpace):
+
+ def __init__(self, args):
+ self.name = 'block.db'
+ super(PrepareBluestoreBlockDB, self).__init__(args)
+
+ def get_space_size(self):
+ block_db_size = get_conf(
+ cluster=self.args.cluster,
+ variable='bluestore_block_db_size',
+ )
+
+ if block_db_size is None or int(block_db_size) == 0:
+ block_size = get_conf(
+ cluster=self.args.cluster,
+ variable='bluestore_block_size',
+ )
+ if block_size is None:
+ return 1024 # MB
+ size = int(block_size) / 100 / 1048576
+ return max(size, 1024) # MB
+ else:
+ return int(block_db_size) / 1048576 # MB
+
+ def desired_partition_number(self):
+ if getattr(self.args, 'block.db') == self.args.data:
+ num = 3
+ else:
+ num = 0
+ return num
+
+ def wants_space(self):
+ return False
+
+ @staticmethod
+ def parser():
+ parser = PrepareSpace.parser('block.db', positional=False)
+ parser.add_argument(
+ '--block.db',
+ metavar='BLOCKDB',
+ help='path to the device or file for bluestore block.db',
+ )
+ return parser
+
+
+class PrepareBluestoreBlockWAL(PrepareSpace):
+
+ def __init__(self, args):
+ self.name = 'block.wal'
+ super(PrepareBluestoreBlockWAL, self).__init__(args)
+
+ def get_space_size(self):
+ block_size = get_conf(
+ cluster=self.args.cluster,
+ variable='bluestore_block_wal_size',
+ )
+
+ if block_size is None:
+ return 576 # MB, default value
+ else:
+ return int(block_size) / 1048576 # MB
+
+ def desired_partition_number(self):
+ if getattr(self.args, 'block.wal') == self.args.data:
+ num = 4
+ else:
+ num = 0
+ return num
+
+ def wants_space(self):
+ return False
+
+ @staticmethod
+ def parser():
+ parser = PrepareSpace.parser('block.wal', positional=False)
+ parser.add_argument(
+ '--block.wal',
+ metavar='BLOCKWAL',
+ help='path to the device or file for bluestore block.wal',
+ )
+ return parser
+
+
+class CryptHelpers(object):
+
+ @staticmethod
+ def get_cryptsetup_parameters(args):
+ cryptsetup_parameters_str = get_conf(
+ cluster=args.cluster,
+ variable='osd_cryptsetup_parameters',
+ )
+ if cryptsetup_parameters_str is None:
+ return []
+ else:
+ return shlex.split(cryptsetup_parameters_str)
+
+ @staticmethod
+ def get_dmcrypt_keysize(args):
+ dmcrypt_keysize_str = get_conf(
+ cluster=args.cluster,
+ variable='osd_dmcrypt_key_size',
+ )
+ dmcrypt_type = CryptHelpers.get_dmcrypt_type(args)
+ if dmcrypt_type == 'luks':
+ if dmcrypt_keysize_str is None:
+ # As LUKS will hash the 'passphrase' in .luks.key
+ # into a key, set a large default
+ # so if not updated for some time, it is still a
+ # reasonable value.
+ #
+ return 1024
+ else:
+ return int(dmcrypt_keysize_str)
+ elif dmcrypt_type == 'plain':
+ if dmcrypt_keysize_str is None:
+ # This value is hard-coded in the udev script
+ return 256
+ else:
+ LOG.warning('ensure the 95-ceph-osd.rules file has '
+ 'been copied to /etc/udev/rules.d '
+ 'and modified to call cryptsetup '
+ 'with --key-size=%s' % dmcrypt_keysize_str)
+ return int(dmcrypt_keysize_str)
+ else:
+ return 0
+
+ @staticmethod
+ def get_dmcrypt_type(args):
+ if hasattr(args, 'dmcrypt') and args.dmcrypt:
+ dmcrypt_type = get_conf(
+ cluster=args.cluster,
+ variable='osd_dmcrypt_type',
+ )
+
+ if dmcrypt_type is None or dmcrypt_type == 'luks':
+ return 'luks'
+ elif dmcrypt_type == 'plain':
+ return 'plain'
+ else:
+ raise Error('invalid osd_dmcrypt_type parameter '
+ '(must be luks or plain): ', dmcrypt_type)
+ else:
+ return None
+
+
+class Secrets(object):
+
+ def __init__(self):
+ secret, stderr, ret = command(['ceph-authtool', '--gen-print-key'])
+ LOG.debug("stderr " + stderr)
+ assert ret == 0
+ self.keys = {
+ 'cephx_secret': secret.strip(),
+ }
+
+ def write_osd_keyring(self, keyring, osd_id):
+ command_check_call(
+ [
+ 'ceph-authtool', keyring,
+ '--create-keyring',
+ '--name', 'osd.' + str(osd_id),
+ '--add-key', self.keys['cephx_secret'],
+ ])
+ path_set_context(keyring)
+
+ def get_json(self):
+ return bytearray(json.dumps(self.keys), 'ascii')
+
+
+class LockboxSecrets(Secrets):
+
+ def __init__(self, args):
+ super(LockboxSecrets, self).__init__()
+
+ key_size = CryptHelpers.get_dmcrypt_keysize(args)
+ key = open('/dev/urandom', 'rb').read(key_size / 8)
+ base64_key = base64.b64encode(key).decode('ascii')
+
+ secret, stderr, ret = command(['ceph-authtool', '--gen-print-key'])
+ LOG.debug("stderr " + stderr)
+ assert ret == 0
+
+ self.keys.update({
+ 'dmcrypt_key': base64_key,
+ 'cephx_lockbox_secret': secret.strip(),
+ })
+
+ def write_lockbox_keyring(self, path, osd_uuid):
+ keyring = os.path.join(path, 'keyring')
+ command_check_call(
+ [
+ 'ceph-authtool', keyring,
+ '--create-keyring',
+ '--name', 'client.osd-lockbox.' + osd_uuid,
+ '--add-key', self.keys['cephx_lockbox_secret'],
+ ])
+ path_set_context(keyring)
+
+
+class Lockbox(object):
+
+ def __init__(self, args):
+ self.args = args
+ self.partition = None
+ self.device = None
+
+ if hasattr(self.args, 'lockbox') and self.args.lockbox is None:
+ self.args.lockbox = self.args.data
+
+ def set_partition(self, partition):
+ self.partition = partition
+
+ @staticmethod
+ def parser():
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument(
+ '--lockbox',
+ help='path to the device to store the lockbox',
+ )
+ parser.add_argument(
+ '--lockbox-uuid',
+ metavar='UUID',
+ help='unique lockbox uuid',
+ )
+ return parser
+
+ def create_partition(self):
+ self.device = Device.factory(self.args.lockbox, argparse.Namespace())
+ partition_number = 5
+ self.device.create_partition(uuid=self.args.lockbox_uuid,
+ name='lockbox',
+ num=partition_number,
+ size=10) # MB
+ return self.device.get_partition(partition_number)
+
+ def set_or_create_partition(self):
+ if is_partition(self.args.lockbox):
+ LOG.debug('OSD lockbox device %s is a partition',
+ self.args.lockbox)
+ self.partition = DevicePartition.factory(
+ path=None, dev=self.args.lockbox, args=self.args)
+ ptype = self.partition.get_ptype()
+ ready = Ptype.get_ready_by_name('lockbox')
+ if ptype not in ready:
+ LOG.warning('incorrect partition UUID: %s, expected %s'
+ % (ptype, str(ready)))
+ else:
+ LOG.debug('Creating osd partition on %s',
+ self.args.lockbox)
+ self.partition = self.create_partition()
+
+ def create_key(self):
+ cluster = self.args.cluster
+ bootstrap = self.args.prepare_key_template.format(cluster=cluster,
+ statedir=STATEDIR)
+ path = self.get_mount_point()
+ secrets = LockboxSecrets(self.args)
+ id_arg = self.args.osd_id and [self.args.osd_id] or []
+ osd_id = command_with_stdin(
+ [
+ 'ceph',
+ '--cluster', cluster,
+ '--name', 'client.bootstrap-osd',
+ '--keyring', bootstrap,
+ '-i', '-',
+ 'osd', 'new', self.args.osd_uuid,
+ ] + id_arg,
+ secrets.get_json()
+ )
+ secrets.write_lockbox_keyring(path, self.args.osd_uuid)
+ osd_id = must_be_one_line(osd_id)
+ check_osd_id(osd_id)
+ write_one_line(path, 'whoami', osd_id)
+ secrets.write_osd_keyring(os.path.join(path, 'osd_keyring'), osd_id)
+ write_one_line(path, 'key-management-mode', KEY_MANAGEMENT_MODE_V1)
+
+ def symlink_spaces(self, path):
+ target = self.get_mount_point()
+ for name in Space.NAMES:
+ if (hasattr(self.args, name + '_uuid') and
+ getattr(self.args, name + '_uuid')):
+ uuid = getattr(self.args, name + '_uuid')
+ symlink = os.path.join(STATEDIR, 'osd-lockbox', uuid)
+ adjust_symlink(target, symlink)
+ write_one_line(path, name + '-uuid', uuid)
+
+ def populate(self):
+ maybe_mkdir(os.path.join(STATEDIR, 'osd-lockbox'))
+ args = ['mkfs', '-t', 'ext4', self.partition.get_dev()]
+ LOG.debug('Creating lockbox fs on %s: ' + str(" ".join(args)))
+ command_check_call(args)
+ path = self.get_mount_point()
+ maybe_mkdir(path)
+ args = ['mount', '-t', 'ext4', self.partition.get_dev(), path]
+ LOG.debug('Mounting lockbox ' + str(" ".join(args)))
+ command_check_call(args)
+ write_one_line(path, 'osd-uuid', self.args.osd_uuid)
+ if self.args.cluster_uuid is None:
+ self.args.cluster_uuid = get_fsid(cluster=self.args.cluster)
+ write_one_line(path, 'ceph_fsid', self.args.cluster_uuid)
+ self.create_key()
+ self.symlink_spaces(path)
+ write_one_line(path, 'magic', CEPH_LOCKBOX_ONDISK_MAGIC)
+ if self.device is not None:
+ command_check_call(
+ [
+ 'sgdisk',
+ '--typecode={num}:{uuid}'.format(
+ num=self.partition.get_partition_number(),
+ uuid=self.partition.ptype_for_name('lockbox'),
+ ),
+ '--',
+ get_partition_base(self.partition.get_dev()),
+ ],
+ )
+
+ def get_mount_point(self):
+ return os.path.join(STATEDIR, 'osd-lockbox', self.args.osd_uuid)
+
+ def get_osd_uuid(self):
+ return self.args.osd_uuid
+
+ def activate(self):
+ path = is_mounted(self.partition.get_dev())
+ if path:
+ LOG.info("Lockbox already mounted at " + path)
+ return
+
+ path = tempfile.mkdtemp(
+ prefix='mnt.',
+ dir=STATEDIR + '/tmp',
+ )
+ args = ['mount', '-t', 'ext4', '-o', 'ro',
+ self.partition.get_dev(),
+ path]
+ LOG.debug('Mounting lockbox temporarily ' + str(" ".join(args)))
+ command_check_call(args)
+ self.args.osd_uuid = get_oneliner(path, 'osd-uuid')
+ command_check_call(['umount', path])
+ LOG.debug('Mounting lockbox readonly ' + str(" ".join(args)))
+ args = ['mount', '-t', 'ext4', '-o', 'ro',
+ self.partition.get_dev(),
+ self.get_mount_point()]
+ command_check_call(args)
+ for name in Space.NAMES + ('osd',):
+ uuid_path = os.path.join(self.get_mount_point(), name + '-uuid')
+ if os.path.exists(uuid_path):
+ uuid = get_oneliner(self.get_mount_point(), name + '-uuid')
+ dev = os.path.join('/dev/disk/by-partuuid/', uuid.lower())
+ args = ['ceph-disk', 'trigger', dev]
+ command_check_call(args)
+
+ def prepare(self):
+ verify_not_in_use(self.args.lockbox, check_partitions=True)
+ self.set_or_create_partition()
+ self.populate()
+
+
+class PrepareData(object):
+
+ FILE = 1
+ DEVICE = 2
+
+ def __init__(self, args):
+
+ self.args = args
+ self.partition = None
+ self.set_type()
+ if self.args.cluster_uuid is None:
+ self.args.cluster_uuid = get_fsid(cluster=self.args.cluster)
+
+ if self.args.osd_uuid is None:
+ self.args.osd_uuid = str(uuid.uuid4())
+
+ def set_type(self):
+ dmode = os.stat(self.args.data).st_mode
+
+ if stat.S_ISDIR(dmode):
+ self.type = self.FILE
+ elif stmode_is_diskdevice(dmode):
+ self.type = self.DEVICE
+ else:
+ raise Error('not a dir or block device', self.args.data)
+
+ def is_file(self):
+ return self.type == self.FILE
+
+ def is_device(self):
+ return self.type == self.DEVICE
+
+ @staticmethod
+ def parser():
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument(
+ '--fs-type',
+ help='file system type to use (e.g. "ext4")',
+ )
+ parser.add_argument(
+ '--zap-disk',
+ action='store_true', default=None,
+ help='destroy the partition table (and content) of a disk',
+ )
+ parser.add_argument(
+ '--data-dir',
+ action='store_true', default=None,
+ help='verify that DATA is a dir',
+ )
+ parser.add_argument(
+ '--data-dev',
+ action='store_true', default=None,
+ help='verify that DATA is a block device',
+ )
+ parser.add_argument(
+ 'data',
+ metavar='DATA',
+ help='path to OSD data (a disk block device or directory)',
+ )
+ return parser
+
+ def populate_data_path_file(self, path, *to_prepare_list):
+ self.populate_data_path(path, *to_prepare_list)
+
+ def populate_data_path(self, path, *to_prepare_list):
+ if os.path.exists(os.path.join(path, 'magic')):
+ LOG.debug('Data dir %s already exists', path)
+ return
+ else:
+ LOG.debug('Preparing osd data dir %s', path)
+
+ if self.args.osd_uuid is None:
+ self.args.osd_uuid = str(uuid.uuid4())
+
+ write_one_line(path, 'ceph_fsid', self.args.cluster_uuid)
+ write_one_line(path, 'fsid', self.args.osd_uuid)
+ if self.args.osd_id:
+ write_one_line(path, 'wanttobe', self.args.osd_id)
+ if self.args.crush_device_class:
+ write_one_line(path, 'crush_device_class',
+ self.args.crush_device_class)
+ write_one_line(path, 'magic', CEPH_OSD_ONDISK_MAGIC)
+
+ for to_prepare in to_prepare_list:
+ to_prepare.populate_data_path(path)
+
+ def prepare(self, *to_prepare_list):
+ if self.type == self.DEVICE:
+ self.prepare_device(*to_prepare_list)
+ elif self.type == self.FILE:
+ self.prepare_file(*to_prepare_list)
+ else:
+ raise Error('unexpected type ', self.type)
+
+ def prepare_file(self, *to_prepare_list):
+
+ if not os.path.exists(self.args.data):
+ raise Error('data path for directory does not exist',
+ self.args.data)
+
+ if self.args.data_dev:
+ raise Error('data path is not a block device', self.args.data)
+
+ for to_prepare in to_prepare_list:
+ to_prepare.prepare()
+
+ self.populate_data_path_file(self.args.data, *to_prepare_list)
+
+ def sanity_checks(self):
+ if not os.path.exists(self.args.data):
+ raise Error('data path for device does not exist',
+ self.args.data)
+ verify_not_in_use(self.args.data,
+ check_partitions=not self.args.dmcrypt)
+
+ def set_variables(self):
+ if self.args.fs_type is None:
+ self.args.fs_type = get_conf(
+ cluster=self.args.cluster,
+ variable='osd_mkfs_type',
+ )
+ if self.args.fs_type is None:
+ self.args.fs_type = get_conf(
+ cluster=self.args.cluster,
+ variable='osd_fs_type',
+ )
+ if self.args.fs_type is None:
+ self.args.fs_type = DEFAULT_FS_TYPE
+
+ self.mkfs_args = get_conf(
+ cluster=self.args.cluster,
+ variable='osd_mkfs_options_{fstype}'.format(
+ fstype=self.args.fs_type,
+ ),
+ )
+ if self.mkfs_args is None:
+ self.mkfs_args = get_conf(
+ cluster=self.args.cluster,
+ variable='osd_fs_mkfs_options_{fstype}'.format(
+ fstype=self.args.fs_type,
+ ),
+ )
+
+ self.mount_options = get_mount_options(cluster=self.args.cluster,
+ fs_type=self.args.fs_type)
+
+ if self.args.osd_uuid is None:
+ self.args.osd_uuid = str(uuid.uuid4())
+
+ def prepare_device(self, *to_prepare_list):
+ self.sanity_checks()
+ self.set_variables()
+ if self.args.zap_disk is not None:
+ zap(self.args.data)
+
+ def create_data_partition(self):
+ device = Device.factory(self.args.data, self.args)
+ partition_number = 1
+ device.create_partition(uuid=self.args.osd_uuid,
+ name='data',
+ num=partition_number,
+ size=self.get_space_size())
+ return device.get_partition(partition_number)
+
+ def set_data_partition(self):
+ if is_partition(self.args.data):
+ LOG.debug('OSD data device %s is a partition',
+ self.args.data)
+ self.partition = DevicePartition.factory(
+ path=None, dev=self.args.data, args=self.args)
+ ptype = self.partition.get_ptype()
+ ready = Ptype.get_ready_by_name('osd')
+ if ptype not in ready:
+ LOG.warning('incorrect partition UUID: %s, expected %s'
+ % (ptype, str(ready)))
+ else:
+ LOG.debug('Creating osd partition on %s',
+ self.args.data)
+ self.partition = self.create_data_partition()
+
+ def populate_data_path_device(self, *to_prepare_list):
+ partition = self.partition
+
+ if isinstance(partition, DevicePartitionCrypt):
+ partition.map()
+
+ try:
+ args = [
+ 'mkfs',
+ '-t',
+ self.args.fs_type,
+ ]
+ if self.mkfs_args is not None:
+ args.extend(self.mkfs_args.split())
+ if self.args.fs_type == 'xfs':
+ args.extend(['-f']) # always force
+ else:
+ args.extend(MKFS_ARGS.get(self.args.fs_type, []))
+ args.extend([
+ '--',
+ partition.get_dev(),
+ ])
+ LOG.debug('Creating %s fs on %s',
+ self.args.fs_type, partition.get_dev())
+ command_check_call(args, exit=True)
+
+ path = mount(dev=partition.get_dev(),
+ fstype=self.args.fs_type,
+ options=self.mount_options)
+
+ try:
+ self.populate_data_path(path, *to_prepare_list)
+ finally:
+ path_set_context(path)
+ unmount(path)
+ finally:
+ if isinstance(partition, DevicePartitionCrypt):
+ partition.unmap()
+
+ if not is_partition(self.args.data):
+ command_check_call(
+ [
+ 'sgdisk',
+ '--typecode=%d:%s' % (partition.get_partition_number(),
+ partition.ptype_for_name('osd')),
+ '--',
+ self.args.data,
+ ],
+ exit=True,
+ )
+ update_partition(self.args.data, 'prepared')
+ command_check_call(['udevadm', 'trigger',
+ '--action=add',
+ '--sysname-match',
+ os.path.basename(partition.rawdev)])
+
+
+class PrepareFilestoreData(PrepareData):
+
+ def get_space_size(self):
+ return 0 # get as much space as possible
+
+ def prepare_device(self, *to_prepare_list):
+ super(PrepareFilestoreData, self).prepare_device(*to_prepare_list)
+ for to_prepare in to_prepare_list:
+ to_prepare.prepare()
+ self.set_data_partition()
+ self.populate_data_path_device(*to_prepare_list)
+
+ def populate_data_path(self, path, *to_prepare_list):
+ super(PrepareFilestoreData, self).populate_data_path(path,
+ *to_prepare_list)
+ write_one_line(path, 'type', 'filestore')
+
+
+class PrepareBluestoreData(PrepareData):
+
+ def get_space_size(self):
+ return 100 # MB
+
+ def prepare_device(self, *to_prepare_list):
+ super(PrepareBluestoreData, self).prepare_device(*to_prepare_list)
+ self.set_data_partition()
+ for to_prepare in to_prepare_list:
+ to_prepare.prepare()
+ self.populate_data_path_device(*to_prepare_list)
+
+ def populate_data_path(self, path, *to_prepare_list):
+ super(PrepareBluestoreData, self).populate_data_path(path,
+ *to_prepare_list)
+ write_one_line(path, 'type', 'bluestore')
+
+
+def mkfs(
+ path,
+ cluster,
+ osd_id,
+ fsid,
+ keyring,
+):
+ monmap = os.path.join(path, 'activate.monmap')
+ command_check_call(
+ [
+ 'ceph',
+ '--cluster', cluster,
+ '--name', 'client.bootstrap-osd',
+ '--keyring', keyring,
+ 'mon', 'getmap', '-o', monmap,
+ ],
+ )
+
+ osd_type = read_one_line(path, 'type')
+
+ if osd_type == 'bluestore':
+ command_check_call(
+ [
+ 'ceph-osd',
+ '--cluster', cluster,
+ '--mkfs',
+ '-i', osd_id,
+ '--monmap', monmap,
+ '--osd-data', path,
+ '--osd-uuid', fsid,
+ '--setuser', get_ceph_user(),
+ '--setgroup', get_ceph_group(),
+ ],
+ )
+ elif osd_type == 'filestore':
+ command_check_call(
+ [
+ 'ceph-osd',
+ '--cluster', cluster,
+ '--mkfs',
+ '-i', osd_id,
+ '--monmap', monmap,
+ '--osd-data', path,
+ '--osd-journal', os.path.join(path, 'journal'),
+ '--osd-uuid', fsid,
+ '--setuser', get_ceph_user(),
+ '--setgroup', get_ceph_group(),
+ ],
+ )
+ else:
+ raise Error('unrecognized objectstore type %s' % osd_type)
+
+
+def get_mount_point(cluster, osd_id):
+ parent = STATEDIR + '/osd'
+ return os.path.join(
+ parent,
+ '{cluster}-{osd_id}'.format(cluster=cluster, osd_id=osd_id),
+ )
+
+
+def move_mount(
+ dev,
+ path,
+ cluster,
+ osd_id,
+ fstype,
+ mount_options,
+):
+ LOG.debug('Moving mount to final location...')
+ osd_data = get_mount_point(cluster, osd_id)
+ maybe_mkdir(osd_data)
+
+ # pick best-of-breed mount options based on fs type
+ if mount_options is None:
+ mount_options = MOUNT_OPTIONS.get(fstype, '')
+
+ # we really want to mount --move, but that is not supported when
+ # the parent mount is shared, as it is by default on RH, Fedora,
+ # and probably others. Also, --bind doesn't properly manipulate
+ # /etc/mtab, which *still* isn't a symlink to /proc/mounts despite
+ # this being 2013. Instead, mount the original device at the final
+ # location.
+ command_check_call(
+ [
+ '/bin/mount',
+ '-o',
+ mount_options,
+ '--',
+ dev,
+ osd_data,
+ ],
+ )
+ command_check_call(
+ [
+ '/bin/umount',
+ '-l', # lazy, in case someone else is peeking at the
+ # wrong moment
+ '--',
+ path,
+ ],
+ )
+
+
+#
+# For upgrade purposes, to make sure there are no competing units,
+# both --runtime unit and the default should be disabled. There can be
+# two units at the same time: one with --runtime and another without
+# it. If, for any reason (manual or ceph-disk) the two units co-exist
+# they will compete with each other.
+#
+def systemd_disable(
+ path,
+ osd_id,
+):
+ # ensure there is no duplicate ceph-osd@.service
+ for style in ([], ['--runtime']):
+ command_check_call(
+ [
+ 'systemctl',
+ 'disable',
+ 'ceph-osd@{osd_id}'.format(osd_id=osd_id),
+ ] + style,
+ )
+
+
+def systemd_start(
+ path,
+ osd_id,
+):
+ systemd_disable(path, osd_id)
+ if os.path.ismount(path):
+ style = ['--runtime']
+ else:
+ style = []
+ command_check_call(
+ [
+ 'systemctl',
+ 'enable',
+ 'ceph-osd@{osd_id}'.format(osd_id=osd_id),
+ ] + style,
+ )
+ command_check_call(
+ [
+ 'systemctl',
+ 'start',
+ 'ceph-osd@{osd_id}'.format(osd_id=osd_id),
+ ],
+ )
+
+
+def systemd_stop(
+ path,
+ osd_id,
+):
+ systemd_disable(path, osd_id)
+ command_check_call(
+ [
+ 'systemctl',
+ 'stop',
+ 'ceph-osd@{osd_id}'.format(osd_id=osd_id),
+ ],
+ )
+
+
+def start_daemon(
+ cluster,
+ osd_id,
+):
+ LOG.debug('Starting %s osd.%s...', cluster, osd_id)
+
+ path = (STATEDIR + '/osd/{cluster}-{osd_id}').format(
+ cluster=cluster, osd_id=osd_id)
+
+ try:
+ if os.path.exists(os.path.join(path, 'upstart')):
+ command_check_call(
+ [
+ '/sbin/initctl',
+ # use emit, not start, because start would fail if the
+ # instance was already running
+ 'emit',
+ # since the daemon starting doesn't guarantee much about
+ # the service being operational anyway, don't bother
+ # waiting for it
+ '--no-wait',
+ '--',
+ 'ceph-osd',
+ 'cluster={cluster}'.format(cluster=cluster),
+ 'id={osd_id}'.format(osd_id=osd_id),
+ ],
+ )
+ elif os.path.exists(os.path.join(path, 'sysvinit')):
+ if os.path.exists('/usr/sbin/service'):
+ svc = '/usr/sbin/service'
+ else:
+ svc = '/sbin/service'
+ command_check_call(
+ [
+ svc,
+ 'ceph',
+ '--cluster',
+ '{cluster}'.format(cluster=cluster),
+ 'start',
+ 'osd.{osd_id}'.format(osd_id=osd_id),
+ ],
+ )
+ elif os.path.exists(os.path.join(path, 'systemd')):
+ systemd_start(path, osd_id)
+ elif os.path.exists(os.path.join(path, 'openrc')):
+ base_script = '/etc/init.d/ceph-osd'
+ osd_script = '{base}.{osd_id}'.format(
+ base=base_script,
+ osd_id=osd_id
+ )
+ if not os.path.exists(osd_script):
+ os.symlink(base_script, osd_script)
+ command_check_call(
+ [
+ osd_script,
+ 'start',
+ ],
+ )
+ elif os.path.exists(os.path.join(path, 'bsdrc')):
+ command_check_call(
+ [
+ '/usr/sbin/service', 'ceph', 'start',
+ 'osd.{osd_id}'.format(osd_id=osd_id),
+ ],
+ )
+ else:
+ raise Error('{cluster} osd.{osd_id} '
+ 'is not tagged with an init system'
+ .format(
+ cluster=cluster,
+ osd_id=osd_id,
+ ))
+ except subprocess.CalledProcessError as e:
+ raise Error('ceph osd start failed', e)
+
+
+def stop_daemon(
+ cluster,
+ osd_id,
+):
+ LOG.debug('Stoping %s osd.%s...', cluster, osd_id)
+
+ path = (STATEDIR + '/osd/{cluster}-{osd_id}').format(
+ cluster=cluster, osd_id=osd_id)
+
+ try:
+ if os.path.exists(os.path.join(path, 'upstart')):
+ command_check_call(
+ [
+ '/sbin/initctl',
+ 'stop',
+ 'ceph-osd',
+ 'cluster={cluster}'.format(cluster=cluster),
+ 'id={osd_id}'.format(osd_id=osd_id),
+ ],
+ )
+ elif os.path.exists(os.path.join(path, 'sysvinit')):
+ svc = which('service')
+ command_check_call(
+ [
+ svc,
+ 'ceph',
+ '--cluster',
+ '{cluster}'.format(cluster=cluster),
+ 'stop',
+ 'osd.{osd_id}'.format(osd_id=osd_id),
+ ],
+ )
+ elif os.path.exists(os.path.join(path, 'systemd')):
+ systemd_stop(path, osd_id)
+ elif os.path.exists(os.path.join(path, 'openrc')):
+ command_check_call(
+ [
+ '/etc/init.d/ceph-osd.{osd_id}'.format(osd_id=osd_id),
+ 'stop',
+ ],
+ )
+ elif os.path.exists(os.path.join(path, 'bsdrc')):
+ command_check_call(
+ [
+ '/usr/local/etc/rc.d/ceph stop osd.{osd_id}'
+ .format(osd_id=osd_id),
+ ],
+ )
+ else:
+ raise Error('{cluster} osd.{osd_id} '
+ 'is not tagged with an init system'
+ .format(cluster=cluster, osd_id=osd_id))
+ except subprocess.CalledProcessError as e:
+ raise Error('ceph osd stop failed', e)
+
+
+def detect_fstype(dev):
+ if FREEBSD:
+ fstype = _check_output(
+ args=[
+ 'fstyp',
+ '-u',
+ dev,
+ ],
+ )
+ else:
+ fstype = _check_output(
+ args=[
+ '/sbin/blkid',
+ # we don't want stale cached results
+ '-p',
+ '-s', 'TYPE',
+ '-o', 'value',
+ '--',
+ dev,
+ ],
+ )
+ fstype = must_be_one_line(fstype)
+ return fstype
+
+
+def dmcrypt_is_mapped(uuid):
+ path = os.path.join('/dev/mapper', uuid)
+ if os.path.exists(path):
+ return path
+ else:
+ return None
+
+
+def dmcrypt_map(dev, dmcrypt_key_dir):
+ ptype = get_partition_type(dev)
+ if ptype in Ptype.get_ready_by_type('plain'):
+ luks = False
+ cryptsetup_parameters = ['--key-size', '256']
+ elif ptype in Ptype.get_ready_by_type('luks'):
+ luks = True
+ cryptsetup_parameters = []
+ else:
+ raise Error('--dmcrypt called for dev %s with invalid ptype %s'
+ % (dev, ptype))
+ part_uuid = get_partition_uuid(dev)
+ dmcrypt_key = get_dmcrypt_key(part_uuid, dmcrypt_key_dir, luks)
+ return _dmcrypt_map(
+ rawdev=dev,
+ key=dmcrypt_key,
+ _uuid=part_uuid,
+ cryptsetup_parameters=cryptsetup_parameters,
+ luks=luks,
+ format_dev=False,
+ )
+
+
+def mount_activate(
+ dev,
+ activate_key_template,
+ init,
+ dmcrypt,
+ dmcrypt_key_dir,
+ reactivate=False,
+):
+
+ if dmcrypt:
+ part_uuid = get_partition_uuid(dev)
+ dev = dmcrypt_map(dev, dmcrypt_key_dir)
+ try:
+ fstype = detect_fstype(dev=dev)
+ except (subprocess.CalledProcessError,
+ TruncatedLineError,
+ TooManyLinesError) as e:
+ raise FilesystemTypeError(
+ 'device {dev}'.format(dev=dev),
+ e,
+ )
+
+ # TODO always using mount options from cluster=ceph for
+ # now; see http://tracker.newdream.net/issues/3253
+ mount_options = get_mount_options(cluster='ceph', fs_type=fstype)
+
+ path = mount(dev=dev, fstype=fstype, options=mount_options)
+
+ # check if the disk is deactive, change the journal owner, group
+ # mode for correct user and group.
+ if os.path.exists(os.path.join(path, 'deactive')):
+ # logging to syslog will help us easy to know udev triggered failure
+ if not reactivate:
+ unmount(path)
+ # we need to unmap again because dmcrypt map will create again
+ # on bootup stage (due to deactivate)
+ if '/dev/mapper/' in dev:
+ part_uuid = dev.replace('/dev/mapper/', '')
+ dmcrypt_unmap(part_uuid)
+ LOG.info('OSD deactivated! reactivate with: --reactivate')
+ raise Error('OSD deactivated! reactivate with: --reactivate')
+ # flag to activate a deactive osd.
+ deactive = True
+ else:
+ deactive = False
+
+ osd_id = None
+ cluster = None
+ try:
+ (osd_id, cluster) = activate(path, activate_key_template, init)
+
+ # Now active successfully
+ # If we got reactivate and deactive, remove the deactive file
+ if deactive and reactivate:
+ os.remove(os.path.join(path, 'deactive'))
+ LOG.info('Remove `deactive` file.')
+
+ # check if the disk is already active, or if something else is already
+ # mounted there
+ active = False
+ other = False
+ src_dev = os.stat(path).st_dev
+ try:
+ dst_dev = os.stat((STATEDIR + '/osd/{cluster}-{osd_id}').format(
+ cluster=cluster,
+ osd_id=osd_id)).st_dev
+ if src_dev == dst_dev:
+ active = True
+ else:
+ parent_dev = os.stat(STATEDIR + '/osd').st_dev
+ if dst_dev != parent_dev:
+ other = True
+ elif os.listdir(get_mount_point(cluster, osd_id)):
+ LOG.info(get_mount_point(cluster, osd_id) +
+ " is not empty, won't override")
+ other = True
+
+ except OSError:
+ pass
+
+ if active:
+ LOG.info('%s osd.%s already mounted in position; unmounting ours.'
+ % (cluster, osd_id))
+ unmount(path)
+ elif other:
+ raise Error('another %s osd.%s already mounted in position '
+ '(old/different cluster instance?); unmounting ours.'
+ % (cluster, osd_id))
+ else:
+ move_mount(
+ dev=dev,
+ path=path,
+ cluster=cluster,
+ osd_id=osd_id,
+ fstype=fstype,
+ mount_options=mount_options,
+ )
+ return cluster, osd_id
+
+ except:
+ LOG.error('Failed to activate')
+ unmount(path)
+ raise
+ finally:
+ # remove our temp dir
+ if os.path.exists(path):
+ os.rmdir(path)
+
+
+def activate_dir(
+ path,
+ activate_key_template,
+ init,
+):
+
+ if not os.path.exists(path):
+ raise Error(
+ 'directory %s does not exist' % path
+ )
+
+ (osd_id, cluster) = activate(path, activate_key_template, init)
+
+ if init not in (None, 'none'):
+ canonical = (STATEDIR + '/osd/{cluster}-{osd_id}').format(
+ cluster=cluster,
+ osd_id=osd_id)
+ if path != canonical:
+ # symlink it from the proper location
+ create = True
+ if os.path.lexists(canonical):
+ old = os.readlink(canonical)
+ if old != path:
+ LOG.debug('Removing old symlink %s -> %s', canonical, old)
+ try:
+ os.unlink(canonical)
+ except:
+ raise Error('unable to remove old symlink', canonical)
+ else:
+ create = False
+ if create:
+ LOG.debug('Creating symlink %s -> %s', canonical, path)
+ try:
+ os.symlink(path, canonical)
+ except:
+ raise Error('unable to create symlink %s -> %s'
+ % (canonical, path))
+
+ return cluster, osd_id
+
+
+def find_cluster_by_uuid(_uuid):
+ """
+ Find a cluster name by searching /etc/ceph/*.conf for a conf file
+ with the right uuid.
+ """
+ _uuid = _uuid.lower()
+ no_fsid = []
+ if not os.path.exists(SYSCONFDIR):
+ return None
+ for conf_file in os.listdir(SYSCONFDIR):
+ if not conf_file.endswith('.conf'):
+ continue
+ cluster = conf_file[:-5]
+ try:
+ fsid = get_fsid(cluster)
+ except Error as e:
+ if 'getting cluster uuid from configuration failed' not in str(e):
+ raise e
+ no_fsid.append(cluster)
+ else:
+ if fsid == _uuid:
+ return cluster
+ # be tolerant of /etc/ceph/ceph.conf without an fsid defined.
+ if len(no_fsid) == 1 and no_fsid[0] == 'ceph':
+ LOG.warning('No fsid defined in ' + SYSCONFDIR +
+ '/ceph.conf; using anyway')
+ return 'ceph'
+ return None
+
+
+def activate(
+ path,
+ activate_key_template,
+ init,
+):
+
+ check_osd_magic(path)
+
+ ceph_fsid = read_one_line(path, 'ceph_fsid')
+ if ceph_fsid is None:
+ raise Error('No cluster uuid assigned.')
+ LOG.debug('Cluster uuid is %s', ceph_fsid)
+
+ cluster = find_cluster_by_uuid(ceph_fsid)
+ if cluster is None:
+ raise Error('No cluster conf found in ' + SYSCONFDIR +
+ ' with fsid %s' % ceph_fsid)
+ LOG.debug('Cluster name is %s', cluster)
+
+ fsid = read_one_line(path, 'fsid')
+ if fsid is None:
+ raise Error('No OSD uuid assigned.')
+ LOG.debug('OSD uuid is %s', fsid)
+
+ keyring = activate_key_template.format(cluster=cluster,
+ statedir=STATEDIR)
+
+ osd_id = get_osd_id(path)
+ if osd_id is None:
+ osd_id = allocate_osd_id(
+ cluster=cluster,
+ fsid=fsid,
+ keyring=keyring,
+ path=path,
+ )
+ write_one_line(path, 'whoami', osd_id)
+ LOG.debug('OSD id is %s', osd_id)
+
+ if not os.path.exists(os.path.join(path, 'ready')):
+ LOG.debug('Initializing OSD...')
+ # re-running mkfs is safe, so just run until it completes
+ mkfs(
+ path=path,
+ cluster=cluster,
+ osd_id=osd_id,
+ fsid=fsid,
+ keyring=keyring,
+ )
+
+ if init not in (None, 'none'):
+ if init == 'auto':
+ conf_val = get_conf(
+ cluster=cluster,
+ variable='init'
+ )
+ if conf_val is not None:
+ init = conf_val
+ else:
+ init = init_get()
+
+ LOG.debug('Marking with init system %s', init)
+ init_path = os.path.join(path, init)
+ with open(init_path, 'w'):
+ path_set_context(init_path)
+
+ # remove markers for others, just in case.
+ for other in INIT_SYSTEMS:
+ if other != init:
+ try:
+ os.unlink(os.path.join(path, other))
+ except OSError:
+ pass
+
+ if not os.path.exists(os.path.join(path, 'active')):
+ write_one_line(path, 'active', 'ok')
+ LOG.debug('%s osd.%s data dir is ready at %s', cluster, osd_id, path)
+ return (osd_id, cluster)
+
+
+def main_activate(args):
+ cluster = None
+ osd_id = None
+
+ LOG.info('path = ' + str(args.path))
+ if not os.path.exists(args.path):
+ raise Error('%s does not exist' % args.path)
+
+ if is_suppressed(args.path):
+ LOG.info('suppressed activate request on %s', args.path)
+ return
+
+ with activate_lock:
+ mode = os.stat(args.path).st_mode
+ if stmode_is_diskdevice(mode):
+ if (is_partition(args.path) and
+ (get_partition_type(args.path) ==
+ PTYPE['mpath']['osd']['ready']) and
+ not is_mpath(args.path)):
+ raise Error('%s is not a multipath block device' %
+ args.path)
+ (cluster, osd_id) = mount_activate(
+ dev=args.path,
+ activate_key_template=args.activate_key_template,
+ init=args.mark_init,
+ dmcrypt=args.dmcrypt,
+ dmcrypt_key_dir=args.dmcrypt_key_dir,
+ reactivate=args.reactivate,
+ )
+ osd_data = get_mount_point(cluster, osd_id)
+
+ args.cluster = cluster
+ if args.dmcrypt:
+ for name in Space.NAMES:
+ # Check if encrypted device in journal
+ dev_path = os.path.join(osd_data, name + '_dmcrypt')
+ if not os.path.exists(dev_path):
+ continue
+ partition = DevicePartition.factory(
+ path=None,
+ dev=dev_path,
+ args=args)
+ partition.rawdev = args.path
+ partition.map()
+
+ elif stat.S_ISDIR(mode):
+ (cluster, osd_id) = activate_dir(
+ path=args.path,
+ activate_key_template=args.activate_key_template,
+ init=args.mark_init,
+ )
+ osd_data = args.path
+
+ else:
+ raise Error('%s is not a directory or block device' % args.path)
+
+ # exit with 0 if the journal device is not up, yet
+ # journal device will do the activation
+ osd_journal = '{path}/journal'.format(path=osd_data)
+ if os.path.islink(osd_journal) and not os.access(osd_journal, os.F_OK):
+ LOG.info("activate: Journal not present, not starting, yet")
+ return
+
+ if (not args.no_start_daemon and args.mark_init == 'none'):
+ command_check_call(
+ [
+ 'ceph-osd',
+ '--cluster={cluster}'.format(cluster=cluster),
+ '--id={osd_id}'.format(osd_id=osd_id),
+ '--osd-data={path}'.format(path=osd_data),
+ '--osd-journal={journal}'.format(journal=osd_journal),
+ ],
+ )
+
+ if (not args.no_start_daemon and
+ args.mark_init not in (None, 'none')):
+
+ start_daemon(
+ cluster=cluster,
+ osd_id=osd_id,
+ )
+
+
+def main_activate_lockbox(args):
+ with activate_lock:
+ main_activate_lockbox_protected(args)
+
+
+def main_activate_lockbox_protected(args):
+ partition = DevicePartition.factory(
+ path=None, dev=args.path, args=args)
+
+ lockbox = Lockbox(args)
+ lockbox.set_partition(partition)
+ lockbox.activate()
+
+
+###########################
+
+def _mark_osd_out(cluster, osd_id):
+ LOG.info('Prepare to mark osd.%d out...', osd_id)
+ command([
+ 'ceph',
+ 'osd',
+ 'out',
+ 'osd.%d' % osd_id,
+ ])
+
+
+def _check_osd_status(cluster, osd_id):
+ """
+ report the osd status:
+ 00(0) : means OSD OUT AND DOWN
+ 01(1) : means OSD OUT AND UP
+ 10(2) : means OSD IN AND DOWN
+ 11(3) : means OSD IN AND UP
+ """
+ LOG.info("Checking osd id: %s ..." % osd_id)
+ found = False
+ status_code = 0
+ out, err, ret = command([
+ 'ceph',
+ 'osd',
+ 'dump',
+ '--cluster={cluster}'.format(
+ cluster=cluster,
+ ),
+ '--format',
+ 'json',
+ ])
+ out_json = json.loads(out)
+ for item in out_json[u'osds']:
+ if item.get(u'osd') == int(osd_id):
+ found = True
+ if item.get(u'in') is 1:
+ status_code += 2
+ if item.get(u'up') is 1:
+ status_code += 1
+ if not found:
+ raise Error('Could not osd.%s in osd tree!' % osd_id)
+ return status_code
+
+
+def _remove_osd_directory_files(mounted_path, cluster):
+ """
+ To remove the 'ready', 'active', INIT-specific files.
+ """
+ if os.path.exists(os.path.join(mounted_path, 'ready')):
+ os.remove(os.path.join(mounted_path, 'ready'))
+ LOG.info('Remove `ready` file.')
+ else:
+ LOG.info('`ready` file is already removed.')
+
+ if os.path.exists(os.path.join(mounted_path, 'active')):
+ os.remove(os.path.join(mounted_path, 'active'))
+ LOG.info('Remove `active` file.')
+ else:
+ LOG.info('`active` file is already removed.')
+
+ # Just check `upstart` and `sysvinit` directly if filename is init-spec.
+ conf_val = get_conf(
+ cluster=cluster,
+ variable='init'
+ )
+ if conf_val is not None:
+ init = conf_val
+ else:
+ init = init_get()
+ os.remove(os.path.join(mounted_path, init))
+ LOG.info('Remove `%s` file.', init)
+ return
+
+
+def main_deactivate(args):
+ with activate_lock:
+ main_deactivate_locked(args)
+
+
+def main_deactivate_locked(args):
+ osd_id = args.deactivate_by_id
+ path = args.path
+ target_dev = None
+ dmcrypt = False
+ devices = list_devices()
+
+ # list all devices and found we need
+ for device in devices:
+ if 'partitions' in device:
+ for dev_part in device.get('partitions'):
+ if (osd_id and
+ 'whoami' in dev_part and
+ dev_part['whoami'] == osd_id):
+ target_dev = dev_part
+ elif (path and
+ 'path' in dev_part and
+ dev_part['path'] == path):
+ target_dev = dev_part
+ if not target_dev:
+ raise Error('Cannot find any match device!!')
+
+ # set up all we need variable
+ osd_id = target_dev['whoami']
+ part_type = target_dev['ptype']
+ mounted_path = target_dev['mount']
+ if Ptype.is_dmcrypt(part_type, 'osd'):
+ dmcrypt = True
+
+ # Do not do anything if osd is already down.
+ status_code = _check_osd_status(args.cluster, osd_id)
+ if status_code == OSD_STATUS_IN_UP:
+ if args.mark_out is True:
+ _mark_osd_out(args.cluster, int(osd_id))
+ stop_daemon(args.cluster, osd_id)
+ elif status_code == OSD_STATUS_IN_DOWN:
+ if args.mark_out is True:
+ _mark_osd_out(args.cluster, int(osd_id))
+ LOG.info("OSD already out/down. Do not do anything now.")
+ return
+ elif status_code == OSD_STATUS_OUT_UP:
+ stop_daemon(args.cluster, osd_id)
+ elif status_code == OSD_STATUS_OUT_DOWN:
+ LOG.info("OSD already out/down. Do not do anything now.")
+ return
+
+ if not args.once:
+ # remove 'ready', 'active', and INIT-specific files.
+ _remove_osd_directory_files(mounted_path, args.cluster)
+
+ # Write deactivate to osd directory!
+ with open(os.path.join(mounted_path, 'deactive'), 'w'):
+ path_set_context(os.path.join(mounted_path, 'deactive'))
+
+ unmount(mounted_path, do_rm=not args.once)
+ LOG.info("Umount `%s` successfully.", mounted_path)
+
+ if dmcrypt:
+ lockbox = os.path.join(STATEDIR, 'osd-lockbox')
+ command(['umount', os.path.join(lockbox, target_dev['uuid'])])
+
+ dmcrypt_unmap(target_dev['uuid'])
+ for name in Space.NAMES:
+ if name + '_uuid' in target_dev:
+ dmcrypt_unmap(target_dev[name + '_uuid'])
+
+###########################
+
+
+def _remove_lockbox(uuid, cluster):
+ lockbox = os.path.join(STATEDIR, 'osd-lockbox')
+ if not os.path.exists(lockbox):
+ return
+ canonical = os.path.join(lockbox, uuid)
+ command(['umount', canonical])
+ for name in os.listdir(lockbox):
+ path = os.path.join(lockbox, name)
+ if os.path.islink(path) and os.readlink(path) == canonical:
+ os.unlink(path)
+
+
+def destroy_lookup_device(args, predicate, description):
+ devices = list_devices()
+ for device in devices:
+ for partition in device.get('partitions', []):
+ if partition['type'] == 'lockbox':
+ if not is_mounted(partition['path']):
+ main_activate_lockbox_protected(
+ argparse.Namespace(verbose=args.verbose,
+ path=partition['path']))
+ for device in devices:
+ for partition in device.get('partitions', []):
+ if partition['dmcrypt']:
+ dmcrypt_path = dmcrypt_is_mapped(partition['uuid'])
+ if dmcrypt_path:
+ unmap = False
+ else:
+ dmcrypt_path = dmcrypt_map(partition['path'],
+ args.dmcrypt_key_dir)
+ unmap = True
+ list_dev_osd(dmcrypt_path, {}, partition)
+ if unmap:
+ dmcrypt_unmap(partition['uuid'])
+ dmcrypt = True
+ else:
+ dmcrypt = False
+ if predicate(partition):
+ return dmcrypt, partition
+ raise Error('found no device matching ', description)
+
+
+def main_destroy(args):
+ with activate_lock:
+ main_destroy_locked(args)
+
+
+def main_destroy_locked(args):
+ osd_id = args.destroy_by_id
+ path = args.path
+ target_dev = None
+
+ if path:
+ if not is_partition(path):
+ raise Error(path + " must be a partition device")
+ path = os.path.realpath(path)
+
+ if path:
+ (dmcrypt, target_dev) = destroy_lookup_device(
+ args, lambda x: x.get('path') == path,
+ path)
+ elif osd_id:
+ (dmcrypt, target_dev) = destroy_lookup_device(
+ args, lambda x: x.get('whoami') == osd_id,
+ 'osd id ' + str(osd_id))
+
+ osd_id = target_dev['whoami']
+ dev_path = target_dev['path']
+ if target_dev['ptype'] == PTYPE['mpath']['osd']['ready']:
+ base_dev = get_partition_base_mpath(dev_path)
+ else:
+ base_dev = get_partition_base(dev_path)
+
+ # Before osd deactivate, we cannot destroy it
+ status_code = _check_osd_status(args.cluster, osd_id)
+ if status_code != OSD_STATUS_OUT_DOWN and \
+ status_code != OSD_STATUS_IN_DOWN:
+ raise Error("Could not destroy the active osd. (osd-id: %s)" %
+ osd_id)
+
+ if args.purge:
+ action = 'purge'
+ else:
+ action = 'destroy'
+ LOG.info("Prepare to %s osd.%s" % (action, osd_id))
+ command([
+ 'ceph',
+ 'osd',
+ action,
+ 'osd.%s' % osd_id,
+ '--yes-i-really-mean-it',
+ ])
+
+ # we remove the crypt map and device mapper (if dmcrypt is True)
+ if dmcrypt:
+ for name in Space.NAMES:
+ if target_dev.get(name + '_uuid'):
+ dmcrypt_unmap(target_dev[name + '_uuid'])
+ _remove_lockbox(target_dev['uuid'], args.cluster)
+
+ # Check zap flag. If we found zap flag, we need to find device for
+ # destroy this osd data.
+ if args.zap is True:
+ # erase the osd data
+ LOG.info("Prepare to zap the device %s" % base_dev)
+ zap(base_dev)
+
+
+def get_space_osd_uuid(name, path):
+ if not os.path.exists(path):
+ raise Error('%s does not exist' % path)
+
+ if not path_is_diskdevice(path):
+ raise Error('%s is not a block device' % path)
+
+ if (is_partition(path) and
+ get_partition_type(path) in (PTYPE['mpath']['journal']['ready'],
+ PTYPE['mpath']['block']['ready']) and
+ not is_mpath(path)):
+ raise Error('%s is not a multipath block device' %
+ path)
+
+ try:
+ out = _check_output(
+ args=[
+ 'ceph-osd',
+ '--get-device-fsid',
+ path,
+ ],
+ close_fds=True,
+ )
+ except subprocess.CalledProcessError as e:
+ raise Error(
+ 'failed to get osd uuid/fsid from %s' % name,
+ e,
+ )
+ value = str(out).split('\n', 1)[0]
+ LOG.debug('%s %s has OSD UUID %s', name.capitalize(), path, value)
+ return value
+
+
+def main_activate_space(name, args):
+ if not os.path.exists(args.dev):
+ raise Error('%s does not exist' % args.dev)
+
+ if is_suppressed(args.dev):
+ LOG.info('suppressed activate request on space %s', args.dev)
+ return
+
+ cluster = None
+ osd_id = None
+ osd_uuid = None
+ dev = None
+ with activate_lock:
+ if args.dmcrypt:
+ dev = dmcrypt_map(args.dev, args.dmcrypt_key_dir)
+ else:
+ dev = args.dev
+ # FIXME: For an encrypted journal dev, does this return the
+ # cyphertext or plaintext dev uuid!? Also, if the journal is
+ # encrypted, is the data partition also always encrypted, or
+ # are mixed pairs supported!?
+ osd_uuid = get_space_osd_uuid(name, dev)
+ path = os.path.join('/dev/disk/by-partuuid/', osd_uuid.lower())
+
+ if is_suppressed(path):
+ LOG.info('suppressed activate request on %s', path)
+ return
+
+ # warn and exit with 0 if the data device is not up, yet
+ # data device will do the activation
+ if not os.access(path, os.F_OK):
+ LOG.info("activate: OSD device not present, not starting, yet")
+ return
+
+ (cluster, osd_id) = mount_activate(
+ dev=path,
+ activate_key_template=args.activate_key_template,
+ init=args.mark_init,
+ dmcrypt=args.dmcrypt,
+ dmcrypt_key_dir=args.dmcrypt_key_dir,
+ reactivate=args.reactivate,
+ )
+
+ start_daemon(
+ cluster=cluster,
+ osd_id=osd_id,
+ )
+
+
+###########################
+
+
+def main_activate_all(args):
+ dir = '/dev/disk/by-parttypeuuid'
+ LOG.debug('Scanning %s', dir)
+ if not os.path.exists(dir):
+ return
+ err = False
+ for name in os.listdir(dir):
+ if name.find('.') < 0:
+ continue
+ (tag, uuid) = name.split('.')
+
+ if tag in Ptype.get_ready_by_name('osd'):
+
+ if Ptype.is_dmcrypt(tag, 'osd'):
+ path = os.path.join('/dev/mapper', uuid)
+ else:
+ path = os.path.join(dir, name)
+
+ if is_suppressed(path):
+ LOG.info('suppressed activate request on %s', path)
+ continue
+
+ LOG.info('Activating %s', path)
+ with activate_lock:
+ try:
+ # never map dmcrypt cyphertext devices
+ (cluster, osd_id) = mount_activate(
+ dev=path,
+ activate_key_template=args.activate_key_template,
+ init=args.mark_init,
+ dmcrypt=False,
+ dmcrypt_key_dir='',
+ )
+ start_daemon(
+ cluster=cluster,
+ osd_id=osd_id,
+ )
+
+ except Exception as e:
+ print(
+ '{prog}: {msg}'.format(prog=args.prog, msg=e),
+ file=sys.stderr
+ )
+
+ err = True
+
+ if err:
+ raise Error('One or more partitions failed to activate')
+
+
+###########################
+
+def is_swap(dev):
+ dev = os.path.realpath(dev)
+ with open(PROCDIR + '/swaps', 'rb') as proc_swaps:
+ for line in proc_swaps.readlines()[1:]:
+ fields = line.split()
+ if len(fields) < 3:
+ continue
+ swaps_dev = fields[0]
+ if os.path.isabs(swaps_dev) and os.path.exists(swaps_dev):
+ swaps_dev = os.path.realpath(swaps_dev)
+ if swaps_dev == dev:
+ return True
+ return False
+
+
+def get_oneliner(base, name):
+ path = os.path.join(base, name)
+ if os.path.isfile(path):
+ with open(path, 'rb') as _file:
+ return _bytes2str(_file.readline().rstrip())
+ return None
+
+
+def get_dev_fs(dev):
+ if FREEBSD:
+ fstype, _, ret = command(
+ [
+ 'fstyp',
+ '-u',
+ dev,
+ ],
+ )
+ if ret == 0:
+ return fstype
+ else:
+ fscheck, _, _ = command(
+ [
+ 'blkid',
+ '-s',
+ 'TYPE',
+ dev,
+ ],
+ )
+ if 'TYPE' in fscheck:
+ fstype = fscheck.split()[1].split('"')[1]
+ return fstype
+ return None
+
+
+def split_dev_base_partnum(dev):
+ if is_mpath(dev):
+ partnum = partnum_mpath(dev)
+ base = get_partition_base_mpath(dev)
+ else:
+ b = block_path(dev)
+ partnum = open(os.path.join(b, 'partition')).read().strip()
+ base = get_partition_base(dev)
+ return base, partnum
+
+
+def get_partition_type(part):
+ return get_blkid_partition_info(part, 'ID_PART_ENTRY_TYPE')
+
+
+def get_partition_uuid(part):
+ return get_blkid_partition_info(part, 'ID_PART_ENTRY_UUID')
+
+
+def get_blkid_partition_info(dev, what=None):
+ out, _, _ = command(
+ [
+ 'blkid',
+ '-o',
+ 'udev',
+ '-p',
+ dev,
+ ]
+ )
+ p = {}
+ for line in out.splitlines():
+ (key, value) = line.split('=')
+ p[key] = value
+ if what:
+ return p.get(what)
+ else:
+ return p
+
+
+def more_osd_info(path, uuid_map, desc):
+ desc['ceph_fsid'] = get_oneliner(path, 'ceph_fsid')
+ if desc['ceph_fsid']:
+ desc['cluster'] = find_cluster_by_uuid(desc['ceph_fsid'])
+ desc['whoami'] = get_oneliner(path, 'whoami')
+ for name in Space.NAMES:
+ uuid = get_oneliner(path, name + '_uuid')
+ if uuid:
+ desc[name + '_uuid'] = uuid.lower()
+ if desc[name + '_uuid'] in uuid_map:
+ desc[name + '_dev'] = uuid_map[desc[name + '_uuid']]
+
+
+def list_dev_osd(dev, uuid_map, desc):
+ desc['mount'] = is_mounted(dev)
+ desc['fs_type'] = get_dev_fs(dev)
+ desc['state'] = 'unprepared'
+ if desc['mount']:
+ desc['state'] = 'active'
+ more_osd_info(desc['mount'], uuid_map, desc)
+ elif desc['fs_type']:
+ try:
+ tpath = mount(dev=dev, fstype=desc['fs_type'], options='')
+ if tpath:
+ try:
+ magic = get_oneliner(tpath, 'magic')
+ if magic is not None:
+ desc['magic'] = magic
+ desc['state'] = 'prepared'
+ more_osd_info(tpath, uuid_map, desc)
+ finally:
+ unmount(tpath)
+ except MountError:
+ pass
+
+
+def list_dev_lockbox(dev, uuid_map, desc):
+ desc['mount'] = is_mounted(dev)
+ desc['fs_type'] = get_dev_fs(dev)
+ desc['state'] = 'unprepared'
+ if desc['mount']:
+ desc['state'] = 'active'
+ desc['osd_uuid'] = get_oneliner(desc['mount'], 'osd-uuid')
+ elif desc['fs_type']:
+ try:
+ tpath = tempfile.mkdtemp(prefix='mnt.', dir=STATEDIR + '/tmp')
+ args = ['mount', '-t', 'ext4', dev, tpath]
+ LOG.debug('Mounting lockbox ' + str(" ".join(args)))
+ command_check_call(args)
+ magic = get_oneliner(tpath, 'magic')
+ if magic is not None:
+ desc['magic'] = magic
+ desc['state'] = 'prepared'
+ desc['osd_uuid'] = get_oneliner(tpath, 'osd-uuid')
+ unmount(tpath)
+ except subprocess.CalledProcessError:
+ pass
+ if desc.get('osd_uuid') in uuid_map:
+ desc['lockbox_for'] = uuid_map[desc['osd_uuid']]
+
+
+def list_format_lockbox_plain(dev):
+ desc = []
+ if dev.get('lockbox_for'):
+ desc.append('for ' + dev['lockbox_for'])
+ elif dev.get('osd_uuid'):
+ desc.append('for osd ' + dev['osd_uuid'])
+ return desc
+
+
+def list_format_more_osd_info_plain(dev):
+ desc = []
+ if dev.get('ceph_fsid'):
+ if dev.get('cluster'):
+ desc.append('cluster ' + dev['cluster'])
+ else:
+ desc.append('unknown cluster ' + dev['ceph_fsid'])
+ if dev.get('whoami'):
+ desc.append('osd.%s' % dev['whoami'])
+ for name in Space.NAMES:
+ if dev.get(name + '_dev'):
+ desc.append(name + ' %s' % dev[name + '_dev'])
+ return desc
+
+
+def list_format_dev_plain(dev, prefix=''):
+ desc = []
+ if dev['ptype'] == PTYPE['regular']['osd']['ready']:
+ desc = (['ceph data', dev['state']] +
+ list_format_more_osd_info_plain(dev))
+ elif dev['ptype'] in (PTYPE['regular']['lockbox']['ready'],
+ PTYPE['mpath']['lockbox']['ready']):
+ desc = (['ceph lockbox', dev['state']] +
+ list_format_lockbox_plain(dev))
+ elif Ptype.is_dmcrypt(dev['ptype'], 'osd'):
+ dmcrypt = dev['dmcrypt']
+ if not dmcrypt['holders']:
+ desc = ['ceph data (dmcrypt %s)' % dmcrypt['type'],
+ 'not currently mapped']
+ elif len(dmcrypt['holders']) == 1:
+ holder = get_dev_path(dmcrypt['holders'][0])
+ desc = ['ceph data (dmcrypt %s %s)' %
+ (dmcrypt['type'], holder)]
+ desc += list_format_more_osd_info_plain(dev)
+ else:
+ desc = ['ceph data (dmcrypt %s)' % dmcrypt['type'],
+ 'holders: ' + ','.join(dmcrypt['holders'])]
+ elif Ptype.is_regular_space(dev['ptype']):
+ name = Ptype.space_ptype_to_name(dev['ptype'])
+ desc.append('ceph ' + name)
+ if dev.get(name + '_for'):
+ desc.append('for %s' % dev[name + '_for'])
+ elif Ptype.is_dmcrypt_space(dev['ptype']):
+ name = Ptype.space_ptype_to_name(dev['ptype'])
+ dmcrypt = dev['dmcrypt']
+ if dmcrypt['holders'] and len(dmcrypt['holders']) == 1:
+ holder = get_dev_path(dmcrypt['holders'][0])
+ desc = ['ceph ' + name + ' (dmcrypt %s %s)' %
+ (dmcrypt['type'], holder)]
+ else:
+ desc = ['ceph ' + name + ' (dmcrypt %s)' % dmcrypt['type']]
+ if dev.get(name + '_for'):
+ desc.append('for %s' % dev[name + '_for'])
+ else:
+ desc.append(dev['type'])
+ if dev.get('fs_type'):
+ desc.append(dev['fs_type'])
+ elif dev.get('ptype'):
+ desc.append(dev['ptype'])
+ if dev.get('mount'):
+ desc.append('mounted on %s' % dev['mount'])
+ return '%s%s %s' % (prefix, dev['path'], ', '.join(desc))
+
+
+def list_format_plain(devices):
+ lines = []
+ for device in devices:
+ if device.get('partitions'):
+ lines.append('%s :' % device['path'])
+ for p in sorted(device['partitions'], key=lambda x: x['path']):
+ lines.append(list_format_dev_plain(dev=p,
+ prefix=' '))
+ else:
+ lines.append(list_format_dev_plain(dev=device,
+ prefix=''))
+ return "\n".join(lines)
+
+
+def list_dev(dev, uuid_map, space_map):
+ info = {
+ 'path': dev,
+ 'dmcrypt': {},
+ }
+
+ info['is_partition'] = is_partition(dev)
+ if info['is_partition']:
+ ptype = get_partition_type(dev)
+ info['uuid'] = get_partition_uuid(dev)
+ else:
+ ptype = 'unknown'
+ info['ptype'] = ptype
+ LOG.info("list_dev(dev = " + dev + ", ptype = " + str(ptype) + ")")
+ if ptype in (PTYPE['regular']['osd']['ready'],
+ PTYPE['mpath']['osd']['ready']):
+ info['type'] = 'data'
+ if ptype == PTYPE['mpath']['osd']['ready']:
+ info['multipath'] = True
+ list_dev_osd(dev, uuid_map, info)
+ elif ptype in (PTYPE['regular']['lockbox']['ready'],
+ PTYPE['mpath']['lockbox']['ready']):
+ info['type'] = 'lockbox'
+ if ptype == PTYPE['mpath']['osd']['ready']:
+ info['multipath'] = True
+ list_dev_lockbox(dev, uuid_map, info)
+ elif ptype == PTYPE['plain']['osd']['ready']:
+ holders = is_held(dev)
+ info['type'] = 'data'
+ info['dmcrypt']['holders'] = holders
+ info['dmcrypt']['type'] = 'plain'
+ if len(holders) == 1:
+ list_dev_osd(get_dev_path(holders[0]), uuid_map, info)
+ elif ptype == PTYPE['luks']['osd']['ready']:
+ holders = is_held(dev)
+ info['type'] = 'data'
+ info['dmcrypt']['holders'] = holders
+ info['dmcrypt']['type'] = 'LUKS'
+ if len(holders) == 1:
+ list_dev_osd(get_dev_path(holders[0]), uuid_map, info)
+ elif Ptype.is_regular_space(ptype) or Ptype.is_mpath_space(ptype):
+ name = Ptype.space_ptype_to_name(ptype)
+ info['type'] = name
+ if ptype == PTYPE['mpath'][name]['ready']:
+ info['multipath'] = True
+ if info.get('uuid') in space_map:
+ info[name + '_for'] = space_map[info['uuid']]
+ elif Ptype.is_plain_space(ptype):
+ name = Ptype.space_ptype_to_name(ptype)
+ holders = is_held(dev)
+ info['type'] = name
+ info['dmcrypt']['type'] = 'plain'
+ info['dmcrypt']['holders'] = holders
+ if info.get('uuid') in space_map:
+ info[name + '_for'] = space_map[info['uuid']]
+ elif Ptype.is_luks_space(ptype):
+ name = Ptype.space_ptype_to_name(ptype)
+ holders = is_held(dev)
+ info['type'] = name
+ info['dmcrypt']['type'] = 'LUKS'
+ info['dmcrypt']['holders'] = holders
+ if info.get('uuid') in space_map:
+ info[name + '_for'] = space_map[info['uuid']]
+ else:
+ path = is_mounted(dev)
+ fs_type = get_dev_fs(dev)
+ if is_swap(dev):
+ info['type'] = 'swap'
+ else:
+ info['type'] = 'other'
+ if fs_type:
+ info['fs_type'] = fs_type
+ if path:
+ info['mount'] = path
+
+ return info
+
+
+def list_devices():
+ partmap = list_all_partitions()
+
+ uuid_map = {}
+ space_map = {}
+ for base, parts in sorted(partmap.items()):
+ for p in parts:
+ dev = get_dev_path(p)
+ part_uuid = get_partition_uuid(dev)
+ if part_uuid:
+ uuid_map[part_uuid] = dev
+ ptype = get_partition_type(dev)
+ LOG.debug("main_list: " + dev +
+ " ptype = " + str(ptype) +
+ " uuid = " + str(part_uuid))
+ if ptype in Ptype.get_ready_by_name('osd'):
+ if Ptype.is_dmcrypt(ptype, 'osd'):
+ holders = is_held(dev)
+ if len(holders) != 1:
+ continue
+ dev_to_mount = get_dev_path(holders[0])
+ else:
+ dev_to_mount = dev
+
+ fs_type = get_dev_fs(dev_to_mount)
+ if fs_type is not None:
+ mount_options = get_mount_options(cluster='ceph',
+ fs_type=fs_type)
+ try:
+ tpath = mount(dev=dev_to_mount,
+ fstype=fs_type, options=mount_options)
+ try:
+ for name in Space.NAMES:
+ space_uuid = get_oneliner(tpath,
+ name + '_uuid')
+ if space_uuid:
+ space_map[space_uuid.lower()] = dev
+ finally:
+ unmount(tpath)
+ except MountError:
+ pass
+
+ LOG.debug("main_list: " + str(partmap) + ", uuid_map = " +
+ str(uuid_map) + ", space_map = " + str(space_map))
+
+ devices = []
+ for base, parts in sorted(partmap.items()):
+ if parts:
+ disk = {'path': get_dev_path(base)}
+ partitions = []
+ for p in sorted(parts):
+ partitions.append(list_dev(get_dev_path(p),
+ uuid_map,
+ space_map))
+ disk['partitions'] = partitions
+ devices.append(disk)
+ else:
+ device = list_dev(get_dev_path(base), uuid_map, space_map)
+ device['path'] = get_dev_path(base)
+ devices.append(device)
+ LOG.debug("list_devices: " + str(devices))
+ return devices
+
+
+def list_zfs():
+ try:
+ out, err, ret = command(
+ [
+ 'zfs',
+ 'list',
+ '-o', 'name,mountpoint'
+ ]
+ )
+ except subprocess.CalledProcessError as e:
+ LOG.info('zfs list -o name,mountpoint '
+ 'fails.\n (Error: %s)' % e)
+ raise
+ lines = out.splitlines()
+ for line in lines[1:]:
+ vdevline = line.split()
+ if os.path.exists(os.path.join(vdevline[1], 'active')):
+ elems = os.path.split(vdevline[1])
+ print(vdevline[0], "ceph data, active, cluster ceph,", elems[1],
+ "mounted on:", vdevline[1])
+ else:
+ print(vdevline[0] + " other, zfs, mounted on: " + vdevline[1])
+
+
+def main_list(args):
+ with activate_lock:
+ if FREEBSD:
+ main_list_freebsd(args)
+ else:
+ main_list_protected(args)
+
+
+def main_list_protected(args):
+ devices = list_devices()
+ if args.path:
+ paths = []
+ for path in args.path:
+ if os.path.exists(path):
+ paths.append(os.path.realpath(path))
+ else:
+ paths.append(path)
+ selected_devices = []
+ for device in devices:
+ for path in paths:
+ if re.search(path + '$', device['path']):
+ selected_devices.append(device)
+ else:
+ selected_devices = devices
+ if args.format == 'json':
+ print(json.dumps(selected_devices))
+ else:
+ output = list_format_plain(selected_devices)
+ if output:
+ print(output)
+
+
+def main_list_freebsd(args):
+ # Currently accomodate only ZFS Filestore partitions
+ # return a list of VDEVs and mountpoints
+ # > zfs list
+ # NAME USED AVAIL REFER MOUNTPOINT
+ # osd0 1.01G 1.32T 1.01G /var/lib/ceph/osd/osd.0
+ # osd1 1.01G 1.32T 1.01G /var/lib/ceph/osd/osd.1
+ list_zfs()
+
+
+###########################
+#
+# Mark devices that we want to suppress activates on with a
+# file like
+#
+# /var/lib/ceph/tmp/suppress-activate.sdb
+#
+# where the last bit is the sanitized device name (/dev/X without the
+# /dev/ prefix) and the is_suppress() check matches a prefix. That
+# means suppressing sdb will stop activate on sdb1, sdb2, etc.
+#
+
+def is_suppressed(path):
+ disk = os.path.realpath(path)
+ try:
+ if (not disk.startswith('/dev/') or
+ not ldev_is_diskdevice(disk)):
+ return False
+ base = get_dev_name(disk)
+ while len(base):
+ if os.path.exists(SUPPRESS_PREFIX + base): # noqa
+ return True
+ base = base[:-1]
+ except:
+ return False
+
+
+def set_suppress(path):
+ disk = os.path.realpath(path)
+ if not os.path.exists(disk):
+ raise Error('does not exist', path)
+ if not ldev_is_diskdevice(path):
+ raise Error('not a block device', path)
+ base = get_dev_name(disk)
+
+ with open(SUPPRESS_PREFIX + base, 'w') as f: # noqa
+ pass
+ LOG.info('set suppress flag on %s', base)
+
+
+def unset_suppress(path):
+ disk = os.path.realpath(path)
+ if not os.path.exists(disk):
+ raise Error('does not exist', path)
+ if not ldev_is_diskdevice(path):
+ raise Error('not a block device', path)
+ assert disk.startswith('/dev/')
+ base = get_dev_name(disk)
+
+ fn = SUPPRESS_PREFIX + base # noqa
+ if not os.path.exists(fn):
+ raise Error('not marked as suppressed', path)
+
+ try:
+ os.unlink(fn)
+ LOG.info('unset suppress flag on %s', base)
+ except OSError as e:
+ raise Error('failed to unsuppress', e)
+
+
+def main_suppress(args):
+ set_suppress(args.path)
+
+
+def main_unsuppress(args):
+ unset_suppress(args.path)
+
+
+def main_zap(args):
+ for dev in args.dev:
+ zap(dev)
+
+
+def main_trigger(args):
+ LOG.debug("main_trigger: " + str(args))
+ if is_systemd() and not args.sync:
+ # http://www.freedesktop.org/software/systemd/man/systemd-escape.html
+ escaped_dev = args.dev[1:].replace('-', '\\x2d')
+ service = 'ceph-disk@{dev}.service'.format(dev=escaped_dev)
+ LOG.info('systemd detected, triggering %s' % service)
+ command(
+ [
+ 'systemctl',
+ '--no-block',
+ 'restart',
+ service,
+ ]
+ )
+ return
+ if is_upstart() and not args.sync:
+ LOG.info('upstart detected, triggering ceph-disk task')
+ command(
+ [
+ 'initctl',
+ 'emit',
+ 'ceph-disk',
+ 'dev={dev}'.format(dev=args.dev),
+ 'pid={pid}'.format(pid=os.getpid()),
+ ]
+ )
+ return
+
+ if get_ceph_user() == 'ceph':
+ command_check_call(['chown', 'ceph:ceph', args.dev])
+ parttype = get_partition_type(args.dev)
+ partid = get_partition_uuid(args.dev)
+
+ LOG.info('trigger {dev} parttype {parttype} uuid {partid}'.format(
+ dev=args.dev,
+ parttype=parttype,
+ partid=partid,
+ ))
+
+ ceph_disk = ['ceph-disk']
+ if args.verbose:
+ ceph_disk.append('--verbose')
+
+ if parttype in (PTYPE['regular']['osd']['ready'],
+ PTYPE['mpath']['osd']['ready']):
+ out, err, ret = command(
+ ceph_disk +
+ [
+ 'activate',
+ args.dev,
+ ]
+ )
+
+ elif parttype in (PTYPE['plain']['osd']['ready'],
+ PTYPE['luks']['osd']['ready']):
+ out, err, ret = command(
+ ceph_disk +
+ [
+ 'activate',
+ '--dmcrypt',
+ args.dev,
+ ]
+ )
+
+ elif parttype in (PTYPE['regular']['journal']['ready'],
+ PTYPE['mpath']['journal']['ready']):
+ out, err, ret = command(
+ ceph_disk +
+ [
+ 'activate-journal',
+ args.dev,
+ ]
+ )
+
+ elif parttype in (PTYPE['plain']['journal']['ready'],
+ PTYPE['luks']['journal']['ready']):
+ out, err, ret = command(
+ ceph_disk +
+ [
+ 'activate-journal',
+ '--dmcrypt',
+ args.dev,
+ ]
+ )
+
+ elif parttype in (PTYPE['regular']['block']['ready'],
+ PTYPE['regular']['block.db']['ready'],
+ PTYPE['regular']['block.wal']['ready'],
+ PTYPE['mpath']['block']['ready'],
+ PTYPE['mpath']['block.db']['ready'],
+ PTYPE['mpath']['block.wal']['ready']):
+ out, err, ret = command(
+ ceph_disk +
+ [
+ 'activate-block',
+ args.dev,
+ ]
+ )
+
+ elif parttype in (PTYPE['plain']['block']['ready'],
+ PTYPE['plain']['block.db']['ready'],
+ PTYPE['plain']['block.wal']['ready'],
+ PTYPE['luks']['block']['ready'],
+ PTYPE['luks']['block.db']['ready'],
+ PTYPE['luks']['block.wal']['ready']):
+ out, err, ret = command(
+ ceph_disk +
+ [
+ 'activate-block',
+ '--dmcrypt',
+ args.dev,
+ ]
+ )
+
+ elif parttype in (PTYPE['regular']['lockbox']['ready'],
+ PTYPE['mpath']['lockbox']['ready']):
+ out, err, ret = command(
+ ceph_disk +
+ [
+ 'activate-lockbox',
+ args.dev,
+ ]
+ )
+
+ else:
+ raise Error('unrecognized partition type %s' % parttype)
+
+ if ret != 0:
+ LOG.info(out)
+ LOG.error(err)
+ raise Error('return code ' + str(ret))
+ else:
+ LOG.debug(out)
+ LOG.debug(err)
+
+
+def main_fix(args):
+ # A hash table containing 'path': ('uid', 'gid', blocking, recursive)
+ fix_table = [
+ ('/usr/bin/ceph-mon', 'root', ROOTGROUP, True, False),
+ ('/usr/bin/ceph-mds', 'root', ROOTGROUP, True, False),
+ ('/usr/bin/ceph-osd', 'root', ROOTGROUP, True, False),
+ ('/usr/bin/radosgw', 'root', ROOTGROUP, True, False),
+ ('/etc/ceph', 'root', ROOTGROUP, True, True),
+ ('/var/run/ceph', 'ceph', 'ceph', True, True),
+ ('/var/log/ceph', 'ceph', 'ceph', True, True),
+ ('/var/log/radosgw', 'ceph', 'ceph', True, True),
+ ('/var/lib/ceph', 'ceph', 'ceph', True, False),
+ ]
+
+ # Relabel/chown all files under /var/lib/ceph/ recursively (except for osd)
+ for directory in glob.glob('/var/lib/ceph/*'):
+ if directory == '/var/lib/ceph/osd':
+ fix_table.append((directory, 'ceph', 'ceph', True, False))
+ else:
+ fix_table.append((directory, 'ceph', 'ceph', True, True))
+
+ # Relabel/chown the osds recursively and in parallel
+ for directory in glob.glob('/var/lib/ceph/osd/*'):
+ fix_table.append((directory, 'ceph', 'ceph', False, True))
+
+ LOG.debug("fix_table: " + str(fix_table))
+
+ # The lists of background processes
+ all_processes = []
+ permissions_processes = []
+ selinux_processes = []
+
+ # Preliminary checks
+ if args.selinux or args.all:
+ out, err, ret = command(['selinuxenabled'])
+ if ret:
+ LOG.error('SELinux is not enabled, please enable it, first.')
+ raise Error('no SELinux')
+
+ for daemon in ['ceph-mon', 'ceph-osd', 'ceph-mds', 'radosgw', 'ceph-mgr']:
+ out, err, ret = command(['pgrep', daemon])
+ if ret == 0:
+ LOG.error(daemon + ' is running, please stop it, first')
+ raise Error(daemon + ' running')
+
+ # Relabel the basic system data without the ceph files
+ if args.system or args.all:
+ c = ['restorecon', '-R', '/']
+ for directory, _, _, _, _ in fix_table:
+ # Skip /var/lib/ceph subdirectories
+ if directory.startswith('/var/lib/ceph/'):
+ continue
+ c.append('-e')
+ c.append(directory)
+
+ out, err, ret = command(c)
+
+ if ret:
+ LOG.error("Failed to restore labels of the underlying system")
+ LOG.error(err)
+ raise Error("basic restore failed")
+
+ # Use find to relabel + chown ~simultaenously
+ if args.all:
+ for directory, uid, gid, blocking, recursive in fix_table:
+ # Skip directories/files that are not installed
+ if not os.access(directory, os.F_OK):
+ continue
+
+ c = [
+ 'find',
+ directory,
+ '-exec',
+ 'chown',
+ ':'.join((uid, gid)),
+ '{}',
+ '+',
+ '-exec',
+ 'restorecon',
+ '{}',
+ '+',
+ ]
+
+ # Just pass -maxdepth 0 for non-recursive calls
+ if not recursive:
+ c += ['-maxdepth', '0']
+
+ if blocking:
+ out, err, ret = command(c)
+
+ if ret:
+ LOG.error("Failed to fix " + directory)
+ LOG.error(err)
+ raise Error(directory + " fix failed")
+ else:
+ all_processes.append(command_init(c))
+
+ LOG.debug("all_processes: " + str(all_processes))
+ for process in all_processes:
+ out, err, ret = command_wait(process)
+ if ret:
+ LOG.error("A background find process failed")
+ LOG.error(err)
+ raise Error("background failed")
+
+ # Fix permissions
+ if args.permissions:
+ for directory, uid, gid, blocking, recursive in fix_table:
+ # Skip directories/files that are not installed
+ if not os.access(directory, os.F_OK):
+ continue
+
+ if recursive:
+ c = [
+ 'chown',
+ '-R',
+ ':'.join((uid, gid)),
+ directory
+ ]
+ else:
+ c = [
+ 'chown',
+ ':'.join((uid, gid)),
+ directory
+ ]
+
+ if blocking:
+ out, err, ret = command(c)
+
+ if ret:
+ LOG.error("Failed to chown " + directory)
+ LOG.error(err)
+ raise Error(directory + " chown failed")
+ else:
+ permissions_processes.append(command_init(c))
+
+ LOG.debug("permissions_processes: " + str(permissions_processes))
+ for process in permissions_processes:
+ out, err, ret = command_wait(process)
+ if ret:
+ LOG.error("A background permissions process failed")
+ LOG.error(err)
+ raise Error("background failed")
+
+ # Fix SELinux labels
+ if args.selinux:
+ for directory, uid, gid, blocking, recursive in fix_table:
+ # Skip directories/files that are not installed
+ if not os.access(directory, os.F_OK):
+ continue
+
+ if recursive:
+ c = [
+ 'restorecon',
+ '-R',
+ directory
+ ]
+ else:
+ c = [
+ 'restorecon',
+ directory
+ ]
+
+ if blocking:
+ out, err, ret = command(c)
+
+ if ret:
+ LOG.error("Failed to restore labels for " + directory)
+ LOG.error(err)
+ raise Error(directory + " relabel failed")
+ else:
+ selinux_processes.append(command_init(c))
+
+ LOG.debug("selinux_processes: " + str(selinux_processes))
+ for process in selinux_processes:
+ out, err, ret = command_wait(process)
+ if ret:
+ LOG.error("A background selinux process failed")
+ LOG.error(err)
+ raise Error("background failed")
+
+ LOG.info(
+ "The ceph files has been fixed, please reboot "
+ "the system for the changes to take effect."
+ )
+
+
+def setup_statedir(dir):
+ # XXX The following use of globals makes linting
+ # really hard. Global state in Python is iffy and
+ # should be avoided.
+ global STATEDIR
+ STATEDIR = dir
+
+ if not os.path.exists(STATEDIR):
+ os.mkdir(STATEDIR)
+ if not os.path.exists(STATEDIR + "/tmp"):
+ os.mkdir(STATEDIR + "/tmp")
+
+ global prepare_lock
+ prepare_lock = FileLock(STATEDIR + '/tmp/ceph-disk.prepare.lock')
+
+ global activate_lock
+ activate_lock = FileLock(STATEDIR + '/tmp/ceph-disk.activate.lock')
+
+ global SUPPRESS_PREFIX
+ SUPPRESS_PREFIX = STATEDIR + '/tmp/suppress-activate.'
+
+
+def setup_sysconfdir(dir):
+ global SYSCONFDIR
+ SYSCONFDIR = dir
+
+
+def parse_args(argv):
+ parser = argparse.ArgumentParser(
+ 'ceph-disk',
+ )
+ parser.add_argument(
+ '-v', '--verbose',
+ action='store_true', default=None,
+ help='be more verbose',
+ )
+ parser.add_argument(
+ '--log-stdout',
+ action='store_true', default=None,
+ help='log to stdout',
+ )
+ parser.add_argument(
+ '--prepend-to-path',
+ metavar='PATH',
+ default='/usr/bin',
+ help=('prepend PATH to $PATH for backward compatibility '
+ '(default /usr/bin)'),
+ )
+ parser.add_argument(
+ '--statedir',
+ metavar='PATH',
+ default='/var/lib/ceph',
+ help=('directory in which ceph state is preserved '
+ '(default /var/lib/ceph)'),
+ )
+ parser.add_argument(
+ '--sysconfdir',
+ metavar='PATH',
+ default='/etc/ceph',
+ help=('directory in which ceph configuration files are found '
+ '(default /etc/ceph)'),
+ )
+ parser.add_argument(
+ '--setuser',
+ metavar='USER',
+ default=None,
+ help='use the given user for subprocesses, rather than ceph or root'
+ )
+ parser.add_argument(
+ '--setgroup',
+ metavar='GROUP',
+ default=None,
+ help='use the given group for subprocesses, rather than ceph or root'
+ )
+ parser.set_defaults(
+ # we want to hold on to this, for later
+ prog=parser.prog,
+ )
+
+ subparsers = parser.add_subparsers(
+ title='subcommands',
+ description='valid subcommands',
+ help='sub-command help',
+ )
+
+ Prepare.set_subparser(subparsers)
+ make_activate_parser(subparsers)
+ make_activate_lockbox_parser(subparsers)
+ make_activate_block_parser(subparsers)
+ make_activate_journal_parser(subparsers)
+ make_activate_all_parser(subparsers)
+ make_list_parser(subparsers)
+ make_suppress_parser(subparsers)
+ make_deactivate_parser(subparsers)
+ make_destroy_parser(subparsers)
+ make_zap_parser(subparsers)
+ make_trigger_parser(subparsers)
+ make_fix_parser(subparsers)
+
+ args = parser.parse_args(argv)
+ return args
+
+
+def make_fix_parser(subparsers):
+ fix_parser = subparsers.add_parser(
+ 'fix',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ """)),
+ help='fix SELinux labels and/or file permissions')
+
+ fix_parser.add_argument(
+ '--system',
+ action='store_true',
+ default=False,
+ help='fix SELinux labels for the non-ceph system data'
+ )
+ fix_parser.add_argument(
+ '--selinux',
+ action='store_true',
+ default=False,
+ help='fix SELinux labels for ceph data'
+ )
+ fix_parser.add_argument(
+ '--permissions',
+ action='store_true',
+ default=False,
+ help='fix file permissions for ceph data'
+ )
+ fix_parser.add_argument(
+ '--all',
+ action='store_true',
+ default=False,
+ help='perform all the fix-related operations'
+ )
+ fix_parser.set_defaults(
+ func=main_fix,
+ )
+ return fix_parser
+
+
+def make_trigger_parser(subparsers):
+ trigger_parser = subparsers.add_parser(
+ 'trigger',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ The partition given in argument is activated. The type of the
+ partition (data, lockbox, journal etc.) is detected by its
+ type. If the init system is upstart or systemd, the activation is
+ delegated to it and runs asynchronously, which
+ helps reduce the execution time of udev actions.
+ """)),
+ help='activate any device (called by udev)')
+ trigger_parser.add_argument(
+ 'dev',
+ help=('device'),
+ )
+ trigger_parser.add_argument(
+ '--cluster',
+ metavar='NAME',
+ default='ceph',
+ help='cluster name to assign this disk to',
+ )
+ trigger_parser.add_argument(
+ '--dmcrypt',
+ action='store_true', default=None,
+ help='map devices with dm-crypt',
+ )
+ trigger_parser.add_argument(
+ '--dmcrypt-key-dir',
+ metavar='KEYDIR',
+ default='/etc/ceph/dmcrypt-keys',
+ help='directory where dm-crypt keys are stored',
+ )
+ trigger_parser.add_argument(
+ '--sync',
+ action='store_true', default=None,
+ help='do operation synchronously; do not trigger systemd',
+ )
+ trigger_parser.set_defaults(
+ func=main_trigger,
+ )
+ return trigger_parser
+
+
+def make_activate_parser(subparsers):
+ activate_parser = subparsers.add_parser(
+ 'activate',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ Activate the OSD found at PATH (can be a directory
+ or a device partition, possibly encrypted). When
+ activated for the first time, a unique OSD id is obtained
+ from the cluster. If PATH is a directory, a symbolic
+ link is added in {statedir}/osd/ceph-$id. If PATH is
+ a partition, it is mounted on {statedir}/osd/ceph-$id.
+ Finally, the OSD daemon is run.
+
+ If the OSD depends on auxiliary partitions (journal, block, ...)
+ they need to be available otherwise activation will fail. It
+ may happen if a journal is encrypted and cryptsetup was not
+ run yet.
+ """.format(statedir=STATEDIR))),
+ help='Activate a Ceph OSD')
+ activate_parser.add_argument(
+ '--mount',
+ action='store_true', default=None,
+ help='mount a block device [deprecated, ignored]',
+ )
+ activate_parser.add_argument(
+ '--activate-key',
+ metavar='PATH',
+ help='bootstrap-osd keyring path template (%(default)s)',
+ dest='activate_key_template',
+ )
+ activate_parser.add_argument(
+ '--mark-init',
+ metavar='INITSYSTEM',
+ help='init system to manage this dir',
+ default='auto',
+ choices=INIT_SYSTEMS,
+ )
+ activate_parser.add_argument(
+ '--no-start-daemon',
+ action='store_true', default=None,
+ help='do not start the daemon',
+ )
+ activate_parser.add_argument(
+ 'path',
+ metavar='PATH',
+ help='path to block device or directory',
+ )
+ activate_parser.add_argument(
+ '--dmcrypt',
+ action='store_true', default=None,
+ help='map DATA and/or JOURNAL devices with dm-crypt',
+ )
+ activate_parser.add_argument(
+ '--dmcrypt-key-dir',
+ metavar='KEYDIR',
+ default='/etc/ceph/dmcrypt-keys',
+ help='directory where dm-crypt keys are stored',
+ )
+ activate_parser.add_argument(
+ '--reactivate',
+ action='store_true', default=False,
+ help='activate the deactived OSD',
+ )
+ activate_parser.set_defaults(
+ activate_key_template='{statedir}/bootstrap-osd/{cluster}.keyring',
+ func=main_activate,
+ )
+ return activate_parser
+
+
+def make_activate_lockbox_parser(subparsers):
+ parser = subparsers.add_parser(
+ 'activate-lockbox',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ Mount the partition found at PATH on {statedir}/osd-lockbox/$uuid
+ where $uuid uniquely identifies the OSD that needs this lockbox
+ to retrieve keys from the monitor and unlock its partitions.
+
+ If the OSD has one or more auxiliary devices (journal, block, ...)
+ symbolic links are created at {statedir}/osd-lockbox/$other_uuid
+ and point to {statedir}/osd-lockbox/$uuid. This will, for instance,
+ allow a journal encrypted in a partition identified by $other_uuid to
+ fetch the keys it needs from the monitor.
+
+ Finally the OSD is activated, as it would be with ceph-disk activate.
+ """.format(statedir=STATEDIR))),
+ help='Activate a Ceph lockbox')
+ parser.add_argument(
+ '--activate-key',
+ help='bootstrap-osd keyring path template (%(default)s)',
+ dest='activate_key_template',
+ )
+ parser.add_argument(
+ '--dmcrypt-key-dir',
+ metavar='KEYDIR',
+ default='/etc/ceph/dmcrypt-keys',
+ help='directory where dm-crypt keys are stored',
+ )
+ parser.add_argument(
+ 'path',
+ metavar='PATH',
+ help='path to block device',
+ )
+ parser.set_defaults(
+ activate_key_template='{statedir}/bootstrap-osd/{cluster}.keyring',
+ func=main_activate_lockbox,
+ )
+ return parser
+
+
+def make_activate_block_parser(subparsers):
+ return make_activate_space_parser('block', subparsers)
+
+
+def make_activate_journal_parser(subparsers):
+ return make_activate_space_parser('journal', subparsers)
+
+
+def make_activate_space_parser(name, subparsers):
+ activate_space_parser = subparsers.add_parser(
+ 'activate-%s' % name,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ Activating a {name} partition is only meaningfull
+ if it is encrypted and it will map it using
+ cryptsetup.
+
+ Finally the corresponding OSD is activated,
+ as it would be with ceph-disk activate.
+ """.format(name=name))),
+ help='Activate an OSD via its %s device' % name)
+ activate_space_parser.add_argument(
+ 'dev',
+ metavar='DEV',
+ help='path to %s block device' % name,
+ )
+ activate_space_parser.add_argument(
+ '--activate-key',
+ metavar='PATH',
+ help='bootstrap-osd keyring path template (%(default)s)',
+ dest='activate_key_template',
+ )
+ activate_space_parser.add_argument(
+ '--mark-init',
+ metavar='INITSYSTEM',
+ help='init system to manage this dir',
+ default='auto',
+ choices=INIT_SYSTEMS,
+ )
+ activate_space_parser.add_argument(
+ '--dmcrypt',
+ action='store_true', default=None,
+ help=('map data and/or auxiliariy (journal, etc.) '
+ 'devices with dm-crypt'),
+ )
+ activate_space_parser.add_argument(
+ '--dmcrypt-key-dir',
+ metavar='KEYDIR',
+ default='/etc/ceph/dmcrypt-keys',
+ help='directory where dm-crypt keys are stored',
+ )
+ activate_space_parser.add_argument(
+ '--reactivate',
+ action='store_true', default=False,
+ help='activate the deactived OSD',
+ )
+ activate_space_parser.set_defaults(
+ activate_key_template='{statedir}/bootstrap-osd/{cluster}.keyring',
+ func=lambda args: main_activate_space(name, args),
+ )
+ return activate_space_parser
+
+
+def make_activate_all_parser(subparsers):
+ activate_all_parser = subparsers.add_parser(
+ 'activate-all',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ Activate all OSD partitions found in /dev/disk/by-parttypeuuid.
+ The partitions containing auxiliary devices (journal, block, ...)
+ are not activated.
+ """)),
+ help='Activate all tagged OSD partitions')
+ activate_all_parser.add_argument(
+ '--activate-key',
+ metavar='PATH',
+ help='bootstrap-osd keyring path template (%(default)s)',
+ dest='activate_key_template',
+ )
+ activate_all_parser.add_argument(
+ '--mark-init',
+ metavar='INITSYSTEM',
+ help='init system to manage this dir',
+ default='auto',
+ choices=INIT_SYSTEMS,
+ )
+ activate_all_parser.set_defaults(
+ activate_key_template='{statedir}/bootstrap-osd/{cluster}.keyring',
+ func=main_activate_all,
+ )
+ return activate_all_parser
+
+
+def make_list_parser(subparsers):
+ list_parser = subparsers.add_parser(
+ 'list',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ Display all partitions on the system and their
+ associated Ceph information, if any.
+ """)),
+ help='List disks, partitions, and Ceph OSDs')
+ list_parser.add_argument(
+ '--format',
+ help='output format',
+ default='plain',
+ choices=['json', 'plain'],
+ )
+ list_parser.add_argument(
+ 'path',
+ metavar='PATH',
+ nargs='*',
+ help='path to block devices, relative to /sys/block',
+ )
+ list_parser.set_defaults(
+ func=main_list,
+ )
+ return list_parser
+
+
+def make_suppress_parser(subparsers):
+ suppress_parser = subparsers.add_parser(
+ 'suppress-activate',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ Add a prefix to the list of suppressed device names
+ so that they are ignored by all activate* subcommands.
+ """)),
+ help='Suppress activate on a device (prefix)')
+ suppress_parser.add_argument(
+ 'path',
+ metavar='PATH',
+ help='path to block device or directory',
+ )
+ suppress_parser.set_defaults(
+ func=main_suppress,
+ )
+
+ unsuppress_parser = subparsers.add_parser(
+ 'unsuppress-activate',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ Remove a prefix from the list of suppressed device names
+ so that they are no longer ignored by all
+ activate* subcommands.
+ """)),
+ help='Stop suppressing activate on a device (prefix)')
+ unsuppress_parser.add_argument(
+ 'path',
+ metavar='PATH',
+ help='path to block device or directory',
+ )
+ unsuppress_parser.set_defaults(
+ func=main_unsuppress,
+ )
+ return suppress_parser
+
+
+def make_deactivate_parser(subparsers):
+ deactivate_parser = subparsers.add_parser(
+ 'deactivate',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ Deactivate the OSD located at PATH. It stops the OSD daemon
+ and optionally marks it out (with --mark-out). The content of
+ the OSD is left untouched.
+
+ By default, the, ready, active, INIT-specific files are
+ removed (so that it is not automatically re-activated by the
+ udev rules or ceph-disk trigger) and the file deactive is
+ created to remember the OSD is deactivated.
+
+ If the --once option is given, the ready, active, INIT-specific
+ files are not removed and the OSD will reactivate whenever
+ ceph-disk trigger is run on one of the devices (journal, data,
+ block, lockbox, ...).
+
+ If the OSD is dmcrypt, remove the data dmcrypt map. When
+ deactivate finishes, the OSD is down.
+ """)),
+ help='Deactivate a Ceph OSD')
+ deactivate_parser.add_argument(
+ '--cluster',
+ metavar='NAME',
+ default='ceph',
+ help='cluster name to assign this disk to',
+ )
+ deactivate_parser.add_argument(
+ 'path',
+ metavar='PATH',
+ nargs='?',
+ help='path to block device or directory',
+ )
+ deactivate_parser.add_argument(
+ '--deactivate-by-id',
+ metavar='<id>',
+ help='ID of OSD to deactive'
+ )
+ deactivate_parser.add_argument(
+ '--mark-out',
+ action='store_true', default=False,
+ help='option to mark the osd out',
+ )
+ deactivate_parser.add_argument(
+ '--once',
+ action='store_true', default=False,
+ help='does not need --reactivate to activate again',
+ )
+ deactivate_parser.set_defaults(
+ func=main_deactivate,
+ )
+
+
+def make_destroy_parser(subparsers):
+ destroy_parser = subparsers.add_parser(
+ 'destroy',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\ Destroy the OSD located at PATH. It removes the OSD from the
+ cluster and marks it destroyed. An OSD must be down before it
+ can be destroyed. Once it is destroyed, a new OSD can be created
+ in its place, reusing the same OSD id and position (e.g. after
+ a failed HDD or SSD is replaced). Alternatively, if the
+ --purge option is also specified, the OSD is removed from the
+ CRUSH map and the OSD id is deallocated.""")),
+ help='Destroy a Ceph OSD')
+ destroy_parser.add_argument(
+ '--cluster',
+ metavar='NAME',
+ default='ceph',
+ help='cluster name to assign this disk to',
+ )
+ destroy_parser.add_argument(
+ 'path',
+ metavar='PATH',
+ nargs='?',
+ help='path to block device or directory',
+ )
+ destroy_parser.add_argument(
+ '--destroy-by-id',
+ metavar='<id>',
+ help='ID of OSD to destroy'
+ )
+ destroy_parser.add_argument(
+ '--dmcrypt-key-dir',
+ metavar='KEYDIR',
+ default='/etc/ceph/dmcrypt-keys',
+ help=('directory where dm-crypt keys are stored '
+ '(If you don\'t know how it work, '
+ 'dont use it. we have default value)'),
+ )
+ destroy_parser.add_argument(
+ '--zap',
+ action='store_true', default=False,
+ help='option to erase data and partition',
+ )
+ destroy_parser.add_argument(
+ '--purge',
+ action='store_true', default=False,
+ help='option to remove OSD from CRUSH map and deallocate the id',
+ )
+ destroy_parser.set_defaults(
+ func=main_destroy,
+ )
+
+
+def make_zap_parser(subparsers):
+ zap_parser = subparsers.add_parser(
+ 'zap',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.fill(textwrap.dedent("""\
+ Zap/erase/destroy a device's partition table and contents. It
+ actually uses sgdisk and it's option --zap-all to
+ destroy both GPT and MBR data structures so that the disk
+ becomes suitable for repartitioning.
+ """)),
+ help='Zap/erase/destroy a device\'s partition table (and contents)')
+ zap_parser.add_argument(
+ 'dev',
+ metavar='DEV',
+ nargs='+',
+ help='path to block device',
+ )
+ zap_parser.set_defaults(
+ func=main_zap,
+ )
+ return zap_parser
+
+
+def main(argv):
+ args = parse_args(argv)
+
+ setup_logging(args.verbose, args.log_stdout)
+
+ if args.prepend_to_path != '':
+ path = os.environ.get('PATH', os.defpath)
+ os.environ['PATH'] = args.prepend_to_path + ":" + path
+
+ if args.func.__name__ != 'main_trigger':
+ # trigger may run when statedir is unavailable and does not use it
+ setup_statedir(args.statedir)
+ setup_sysconfdir(args.sysconfdir)
+
+ global CEPH_PREF_USER
+ CEPH_PREF_USER = args.setuser
+ global CEPH_PREF_GROUP
+ CEPH_PREF_GROUP = args.setgroup
+
+ if args.verbose:
+ args.func(args)
+ else:
+ main_catch(args.func, args)
+
+
+def setup_logging(verbose, log_stdout):
+ loglevel = logging.WARNING
+ if verbose:
+ loglevel = logging.DEBUG
+
+ if log_stdout:
+ ch = logging.StreamHandler(stream=sys.stdout)
+ ch.setLevel(loglevel)
+ formatter = logging.Formatter('%(funcName)s: %(message)s')
+ ch.setFormatter(formatter)
+ LOG.addHandler(ch)
+ LOG.setLevel(loglevel)
+ else:
+ logging.basicConfig(
+ level=loglevel,
+ format='%(funcName)s: %(message)s',
+ )
+
+
+def main_catch(func, args):
+
+ try:
+ func(args)
+
+ except Error as e:
+ raise SystemExit(
+ '{prog}: {msg}'.format(
+ prog=args.prog,
+ msg=e,
+ )
+ )
+
+ except CephDiskException as error:
+ exc_name = error.__class__.__name__
+ raise SystemExit(
+ '{prog} {exc_name}: {msg}'.format(
+ prog=args.prog,
+ exc_name=exc_name,
+ msg=error,
+ )
+ )
+
+
+def run():
+ main(sys.argv[1:])
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
+ warned_about = {}