[gnuoy,r=wolsen] Use current DC to determine leader.
This commit is contained in:
commit
1cf8583944
|
@ -52,6 +52,8 @@ from charmhelpers.core.strutils import (
|
|||
bool_from_string,
|
||||
)
|
||||
|
||||
DC_RESOURCE_NAME = 'DC'
|
||||
|
||||
|
||||
class HAIncompleteConfig(Exception):
|
||||
pass
|
||||
|
@ -95,6 +97,27 @@ def is_clustered():
|
|||
return False
|
||||
|
||||
|
||||
def is_crm_dc():
|
||||
"""
|
||||
Determine leadership by querying the pacemaker Designated Controller
|
||||
"""
|
||||
cmd = ['crm', 'status']
|
||||
try:
|
||||
status = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||
if not isinstance(status, six.text_type):
|
||||
status = six.text_type(status, "utf-8")
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
current_dc = ''
|
||||
for line in status.split('\n'):
|
||||
if line.startswith('Current DC'):
|
||||
# Current DC: juju-lytrusty-machine-2 (168108163) - partition with quorum
|
||||
current_dc = line.split(':')[1].split()[0]
|
||||
if current_dc == get_unit_hostname():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@retry_on_exception(5, base_delay=2, exc_type=CRMResourceNotFound)
|
||||
def is_crm_leader(resource, retry=False):
|
||||
"""
|
||||
|
@ -104,6 +127,8 @@ def is_crm_leader(resource, retry=False):
|
|||
We allow this operation to be retried to avoid the possibility of getting a
|
||||
false negative. See LP #1396246 for more info.
|
||||
"""
|
||||
if resource == DC_RESOURCE_NAME:
|
||||
return is_crm_dc()
|
||||
cmd = ['crm', 'resource', 'show', resource]
|
||||
try:
|
||||
status = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||
|
|
|
@ -73,6 +73,8 @@ def peer_retrieve_by_prefix(prefix, relation_name='cluster', delimiter='_',
|
|||
exc_list = exc_list if exc_list else []
|
||||
peerdb_settings = peer_retrieve('-', relation_name=relation_name)
|
||||
matched = {}
|
||||
if peerdb_settings is None:
|
||||
return matched
|
||||
for k, v in peerdb_settings.items():
|
||||
full_prefix = prefix + delimiter
|
||||
if k.startswith(full_prefix):
|
||||
|
|
|
@ -21,12 +21,14 @@
|
|||
# Charm Helpers Developers <juju@lists.ubuntu.com>
|
||||
|
||||
from __future__ import print_function
|
||||
from functools import wraps
|
||||
import os
|
||||
import json
|
||||
import yaml
|
||||
import subprocess
|
||||
import sys
|
||||
import errno
|
||||
import tempfile
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
import six
|
||||
|
@ -58,15 +60,17 @@ def cached(func):
|
|||
|
||||
will cache the result of unit_get + 'test' for future calls.
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
global cache
|
||||
key = str((func, args, kwargs))
|
||||
try:
|
||||
return cache[key]
|
||||
except KeyError:
|
||||
res = func(*args, **kwargs)
|
||||
cache[key] = res
|
||||
return res
|
||||
pass # Drop out of the exception handler scope.
|
||||
res = func(*args, **kwargs)
|
||||
cache[key] = res
|
||||
return res
|
||||
return wrapper
|
||||
|
||||
|
||||
|
@ -178,7 +182,7 @@ def local_unit():
|
|||
|
||||
def remote_unit():
|
||||
"""The remote unit for the current relation hook"""
|
||||
return os.environ['JUJU_REMOTE_UNIT']
|
||||
return os.environ.get('JUJU_REMOTE_UNIT', None)
|
||||
|
||||
|
||||
def service_name():
|
||||
|
@ -250,6 +254,12 @@ class Config(dict):
|
|||
except KeyError:
|
||||
return (self._prev_dict or {})[key]
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def keys(self):
|
||||
prev_keys = []
|
||||
if self._prev_dict is not None:
|
||||
|
@ -353,14 +363,29 @@ def relation_set(relation_id=None, relation_settings=None, **kwargs):
|
|||
"""Set relation information for the current unit"""
|
||||
relation_settings = relation_settings if relation_settings else {}
|
||||
relation_cmd_line = ['relation-set']
|
||||
accepts_file = "--file" in subprocess.check_output(
|
||||
relation_cmd_line + ["--help"])
|
||||
if relation_id is not None:
|
||||
relation_cmd_line.extend(('-r', relation_id))
|
||||
for k, v in (list(relation_settings.items()) + list(kwargs.items())):
|
||||
if v is None:
|
||||
relation_cmd_line.append('{}='.format(k))
|
||||
else:
|
||||
relation_cmd_line.append('{}={}'.format(k, v))
|
||||
subprocess.check_call(relation_cmd_line)
|
||||
settings = relation_settings.copy()
|
||||
settings.update(kwargs)
|
||||
if accepts_file:
|
||||
# --file was introduced in Juju 1.23.2. Use it by default if
|
||||
# available, since otherwise we'll break if the relation data is
|
||||
# too big. Ideally we should tell relation-set to read the data from
|
||||
# stdin, but that feature is broken in 1.23.2: Bug #1454678.
|
||||
with tempfile.NamedTemporaryFile(delete=False) as settings_file:
|
||||
settings_file.write(yaml.safe_dump(settings).encode("utf-8"))
|
||||
subprocess.check_call(
|
||||
relation_cmd_line + ["--file", settings_file.name])
|
||||
os.remove(settings_file.name)
|
||||
else:
|
||||
for key, value in settings.items():
|
||||
if value is None:
|
||||
relation_cmd_line.append('{}='.format(key))
|
||||
else:
|
||||
relation_cmd_line.append('{}={}'.format(key, value))
|
||||
subprocess.check_call(relation_cmd_line)
|
||||
# Flush cache of any relation-gets for local unit
|
||||
flush(local_unit())
|
||||
|
||||
|
@ -509,6 +534,11 @@ def unit_get(attribute):
|
|||
return None
|
||||
|
||||
|
||||
def unit_public_ip():
|
||||
"""Get this unit's public IP address"""
|
||||
return unit_get('public-address')
|
||||
|
||||
|
||||
def unit_private_ip():
|
||||
"""Get this unit's private IP address"""
|
||||
return unit_get('private-address')
|
||||
|
@ -605,3 +635,49 @@ def action_fail(message):
|
|||
|
||||
The results set by action_set are preserved."""
|
||||
subprocess.check_call(['action-fail', message])
|
||||
|
||||
|
||||
def status_set(workload_state, message):
|
||||
"""Set the workload state with a message
|
||||
|
||||
Use status-set to set the workload state with a message which is visible
|
||||
to the user via juju status. If the status-set command is not found then
|
||||
assume this is juju < 1.23 and juju-log the message unstead.
|
||||
|
||||
workload_state -- valid juju workload state.
|
||||
message -- status update message
|
||||
"""
|
||||
valid_states = ['maintenance', 'blocked', 'waiting', 'active']
|
||||
if workload_state not in valid_states:
|
||||
raise ValueError(
|
||||
'{!r} is not a valid workload state'.format(workload_state)
|
||||
)
|
||||
cmd = ['status-set', workload_state, message]
|
||||
try:
|
||||
ret = subprocess.call(cmd)
|
||||
if ret == 0:
|
||||
return
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
log_message = 'status-set failed: {} {}'.format(workload_state,
|
||||
message)
|
||||
log(log_message, level='INFO')
|
||||
|
||||
|
||||
def status_get():
|
||||
"""Retrieve the previously set juju workload state
|
||||
|
||||
If the status-set command is not found then assume this is juju < 1.23 and
|
||||
return 'unknown'
|
||||
"""
|
||||
cmd = ['status-get']
|
||||
try:
|
||||
raw_status = subprocess.check_output(cmd, universal_newlines=True)
|
||||
status = raw_status.rstrip()
|
||||
return status
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
return 'unknown'
|
||||
else:
|
||||
raise
|
||||
|
|
|
@ -90,7 +90,7 @@ def service_available(service_name):
|
|||
['service', service_name, 'status'],
|
||||
stderr=subprocess.STDOUT).decode('UTF-8')
|
||||
except subprocess.CalledProcessError as e:
|
||||
return 'unrecognized service' not in e.output
|
||||
return b'unrecognized service' not in e.output
|
||||
else:
|
||||
return True
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
import os
|
||||
import re
|
||||
import json
|
||||
from collections import Iterable
|
||||
from collections import Iterable, OrderedDict
|
||||
|
||||
from charmhelpers.core import host
|
||||
from charmhelpers.core import hookenv
|
||||
|
@ -119,7 +119,7 @@ class ServiceManager(object):
|
|||
"""
|
||||
self._ready_file = os.path.join(hookenv.charm_dir(), 'READY-SERVICES.json')
|
||||
self._ready = None
|
||||
self.services = {}
|
||||
self.services = OrderedDict()
|
||||
for service in services or []:
|
||||
service_name = service['service']
|
||||
self.services[service_name] = service
|
||||
|
|
|
@ -158,7 +158,7 @@ def filter_installed_packages(packages):
|
|||
|
||||
def apt_cache(in_memory=True):
|
||||
"""Build and return an apt cache"""
|
||||
import apt_pkg
|
||||
from apt import apt_pkg
|
||||
apt_pkg.init()
|
||||
if in_memory:
|
||||
apt_pkg.config.set("Dir::Cache::pkgcache", "")
|
||||
|
|
|
@ -56,6 +56,7 @@ from charmhelpers.contrib.database.mysql import (
|
|||
PerconaClusterHelper,
|
||||
)
|
||||
from charmhelpers.contrib.hahelpers.cluster import (
|
||||
DC_RESOURCE_NAME,
|
||||
peer_units,
|
||||
oldest_peer,
|
||||
eligible_leader,
|
||||
|
@ -74,7 +75,6 @@ from charmhelpers.contrib.charmsupport import nrpe
|
|||
|
||||
hooks = Hooks()
|
||||
|
||||
LEADER_RES = 'grp_percona_cluster'
|
||||
RES_MONITOR_PARAMS = ('params user="sstuser" password="%(sstpass)s" '
|
||||
'pid="/var/run/mysqld/mysqld.pid" '
|
||||
'socket="/var/run/mysqld/mysqld.sock" '
|
||||
|
@ -207,7 +207,7 @@ def cluster_changed():
|
|||
@hooks.hook('db-relation-changed')
|
||||
@hooks.hook('db-admin-relation-changed')
|
||||
def db_changed(relation_id=None, unit=None, admin=None):
|
||||
if not eligible_leader(LEADER_RES):
|
||||
if not eligible_leader(DC_RESOURCE_NAME):
|
||||
log('Service is peered, clearing db relation'
|
||||
' as this service unit is not the leader')
|
||||
relation_clear(relation_id)
|
||||
|
@ -269,7 +269,7 @@ def configure_db_for_hosts(hosts, database, username, db_helper):
|
|||
# TODO: This could be a hook common between mysql and percona-cluster
|
||||
@hooks.hook('shared-db-relation-changed')
|
||||
def shared_db_changed(relation_id=None, unit=None):
|
||||
if not eligible_leader(LEADER_RES):
|
||||
if not eligible_leader(DC_RESOURCE_NAME):
|
||||
relation_clear(relation_id)
|
||||
# Each unit needs to set the db information otherwise if the unit
|
||||
# with the info dies the settings die with it Bug# 1355848
|
||||
|
@ -412,7 +412,7 @@ def ha_relation_joined():
|
|||
password=cfg_passwd)
|
||||
resource_params = {'res_mysql_vip': vip_params,
|
||||
'res_mysql_monitor':
|
||||
RES_MONITOR_PARAMS % {'sstpass': sstpsswd}}
|
||||
RES_MONITOR_PARAMS % {'sstpass': sstpsswd}}
|
||||
groups = {'grp_percona_cluster': 'res_mysql_vip'}
|
||||
|
||||
clones = {'cl_mysql_monitor': 'res_mysql_monitor meta interleave=true'}
|
||||
|
@ -437,7 +437,7 @@ def ha_relation_joined():
|
|||
@hooks.hook('ha-relation-changed')
|
||||
def ha_relation_changed():
|
||||
clustered = relation_get('clustered')
|
||||
if (clustered and is_leader(LEADER_RES)):
|
||||
if (clustered and is_leader(DC_RESOURCE_NAME)):
|
||||
log('Cluster configured, notifying other services')
|
||||
# Tell all related services to start using the VIP
|
||||
for r_id in relation_ids('shared-db'):
|
||||
|
|
Loading…
Reference in New Issue