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
This commit is contained in:
James Page 2017-09-14 14:44:22 -06:00
parent f936367cac
commit 71390fe0cf
13 changed files with 137 additions and 8 deletions

2
.gitignore vendored
View File

@ -2,9 +2,11 @@ bin
.idea
.coverage
.testrepository
.stestr
.tox
*.sw[nop]
.idea
*.pyc
.unit-state.db
func-results.json
.pydevproject

View File

@ -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

View File

@ -0,0 +1 @@
ceph_hooks.py

View File

@ -0,0 +1 @@
ceph_hooks.py

View File

@ -0,0 +1 @@
ceph_hooks.py

View File

@ -0,0 +1 @@
ceph_hooks.py

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -29,6 +29,8 @@ provides:
interface: ceph-osd
radosgw:
interface: ceph-radosgw
bootstrap-source:
interface: ceph-bootstrap
storage:
osd-devices:
type: block

View File

@ -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)

View File

@ -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)