From 50f345904a03fac5eb8a9cab716afdc43f8a17ed Mon Sep 17 00:00:00 2001 From: Yuri Weinstein Date: Fri, 20 May 2016 10:13:14 -0700 Subject: [PATCH] Testing https://github.com/ceph/ceph-deploy/pull/393 Added more logging from oms101 Signed-off-by: Yuri Weinstein --- ceph_deploy/gatherkeys.py | 301 ++++++++++++++---- ceph_deploy/tests/test_gather_keys.py | 137 ++++++++ ceph_deploy/tests/test_gather_keys_missing.py | 163 ++++++++++ .../tests/test_gather_keys_with_mon.py | 214 +++++++++++++ ceph_deploy/tests/test_keys_equivalent.py | 171 ++++++++++ docs/source/admin.rst | 26 ++ docs/source/gatherkeys.rst | 55 ++++ docs/source/index.rst | 44 ++- docs/source/mon.rst | 6 + 9 files changed, 1043 insertions(+), 74 deletions(-) create mode 100644 ceph_deploy/tests/test_gather_keys.py create mode 100644 ceph_deploy/tests/test_gather_keys_missing.py create mode 100644 ceph_deploy/tests/test_gather_keys_with_mon.py create mode 100644 ceph_deploy/tests/test_keys_equivalent.py create mode 100644 docs/source/admin.rst create mode 100644 docs/source/gatherkeys.rst diff --git a/ceph_deploy/gatherkeys.py b/ceph_deploy/gatherkeys.py index d35a9ae..5f9ac0b 100644 --- a/ceph_deploy/gatherkeys.py +++ b/ceph_deploy/gatherkeys.py @@ -1,84 +1,257 @@ 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): """ diff --git a/ceph_deploy/tests/test_gather_keys.py b/ceph_deploy/tests/test_gather_keys.py new file mode 100644 index 0000000..8b2c624 --- /dev/null +++ b/ceph_deploy/tests/test_gather_keys.py @@ -0,0 +1,137 @@ +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 diff --git a/ceph_deploy/tests/test_gather_keys_missing.py b/ceph_deploy/tests/test_gather_keys_missing.py new file mode 100644 index 0000000..84f0af0 --- /dev/null +++ b/ceph_deploy/tests/test_gather_keys_missing.py @@ -0,0 +1,163 @@ +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 + ) + diff --git a/ceph_deploy/tests/test_gather_keys_with_mon.py b/ceph_deploy/tests/test_gather_keys_with_mon.py new file mode 100644 index 0000000..7ac6426 --- /dev/null +++ b/ceph_deploy/tests/test_gather_keys_with_mon.py @@ -0,0 +1,214 @@ +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 diff --git a/ceph_deploy/tests/test_keys_equivalent.py b/ceph_deploy/tests/test_keys_equivalent.py new file mode 100644 index 0000000..5e0d33f --- /dev/null +++ b/ceph_deploy/tests/test_keys_equivalent.py @@ -0,0 +1,171 @@ +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) diff --git a/docs/source/admin.rst b/docs/source/admin.rst new file mode 100644 index 0000000..8ae7395 --- /dev/null +++ b/docs/source/admin.rst @@ -0,0 +1,26 @@ +.. _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. diff --git a/docs/source/gatherkeys.rst b/docs/source/gatherkeys.rst new file mode 100644 index 0000000..6a1bdea --- /dev/null +++ b/docs/source/gatherkeys.rst @@ -0,0 +1,55 @@ +.. _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. diff --git a/docs/source/index.rst b/docs/source/index.rst index 30f8e20..c97ddd8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -175,6 +175,19 @@ hosts, for example:: 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 ================== @@ -187,10 +200,13 @@ Without explicit hosts listed, hosts in ``mon_initial_members`` in the 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...] @@ -199,6 +215,23 @@ where ``HOST`` is one of the monitor hosts. 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 ============== @@ -222,15 +255,6 @@ OSD, you can also do:: 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 =========== diff --git a/docs/source/mon.rst b/docs/source/mon.rst index 86f76ab..7942500 100644 --- a/docs/source/mon.rst +++ b/docs/source/mon.rst @@ -7,6 +7,12 @@ monitors. The tool makes a few assumptions that are needed to implement the 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 -- 2.47.3