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)