]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-deploy.git/commitdiff
Testing https://github.com/ceph/ceph-deploy/pull/393
authorYuri Weinstein <yweinste@redhat.com>
Fri, 20 May 2016 17:13:14 +0000 (10:13 -0700)
committerYuri Weinstein <yweinste@redhat.com>
Mon, 23 May 2016 15:53:44 +0000 (08:53 -0700)
Added more logging from oms101

Signed-off-by: Yuri Weinstein <yweinste@redhat.com>
ceph_deploy/gatherkeys.py
ceph_deploy/tests/test_gather_keys.py [new file with mode: 0644]
ceph_deploy/tests/test_gather_keys_missing.py [new file with mode: 0644]
ceph_deploy/tests/test_gather_keys_with_mon.py [new file with mode: 0644]
ceph_deploy/tests/test_keys_equivalent.py [new file with mode: 0644]
docs/source/admin.rst [new file with mode: 0644]
docs/source/gatherkeys.rst [new file with mode: 0644]
docs/source/index.rst
docs/source/mon.rst

index d35a9ae35e054ec41f79c7673df8d12064f92650..5f9ac0b12e3fcebfe7a21b55edd10f7b2cb001b5 100644 (file)
 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 (file)
index 0000000..8b2c624
--- /dev/null
@@ -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 (file)
index 0000000..84f0af0
--- /dev/null
@@ -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 (file)
index 0000000..7ac6426
--- /dev/null
@@ -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 (file)
index 0000000..5e0d33f
--- /dev/null
@@ -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 (file)
index 0000000..8ae7395
--- /dev/null
@@ -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 (file)
index 0000000..6a1bdea
--- /dev/null
@@ -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.
index 30f8e20eed2dd54becf77a37e32274a60ba07011..c97ddd87bf2991bbfb79ac7514150dd542fa4117 100644 (file)
@@ -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
 ===========
 
index 86f76abd53d4ebd14aa8440e4272d5e9b064367c..7942500811e9d5ffa58b10f3aefdb019a1f5a355 100644 (file)
@@ -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