From 71390fe0cf8778c42b691389d2c4fca78a9447e0 Mon Sep 17 00:00:00 2001 From: James Page Date: Thu, 14 Sep 2017 14:44:22 -0600 Subject: [PATCH] Add support for ceph-mon bootstrap Add new relation to support bootstrapping a new deployment of the ceph-mon charm from an existing ceph charm deployment, supporting migration away from the deprecated ceph charm. Each member of the existing ceph application will present the required fsid and monitor-secret values, as well as its public address so that the related ceph-mon units can correctly seed from the exisitng MON cluster. Provide stop hook implementation, which will leaves OSD services running but will remove the ceph.conf provided directly from this charm, falling back to ceph.conf provided by other charms installed on the same machine. MON and MGR services will be shutdown and disabled. Closes-Bug: 1665159 Change-Id: I9bd1d7630a8eff53c65cb0f07d17e095fc7f32a9 Depends-On: Iac34d1bee4b51b55dfb3d14d315aae8526a0893c --- .gitignore | 2 + README.md | 12 ++++- hooks/bootstrap-source-relation-broken | 1 + hooks/bootstrap-source-relation-changed | 1 + hooks/bootstrap-source-relation-departed | 1 + hooks/bootstrap-source-relation-joined | 1 + hooks/ceph_hooks.py | 48 ++++++++++++++++++- .../contrib/hardening/audits/apache.py | 4 +- .../contrib/openstack/alternatives.py | 13 +++++ hooks/charmhelpers/core/hookenv.py | 7 ++- metadata.yaml | 2 + tests/charmhelpers/core/hookenv.py | 7 ++- unit_tests/test_ceph_hooks.py | 46 +++++++++++++++++- 13 files changed, 137 insertions(+), 8 deletions(-) create mode 120000 hooks/bootstrap-source-relation-broken create mode 120000 hooks/bootstrap-source-relation-changed create mode 120000 hooks/bootstrap-source-relation-departed create mode 120000 hooks/bootstrap-source-relation-joined diff --git a/.gitignore b/.gitignore index b813b11..733c283 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,11 @@ bin .idea .coverage .testrepository +.stestr .tox *.sw[nop] .idea *.pyc .unit-state.db func-results.json +.pydevproject diff --git a/README.md b/README.md index 4e754e1..f146edd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,21 @@ # Overview +--- +**NOTE** + +This charm is deprecated and will not receive updates past February 2018. + +Existing users should refer to [Appendix A](https://docs.openstack.org/charm-deployment-guide/latest/) +of the Charm Deployment Guide for details of how to migration existing +deployments to the preferred ceph-mon and ceph-osd charms. + +--- + Ceph is a distributed storage and network file system designed to provide excellent performance, reliability, and scalability. This charm deploys a Ceph cluster. - # Usage The ceph charm has two pieces of mandatory configuration for which no defaults diff --git a/hooks/bootstrap-source-relation-broken b/hooks/bootstrap-source-relation-broken new file mode 120000 index 0000000..52d9663 --- /dev/null +++ b/hooks/bootstrap-source-relation-broken @@ -0,0 +1 @@ +ceph_hooks.py \ No newline at end of file diff --git a/hooks/bootstrap-source-relation-changed b/hooks/bootstrap-source-relation-changed new file mode 120000 index 0000000..52d9663 --- /dev/null +++ b/hooks/bootstrap-source-relation-changed @@ -0,0 +1 @@ +ceph_hooks.py \ No newline at end of file diff --git a/hooks/bootstrap-source-relation-departed b/hooks/bootstrap-source-relation-departed new file mode 120000 index 0000000..52d9663 --- /dev/null +++ b/hooks/bootstrap-source-relation-departed @@ -0,0 +1 @@ +ceph_hooks.py \ No newline at end of file diff --git a/hooks/bootstrap-source-relation-joined b/hooks/bootstrap-source-relation-joined new file mode 120000 index 0000000..52d9663 --- /dev/null +++ b/hooks/bootstrap-source-relation-joined @@ -0,0 +1 @@ +ceph_hooks.py \ No newline at end of file diff --git a/hooks/ceph_hooks.py b/hooks/ceph_hooks.py index ce7b363..10dbeb7 100755 --- a/hooks/ceph_hooks.py +++ b/hooks/ceph_hooks.py @@ -17,6 +17,7 @@ import os import sys import socket +import subprocess sys.path.append('lib') import ceph.utils as ceph @@ -47,6 +48,7 @@ from charmhelpers.core.hookenv import ( ) from charmhelpers.core.host import ( service_restart, + service_pause, umount, mkdir, write_file, @@ -62,7 +64,10 @@ from charmhelpers.fetch import ( get_upstream_version, ) from charmhelpers.payload.execd import execd_preinstall -from charmhelpers.contrib.openstack.alternatives import install_alternative +from charmhelpers.contrib.openstack.alternatives import ( + install_alternative, + remove_alternative, +) from charmhelpers.contrib.network.ip import ( get_ipv6_addr, format_ipv6_addr, @@ -235,10 +240,14 @@ def get_ceph_context(): return cephcontext +def ceph_conf_path(): + return "/var/lib/charm/{}/ceph.conf".format(service_name()) + + def emit_cephconf(): # Install ceph.conf as an alternative to support # co-existence with other charms that write this file - charm_ceph_conf = "/var/lib/charm/{}/ceph.conf".format(service_name()) + charm_ceph_conf = ceph_conf_path() mkdir(os.path.dirname(charm_ceph_conf), owner=ceph.ceph_user(), group=ceph.ceph_user()) render('ceph.conf', charm_ceph_conf, get_ceph_context(), perms=0o644) @@ -553,6 +562,19 @@ def client_relation_changed(): log('mon cluster not in quorum', level=DEBUG) +@hooks.hook('bootstrap-source-relation-joined') +def bootstrap_source_joined(relid=None): + """Provide required information to bootstrap ceph-mon cluster""" + if ceph.is_quorum(): + source = { + 'fsid': config('fsid'), + 'monitor-secret': config('monitor-secret'), + 'ceph-public-address': get_public_addr(), + } + relation_set(relation_id=relid, + relation_settings=source) + + @hooks.hook('upgrade-charm.real') @harden() def upgrade_charm(): @@ -653,6 +675,28 @@ def update_status(): log('Updating status.') +@hooks.hook('stop') +def stop(): + # NOTE(jamespage) + # Ensure monitor is removed from monmap prior to shutdown + # otherwise we end up with odd quorum loss issues during + # migration. + cmd = ['ceph', 'mon', 'rm', socket.gethostname()] + subprocess.check_call(cmd) + # NOTE(jamespage) + # Pause MON and MGR processes running on this unit, leaving + # any OSD processes running, supporting the migration to + # using the ceph-mon charm. + service_pause('ceph-mon') + if cmp_pkgrevno('ceph', '12.0.0') >= 0: + service_pause('ceph-mgr@{}'.format(socket.gethostname())) + # NOTE(jamespage) + # Remove the ceph.conf provided by this charm so + # that the ceph.conf from other deployed applications + # can take priority post removal. + remove_alternative('ceph.conf', ceph_conf_path()) + + if __name__ == '__main__': try: hooks.execute(sys.argv) diff --git a/hooks/charmhelpers/contrib/hardening/audits/apache.py b/hooks/charmhelpers/contrib/hardening/audits/apache.py index d812948..d32bf44 100644 --- a/hooks/charmhelpers/contrib/hardening/audits/apache.py +++ b/hooks/charmhelpers/contrib/hardening/audits/apache.py @@ -70,12 +70,12 @@ class DisabledModuleAudit(BaseAudit): """Returns the modules which are enabled in Apache.""" output = subprocess.check_output(['apache2ctl', '-M']) modules = [] - for line in output.strip().split(): + for line in output.splitlines(): # Each line of the enabled module output looks like: # module_name (static|shared) # Plus a header line at the top of the output which is stripped # out by the regex. - matcher = re.search(r'^ (\S*)', line) + matcher = re.search(r'^ (\S*)_module (\S*)', line) if matcher: modules.append(matcher.group(1)) return modules diff --git a/hooks/charmhelpers/contrib/openstack/alternatives.py b/hooks/charmhelpers/contrib/openstack/alternatives.py index 1501641..547de09 100644 --- a/hooks/charmhelpers/contrib/openstack/alternatives.py +++ b/hooks/charmhelpers/contrib/openstack/alternatives.py @@ -29,3 +29,16 @@ def install_alternative(name, target, source, priority=50): target, name, source, str(priority) ] subprocess.check_call(cmd) + + +def remove_alternative(name, source): + """Remove an installed alternative configuration file + + :param name: string name of the alternative to remove + :param source: string full path to alternative to remove + """ + cmd = [ + 'update-alternatives', '--remove', + name, source + ] + subprocess.check_call(cmd) diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py index 12f37b2..899722f 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/hooks/charmhelpers/core/hookenv.py @@ -218,6 +218,8 @@ def principal_unit(): for rid in relation_ids(reltype): for unit in related_units(rid): md = _metadata_unit(unit) + if not md: + continue subordinate = md.pop('subordinate', None) if not subordinate: return unit @@ -511,7 +513,10 @@ def _metadata_unit(unit): """ basedir = os.sep.join(charm_dir().split(os.sep)[:-2]) unitdir = 'unit-{}'.format(unit.replace(os.sep, '-')) - with open(os.path.join(basedir, unitdir, 'charm', 'metadata.yaml')) as md: + joineddir = os.path.join(basedir, unitdir, 'charm', 'metadata.yaml') + if not os.path.exists(joineddir): + return None + with open(joineddir) as md: return yaml.safe_load(md) diff --git a/metadata.yaml b/metadata.yaml index 9b3eb4b..174d3a3 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -29,6 +29,8 @@ provides: interface: ceph-osd radosgw: interface: ceph-radosgw + bootstrap-source: + interface: ceph-bootstrap storage: osd-devices: type: block diff --git a/tests/charmhelpers/core/hookenv.py b/tests/charmhelpers/core/hookenv.py index 12f37b2..899722f 100644 --- a/tests/charmhelpers/core/hookenv.py +++ b/tests/charmhelpers/core/hookenv.py @@ -218,6 +218,8 @@ def principal_unit(): for rid in relation_ids(reltype): for unit in related_units(rid): md = _metadata_unit(unit) + if not md: + continue subordinate = md.pop('subordinate', None) if not subordinate: return unit @@ -511,7 +513,10 @@ def _metadata_unit(unit): """ basedir = os.sep.join(charm_dir().split(os.sep)[:-2]) unitdir = 'unit-{}'.format(unit.replace(os.sep, '-')) - with open(os.path.join(basedir, unitdir, 'charm', 'metadata.yaml')) as md: + joineddir = os.path.join(basedir, unitdir, 'charm', 'metadata.yaml') + if not os.path.exists(joineddir): + return None + with open(joineddir) as md: return yaml.safe_load(md) diff --git a/unit_tests/test_ceph_hooks.py b/unit_tests/test_ceph_hooks.py index c99c373..ac89a1a 100644 --- a/unit_tests/test_ceph_hooks.py +++ b/unit_tests/test_ceph_hooks.py @@ -15,7 +15,7 @@ import copy import unittest -from mock import patch, DEFAULT +from mock import patch, DEFAULT, call import charmhelpers.contrib.storage.linux.ceph as ceph import ceph_hooks @@ -287,3 +287,47 @@ class CephHooksTestCase(unittest.TestCase): ceph_hooks.upgrade_charm() mocks["apt_install"].assert_called_with( ["python-dbus", "lockfile-progs"]) + + +class StopHookTestCase(unittest.TestCase): + + @patch.object(ceph_hooks, 'ceph_conf_path') + @patch.object(ceph_hooks, 'socket') + @patch.object(ceph_hooks, 'subprocess') + @patch.object(ceph_hooks, 'service_pause') + @patch.object(ceph_hooks, 'cmp_pkgrevno') + @patch.object(ceph_hooks, 'remove_alternative') + def _test_stop(self, + remove_alternative, + cmp_pkgrevno, + service_pause, + subprocess, + socket, + ceph_conf_path, + ceph_mgr=False): + if ceph_mgr: + cmp_pkgrevno.return_value = 1 + else: + cmp_pkgrevno.return_value = -1 + socket.gethostname.return_value = 'myself' + ceph_conf_path.return_value = '/var/lib/charm/me/ceph.conf' + ceph_hooks.stop() + subprocess.check_call.assert_called_with( + ['ceph', 'mon', 'rm', 'myself'] + ) + if ceph_mgr: + service_pause.assert_has_calls([ + call('ceph-mon'), + call('ceph-mgr@myself') + ]) + else: + service_pause.assert_called_once_with('ceph-mon') + + remove_alternative.assert_called_with('ceph.conf', + '/var/lib/charm/me/ceph.conf') + + def test_stop_jewel(self): + self._test_stop() + + def test_stop_luminous(self): + self._test_stop(ceph_mgr=True)