Sync charm-helpers

This commit is contained in:
Corey Bryant 2015-10-07 20:48:04 +00:00
parent 0158686165
commit c0859c70fc
15 changed files with 462 additions and 62 deletions

View File

@ -23,7 +23,7 @@ import socket
from functools import partial from functools import partial
from charmhelpers.core.hookenv import unit_get 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 ( from charmhelpers.core.hookenv import (
log, log,
WARNING, WARNING,
@ -32,13 +32,15 @@ from charmhelpers.core.hookenv import (
try: try:
import netifaces import netifaces
except ImportError: except ImportError:
apt_install('python-netifaces') apt_update(fatal=True)
apt_install('python-netifaces', fatal=True)
import netifaces import netifaces
try: try:
import netaddr import netaddr
except ImportError: except ImportError:
apt_install('python-netaddr') apt_update(fatal=True)
apt_install('python-netaddr', fatal=True)
import netaddr import netaddr

View File

@ -58,19 +58,17 @@ class OpenStackAmuletDeployment(AmuletDeployment):
else: else:
base_series = self.current_next base_series = self.current_next
if self.stable:
for svc in other_services: for svc in other_services:
if svc['name'] in force_series_current: if svc['name'] in force_series_current:
base_series = self.current_next 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/{}/{}' temp = 'lp:charms/{}/{}'
svc['location'] = temp.format(base_series, svc['location'] = temp.format(base_series,
svc['name']) svc['name'])
else: else:
for svc in other_services:
if svc['name'] in force_series_current:
base_series = self.current_next
if svc['name'] in base_charms: if svc['name'] in base_charms:
temp = 'lp:charms/{}/{}' temp = 'lp:charms/{}/{}'
svc['location'] = temp.format(base_series, svc['location'] = temp.format(base_series,
@ -79,6 +77,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
temp = 'lp:~openstack-charmers/charms/{}/{}/next' temp = 'lp:~openstack-charmers/charms/{}/{}/next'
svc['location'] = temp.format(self.current_next, svc['location'] = temp.format(self.current_next,
svc['name']) svc['name'])
return other_services return other_services
def _add_services(self, this_service, other_services): def _add_services(self, this_service, other_services):

View File

@ -752,7 +752,7 @@ class OpenStackAmuletUtils(AmuletUtils):
self.log.debug('SSL is enabled @{}:{} ' self.log.debug('SSL is enabled @{}:{} '
'({})'.format(host, port, unit_name)) '({})'.format(host, port, unit_name))
return True return True
elif not port and not conf_ssl: elif not conf_ssl:
self.log.debug('SSL not enabled @{}:{} ' self.log.debug('SSL not enabled @{}:{} '
'({})'.format(host, port, unit_name)) '({})'.format(host, port, unit_name))
return False return False

View File

@ -14,6 +14,7 @@
# You should have received a copy of the GNU Lesser General Public License # 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/>. # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
import glob
import json import json
import os import os
import re import re
@ -194,10 +195,50 @@ def config_flags_parser(config_flags):
class OSContextGenerator(object): class OSContextGenerator(object):
"""Base class for all context generators.""" """Base class for all context generators."""
interfaces = [] interfaces = []
related = False
complete = False
missing_data = []
def __call__(self): def __call__(self):
raise NotImplementedError 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): class SharedDBContext(OSContextGenerator):
interfaces = ['shared-db'] interfaces = ['shared-db']
@ -213,6 +254,7 @@ class SharedDBContext(OSContextGenerator):
self.database = database self.database = database
self.user = user self.user = user
self.ssl_dir = ssl_dir self.ssl_dir = ssl_dir
self.rel_name = self.interfaces[0]
def __call__(self): def __call__(self):
self.database = self.database or config('database') self.database = self.database or config('database')
@ -246,6 +288,7 @@ class SharedDBContext(OSContextGenerator):
password_setting = self.relation_prefix + '_password' password_setting = self.relation_prefix + '_password'
for rid in relation_ids(self.interfaces[0]): for rid in relation_ids(self.interfaces[0]):
self.related = True
for unit in related_units(rid): for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit) rdata = relation_get(rid=rid, unit=unit)
host = rdata.get('db_host') host = rdata.get('db_host')
@ -257,7 +300,7 @@ class SharedDBContext(OSContextGenerator):
'database_password': rdata.get(password_setting), 'database_password': rdata.get(password_setting),
'database_type': 'mysql' 'database_type': 'mysql'
} }
if context_complete(ctxt): if self.context_complete(ctxt):
db_ssl(rdata, ctxt, self.ssl_dir) db_ssl(rdata, ctxt, self.ssl_dir)
return ctxt return ctxt
return {} return {}
@ -278,6 +321,7 @@ class PostgresqlDBContext(OSContextGenerator):
ctxt = {} ctxt = {}
for rid in relation_ids(self.interfaces[0]): for rid in relation_ids(self.interfaces[0]):
self.related = True
for unit in related_units(rid): for unit in related_units(rid):
rel_host = relation_get('host', rid=rid, unit=unit) rel_host = relation_get('host', rid=rid, unit=unit)
rel_user = relation_get('user', 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_user': rel_user,
'database_password': rel_passwd, 'database_password': rel_passwd,
'database_type': 'postgresql'} 'database_type': 'postgresql'}
if context_complete(ctxt): if self.context_complete(ctxt):
return ctxt return ctxt
return {} return {}
@ -348,6 +392,7 @@ class IdentityServiceContext(OSContextGenerator):
ctxt['signing_dir'] = cachedir ctxt['signing_dir'] = cachedir
for rid in relation_ids(self.rel_name): for rid in relation_ids(self.rel_name):
self.related = True
for unit in related_units(rid): for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit) rdata = relation_get(rid=rid, unit=unit)
serv_host = rdata.get('service_host') serv_host = rdata.get('service_host')
@ -366,7 +411,7 @@ class IdentityServiceContext(OSContextGenerator):
'service_protocol': svc_protocol, 'service_protocol': svc_protocol,
'auth_protocol': auth_protocol}) 'auth_protocol': auth_protocol})
if context_complete(ctxt): if self.context_complete(ctxt):
# NOTE(jamespage) this is required for >= icehouse # NOTE(jamespage) this is required for >= icehouse
# so a missing value just indicates keystone needs # so a missing value just indicates keystone needs
# upgrading # upgrading
@ -405,6 +450,7 @@ class AMQPContext(OSContextGenerator):
ctxt = {} ctxt = {}
for rid in relation_ids(self.rel_name): for rid in relation_ids(self.rel_name):
ha_vip_only = False ha_vip_only = False
self.related = True
for unit in related_units(rid): for unit in related_units(rid):
if relation_get('clustered', rid=rid, unit=unit): if relation_get('clustered', rid=rid, unit=unit):
ctxt['clustered'] = True ctxt['clustered'] = True
@ -437,7 +483,7 @@ class AMQPContext(OSContextGenerator):
ha_vip_only = relation_get('ha-vip-only', ha_vip_only = relation_get('ha-vip-only',
rid=rid, unit=unit) is not None rid=rid, unit=unit) is not None
if context_complete(ctxt): if self.context_complete(ctxt):
if 'rabbit_ssl_ca' in ctxt: if 'rabbit_ssl_ca' in ctxt:
if not self.ssl_dir: if not self.ssl_dir:
log("Charm not setup for ssl support but ssl ca " log("Charm not setup for ssl support but ssl ca "
@ -469,7 +515,7 @@ class AMQPContext(OSContextGenerator):
ctxt['oslo_messaging_flags'] = config_flags_parser( ctxt['oslo_messaging_flags'] = config_flags_parser(
oslo_messaging_flags) oslo_messaging_flags)
if not context_complete(ctxt): if not self.complete:
return {} return {}
return ctxt return ctxt
@ -507,7 +553,7 @@ class CephContext(OSContextGenerator):
if not os.path.isdir('/etc/ceph'): if not os.path.isdir('/etc/ceph'):
os.mkdir('/etc/ceph') os.mkdir('/etc/ceph')
if not context_complete(ctxt): if not self.context_complete(ctxt):
return {} return {}
ensure_packages(['ceph-common']) ensure_packages(['ceph-common'])
@ -906,6 +952,19 @@ class NeutronContext(OSContextGenerator):
'config': config} 'config': config}
return ovs_ctxt 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): def __call__(self):
if self.network_manager not in ['quantum', 'neutron']: if self.network_manager not in ['quantum', 'neutron']:
return {} return {}
@ -927,6 +986,8 @@ class NeutronContext(OSContextGenerator):
ctxt.update(self.nuage_ctxt()) ctxt.update(self.nuage_ctxt())
elif self.plugin == 'plumgrid': elif self.plugin == 'plumgrid':
ctxt.update(self.pg_ctxt()) ctxt.update(self.pg_ctxt())
elif self.plugin == 'midonet':
ctxt.update(self.midonet_ctxt())
alchemy_flags = config('neutron-alchemy-flags') alchemy_flags = config('neutron-alchemy-flags')
if alchemy_flags: if alchemy_flags:
@ -1318,7 +1379,7 @@ class DataPortContext(NeutronPortContext):
normalized.update({port: port for port in resolved normalized.update({port: port for port in resolved
if port in ports}) if port in ports})
if resolved: 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()} six.iteritems(portmap) if port in normalized.keys()}
return None return None
@ -1329,12 +1390,22 @@ class PhyNICMTUContext(DataPortContext):
def __call__(self): def __call__(self):
ctxt = {} ctxt = {}
mappings = super(PhyNICMTUContext, self).__call__() mappings = super(PhyNICMTUContext, self).__call__()
if mappings and mappings.values(): if mappings and mappings.keys():
ports = mappings.values() ports = sorted(mappings.keys())
napi_settings = NeutronAPIContext()() napi_settings = NeutronAPIContext()()
mtu = napi_settings.get('network_device_mtu') 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: if mtu:
ctxt["devs"] = '\\n'.join(ports) ctxt["devs"] = '\\n'.join(all_ports)
ctxt['mtu'] = mtu ctxt['mtu'] = mtu
return ctxt return ctxt
@ -1366,6 +1437,6 @@ class NetworkServiceContext(OSContextGenerator):
'auth_protocol': 'auth_protocol':
rdata.get('auth_protocol') or 'http', rdata.get('auth_protocol') or 'http',
} }
if context_complete(ctxt): if self.context_complete(ctxt):
return ctxt return ctxt
return {} return {}

View File

@ -209,6 +209,20 @@ def neutron_plugins():
'server_packages': ['neutron-server', 'server_packages': ['neutron-server',
'neutron-plugin-plumgrid'], 'neutron-plugin-plumgrid'],
'server_services': ['neutron-server'] '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': if release >= 'icehouse':
@ -310,10 +324,10 @@ def parse_bridge_mappings(mappings):
def parse_data_port_mappings(mappings, default_bridge='br-data'): def parse_data_port_mappings(mappings, default_bridge='br-data'):
"""Parse data port mappings. """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 Returns dict of the form {port:bridge} where ports may be mac addresses or
interface name. interface names.
""" """
# NOTE(dosaboy): we use rvalue for key to allow multiple values to be # NOTE(dosaboy): we use rvalue for key to allow multiple values to be

View File

@ -13,3 +13,9 @@ log to syslog = {{ use_syslog }}
err to syslog = {{ use_syslog }} err to syslog = {{ use_syslog }}
clog 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 %}

View File

@ -18,7 +18,7 @@ import os
import six import six
from charmhelpers.fetch import apt_install from charmhelpers.fetch import apt_install, apt_update
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
log, log,
ERROR, ERROR,
@ -29,6 +29,7 @@ from charmhelpers.contrib.openstack.utils import OPENSTACK_CODENAMES
try: try:
from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
except ImportError: except ImportError:
apt_update(fatal=True)
apt_install('python-jinja2', fatal=True) apt_install('python-jinja2', fatal=True)
from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
@ -112,7 +113,7 @@ class OSConfigTemplate(object):
def complete_contexts(self): 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: if self._complete_contexts:
return self._complete_contexts return self._complete_contexts
@ -293,3 +294,30 @@ class OSConfigRenderer(object):
[interfaces.extend(i.complete_contexts()) [interfaces.extend(i.complete_contexts())
for i in six.itervalues(self.templates)] for i in six.itervalues(self.templates)]
return interfaces 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

View File

@ -42,7 +42,9 @@ from charmhelpers.core.hookenv import (
charm_dir, charm_dir,
INFO, INFO,
relation_ids, relation_ids,
relation_set relation_set,
status_set,
hook_name
) )
from charmhelpers.contrib.storage.linux.lvm import ( from charmhelpers.contrib.storage.linux.lvm import (
@ -52,7 +54,8 @@ from charmhelpers.contrib.storage.linux.lvm import (
) )
from charmhelpers.contrib.network.ip import ( from charmhelpers.contrib.network.ip import (
get_ipv6_addr get_ipv6_addr,
is_ipv6,
) )
from charmhelpers.contrib.python.packages import ( from charmhelpers.contrib.python.packages import (
@ -517,6 +520,12 @@ def sync_db_with_multi_ipv6_addresses(database, database_user,
relation_prefix=None): relation_prefix=None):
hosts = get_ipv6_addr(dynamic_only=False) 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, kwargs = {'database': database,
'username': database_user, 'username': database_user,
'hostname': json.dumps(hosts)} 'hostname': json.dumps(hosts)}
@ -754,6 +763,176 @@ def git_yaml_value(projects_yaml, key):
return None 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): def do_action_openstack_upgrade(package, upgrade_callback, configs):
"""Perform action-managed OpenStack upgrade. """Perform action-managed OpenStack upgrade.

View File

@ -623,6 +623,38 @@ def unit_private_ip():
return unit_get('private-address') 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): class UnregisteredHookError(Exception):
"""Raised when an undefined hook is called""" """Raised when an undefined hook is called"""
pass pass

View File

@ -25,11 +25,13 @@ from charmhelpers.core.host import (
fstab_mount, fstab_mount,
mkdir, mkdir,
) )
from charmhelpers.core.strutils import bytes_from_string
from subprocess import check_output
def hugepage_support(user, group='hugetlb', nr_hugepages=256, def hugepage_support(user, group='hugetlb', nr_hugepages=256,
max_map_count=65536, mnt_point='/run/hugepages/kvm', max_map_count=65536, mnt_point='/run/hugepages/kvm',
pagesize='2MB', mount=True): pagesize='2MB', mount=True, set_shmmax=False):
"""Enable hugepages on system. """Enable hugepages on system.
Args: Args:
@ -49,6 +51,11 @@ def hugepage_support(user, group='hugetlb', nr_hugepages=256,
'vm.max_map_count': max_map_count, 'vm.max_map_count': max_map_count,
'vm.hugetlb_shm_group': gid, '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') sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf')
mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False) mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False)
lfstab = fstab.Fstab() lfstab = fstab.Fstab()

View File

@ -18,6 +18,7 @@
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
import six import six
import re
def bool_from_string(value): def bool_from_string(value):
@ -40,3 +41,32 @@ def bool_from_string(value):
msg = "Unable to interpret string value '%s' as boolean" % (value) msg = "Unable to interpret string value '%s' as boolean" % (value)
raise ValueError(msg) 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)])

View File

@ -51,7 +51,8 @@ class AmuletDeployment(object):
if 'units' not in this_service: if 'units' not in this_service:
this_service['units'] = 1 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: for svc in other_services:
if 'location' in svc: if 'location' in svc:
@ -64,7 +65,8 @@ class AmuletDeployment(object):
if 'units' not in svc: if 'units' not in svc:
svc['units'] = 1 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): def _add_relations(self, relations):
"""Add all of the relations for the services.""" """Add all of the relations for the services."""

View File

@ -326,7 +326,7 @@ class AmuletUtils(object):
def service_restarted_since(self, sentry_unit, mtime, service, def service_restarted_since(self, sentry_unit, mtime, service,
pgrep_full=None, sleep_time=20, 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. """Check if service was been started after a given time.
Args: Args:
@ -334,8 +334,9 @@ class AmuletUtils(object):
mtime (float): The epoch time to check against mtime (float): The epoch time to check against
service (string): service name to look for in process table service (string): service name to look for in process table
pgrep_full: [Deprecated] Use full command line search mode with pgrep pgrep_full: [Deprecated] Use full command line search mode with pgrep
sleep_time (int): Seconds to sleep before looking for process sleep_time (int): Initial sleep time (s) before looking for file
retry_count (int): If service is not found, how many times to retry retry_sleep_time (int): Time (s) to sleep between retries
retry_count (int): If file is not found, how many times to retry
Returns: Returns:
bool: True if service found and its start time it newer than mtime, bool: True if service found and its start time it newer than mtime,
@ -359,11 +360,12 @@ class AmuletUtils(object):
pgrep_full) pgrep_full)
self.log.debug('Attempt {} to get {} proc start time on {} ' self.log.debug('Attempt {} to get {} proc start time on {} '
'OK'.format(tries, service, unit_name)) 'OK'.format(tries, service, unit_name))
except IOError: except IOError as e:
# NOTE(beisner) - race avoidance, proc may not exist yet. # NOTE(beisner) - race avoidance, proc may not exist yet.
# https://bugs.launchpad.net/charm-helpers/+bug/1474030 # https://bugs.launchpad.net/charm-helpers/+bug/1474030
self.log.debug('Attempt {} to get {} proc start time on {} ' 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) time.sleep(retry_sleep_time)
tries += 1 tries += 1
@ -383,35 +385,62 @@ class AmuletUtils(object):
return False return False
def config_updated_since(self, sentry_unit, filename, mtime, 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. """Check if file was modified after a given time.
Args: Args:
sentry_unit (sentry): The sentry unit to check the file mtime on sentry_unit (sentry): The sentry unit to check the file mtime on
filename (string): The file to check mtime of filename (string): The file to check mtime of
mtime (float): The epoch time to check against 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: Returns:
bool: True if file was modified more recently than mtime, False if 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) time.sleep(sleep_time)
file_mtime = None
tries = 0
while tries <= retry_count and not file_mtime:
try:
file_mtime = self._get_file_mtime(sentry_unit, filename) 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: if file_mtime >= mtime:
self.log.debug('File mtime is newer than provided 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 return True
else: else:
self.log.warn('File mtime %s is older than provided mtime %s' self.log.warn('File mtime is older than provided mtime'
% (file_mtime, mtime)) '(%s < on %s) on %s' % (file_mtime,
mtime, unit_name))
return False return False
def validate_service_config_changed(self, sentry_unit, mtime, service, def validate_service_config_changed(self, sentry_unit, mtime, service,
filename, pgrep_full=None, filename, pgrep_full=None,
sleep_time=20, retry_count=2, sleep_time=20, retry_count=30,
retry_sleep_time=30): retry_sleep_time=10):
"""Check service and file were updated after mtime """Check service and file were updated after mtime
Args: Args:
@ -456,7 +485,9 @@ class AmuletUtils(object):
sentry_unit, sentry_unit,
filename, filename,
mtime, mtime,
sleep_time=0) sleep_time=sleep_time,
retry_count=retry_count,
retry_sleep_time=retry_sleep_time)
return service_restart and config_update return service_restart and config_update

View File

@ -58,19 +58,17 @@ class OpenStackAmuletDeployment(AmuletDeployment):
else: else:
base_series = self.current_next base_series = self.current_next
if self.stable:
for svc in other_services: for svc in other_services:
if svc['name'] in force_series_current: if svc['name'] in force_series_current:
base_series = self.current_next 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/{}/{}' temp = 'lp:charms/{}/{}'
svc['location'] = temp.format(base_series, svc['location'] = temp.format(base_series,
svc['name']) svc['name'])
else: else:
for svc in other_services:
if svc['name'] in force_series_current:
base_series = self.current_next
if svc['name'] in base_charms: if svc['name'] in base_charms:
temp = 'lp:charms/{}/{}' temp = 'lp:charms/{}/{}'
svc['location'] = temp.format(base_series, svc['location'] = temp.format(base_series,
@ -79,6 +77,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
temp = 'lp:~openstack-charmers/charms/{}/{}/next' temp = 'lp:~openstack-charmers/charms/{}/{}/next'
svc['location'] = temp.format(self.current_next, svc['location'] = temp.format(self.current_next,
svc['name']) svc['name'])
return other_services return other_services
def _add_services(self, this_service, other_services): def _add_services(self, this_service, other_services):

View File

@ -752,7 +752,7 @@ class OpenStackAmuletUtils(AmuletUtils):
self.log.debug('SSL is enabled @{}:{} ' self.log.debug('SSL is enabled @{}:{} '
'({})'.format(host, port, unit_name)) '({})'.format(host, port, unit_name))
return True return True
elif not port and not conf_ssl: elif not conf_ssl:
self.log.debug('SSL not enabled @{}:{} ' self.log.debug('SSL not enabled @{}:{} '
'({})'.format(host, port, unit_name)) '({})'.format(host, port, unit_name))
return False return False