import os.path
import logging
+import json
+import tempfile
+import shutil
+import time
-from ceph_deploy import hosts, exc
+from ceph_deploy import hosts
from ceph_deploy.cliutil import priority
-
+from ceph_deploy.lib import remoto
+import ceph_deploy.util.paths.mon
LOG = logging.getLogger(__name__)
-def fetch_file(args, frompath, topath, _hosts):
- if os.path.exists(topath):
- LOG.debug('Have %s', topath)
- return True
- else:
- for hostname in _hosts:
- filepath = frompath.format(hostname=hostname)
- LOG.debug('Checking %s for %s', hostname, filepath)
- distro = hosts.get(hostname, username=args.username)
- key = distro.conn.remote_module.get_file(filepath)
+def _keyring_equivalent(keyring_one, keyring_two):
+ """
+ Check two keyrings are identical
+ """
+ def keyring_extract_key(file_path):
+ """
+ Cephx keyring files may or may not have white space before some lines.
+ They may have some values in quotes, so a safe way to compare is to
+ extract the key.
+ """
+ with file(file_path, 'r') as f:
+ for line in f:
+ content = line.strip()
+ if len(content) == 0:
+ continue
+ split_line = content.split('=')
+ if split_line[0].strip() == 'key':
+ return "=".join(split_line[1:]).strip()
+ raise RuntimeError("File '%s' is not a keyring" % file_path)
+ key_one = keyring_extract_key(keyring_one)
+ key_two = keyring_extract_key(keyring_two)
+ return key_one == key_two
+
+
+def keytype_path_to(args, keytype):
+ """
+ Get the local filename for a keyring type
+ """
+ if keytype == "admin":
+ return '{cluster}.client.admin.keyring'.format(
+ cluster=args.cluster)
+ if keytype == "mon":
+ return '{cluster}.mon.keyring'.format(
+ cluster=args.cluster)
+ return '{cluster}.bootstrap-{what}.keyring'.format(
+ cluster=args.cluster,
+ what=keytype)
+
+
+def keytype_identity(keytype):
+ """
+ Get the keyring identity from keyring type.
+
+ This is used in authentication with keyrings and generating keyrings.
+ """
+ ident_dict = {
+ 'admin' : 'client.admin',
+ 'mds' : 'client.bootstrap-mds',
+ 'osd' : 'client.bootstrap-osd',
+ 'rgw' : 'client.bootstrap-rgw',
+ 'mon' : 'mon.'
+ }
+ return ident_dict.get(keytype, None)
+
+
+def keytype_capabilities(keytype):
+ """
+ Get the capabilities of a keyring from keyring type.
+ """
+ cap_dict = {
+ 'admin' : [
+ 'osd', 'allow *',
+ 'mds', 'allow *',
+ 'mon', 'allow *'
+ ],
+ 'mds' : [
+ 'mon', 'allow profile bootstrap-mds'
+ ],
+ 'osd' : [
+ 'mon', 'allow profile bootstrap-osd'
+ ],
+ 'rgw': [
+ 'mon', 'allow profile bootstrap-rgw'
+ ]
+ }
+ return cap_dict.get(keytype, None)
+
- if key is not None:
- LOG.debug('Got %s key from %s.', topath, hostname)
- with file(topath, 'w') as f:
- f.write(key)
- return True
- distro.conn.exit()
- LOG.warning('Unable to find %s on %s', filepath, hostname)
- return False
+def gatherkeys_missing(args, distro, rlogger, keypath, keytype, dest_dir):
+ """
+ Get or create the keyring from the mon using the mon keyring by keytype and
+ copy to dest_dir
+ """
+ arguments = [
+ '/usr/bin/ceph',
+ '--connect-timeout=25',
+ '--cluster={cluster}'.format(
+ cluster=args.cluster),
+ '--name', 'mon.',
+ '--keyring={keypath}'.format(
+ keypath=keypath),
+ 'auth', 'get-or-create',
+ ]
+ identity = keytype_identity(keytype)
+ if identity is None:
+ raise RuntimeError('Could not find identity for keytype:%s' % keytype)
+ arguments.append(identity)
+ capabilites = keytype_capabilities(keytype)
+ if capabilites is None:
+ raise RuntimeError('Could not find capabilites for keytype:%s' % keytype)
+ arguments.extend(capabilites)
+ out, err, code = remoto.process.check(
+ distro.conn,
+ arguments
+ )
+ if code != 0:
+ rlogger.error('"ceph auth get-or-create for keytype %s returned %s', keytype, code)
+ for line in err:
+ rlogger.debug(line)
+ return False
+ keyring_name_local = keytype_path_to(args, keytype)
+ keyring_path_local = os.path.join(dest_dir, keyring_name_local)
+ with file(keyring_path_local, 'w') as f:
+ for line in out:
+ f.write(line + '\n')
+ return True
+
+
+def gatherkeys_with_mon(args, host, dest_dir):
+ """
+ Connect to mon and gather keys if mon is in quorum.
+ """
+ distro = hosts.get(host, username=args.username)
+ dir_keytype_mon = ceph_deploy.util.paths.mon.path(args.cluster, host)
+ path_keytype_mon = "%s/keyring" % (dir_keytype_mon)
+ mon_key = distro.conn.remote_module.get_file(path_keytype_mon)
+ if mon_key is None:
+ LOG.warning("No mon key not found %s:%s", host, path_keytype_mon)
+ return False
+ LOG.info("Found keyring at %s:%s", host, path_keytype_mon)
+ mon_name_local = keytype_path_to(args, "mon")
+ mon_path_local = os.path.join(dest_dir, mon_name_local)
+ with file(mon_path_local, 'w') as f:
+ f.write(mon_key)
+ rlogger = logging.getLogger(host)
+ path_asok = ceph_deploy.util.paths.mon.asok(args.cluster, host)
+ out, err, code = remoto.process.check(
+ distro.conn,
+ [
+ "/usr/bin/ceph",
+ "--connect-timeout=25",
+ "--cluster={cluster}".format(
+ cluster=args.cluster),
+ "--admin-daemon={asok}".format(
+ asok=path_asok),
+ "mon_status"
+ ]
+ )
+ if code != 0:
+ rlogger.error('"ceph mon_status %s" returned %s', host, code)
+ for line in err:
+ rlogger.debug(line)
+ return False
+ try:
+ mon_status = json.loads("".join(out))
+ except ValueError:
+ rlogger.error('"ceph mon_status %s" output was not json', host)
+ for line in out:
+ rlogger.error(line)
+ return False
+ mon_number = None
+ mon_map = mon_status.get('monmap')
+ if mon_map is None:
+ rlogger.error("could not find mon map for mons on '%s'", host)
+ return False
+ mon_quorum = mon_status.get('quorum')
+ if mon_quorum is None:
+ rlogger.error("could not find quorum for mons on '%s'" , host)
+ return False
+ mon_map_mons = mon_map.get('mons')
+ if mon_map_mons is None:
+ rlogger.error("could not find mons in monmap on '%s'", host)
+ return False
+ for mon in mon_map_mons:
+ if mon.get('name') == host:
+ mon_number = mon.get('rank')
+ break
+ if mon_number is None:
+ rlogger.error("could not find '%s' in monmap", host)
+ return False
+ if not mon_number in mon_quorum:
+ rlogger.error("Not yet quorum for '%s'", host)
+ return False
+ for keytype in ["admin", "mds", "osd", "rgw"]:
+ if not gatherkeys_missing(args, distro, rlogger, path_keytype_mon, keytype, dest_dir):
+ # We will return failure if we fail to gather any key
+ rlogger.error("Failed to return '%s' key from host ", keytype, host)
+ return False
+ return True
def gatherkeys(args):
+ """
+ Gather keys from any mon and store in current working directory.
+
+ Backs up keys from previous installs and stores new keys.
+ """
oldmask = os.umask(077)
try:
- # client.admin
- keyring = '/etc/ceph/{cluster}.client.admin.keyring'.format(
- cluster=args.cluster)
- r = fetch_file(
- args=args,
- frompath=keyring,
- topath='{cluster}.client.admin.keyring'.format(
- cluster=args.cluster),
- _hosts=args.mon,
- )
- if not r:
- raise exc.KeyNotFoundError(keyring, args.mon)
-
- # mon.
- keyring = '/var/lib/ceph/mon/{cluster}-{{hostname}}/keyring'.format(
- cluster=args.cluster)
- r = fetch_file(
- args=args,
- frompath=keyring,
- topath='{cluster}.mon.keyring'.format(cluster=args.cluster),
- _hosts=args.mon,
- )
- if not r:
- raise exc.KeyNotFoundError(keyring, args.mon)
-
- # bootstrap
- for what in ['osd', 'mds', 'rgw']:
- keyring = '/var/lib/ceph/bootstrap-{what}/{cluster}.keyring'.format(
- what=what,
- cluster=args.cluster)
- r = fetch_file(
- args=args,
- frompath=keyring,
- topath='{cluster}.bootstrap-{what}.keyring'.format(
- cluster=args.cluster,
- what=what),
- _hosts=args.mon,
- )
- if not r:
- if what in ['osd', 'mds']:
- raise exc.KeyNotFoundError(keyring, args.mon)
- else:
- LOG.warning(("No RGW bootstrap key found. Will not be able to "
- "deploy RGW daemons"))
+ try:
+ tmpd = tempfile.mkdtemp()
+ LOG.info("Storing keys in temp directory %s", tmpd)
+ sucess = False
+ for host in args.mon:
+ sucess = gatherkeys_with_mon(args, host, tmpd)
+ if sucess:
+ break
+ if not sucess:
+ LOG.error("Failed to connect to mon on host:%s" ,', '.join(args.mon))
+ raise RuntimeError('Failed to connect any mon')
+ had_error = False
+ date_string = time.strftime("%Y%m%d%H%M%S")
+ for keytype in ["admin", "mds", "mon", "osd", "rgw"]:
+ filename = keytype_path_to(args, keytype)
+ tmp_path = os.path.join(tmpd, filename)
+ if not os.path.exists(tmp_path):
+ LOG.error("No key retrived for '%s'" , keytype)
+ had_error = True
+ continue
+ if not os.path.exists(filename):
+ LOG.info("Storing %s" % (filename))
+ shutil.move(tmp_path, filename)
+ continue
+ if _keyring_equivalent(tmp_path, filename):
+ LOG.info("keyring '%s' already exists" , filename)
+ continue
+ backup_keyring = "%s-%s" % (filename, date_string)
+ LOG.info("Replacing '%s' and backing up old key as '%s'", filename, backup_keyring)
+ shutil.copy(filename, backup_keyring)
+ shutil.move(tmp_path, filename)
+ if had_error:
+ raise RuntimeError('Failed to get all key types')
+ finally:
+ LOG.info("Destroy temp directory %s" %(tmpd))
+ shutil.rmtree(tmpd)
finally:
os.umask(oldmask)
+
@priority(40)
def make(parser):
"""
--- /dev/null
+from ceph_deploy import gatherkeys
+from ceph_deploy import new
+import mock
+import pytest
+import tempfile
+import os
+import shutil
+
+
+def get_key_static(keytype, key_path):
+ with file(key_path, 'w') as f:
+ f.write("[%s]\n" % (gatherkeys.keytype_identity(keytype)))
+ f.write("key=fred\n")
+
+
+def get_key_dynamic(keytype, key_path):
+ with open(key_path, 'w', 0600) as f:
+ f.write("[%s]\n" % (gatherkeys.keytype_identity(keytype)))
+ f.write("key='%s'" % (new.generate_auth_key()))
+
+
+def mock_time_strftime(time_format):
+ return "20160412144231"
+
+
+def mock_get_keys_fail(args, host, dest_dir):
+ return False
+
+
+def mock_get_keys_sucess_static(args, host, dest_dir):
+ for keytype in ["admin", "mon", "osd", "mds", "rgw"]:
+ keypath = gatherkeys.keytype_path_to(args, keytype)
+ path = "%s/%s" % (dest_dir, keypath)
+ get_key_static(keytype, path)
+ return True
+
+
+def mock_get_keys_sucess_dynamic(args, host, dest_dir):
+ for keytype in ["admin", "mon", "osd", "mds", "rgw"]:
+ keypath = gatherkeys.keytype_path_to(args, keytype)
+ path = "%s/%s" % (dest_dir, keypath)
+ get_key_dynamic(keytype, path)
+ return True
+
+
+class TestGatherKeys(object):
+ """
+ Since we are testing things that effect the content of the current working
+ directory we should test in a clean empty directory.
+ """
+ def setup(self):
+ """
+ Make temp directory for tests and set as current working directory
+ """
+ self.orginaldir = os.getcwd()
+ self.test_dir = tempfile.mkdtemp()
+ os.chdir(self.test_dir)
+
+
+ def teardown(self):
+ """
+ Set current working directory to old value
+ Remove temp directory and content
+ """
+ os.chdir(self.orginaldir)
+ shutil.rmtree(self.test_dir)
+
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_with_mon', mock_get_keys_fail)
+ def test_gatherkeys_fail(self):
+ """
+ Test 'gatherkeys' fails when connecting to mon fails.
+ """
+ args = mock.Mock()
+ args.cluster = "ceph"
+ args.mon = ['host1']
+ with pytest.raises(RuntimeError):
+ gatherkeys.gatherkeys(args)
+
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_with_mon', mock_get_keys_sucess_static)
+ def test_gatherkeys_success(self):
+ """
+ Test 'gatherkeys' succeeds when getinig keys that are always the same.
+ Test 'gatherkeys' does not backup identical keys
+ """
+ args = mock.Mock()
+ args.cluster = "ceph"
+ args.mon = ['host1']
+ gatherkeys.gatherkeys(args)
+ dir_content = os.listdir(self.test_dir)
+ assert "ceph.client.admin.keyring" in dir_content
+ assert "ceph.bootstrap-mds.keyring" in dir_content
+ assert "ceph.mon.keyring" in dir_content
+ assert "ceph.bootstrap-osd.keyring" in dir_content
+ assert "ceph.bootstrap-rgw.keyring" in dir_content
+ assert len(dir_content) == 5
+ # Now we repeat as no new keys are generated
+ gatherkeys.gatherkeys(args)
+ dir_content = os.listdir(self.test_dir)
+ assert len(dir_content) == 5
+
+
+ @mock.patch('ceph_deploy.gatherkeys.time.strftime', mock_time_strftime)
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_with_mon', mock_get_keys_sucess_dynamic)
+ def test_gatherkeys_backs_up(self):
+ """
+ Test 'gatherkeys' succeeds when getinig keys that are always different.
+ Test 'gatherkeys' does backup keys that are not identical.
+ """
+ args = mock.Mock()
+ args.cluster = "ceph"
+ args.mon = ['host1']
+ gatherkeys.gatherkeys(args)
+ dir_content = os.listdir(self.test_dir)
+ assert "ceph.client.admin.keyring" in dir_content
+ assert "ceph.bootstrap-mds.keyring" in dir_content
+ assert "ceph.mon.keyring" in dir_content
+ assert "ceph.bootstrap-osd.keyring" in dir_content
+ assert "ceph.bootstrap-rgw.keyring" in dir_content
+ assert len(dir_content) == 5
+ # Now we repeat as new keys are generated and old
+ # are backed up
+ gatherkeys.gatherkeys(args)
+ dir_content = os.listdir(self.test_dir)
+ mocked_time = mock_time_strftime(None)
+ assert "ceph.client.admin.keyring" in dir_content
+ assert "ceph.bootstrap-mds.keyring" in dir_content
+ assert "ceph.mon.keyring" in dir_content
+ assert "ceph.bootstrap-osd.keyring" in dir_content
+ assert "ceph.bootstrap-rgw.keyring" in dir_content
+ assert "ceph.client.admin.keyring-%s" % (mocked_time) in dir_content
+ assert "ceph.bootstrap-mds.keyring-%s" % (mocked_time) in dir_content
+ assert "ceph.mon.keyring-%s" % (mocked_time) in dir_content
+ assert "ceph.bootstrap-osd.keyring-%s" % (mocked_time) in dir_content
+ assert "ceph.bootstrap-rgw.keyring-%s" % (mocked_time) in dir_content
+ assert len(dir_content) == 10
--- /dev/null
+from ceph_deploy import gatherkeys
+from ceph_deploy import new
+import mock
+import tempfile
+import shutil
+import os
+import pytest
+
+
+class mock_conn(object):
+ def __init__(self):
+ pass
+
+class mock_distro(object):
+ def __init__(self):
+ self.conn = mock_conn()
+
+class mock_rlogger(object):
+ def error(self, *arg):
+ return
+
+ def debug(self, *arg):
+ return
+
+
+def mock_remoto_process_check_success(conn, args):
+ secret = new.generate_auth_key()
+ out = '[mon.]\nkey = %s\ncaps mon = allow *\n' % secret
+ return out.split('\n'), "", 0
+
+
+def mock_remoto_process_check_rc_error(conn, args):
+ return [""], ["this failed\n"], 1
+
+
+class TestGatherKeysMissing(object):
+ """
+ Since we are testing things that effect the content a directory we should
+ test in a clean empty directory.
+ """
+
+ def setup(self):
+ """
+ Make temp directory for tests.
+ """
+ self.args = mock.Mock()
+ self.distro = mock_distro()
+ self.test_dir = tempfile.mkdtemp()
+ self.rlogger = mock_rlogger()
+ self.keypath_remote = "some_path"
+
+ def teardown(self):
+ """
+ Remove temp directory and content
+ """
+ shutil.rmtree(self.test_dir)
+
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+ def test_success_admin(self):
+ keytype = 'admin'
+ rc = gatherkeys.gatherkeys_missing(
+ self.args,
+ self.distro,
+ self.rlogger,
+ self.keypath_remote,
+ keytype,
+ self.test_dir
+ )
+ assert rc is True
+ keyname = gatherkeys.keytype_path_to(self.args, keytype)
+ keypath_gen = os.path.join(self.test_dir, keyname)
+ assert os.path.isfile(keypath_gen)
+
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+ def test_success_mds(self):
+ keytype = 'mds'
+ rc = gatherkeys.gatherkeys_missing(
+ self.args,
+ self.distro,
+ self.rlogger,
+ self.keypath_remote,
+ keytype,
+ self.test_dir
+ )
+ assert rc is True
+ keyname = gatherkeys.keytype_path_to(self.args, keytype)
+ keypath_gen = os.path.join(self.test_dir, keyname)
+ assert os.path.isfile(keypath_gen)
+
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+ def test_success_osd(self):
+ keytype = 'osd'
+ rc = gatherkeys.gatherkeys_missing(
+ self.args,
+ self.distro,
+ self.rlogger,
+ self.keypath_remote,
+ keytype,
+ self.test_dir
+ )
+ assert rc is True
+ keyname = gatherkeys.keytype_path_to(self.args, keytype)
+ keypath_gen = os.path.join(self.test_dir, keyname)
+ assert os.path.isfile(keypath_gen)
+
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+ def test_success_rgw(self):
+ keytype = 'rgw'
+ rc = gatherkeys.gatherkeys_missing(
+ self.args,
+ self.distro,
+ self.rlogger,
+ self.keypath_remote,
+ keytype,
+ self.test_dir
+ )
+ assert rc is True
+ keyname = gatherkeys.keytype_path_to(self.args, keytype)
+ keypath_gen = os.path.join(self.test_dir, keyname)
+ assert os.path.isfile(keypath_gen)
+
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_rc_error)
+ def test_remoto_process_check_rc_error(self):
+ keytype = 'admin'
+ rc = gatherkeys.gatherkeys_missing(
+ self.args,
+ self.distro,
+ self.rlogger,
+ self.keypath_remote,
+ keytype,
+ self.test_dir
+ )
+ assert rc is False
+ keyname = gatherkeys.keytype_path_to(self.args, keytype)
+ keypath_gen = os.path.join(self.test_dir, keyname)
+ assert not os.path.isfile(keypath_gen)
+
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+ def test_fail_identity_missing(self):
+ keytype = 'silly'
+ with pytest.raises(RuntimeError):
+ gatherkeys.gatherkeys_missing(
+ self.args,
+ self.distro,
+ self.rlogger,
+ self.keypath_remote,
+ keytype,
+ self.test_dir
+ )
+
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+ def test_fail_capabilities_missing(self):
+ keytype = 'mon'
+ with pytest.raises(RuntimeError):
+ gatherkeys.gatherkeys_missing(
+ self.args,
+ self.distro,
+ self.rlogger,
+ self.keypath_remote,
+ keytype,
+ self.test_dir
+ )
+
--- /dev/null
+from ceph_deploy import gatherkeys
+from ceph_deploy import new
+import mock
+import json
+import copy
+
+
+remoto_process_check_success_output = {
+ "name": "ceph-node1",
+ "rank": 0,
+ "state": "leader",
+ "election_epoch": 6,
+ "quorum": [
+ 0,
+ 1,
+ 2
+ ],
+ "outside_quorum": [],
+ "extra_probe_peers": [
+ "192.168.99.125:6789\/0",
+ "192.168.99.126:6789\/0"
+ ],
+ "sync_provider": [],
+ "monmap": {
+ "epoch": 1,
+ "fsid": "4dbee7eb-929b-4f3f-ad23-8a4e47235e40",
+ "modified": "2016-04-11 05:35:21.665220",
+ "created": "2016-04-11 05:35:21.665220",
+ "mons": [
+ {
+ "rank": 0,
+ "name": "host0",
+ "addr": "192.168.99.124:6789\/0"
+ },
+ {
+ "rank": 1,
+ "name": "host1",
+ "addr": "192.168.99.125:6789\/0"
+ },
+ {
+ "rank": 2,
+ "name": "host2",
+ "addr": "192.168.99.126:6789\/0"
+ }
+ ]
+ }
+ }
+
+
+class mock_remote_module(object):
+ def get_file(self, path):
+ return self.get_file_result
+
+
+class mock_conn(object):
+ def __init__(self):
+ self.remote_module = mock_remote_module()
+
+
+class mock_distro(object):
+ def __init__(self):
+ self.conn = mock_conn()
+
+
+def mock_hosts_get_file_key_content(host, **kwargs):
+ output = mock_distro()
+ mon_keyring = '[mon.]\nkey = %s\ncaps mon = allow *\n' % new.generate_auth_key()
+ output.conn.remote_module.get_file_result = mon_keyring
+ return output
+
+
+def mock_hosts_get_file_key_content_none(host, **kwargs):
+ output = mock_distro()
+ output.conn.remote_module.get_file_result = None
+ return output
+
+
+def mock_gatherkeys_missing_success(args, distro, rlogger, path_keytype_mon, keytype, dest_dir):
+ return True
+
+
+def mock_gatherkeys_missing_fail(args, distro, rlogger, path_keytype_mon, keytype, dest_dir):
+ return False
+
+
+def mock_remoto_process_check_success(conn, args):
+ out = json.dumps(remoto_process_check_success_output,sort_keys=True, indent=4)
+ return out.split('\n'), "", 0
+
+
+def mock_remoto_process_check_rc_error(conn, args):
+ return [""], ["this failed\n"], 1
+
+
+def mock_remoto_process_check_out_not_json(conn, args):
+ return ["}bad output{"], [""], 0
+
+
+def mock_remoto_process_check_out_missing_quorum(conn, args):
+ outdata = copy.deepcopy(remoto_process_check_success_output)
+ del outdata["quorum"]
+ out = json.dumps(outdata,sort_keys=True, indent=4)
+ return out.split('\n'), "", 0
+
+
+def mock_remoto_process_check_out_missing_quorum_1(conn, args):
+ outdata = copy.deepcopy(remoto_process_check_success_output)
+ del outdata["quorum"][1]
+ out = json.dumps(outdata,sort_keys=True, indent=4)
+ return out.split('\n'), "", 0
+
+
+def mock_remoto_process_check_out_missing_monmap(conn, args):
+ outdata = copy.deepcopy(remoto_process_check_success_output)
+ del outdata["monmap"]
+ out = json.dumps(outdata,sort_keys=True, indent=4)
+ return out.split('\n'), "", 0
+
+
+def mock_remoto_process_check_out_missing_mons(conn, args):
+ outdata = copy.deepcopy(remoto_process_check_success_output)
+ del outdata["monmap"]["mons"]
+ out = json.dumps(outdata,sort_keys=True, indent=4)
+ return out.split('\n'), "", 0
+
+
+def mock_remoto_process_check_out_missing_monmap_host1(conn, args):
+ outdata = copy.deepcopy(remoto_process_check_success_output)
+ del outdata["monmap"]["mons"][1]
+ out = json.dumps(outdata,sort_keys=True, indent=4)
+ return out.split('\n'), "", 0
+
+
+class TestGatherKeysWithMon(object):
+ """
+ Test gatherkeys_with_mon function
+ """
+ def setup(self):
+ self.args = mock.Mock()
+ self.args.cluster = "ceph"
+ self.args.mon = ['host1']
+ self.host = 'host1'
+ self.test_dir = '/tmp'
+
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+ @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+ def test_success(self):
+ rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+ assert rc is True
+
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+ @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content_none)
+ def test_monkey_none(self):
+ rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+ assert rc is False
+
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_fail)
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+ @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+ def test_missing_fail(self):
+ rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+ assert rc is False
+
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_rc_error)
+ @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+ def test_remoto_process_check_rc_error(self):
+ rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+ assert rc is False
+
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_out_not_json)
+ @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+ def test_remoto_process_check_out_not_json(self):
+ rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+ assert rc is False
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_out_missing_quorum)
+ @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+ def test_remoto_process_check_out_missing_quorum(self):
+ rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+ assert rc is False
+
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_out_missing_quorum_1)
+ @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+ def test_remoto_process_check_out_missing_quorum_1(self):
+ rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+ assert rc is False
+
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_out_missing_mons)
+ @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+ def test_remoto_process_check_out_missing_mon(self):
+ rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+ assert rc is False
+
+
+ @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+ @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_out_missing_monmap_host1)
+ @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+ def test_remoto_process_check_out_missing_monmap_host1(self):
+ rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+ assert rc is False
--- /dev/null
+from ceph_deploy import gatherkeys
+from ceph_deploy import new
+import tempfile
+import shutil
+import pytest
+
+
+def write_key_mon_with_caps(path, secret):
+ mon_keyring = '[mon.]\nkey = %s\ncaps mon = allow *\n' % secret
+ with open(path, 'w', 0600) as f:
+ f.write(mon_keyring)
+
+
+def write_key_mon_with_caps_with_tab(path, secret):
+ mon_keyring = '[mon.]\n\tkey = %s\n\tcaps mon = allow *\n' % secret
+ with open(path, 'w', 0600) as f:
+ f.write(mon_keyring)
+
+
+def write_key_mon_with_caps_with_tab_quote(path, secret):
+ mon_keyring = '[mon.]\n\tkey = %s\n\tcaps mon = "allow *"\n' % secret
+ with open(path, 'w', 0600) as f:
+ f.write(mon_keyring)
+
+
+def write_key_mon_without_caps(path, secret):
+ mon_keyring = '[mon.]\nkey = %s\n' % secret
+ with open(path, 'w', 0600) as f:
+ f.write(mon_keyring)
+
+
+class TestKeysEquivalent(object):
+ """
+ Since we are testing things that effect the content of the current working
+ directory we should test in a clean empty directory.
+ """
+ def setup(self):
+ """
+ Make temp directory for tests.
+ """
+ self.test_dir = tempfile.mkdtemp()
+
+
+ def teardown(self):
+ """
+ Remove temp directory and content
+ """
+ shutil.rmtree(self.test_dir)
+
+
+ def test_identical_with_caps(self):
+ secret_01 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_with_caps(key_path_01, secret_01)
+ write_key_mon_with_caps(key_path_02, secret_01)
+ same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+ assert same is True
+
+
+ def test_different_with_caps(self):
+ secret_01 = new.generate_auth_key()
+ secret_02 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_with_caps(key_path_01, secret_01)
+ write_key_mon_with_caps(key_path_02, secret_02)
+ same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+ assert same is False
+
+
+ def test_identical_without_caps(self):
+ secret_01 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_without_caps(key_path_01, secret_01)
+ write_key_mon_without_caps(key_path_02, secret_01)
+ same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+ assert same is True
+
+
+ def test_different_without_caps(self):
+ secret_01 = new.generate_auth_key()
+ secret_02 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_without_caps(key_path_01, secret_01)
+ write_key_mon_without_caps(key_path_02, secret_02)
+ same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+ assert same is False
+
+
+ def test_identical_mixed_caps(self):
+ secret_01 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_with_caps(key_path_01, secret_01)
+ write_key_mon_without_caps(key_path_02, secret_01)
+ same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+ assert same is True
+
+
+ def test_different_mixed_caps(self):
+ secret_01 = new.generate_auth_key()
+ secret_02 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_with_caps(key_path_01, secret_01)
+ write_key_mon_without_caps(key_path_02, secret_02)
+ same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+ assert same is False
+
+
+ def test_identical_caps_mixed_tabs(self):
+ secret_01 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_with_caps(key_path_01, secret_01)
+ write_key_mon_with_caps_with_tab(key_path_02, secret_01)
+ same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+ assert same is True
+
+
+ def test_different_caps_mixed_tabs(self):
+ secret_01 = new.generate_auth_key()
+ secret_02 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_with_caps(key_path_01, secret_01)
+ write_key_mon_with_caps_with_tab(key_path_02, secret_02)
+ same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+ assert same is False
+
+
+ def test_identical_caps_mixed_quote(self):
+ secret_01 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_with_caps_with_tab(key_path_01, secret_01)
+ write_key_mon_with_caps_with_tab_quote(key_path_02, secret_01)
+ same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+ assert same is True
+
+
+ def test_different_caps_mixed_quote(self):
+ secret_01 = new.generate_auth_key()
+ secret_02 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_with_caps_with_tab(key_path_01, secret_01)
+ write_key_mon_with_caps_with_tab_quote(key_path_02, secret_02)
+ same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+ assert same is False
+
+
+ def test_missing_key_1(self):
+ secret_02 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_with_caps_with_tab_quote(key_path_02, secret_02)
+ with pytest.raises(IOError):
+ gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+
+
+ def test_missing_key_2(self):
+ secret_01 = new.generate_auth_key()
+ key_path_01 = self.test_dir + "/01.keyring"
+ key_path_02 = self.test_dir + "/02.keyring"
+ write_key_mon_with_caps_with_tab_quote(key_path_01, secret_01)
+ with pytest.raises(IOError):
+ gatherkeys._keyring_equivalent(key_path_01, key_path_02)
--- /dev/null
+.. _admin:
+
+admin
+=======
+The ``admin`` subcommand provides an interface to add to the cluster's admin
+node.
+
+Example
+-------
+To make a node and admin node run::
+
+ ceph-deploy admin ADMIN [ADMIN..]
+
+This places the the cluster configuration and the admin keyring on the remote
+nodes.
+
+Admin node definition
+---------------------
+
+The definition of an admin node is that both the cluster configuration file
+and the admin keyring. Both of these files are stored in the directory
+/etc/ceph and thier prefix is that of the cluster name.
+
+The default ceph cluster name is "ceph". So with a cluster with a default name
+the admin keyring is named /etc/ceph/ceph.client.admin.keyring while cluster
+configuration file is named /etc/ceph/ceph.conf.
--- /dev/null
+.. _gatherkeys:
+
+==========
+gatherkeys
+==========
+
+The ``gatherkeys`` subcommand provides an interface to get with a cluster's
+cephx bootstrap keys.
+
+keyrings
+========
+The ``gatherkeys`` subcommand retrieves the following keyrings.
+
+ceph.mon.keyring
+----------------
+This keyring is used by all mon nodes to communicate with other mon nodes.
+
+ceph.client.admin.keyring
+-------------------------
+This keyring is ceph client commands by default to administer the ceph cluster.
+
+ceph.bootstrap-osd.keyring
+--------------------------
+This keyring is used to generate cephx keyrings for OSD instances.
+
+ceph.bootstrap-mds.keyring
+--------------------------
+This keyring is used to generate cephx keyrings for MDS instances.
+
+ceph.bootstrap-rgw.keyring
+--------------------------
+This keyring is used to generate cephx keyrings for RGW instances.
+
+Example
+=======
+The ``gatherkeys`` subcommand contacts the mon and creates or retrieves existing
+keyrings from the mon internal store. To run::
+
+ ceph-deploy gatherkeys MON [MON..]
+
+You can optionally add as many mon nodes to the command line as desired. The
+``gatherkeys`` subcommand will succeed on the first mon to respond successfully
+with all the keyrings.
+
+Backing up of old keyrings
+==========================
+
+If old keyrings exist in the current working directory that do not match the
+retrieved keyrings these old keyrings will be renamed with a time stamp
+extention so you will not loose valuable keyrings.
+
+.. note:: Before version v1.5.33 ceph-deploy relied upon ``ceph-create-keys``
+ and did not backup existing keys. Using ``ceph-create-keys`` produced
+ a side effect of deploying all bootstrap keys on the mon node so
+ making all mon nodes admin nodes.
https_proxy=http://host:port
+Creating a new configuration
+============================
+
+To create a new configuration file and secret key, decide what hosts
+will run ``ceph-mon``, and run::
+
+ ceph-deploy new MON [MON..]
+
+For detailed information on new instructions refer to the :ref:`new`
+section.
+
+For detailed information on ``new`` subcommand refer to the
+:ref:`mon` section.
Deploying monitors
==================
config file are deployed. That is, the hosts you passed to
``ceph-deploy new`` are the default value here.
+For detailed information on ``mon`` subcommand refer to the
+:ref:`mon` section.
+
Gather keys
===========
-To gather authenticate keys (for administering the cluster and
+To gather authentication keys (for administering the cluster and
bootstrapping new nodes) to the local directory, run::
ceph-deploy gatherkeys HOST [HOST...]
Once these keys are in the local directory, you can provision new OSDs etc.
+For detailed information on ``gatherkeys`` subcommand refer to the
+:ref:`gatherkeys` section.
+
+Admin hosts
+===========
+
+To prepare a host with a ``ceph.conf`` and ``ceph.client.admin.keyring``
+keyring so that it can administer the cluster, run::
+
+ ceph-deploy admin HOST [HOST ...]
+
+Older versions of ceph-deploy automatically added the admin keyring to
+all mon nodes making them admin nodes. For detailed information on the
+admin command refer to the :ref:`admin` section.
+
+For detailed information on ``admin`` subcommand refer to the
+:ref:`admin` section.
Deploying OSDs
==============
This is useful when you are managing the mounting of volumes yourself.
-
-Admin hosts
-===========
-
-To prepare a host with a ``ceph.conf`` and ``ceph.client.admin.keyring``
-keyring so that it can administer the cluster, run::
-
- ceph-deploy admin HOST [HOST ...]
-
Forget keys
===========
most common scenarios. Monitors are usually very particular in what they need
to work correctly.
+.. note:: Before version v1.5.33 ceph-deploy relied upon ``ceph-create-keys``.
+ Using ``ceph-create-keys`` produced a side effect of deploying all
+ bootstrap keys on the mon node so making all mon nodes admin nodes.
+ This can be recreated by running the admin command on all mon nodes
+ see :ref:`admin` section.
+
create-initial
------------------
Will deploy for monitors defined in ``mon initial members``, wait until