When resuming, exclude haproxy

When resuming services exclude those managed by hacluster, in
this case haproxy. If pacemaker lacks quorum it may shut haproxy
down which will cause this charm to error.

Charmhelper sync included to bring in required
get_managed_services_and_ports method.

Change-Id: I063c168595bee05c924cb23469f8dc866a43982b
This commit is contained in:
Liam Young 2020-01-23 10:30:39 +00:00
parent a7c2e49fcf
commit 0a300b919e
6 changed files with 130 additions and 11 deletions

View File

@ -25,6 +25,7 @@ Helpers for clustering and determining "cluster leadership" and other
clustering-related helpers.
"""
import functools
import subprocess
import os
import time
@ -281,6 +282,10 @@ def determine_apache_port(public_port, singlenode_mode=False):
return public_port - (i * 10)
determine_apache_port_single = functools.partial(
determine_apache_port, singlenode_mode=True)
def get_hacluster_config(exclude_keys=None):
'''
Obtains all relevant configuration from charm configuration required
@ -404,3 +409,43 @@ def distributed_wait(modulo=None, wait=None, operation_name='operation'):
log(msg, DEBUG)
status_set('maintenance', msg)
time.sleep(calculated_wait)
def get_managed_services_and_ports(services, external_ports,
external_services=None,
port_conv_f=determine_apache_port_single):
"""Get the services and ports managed by this charm.
Return only the services and corresponding ports that are managed by this
charm. This excludes haproxy when there is a relation with hacluster. This
is because this charm passes responsability for stopping and starting
haproxy to hacluster.
Similarly, if a relation with hacluster exists then the ports returned by
this method correspond to those managed by the apache server rather than
haproxy.
:param services: List of services.
:type services: List[str]
:param external_ports: List of ports managed by external services.
:type external_ports: List[int]
:param external_services: List of services to be removed if ha relation is
present.
:type external_services: List[str]
:param port_conv_f: Function to apply to ports to calculate the ports
managed by services controlled by this charm.
:type port_convert_func: f()
:returns: A tuple containing a list of services first followed by a list of
ports.
:rtype: Tuple[List[str], List[int]]
"""
if external_services is None:
external_services = ['haproxy']
if relation_ids('ha'):
for svc in external_services:
try:
services.remove(svc)
except ValueError:
pass
external_ports = [port_conv_f(p) for p in external_ports]
return services, external_ports

View File

@ -52,7 +52,7 @@ class RestrictedPackages(BaseAudit):
def __init__(self, pkgs, **kwargs):
super(RestrictedPackages, self).__init__(**kwargs)
if isinstance(pkgs, string_types) or not hasattr(pkgs, '__iter__'):
self.pkgs = [pkgs]
self.pkgs = pkgs.split()
else:
self.pkgs = pkgs
@ -100,4 +100,5 @@ class RestrictedPackages(BaseAudit):
apt_purge(pkg.name)
def is_virtual_package(self, pkg):
return pkg.has_provides and not pkg.has_versions
return (pkg.get('has_provides', False) and
not pkg.get('has_versions', False))

View File

@ -44,6 +44,7 @@ from charmhelpers.core.hookenv import (
INFO,
ERROR,
related_units,
relation_get,
relation_ids,
relation_set,
status_set,
@ -331,6 +332,10 @@ PACKAGE_CODENAMES = {
DEFAULT_LOOPBACK_SIZE = '5G'
DB_SERIES_UPGRADING_KEY = 'cluster-series-upgrading'
DB_MAINTENANCE_KEYS = [DB_SERIES_UPGRADING_KEY]
class CompareOpenStackReleases(BasicStringComparator):
"""Provide comparisons of OpenStack releases.
@ -1912,3 +1917,33 @@ def set_db_initialised():
"""
juju_log('Setting db-initialised to True', 'DEBUG')
leader_set({'db-initialised': True})
def is_db_maintenance_mode(relid=None):
"""Check relation data from notifications of db in maintenance mode.
:returns: Whether db has notified it is in maintenance mode.
:rtype: bool
"""
juju_log('Checking for maintenance notifications', 'DEBUG')
if relid:
r_ids = [relid]
else:
r_ids = relation_ids('shared-db')
rids_units = [(r, u) for r in r_ids for u in related_units(r)]
notifications = []
for r_id, unit in rids_units:
settings = relation_get(unit=unit, rid=r_id)
for key, value in settings.items():
if value and key in DB_MAINTENANCE_KEYS:
juju_log(
'Unit: {}, Key: {}, Value: {}'.format(unit, key, value),
'DEBUG')
try:
notifications.append(bool_from_string(value))
except ValueError:
juju_log(
'Could not discern bool from {}'.format(value),
'WARN')
pass
return True in notifications

View File

@ -38,6 +38,7 @@ so with this we get rid of the dependency.
import locale
import os
import subprocess
import sys
class _container(dict):
@ -59,6 +60,13 @@ class Cache(object):
def __init__(self, progress=None):
pass
def __contains__(self, package):
try:
pkg = self.__getitem__(package)
return pkg is not None
except KeyError:
return False
def __getitem__(self, package):
"""Get information about a package from apt and dpkg databases.
@ -178,6 +186,28 @@ class Cache(object):
return pkgs
class Config(_container):
def __init__(self):
super(Config, self).__init__(self._populate())
def _populate(self):
cfgs = {}
cmd = ['apt-config', 'dump']
output = subprocess.check_output(cmd,
stderr=subprocess.STDOUT,
universal_newlines=True)
for line in output.splitlines():
if not line.startswith("CommandLine"):
k, v = line.split(" ", 1)
cfgs[k] = v.strip(";").strip("\"")
return cfgs
# Backwards compatibility with old apt_pkg module
sys.modules[__name__].config = Config()
def init():
"""Compability shim that does nothing."""
pass

View File

@ -1747,10 +1747,11 @@ def assess_status_func(configs):
if cmp_os_release >= 'train':
required_interfaces.update(REQUIRED_INTERFACES_TRAIN)
required_interfaces.update(get_optional_interfaces())
_services, _ = ch_cluster.get_managed_services_and_ports(services(), [])
return ch_utils.make_assess_status_func(
configs, required_interfaces,
charm_func=check_optional_relations,
services=services(), ports=None)
services=_services, ports=None)
def pause_unit_helper(configs):
@ -1782,8 +1783,9 @@ def _pause_resume_helper(f, configs):
"""
# TODO(ajkavanagh) - ports= has been left off because of the race hazard
# that exists due to service_start()
_services, _ = ch_cluster.get_managed_services_and_ports(services(), [])
f(assess_status_func(configs),
services=services(),
services=_services,
ports=None)

View File

@ -1254,6 +1254,7 @@ class NovaCCUtilsTests(CharmTestCase):
utils.VERSION_PACKAGE
)
@patch.object(utils.ch_cluster, 'get_managed_services_and_ports')
@patch.object(utils, 'get_optional_interfaces')
@patch.object(utils, 'check_optional_relations')
@patch.object(utils, 'REQUIRED_INTERFACES')
@ -1268,8 +1269,10 @@ class NovaCCUtilsTests(CharmTestCase):
services,
REQUIRED_INTERFACES,
check_optional_relations,
get_optional_interfaces):
get_optional_interfaces,
get_managed_services_and_ports):
compare_releases.return_value = 'stein'
get_managed_services_and_ports.return_value = (['s1'], ['p1'])
services.return_value = 's1'
REQUIRED_INTERFACES.copy.return_value = {'int': ['test 1']}
get_optional_interfaces.return_value = {'opt': ['test 2']}
@ -1279,7 +1282,7 @@ class NovaCCUtilsTests(CharmTestCase):
make_assess_status_func.assert_called_once_with(
'test-config',
{'int': ['test 1'], 'opt': ['test 2']},
charm_func=check_optional_relations, services='s1',
charm_func=check_optional_relations, services=['s1'],
ports=None)
make_assess_status_func.reset_mock()
@ -1288,7 +1291,7 @@ class NovaCCUtilsTests(CharmTestCase):
make_assess_status_func.assert_called_once_with(
'test-config',
{'int': ['test 1'], 'placement': ['placement'], 'opt': ['test 2']},
charm_func=check_optional_relations, services='s1',
charm_func=check_optional_relations, services=['s1'],
ports=None)
def test_pause_unit_helper(self):
@ -1301,18 +1304,21 @@ class NovaCCUtilsTests(CharmTestCase):
prh.assert_called_once_with(utils.ch_utils.resume_unit,
'random-config')
@patch.object(utils.ch_cluster, 'get_managed_services_and_ports')
@patch.object(utils, 'services')
@patch.object(utils, 'determine_ports')
def test_pause_resume_helper(self, determine_ports, services):
def test_pause_resume_helper(self, determine_ports, services,
get_managed_services_and_ports):
f = MagicMock()
services.return_value = 's1'
determine_ports.return_value = 'p1'
get_managed_services_and_ports.return_value = (['s1'], ['p1'])
services.return_value = ['s1']
determine_ports.return_value = ['p1']
with patch.object(utils, 'assess_status_func') as asf:
asf.return_value = 'assessor'
utils._pause_resume_helper(f, 'some-config')
asf.assert_called_once_with('some-config')
# ports=None whilst port checks are disabled.
f.assert_called_once_with('assessor', services='s1', ports=None)
f.assert_called_once_with('assessor', services=['s1'], ports=None)
@patch('charmhelpers.fetch.filter_installed_packages')
def test_disable_aws_compat_services_uninstalled(