Sync charm-helpers
This commit is contained in:
parent
0158686165
commit
c0859c70fc
@ -23,7 +23,7 @@ import socket
|
||||
from functools import partial
|
||||
|
||||
from charmhelpers.core.hookenv import unit_get
|
||||
from charmhelpers.fetch import apt_install
|
||||
from charmhelpers.fetch import apt_install, apt_update
|
||||
from charmhelpers.core.hookenv import (
|
||||
log,
|
||||
WARNING,
|
||||
@ -32,13 +32,15 @@ from charmhelpers.core.hookenv import (
|
||||
try:
|
||||
import netifaces
|
||||
except ImportError:
|
||||
apt_install('python-netifaces')
|
||||
apt_update(fatal=True)
|
||||
apt_install('python-netifaces', fatal=True)
|
||||
import netifaces
|
||||
|
||||
try:
|
||||
import netaddr
|
||||
except ImportError:
|
||||
apt_install('python-netaddr')
|
||||
apt_update(fatal=True)
|
||||
apt_install('python-netaddr', fatal=True)
|
||||
import netaddr
|
||||
|
||||
|
||||
|
@ -58,19 +58,17 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
||||
else:
|
||||
base_series = self.current_next
|
||||
|
||||
if self.stable:
|
||||
for svc in other_services:
|
||||
if svc['name'] in force_series_current:
|
||||
base_series = self.current_next
|
||||
|
||||
for svc in other_services:
|
||||
if svc['name'] in force_series_current:
|
||||
base_series = self.current_next
|
||||
# If a location has been explicitly set, use it
|
||||
if svc.get('location'):
|
||||
continue
|
||||
if self.stable:
|
||||
temp = 'lp:charms/{}/{}'
|
||||
svc['location'] = temp.format(base_series,
|
||||
svc['name'])
|
||||
else:
|
||||
for svc in other_services:
|
||||
if svc['name'] in force_series_current:
|
||||
base_series = self.current_next
|
||||
|
||||
else:
|
||||
if svc['name'] in base_charms:
|
||||
temp = 'lp:charms/{}/{}'
|
||||
svc['location'] = temp.format(base_series,
|
||||
@ -79,6 +77,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
||||
temp = 'lp:~openstack-charmers/charms/{}/{}/next'
|
||||
svc['location'] = temp.format(self.current_next,
|
||||
svc['name'])
|
||||
|
||||
return other_services
|
||||
|
||||
def _add_services(self, this_service, other_services):
|
||||
|
@ -752,7 +752,7 @@ class OpenStackAmuletUtils(AmuletUtils):
|
||||
self.log.debug('SSL is enabled @{}:{} '
|
||||
'({})'.format(host, port, unit_name))
|
||||
return True
|
||||
elif not port and not conf_ssl:
|
||||
elif not conf_ssl:
|
||||
self.log.debug('SSL not enabled @{}:{} '
|
||||
'({})'.format(host, port, unit_name))
|
||||
return False
|
||||
|
@ -14,6 +14,7 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@ -194,10 +195,50 @@ def config_flags_parser(config_flags):
|
||||
class OSContextGenerator(object):
|
||||
"""Base class for all context generators."""
|
||||
interfaces = []
|
||||
related = False
|
||||
complete = False
|
||||
missing_data = []
|
||||
|
||||
def __call__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def context_complete(self, ctxt):
|
||||
"""Check for missing data for the required context data.
|
||||
Set self.missing_data if it exists and return False.
|
||||
Set self.complete if no missing data and return True.
|
||||
"""
|
||||
# Fresh start
|
||||
self.complete = False
|
||||
self.missing_data = []
|
||||
for k, v in six.iteritems(ctxt):
|
||||
if v is None or v == '':
|
||||
if k not in self.missing_data:
|
||||
self.missing_data.append(k)
|
||||
|
||||
if self.missing_data:
|
||||
self.complete = False
|
||||
log('Missing required data: %s' % ' '.join(self.missing_data), level=INFO)
|
||||
else:
|
||||
self.complete = True
|
||||
return self.complete
|
||||
|
||||
def get_related(self):
|
||||
"""Check if any of the context interfaces have relation ids.
|
||||
Set self.related and return True if one of the interfaces
|
||||
has relation ids.
|
||||
"""
|
||||
# Fresh start
|
||||
self.related = False
|
||||
try:
|
||||
for interface in self.interfaces:
|
||||
if relation_ids(interface):
|
||||
self.related = True
|
||||
return self.related
|
||||
except AttributeError as e:
|
||||
log("{} {}"
|
||||
"".format(self, e), 'INFO')
|
||||
return self.related
|
||||
|
||||
|
||||
class SharedDBContext(OSContextGenerator):
|
||||
interfaces = ['shared-db']
|
||||
@ -213,6 +254,7 @@ class SharedDBContext(OSContextGenerator):
|
||||
self.database = database
|
||||
self.user = user
|
||||
self.ssl_dir = ssl_dir
|
||||
self.rel_name = self.interfaces[0]
|
||||
|
||||
def __call__(self):
|
||||
self.database = self.database or config('database')
|
||||
@ -246,6 +288,7 @@ class SharedDBContext(OSContextGenerator):
|
||||
password_setting = self.relation_prefix + '_password'
|
||||
|
||||
for rid in relation_ids(self.interfaces[0]):
|
||||
self.related = True
|
||||
for unit in related_units(rid):
|
||||
rdata = relation_get(rid=rid, unit=unit)
|
||||
host = rdata.get('db_host')
|
||||
@ -257,7 +300,7 @@ class SharedDBContext(OSContextGenerator):
|
||||
'database_password': rdata.get(password_setting),
|
||||
'database_type': 'mysql'
|
||||
}
|
||||
if context_complete(ctxt):
|
||||
if self.context_complete(ctxt):
|
||||
db_ssl(rdata, ctxt, self.ssl_dir)
|
||||
return ctxt
|
||||
return {}
|
||||
@ -278,6 +321,7 @@ class PostgresqlDBContext(OSContextGenerator):
|
||||
|
||||
ctxt = {}
|
||||
for rid in relation_ids(self.interfaces[0]):
|
||||
self.related = True
|
||||
for unit in related_units(rid):
|
||||
rel_host = relation_get('host', rid=rid, unit=unit)
|
||||
rel_user = relation_get('user', rid=rid, unit=unit)
|
||||
@ -287,7 +331,7 @@ class PostgresqlDBContext(OSContextGenerator):
|
||||
'database_user': rel_user,
|
||||
'database_password': rel_passwd,
|
||||
'database_type': 'postgresql'}
|
||||
if context_complete(ctxt):
|
||||
if self.context_complete(ctxt):
|
||||
return ctxt
|
||||
|
||||
return {}
|
||||
@ -348,6 +392,7 @@ class IdentityServiceContext(OSContextGenerator):
|
||||
ctxt['signing_dir'] = cachedir
|
||||
|
||||
for rid in relation_ids(self.rel_name):
|
||||
self.related = True
|
||||
for unit in related_units(rid):
|
||||
rdata = relation_get(rid=rid, unit=unit)
|
||||
serv_host = rdata.get('service_host')
|
||||
@ -366,7 +411,7 @@ class IdentityServiceContext(OSContextGenerator):
|
||||
'service_protocol': svc_protocol,
|
||||
'auth_protocol': auth_protocol})
|
||||
|
||||
if context_complete(ctxt):
|
||||
if self.context_complete(ctxt):
|
||||
# NOTE(jamespage) this is required for >= icehouse
|
||||
# so a missing value just indicates keystone needs
|
||||
# upgrading
|
||||
@ -405,6 +450,7 @@ class AMQPContext(OSContextGenerator):
|
||||
ctxt = {}
|
||||
for rid in relation_ids(self.rel_name):
|
||||
ha_vip_only = False
|
||||
self.related = True
|
||||
for unit in related_units(rid):
|
||||
if relation_get('clustered', rid=rid, unit=unit):
|
||||
ctxt['clustered'] = True
|
||||
@ -437,7 +483,7 @@ class AMQPContext(OSContextGenerator):
|
||||
ha_vip_only = relation_get('ha-vip-only',
|
||||
rid=rid, unit=unit) is not None
|
||||
|
||||
if context_complete(ctxt):
|
||||
if self.context_complete(ctxt):
|
||||
if 'rabbit_ssl_ca' in ctxt:
|
||||
if not self.ssl_dir:
|
||||
log("Charm not setup for ssl support but ssl ca "
|
||||
@ -469,7 +515,7 @@ class AMQPContext(OSContextGenerator):
|
||||
ctxt['oslo_messaging_flags'] = config_flags_parser(
|
||||
oslo_messaging_flags)
|
||||
|
||||
if not context_complete(ctxt):
|
||||
if not self.complete:
|
||||
return {}
|
||||
|
||||
return ctxt
|
||||
@ -507,7 +553,7 @@ class CephContext(OSContextGenerator):
|
||||
if not os.path.isdir('/etc/ceph'):
|
||||
os.mkdir('/etc/ceph')
|
||||
|
||||
if not context_complete(ctxt):
|
||||
if not self.context_complete(ctxt):
|
||||
return {}
|
||||
|
||||
ensure_packages(['ceph-common'])
|
||||
@ -906,6 +952,19 @@ class NeutronContext(OSContextGenerator):
|
||||
'config': config}
|
||||
return ovs_ctxt
|
||||
|
||||
def midonet_ctxt(self):
|
||||
driver = neutron_plugin_attribute(self.plugin, 'driver',
|
||||
self.network_manager)
|
||||
midonet_config = neutron_plugin_attribute(self.plugin, 'config',
|
||||
self.network_manager)
|
||||
mido_ctxt = {'core_plugin': driver,
|
||||
'neutron_plugin': 'midonet',
|
||||
'neutron_security_groups': self.neutron_security_groups,
|
||||
'local_ip': unit_private_ip(),
|
||||
'config': midonet_config}
|
||||
|
||||
return mido_ctxt
|
||||
|
||||
def __call__(self):
|
||||
if self.network_manager not in ['quantum', 'neutron']:
|
||||
return {}
|
||||
@ -927,6 +986,8 @@ class NeutronContext(OSContextGenerator):
|
||||
ctxt.update(self.nuage_ctxt())
|
||||
elif self.plugin == 'plumgrid':
|
||||
ctxt.update(self.pg_ctxt())
|
||||
elif self.plugin == 'midonet':
|
||||
ctxt.update(self.midonet_ctxt())
|
||||
|
||||
alchemy_flags = config('neutron-alchemy-flags')
|
||||
if alchemy_flags:
|
||||
@ -1318,7 +1379,7 @@ class DataPortContext(NeutronPortContext):
|
||||
normalized.update({port: port for port in resolved
|
||||
if port in ports})
|
||||
if resolved:
|
||||
return {bridge: normalized[port] for port, bridge in
|
||||
return {normalized[port]: bridge for port, bridge in
|
||||
six.iteritems(portmap) if port in normalized.keys()}
|
||||
|
||||
return None
|
||||
@ -1329,12 +1390,22 @@ class PhyNICMTUContext(DataPortContext):
|
||||
def __call__(self):
|
||||
ctxt = {}
|
||||
mappings = super(PhyNICMTUContext, self).__call__()
|
||||
if mappings and mappings.values():
|
||||
ports = mappings.values()
|
||||
if mappings and mappings.keys():
|
||||
ports = sorted(mappings.keys())
|
||||
napi_settings = NeutronAPIContext()()
|
||||
mtu = napi_settings.get('network_device_mtu')
|
||||
all_ports = set()
|
||||
# If any of ports is a vlan device, its underlying device must have
|
||||
# mtu applied first.
|
||||
for port in ports:
|
||||
for lport in glob.glob("/sys/class/net/%s/lower_*" % port):
|
||||
lport = os.path.basename(lport)
|
||||
all_ports.add(lport.split('_')[1])
|
||||
|
||||
all_ports = list(all_ports)
|
||||
all_ports.extend(ports)
|
||||
if mtu:
|
||||
ctxt["devs"] = '\\n'.join(ports)
|
||||
ctxt["devs"] = '\\n'.join(all_ports)
|
||||
ctxt['mtu'] = mtu
|
||||
|
||||
return ctxt
|
||||
@ -1366,6 +1437,6 @@ class NetworkServiceContext(OSContextGenerator):
|
||||
'auth_protocol':
|
||||
rdata.get('auth_protocol') or 'http',
|
||||
}
|
||||
if context_complete(ctxt):
|
||||
if self.context_complete(ctxt):
|
||||
return ctxt
|
||||
return {}
|
||||
|
@ -209,6 +209,20 @@ def neutron_plugins():
|
||||
'server_packages': ['neutron-server',
|
||||
'neutron-plugin-plumgrid'],
|
||||
'server_services': ['neutron-server']
|
||||
},
|
||||
'midonet': {
|
||||
'config': '/etc/neutron/plugins/midonet/midonet.ini',
|
||||
'driver': 'midonet.neutron.plugin.MidonetPluginV2',
|
||||
'contexts': [
|
||||
context.SharedDBContext(user=config('neutron-database-user'),
|
||||
database=config('neutron-database'),
|
||||
relation_prefix='neutron',
|
||||
ssl_dir=NEUTRON_CONF_DIR)],
|
||||
'services': [],
|
||||
'packages': [[headers_package()] + determine_dkms_package()],
|
||||
'server_packages': ['neutron-server',
|
||||
'python-neutron-plugin-midonet'],
|
||||
'server_services': ['neutron-server']
|
||||
}
|
||||
}
|
||||
if release >= 'icehouse':
|
||||
@ -310,10 +324,10 @@ def parse_bridge_mappings(mappings):
|
||||
def parse_data_port_mappings(mappings, default_bridge='br-data'):
|
||||
"""Parse data port mappings.
|
||||
|
||||
Mappings must be a space-delimited list of port:bridge mappings.
|
||||
Mappings must be a space-delimited list of bridge:port.
|
||||
|
||||
Returns dict of the form {port:bridge} where port may be an mac address or
|
||||
interface name.
|
||||
Returns dict of the form {port:bridge} where ports may be mac addresses or
|
||||
interface names.
|
||||
"""
|
||||
|
||||
# NOTE(dosaboy): we use rvalue for key to allow multiple values to be
|
||||
|
@ -13,3 +13,9 @@ log to syslog = {{ use_syslog }}
|
||||
err to syslog = {{ use_syslog }}
|
||||
clog to syslog = {{ use_syslog }}
|
||||
|
||||
[client]
|
||||
{% if rbd_client_cache_settings -%}
|
||||
{% for key, value in rbd_client_cache_settings.iteritems() -%}
|
||||
{{ key }} = {{ value }}
|
||||
{% endfor -%}
|
||||
{%- endif %}
|
@ -18,7 +18,7 @@ import os
|
||||
|
||||
import six
|
||||
|
||||
from charmhelpers.fetch import apt_install
|
||||
from charmhelpers.fetch import apt_install, apt_update
|
||||
from charmhelpers.core.hookenv import (
|
||||
log,
|
||||
ERROR,
|
||||
@ -29,6 +29,7 @@ from charmhelpers.contrib.openstack.utils import OPENSTACK_CODENAMES
|
||||
try:
|
||||
from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
|
||||
except ImportError:
|
||||
apt_update(fatal=True)
|
||||
apt_install('python-jinja2', fatal=True)
|
||||
from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
|
||||
|
||||
@ -112,7 +113,7 @@ class OSConfigTemplate(object):
|
||||
|
||||
def complete_contexts(self):
|
||||
'''
|
||||
Return a list of interfaces that have atisfied contexts.
|
||||
Return a list of interfaces that have satisfied contexts.
|
||||
'''
|
||||
if self._complete_contexts:
|
||||
return self._complete_contexts
|
||||
@ -293,3 +294,30 @@ class OSConfigRenderer(object):
|
||||
[interfaces.extend(i.complete_contexts())
|
||||
for i in six.itervalues(self.templates)]
|
||||
return interfaces
|
||||
|
||||
def get_incomplete_context_data(self, interfaces):
|
||||
'''
|
||||
Return dictionary of relation status of interfaces and any missing
|
||||
required context data. Example:
|
||||
{'amqp': {'missing_data': ['rabbitmq_password'], 'related': True},
|
||||
'zeromq-configuration': {'related': False}}
|
||||
'''
|
||||
incomplete_context_data = {}
|
||||
|
||||
for i in six.itervalues(self.templates):
|
||||
for context in i.contexts:
|
||||
for interface in interfaces:
|
||||
related = False
|
||||
if interface in context.interfaces:
|
||||
related = context.get_related()
|
||||
missing_data = context.missing_data
|
||||
if missing_data:
|
||||
incomplete_context_data[interface] = {'missing_data': missing_data}
|
||||
if related:
|
||||
if incomplete_context_data.get(interface):
|
||||
incomplete_context_data[interface].update({'related': True})
|
||||
else:
|
||||
incomplete_context_data[interface] = {'related': True}
|
||||
else:
|
||||
incomplete_context_data[interface] = {'related': False}
|
||||
return incomplete_context_data
|
||||
|
@ -42,7 +42,9 @@ from charmhelpers.core.hookenv import (
|
||||
charm_dir,
|
||||
INFO,
|
||||
relation_ids,
|
||||
relation_set
|
||||
relation_set,
|
||||
status_set,
|
||||
hook_name
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.storage.linux.lvm import (
|
||||
@ -52,7 +54,8 @@ from charmhelpers.contrib.storage.linux.lvm import (
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.network.ip import (
|
||||
get_ipv6_addr
|
||||
get_ipv6_addr,
|
||||
is_ipv6,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.python.packages import (
|
||||
@ -517,6 +520,12 @@ def sync_db_with_multi_ipv6_addresses(database, database_user,
|
||||
relation_prefix=None):
|
||||
hosts = get_ipv6_addr(dynamic_only=False)
|
||||
|
||||
if config('vip'):
|
||||
vips = config('vip').split()
|
||||
for vip in vips:
|
||||
if vip and is_ipv6(vip):
|
||||
hosts.append(vip)
|
||||
|
||||
kwargs = {'database': database,
|
||||
'username': database_user,
|
||||
'hostname': json.dumps(hosts)}
|
||||
@ -754,6 +763,176 @@ def git_yaml_value(projects_yaml, key):
|
||||
return None
|
||||
|
||||
|
||||
def os_workload_status(configs, required_interfaces, charm_func=None):
|
||||
"""
|
||||
Decorator to set workload status based on complete contexts
|
||||
"""
|
||||
def wrap(f):
|
||||
@wraps(f)
|
||||
def wrapped_f(*args, **kwargs):
|
||||
# Run the original function first
|
||||
f(*args, **kwargs)
|
||||
# Set workload status now that contexts have been
|
||||
# acted on
|
||||
set_os_workload_status(configs, required_interfaces, charm_func)
|
||||
return wrapped_f
|
||||
return wrap
|
||||
|
||||
|
||||
def set_os_workload_status(configs, required_interfaces, charm_func=None):
|
||||
"""
|
||||
Set workload status based on complete contexts.
|
||||
status-set missing or incomplete contexts
|
||||
and juju-log details of missing required data.
|
||||
charm_func is a charm specific function to run checking
|
||||
for charm specific requirements such as a VIP setting.
|
||||
"""
|
||||
incomplete_rel_data = incomplete_relation_data(configs, required_interfaces)
|
||||
state = 'active'
|
||||
missing_relations = []
|
||||
incomplete_relations = []
|
||||
message = None
|
||||
charm_state = None
|
||||
charm_message = None
|
||||
|
||||
for generic_interface in incomplete_rel_data.keys():
|
||||
related_interface = None
|
||||
missing_data = {}
|
||||
# Related or not?
|
||||
for interface in incomplete_rel_data[generic_interface]:
|
||||
if incomplete_rel_data[generic_interface][interface].get('related'):
|
||||
related_interface = interface
|
||||
missing_data = incomplete_rel_data[generic_interface][interface].get('missing_data')
|
||||
# No relation ID for the generic_interface
|
||||
if not related_interface:
|
||||
juju_log("{} relation is missing and must be related for "
|
||||
"functionality. ".format(generic_interface), 'WARN')
|
||||
state = 'blocked'
|
||||
if generic_interface not in missing_relations:
|
||||
missing_relations.append(generic_interface)
|
||||
else:
|
||||
# Relation ID exists but no related unit
|
||||
if not missing_data:
|
||||
# Edge case relation ID exists but departing
|
||||
if ('departed' in hook_name() or 'broken' in hook_name()) \
|
||||
and related_interface in hook_name():
|
||||
state = 'blocked'
|
||||
if generic_interface not in missing_relations:
|
||||
missing_relations.append(generic_interface)
|
||||
juju_log("{} relation's interface, {}, "
|
||||
"relationship is departed or broken "
|
||||
"and is required for functionality."
|
||||
"".format(generic_interface, related_interface), "WARN")
|
||||
# Normal case relation ID exists but no related unit
|
||||
# (joining)
|
||||
else:
|
||||
juju_log("{} relations's interface, {}, is related but has "
|
||||
"no units in the relation."
|
||||
"".format(generic_interface, related_interface), "INFO")
|
||||
# Related unit exists and data missing on the relation
|
||||
else:
|
||||
juju_log("{} relation's interface, {}, is related awaiting "
|
||||
"the following data from the relationship: {}. "
|
||||
"".format(generic_interface, related_interface,
|
||||
", ".join(missing_data)), "INFO")
|
||||
if state != 'blocked':
|
||||
state = 'waiting'
|
||||
if generic_interface not in incomplete_relations \
|
||||
and generic_interface not in missing_relations:
|
||||
incomplete_relations.append(generic_interface)
|
||||
|
||||
if missing_relations:
|
||||
message = "Missing relations: {}".format(", ".join(missing_relations))
|
||||
if incomplete_relations:
|
||||
message += "; incomplete relations: {}" \
|
||||
"".format(", ".join(incomplete_relations))
|
||||
state = 'blocked'
|
||||
elif incomplete_relations:
|
||||
message = "Incomplete relations: {}" \
|
||||
"".format(", ".join(incomplete_relations))
|
||||
state = 'waiting'
|
||||
|
||||
# Run charm specific checks
|
||||
if charm_func:
|
||||
charm_state, charm_message = charm_func(configs)
|
||||
if charm_state != 'active' and charm_state != 'unknown':
|
||||
state = workload_state_compare(state, charm_state)
|
||||
if message:
|
||||
message = "{} {}".format(message, charm_message)
|
||||
else:
|
||||
message = charm_message
|
||||
|
||||
# Set to active if all requirements have been met
|
||||
if state == 'active':
|
||||
message = "Unit is ready"
|
||||
juju_log(message, "INFO")
|
||||
|
||||
status_set(state, message)
|
||||
|
||||
|
||||
def workload_state_compare(current_workload_state, workload_state):
|
||||
""" Return highest priority of two states"""
|
||||
hierarchy = {'unknown': -1,
|
||||
'active': 0,
|
||||
'maintenance': 1,
|
||||
'waiting': 2,
|
||||
'blocked': 3,
|
||||
}
|
||||
|
||||
if hierarchy.get(workload_state) is None:
|
||||
workload_state = 'unknown'
|
||||
if hierarchy.get(current_workload_state) is None:
|
||||
current_workload_state = 'unknown'
|
||||
|
||||
# Set workload_state based on hierarchy of statuses
|
||||
if hierarchy.get(current_workload_state) > hierarchy.get(workload_state):
|
||||
return current_workload_state
|
||||
else:
|
||||
return workload_state
|
||||
|
||||
|
||||
def incomplete_relation_data(configs, required_interfaces):
|
||||
"""
|
||||
Check complete contexts against required_interfaces
|
||||
Return dictionary of incomplete relation data.
|
||||
|
||||
configs is an OSConfigRenderer object with configs registered
|
||||
|
||||
required_interfaces is a dictionary of required general interfaces
|
||||
with dictionary values of possible specific interfaces.
|
||||
Example:
|
||||
required_interfaces = {'database': ['shared-db', 'pgsql-db']}
|
||||
|
||||
The interface is said to be satisfied if anyone of the interfaces in the
|
||||
list has a complete context.
|
||||
|
||||
Return dictionary of incomplete or missing required contexts with relation
|
||||
status of interfaces and any missing data points. Example:
|
||||
{'message':
|
||||
{'amqp': {'missing_data': ['rabbitmq_password'], 'related': True},
|
||||
'zeromq-configuration': {'related': False}},
|
||||
'identity':
|
||||
{'identity-service': {'related': False}},
|
||||
'database':
|
||||
{'pgsql-db': {'related': False},
|
||||
'shared-db': {'related': True}}}
|
||||
"""
|
||||
complete_ctxts = configs.complete_contexts()
|
||||
incomplete_relations = []
|
||||
for svc_type in required_interfaces.keys():
|
||||
# Avoid duplicates
|
||||
found_ctxt = False
|
||||
for interface in required_interfaces[svc_type]:
|
||||
if interface in complete_ctxts:
|
||||
found_ctxt = True
|
||||
if not found_ctxt:
|
||||
incomplete_relations.append(svc_type)
|
||||
incomplete_context_data = {}
|
||||
for i in incomplete_relations:
|
||||
incomplete_context_data[i] = configs.get_incomplete_context_data(required_interfaces[i])
|
||||
return incomplete_context_data
|
||||
|
||||
|
||||
def do_action_openstack_upgrade(package, upgrade_callback, configs):
|
||||
"""Perform action-managed OpenStack upgrade.
|
||||
|
||||
|
@ -623,6 +623,38 @@ def unit_private_ip():
|
||||
return unit_get('private-address')
|
||||
|
||||
|
||||
@cached
|
||||
def storage_get(attribute="", storage_id=""):
|
||||
"""Get storage attributes"""
|
||||
_args = ['storage-get', '--format=json']
|
||||
if storage_id:
|
||||
_args.extend(('-s', storage_id))
|
||||
if attribute:
|
||||
_args.append(attribute)
|
||||
try:
|
||||
return json.loads(subprocess.check_output(_args).decode('UTF-8'))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
@cached
|
||||
def storage_list(storage_name=""):
|
||||
"""List the storage IDs for the unit"""
|
||||
_args = ['storage-list', '--format=json']
|
||||
if storage_name:
|
||||
_args.append(storage_name)
|
||||
try:
|
||||
return json.loads(subprocess.check_output(_args).decode('UTF-8'))
|
||||
except ValueError:
|
||||
return None
|
||||
except OSError as e:
|
||||
import errno
|
||||
if e.errno == errno.ENOENT:
|
||||
# storage-list does not exist
|
||||
return []
|
||||
raise
|
||||
|
||||
|
||||
class UnregisteredHookError(Exception):
|
||||
"""Raised when an undefined hook is called"""
|
||||
pass
|
||||
|
@ -25,11 +25,13 @@ from charmhelpers.core.host import (
|
||||
fstab_mount,
|
||||
mkdir,
|
||||
)
|
||||
from charmhelpers.core.strutils import bytes_from_string
|
||||
from subprocess import check_output
|
||||
|
||||
|
||||
def hugepage_support(user, group='hugetlb', nr_hugepages=256,
|
||||
max_map_count=65536, mnt_point='/run/hugepages/kvm',
|
||||
pagesize='2MB', mount=True):
|
||||
pagesize='2MB', mount=True, set_shmmax=False):
|
||||
"""Enable hugepages on system.
|
||||
|
||||
Args:
|
||||
@ -49,6 +51,11 @@ def hugepage_support(user, group='hugetlb', nr_hugepages=256,
|
||||
'vm.max_map_count': max_map_count,
|
||||
'vm.hugetlb_shm_group': gid,
|
||||
}
|
||||
if set_shmmax:
|
||||
shmmax_current = int(check_output(['sysctl', '-n', 'kernel.shmmax']))
|
||||
shmmax_minsize = bytes_from_string(pagesize) * nr_hugepages
|
||||
if shmmax_minsize > shmmax_current:
|
||||
sysctl_settings['kernel.shmmax'] = shmmax_minsize
|
||||
sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf')
|
||||
mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False)
|
||||
lfstab = fstab.Fstab()
|
||||
|
@ -18,6 +18,7 @@
|
||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import six
|
||||
import re
|
||||
|
||||
|
||||
def bool_from_string(value):
|
||||
@ -40,3 +41,32 @@ def bool_from_string(value):
|
||||
|
||||
msg = "Unable to interpret string value '%s' as boolean" % (value)
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def bytes_from_string(value):
|
||||
"""Interpret human readable string value as bytes.
|
||||
|
||||
Returns int
|
||||
"""
|
||||
BYTE_POWER = {
|
||||
'K': 1,
|
||||
'KB': 1,
|
||||
'M': 2,
|
||||
'MB': 2,
|
||||
'G': 3,
|
||||
'GB': 3,
|
||||
'T': 4,
|
||||
'TB': 4,
|
||||
'P': 5,
|
||||
'PB': 5,
|
||||
}
|
||||
if isinstance(value, six.string_types):
|
||||
value = six.text_type(value)
|
||||
else:
|
||||
msg = "Unable to interpret non-string value '%s' as boolean" % (value)
|
||||
raise ValueError(msg)
|
||||
matches = re.match("([0-9]+)([a-zA-Z]+)", value)
|
||||
if not matches:
|
||||
msg = "Unable to interpret string value '%s' as bytes" % (value)
|
||||
raise ValueError(msg)
|
||||
return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)])
|
||||
|
@ -51,7 +51,8 @@ class AmuletDeployment(object):
|
||||
if 'units' not in this_service:
|
||||
this_service['units'] = 1
|
||||
|
||||
self.d.add(this_service['name'], units=this_service['units'])
|
||||
self.d.add(this_service['name'], units=this_service['units'],
|
||||
constraints=this_service.get('constraints'))
|
||||
|
||||
for svc in other_services:
|
||||
if 'location' in svc:
|
||||
@ -64,7 +65,8 @@ class AmuletDeployment(object):
|
||||
if 'units' not in svc:
|
||||
svc['units'] = 1
|
||||
|
||||
self.d.add(svc['name'], charm=branch_location, units=svc['units'])
|
||||
self.d.add(svc['name'], charm=branch_location, units=svc['units'],
|
||||
constraints=svc.get('constraints'))
|
||||
|
||||
def _add_relations(self, relations):
|
||||
"""Add all of the relations for the services."""
|
||||
|
@ -326,7 +326,7 @@ class AmuletUtils(object):
|
||||
|
||||
def service_restarted_since(self, sentry_unit, mtime, service,
|
||||
pgrep_full=None, sleep_time=20,
|
||||
retry_count=2, retry_sleep_time=30):
|
||||
retry_count=30, retry_sleep_time=10):
|
||||
"""Check if service was been started after a given time.
|
||||
|
||||
Args:
|
||||
@ -334,8 +334,9 @@ class AmuletUtils(object):
|
||||
mtime (float): The epoch time to check against
|
||||
service (string): service name to look for in process table
|
||||
pgrep_full: [Deprecated] Use full command line search mode with pgrep
|
||||
sleep_time (int): Seconds to sleep before looking for process
|
||||
retry_count (int): If service is not found, how many times to retry
|
||||
sleep_time (int): Initial sleep time (s) before looking for file
|
||||
retry_sleep_time (int): Time (s) to sleep between retries
|
||||
retry_count (int): If file is not found, how many times to retry
|
||||
|
||||
Returns:
|
||||
bool: True if service found and its start time it newer than mtime,
|
||||
@ -359,11 +360,12 @@ class AmuletUtils(object):
|
||||
pgrep_full)
|
||||
self.log.debug('Attempt {} to get {} proc start time on {} '
|
||||
'OK'.format(tries, service, unit_name))
|
||||
except IOError:
|
||||
except IOError as e:
|
||||
# NOTE(beisner) - race avoidance, proc may not exist yet.
|
||||
# https://bugs.launchpad.net/charm-helpers/+bug/1474030
|
||||
self.log.debug('Attempt {} to get {} proc start time on {} '
|
||||
'failed'.format(tries, service, unit_name))
|
||||
'failed\n{}'.format(tries, service,
|
||||
unit_name, e))
|
||||
time.sleep(retry_sleep_time)
|
||||
tries += 1
|
||||
|
||||
@ -383,35 +385,62 @@ class AmuletUtils(object):
|
||||
return False
|
||||
|
||||
def config_updated_since(self, sentry_unit, filename, mtime,
|
||||
sleep_time=20):
|
||||
sleep_time=20, retry_count=30,
|
||||
retry_sleep_time=10):
|
||||
"""Check if file was modified after a given time.
|
||||
|
||||
Args:
|
||||
sentry_unit (sentry): The sentry unit to check the file mtime on
|
||||
filename (string): The file to check mtime of
|
||||
mtime (float): The epoch time to check against
|
||||
sleep_time (int): Seconds to sleep before looking for process
|
||||
sleep_time (int): Initial sleep time (s) before looking for file
|
||||
retry_sleep_time (int): Time (s) to sleep between retries
|
||||
retry_count (int): If file is not found, how many times to retry
|
||||
|
||||
Returns:
|
||||
bool: True if file was modified more recently than mtime, False if
|
||||
file was modified before mtime,
|
||||
file was modified before mtime, or if file not found.
|
||||
"""
|
||||
self.log.debug('Checking %s updated since %s' % (filename, mtime))
|
||||
unit_name = sentry_unit.info['unit_name']
|
||||
self.log.debug('Checking that %s updated since %s on '
|
||||
'%s' % (filename, mtime, unit_name))
|
||||
time.sleep(sleep_time)
|
||||
file_mtime = self._get_file_mtime(sentry_unit, filename)
|
||||
file_mtime = None
|
||||
tries = 0
|
||||
while tries <= retry_count and not file_mtime:
|
||||
try:
|
||||
file_mtime = self._get_file_mtime(sentry_unit, filename)
|
||||
self.log.debug('Attempt {} to get {} file mtime on {} '
|
||||
'OK'.format(tries, filename, unit_name))
|
||||
except IOError as e:
|
||||
# NOTE(beisner) - race avoidance, file may not exist yet.
|
||||
# https://bugs.launchpad.net/charm-helpers/+bug/1474030
|
||||
self.log.debug('Attempt {} to get {} file mtime on {} '
|
||||
'failed\n{}'.format(tries, filename,
|
||||
unit_name, e))
|
||||
time.sleep(retry_sleep_time)
|
||||
tries += 1
|
||||
|
||||
if not file_mtime:
|
||||
self.log.warn('Could not determine file mtime, assuming '
|
||||
'file does not exist')
|
||||
return False
|
||||
|
||||
if file_mtime >= mtime:
|
||||
self.log.debug('File mtime is newer than provided mtime '
|
||||
'(%s >= %s)' % (file_mtime, mtime))
|
||||
'(%s >= %s) on %s (OK)' % (file_mtime,
|
||||
mtime, unit_name))
|
||||
return True
|
||||
else:
|
||||
self.log.warn('File mtime %s is older than provided mtime %s'
|
||||
% (file_mtime, mtime))
|
||||
self.log.warn('File mtime is older than provided mtime'
|
||||
'(%s < on %s) on %s' % (file_mtime,
|
||||
mtime, unit_name))
|
||||
return False
|
||||
|
||||
def validate_service_config_changed(self, sentry_unit, mtime, service,
|
||||
filename, pgrep_full=None,
|
||||
sleep_time=20, retry_count=2,
|
||||
retry_sleep_time=30):
|
||||
sleep_time=20, retry_count=30,
|
||||
retry_sleep_time=10):
|
||||
"""Check service and file were updated after mtime
|
||||
|
||||
Args:
|
||||
@ -456,7 +485,9 @@ class AmuletUtils(object):
|
||||
sentry_unit,
|
||||
filename,
|
||||
mtime,
|
||||
sleep_time=0)
|
||||
sleep_time=sleep_time,
|
||||
retry_count=retry_count,
|
||||
retry_sleep_time=retry_sleep_time)
|
||||
|
||||
return service_restart and config_update
|
||||
|
||||
|
@ -58,19 +58,17 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
||||
else:
|
||||
base_series = self.current_next
|
||||
|
||||
if self.stable:
|
||||
for svc in other_services:
|
||||
if svc['name'] in force_series_current:
|
||||
base_series = self.current_next
|
||||
|
||||
for svc in other_services:
|
||||
if svc['name'] in force_series_current:
|
||||
base_series = self.current_next
|
||||
# If a location has been explicitly set, use it
|
||||
if svc.get('location'):
|
||||
continue
|
||||
if self.stable:
|
||||
temp = 'lp:charms/{}/{}'
|
||||
svc['location'] = temp.format(base_series,
|
||||
svc['name'])
|
||||
else:
|
||||
for svc in other_services:
|
||||
if svc['name'] in force_series_current:
|
||||
base_series = self.current_next
|
||||
|
||||
else:
|
||||
if svc['name'] in base_charms:
|
||||
temp = 'lp:charms/{}/{}'
|
||||
svc['location'] = temp.format(base_series,
|
||||
@ -79,6 +77,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
||||
temp = 'lp:~openstack-charmers/charms/{}/{}/next'
|
||||
svc['location'] = temp.format(self.current_next,
|
||||
svc['name'])
|
||||
|
||||
return other_services
|
||||
|
||||
def _add_services(self, this_service, other_services):
|
||||
|
@ -752,7 +752,7 @@ class OpenStackAmuletUtils(AmuletUtils):
|
||||
self.log.debug('SSL is enabled @{}:{} '
|
||||
'({})'.format(host, port, unit_name))
|
||||
return True
|
||||
elif not port and not conf_ssl:
|
||||
elif not conf_ssl:
|
||||
self.log.debug('SSL not enabled @{}:{} '
|
||||
'({})'.format(host, port, unit_name))
|
||||
return False
|
||||
|
Loading…
Reference in New Issue
Block a user