Enable Zesty-Ocata Amulet Tests

- Turn on Zesty-Ocata Amulet test definitions.
- Standardize test-requirements.txt
- Sync charm helpers for various fixes

Change-Id: Ia9a42cd4a76a6fa8a70e7092b9d61657816d2d65
This commit is contained in:
David Ames 2017-04-27 11:09:28 -07:00
parent 2029cdf6d0
commit a4ef73475d
12 changed files with 147 additions and 97 deletions

View File

@ -373,7 +373,7 @@ def add_init_service_checks(nrpe, services, unit_name, immediate_check=True):
checkpath = '%s/service-check-%s.txt' % (nrpe.homedir, svc) checkpath = '%s/service-check-%s.txt' % (nrpe.homedir, svc)
croncmd = ( croncmd = (
'/usr/local/lib/nagios/plugins/check_exit_status.pl ' '/usr/local/lib/nagios/plugins/check_exit_status.pl '
'-s /etc/init.d/%s status' % svc '-e -s /etc/init.d/%s status' % svc
) )
cron_file = '*/5 * * * * root %s > %s\n' % (croncmd, checkpath) cron_file = '*/5 * * * * root %s > %s\n' % (croncmd, checkpath)
f = open(cronpath, 'w') f = open(cronpath, 'w')

View File

@ -111,11 +111,11 @@ def get_address_in_network(network, fallback=None, fatal=False):
for iface in netifaces.interfaces(): for iface in netifaces.interfaces():
addresses = netifaces.ifaddresses(iface) addresses = netifaces.ifaddresses(iface)
if network.version == 4 and netifaces.AF_INET in addresses: if network.version == 4 and netifaces.AF_INET in addresses:
addr = addresses[netifaces.AF_INET][0]['addr'] for addr in addresses[netifaces.AF_INET]:
netmask = addresses[netifaces.AF_INET][0]['netmask'] cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],
cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask)) addr['netmask']))
if cidr in network: if cidr in network:
return str(cidr.ip) return str(cidr.ip)
if network.version == 6 and netifaces.AF_INET6 in addresses: if network.version == 6 and netifaces.AF_INET6 in addresses:
for addr in addresses[netifaces.AF_INET6]: for addr in addresses[netifaces.AF_INET6]:
@ -239,6 +239,16 @@ def format_ipv6_addr(address):
return None return None
def is_ipv6_disabled():
try:
result = subprocess.check_output(
['sysctl', 'net.ipv6.conf.all.disable_ipv6'],
stderr=subprocess.STDOUT)
return "net.ipv6.conf.all.disable_ipv6 = 1" in result
except subprocess.CalledProcessError:
return True
def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False, def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False,
fatal=True, exc_list=None): fatal=True, exc_list=None):
"""Return the assigned IP address for a given interface, if any. """Return the assigned IP address for a given interface, if any.
@ -544,31 +554,38 @@ def assert_charm_supports_ipv6():
"versions less than Trusty 14.04") "versions less than Trusty 14.04")
def get_relation_ip(interface, config_override=None): def get_relation_ip(interface, cidr_network=None):
"""Return this unit's IP for the given relation. """Return this unit's IP for the given interface.
Allow for an arbitrary interface to use with network-get to select an IP. Allow for an arbitrary interface to use with network-get to select an IP.
Handle all address selection options including configuration parameter Handle all address selection options including passed cidr network and
override and IPv6. IPv6.
Usage: get_relation_ip('amqp', config_override='access-network') Usage: get_relation_ip('amqp', cidr_network='10.0.0.0/8')
@param interface: string name of the relation. @param interface: string name of the relation.
@param config_override: string name of the config option for network @param cidr_network: string CIDR Network to select an address from.
override. Supports legacy network override configuration parameters.
@raises Exception if prefer-ipv6 is configured but IPv6 unsupported. @raises Exception if prefer-ipv6 is configured but IPv6 unsupported.
@returns IPv6 or IPv4 address @returns IPv6 or IPv4 address
""" """
# Select the interface address first
# For possible use as a fallback bellow with get_address_in_network
try:
# Get the interface specific IP
address = network_get_primary_address(interface)
except NotImplementedError:
# If network-get is not available
address = get_host_ip(unit_get('private-address'))
fallback = get_host_ip(unit_get('private-address'))
if config('prefer-ipv6'): if config('prefer-ipv6'):
# Currently IPv6 has priority, eventually we want IPv6 to just be
# another network space.
assert_charm_supports_ipv6() assert_charm_supports_ipv6()
return get_ipv6_addr()[0] return get_ipv6_addr()[0]
elif config_override and config(config_override): elif cidr_network:
return get_address_in_network(config(config_override), # If a specific CIDR network is passed get the address from that
fallback) # network.
else: return get_address_in_network(cidr_network, address)
try:
return network_get_primary_address(interface) # Return the interface address
except NotImplementedError: return address
return fallback

View File

@ -547,7 +547,7 @@ class OpenStackAmuletUtils(AmuletUtils):
"""Create the specified instance.""" """Create the specified instance."""
self.log.debug('Creating instance ' self.log.debug('Creating instance '
'({}|{}|{})'.format(instance_name, image_name, flavor)) '({}|{}|{})'.format(instance_name, image_name, flavor))
image = nova.images.find(name=image_name) image = nova.glance.find_image(image_name)
flavor = nova.flavors.find(name=flavor) flavor = nova.flavors.find(name=flavor)
instance = nova.servers.create(name=instance_name, image=image, instance = nova.servers.create(name=instance_name, image=image,
flavor=flavor) flavor=flavor)

View File

@ -60,6 +60,7 @@ from charmhelpers.core.host import (
pwgen, pwgen,
lsb_release, lsb_release,
CompareHostReleases, CompareHostReleases,
is_container,
) )
from charmhelpers.contrib.hahelpers.cluster import ( from charmhelpers.contrib.hahelpers.cluster import (
determine_apache_port, determine_apache_port,
@ -88,6 +89,7 @@ from charmhelpers.contrib.network.ip import (
format_ipv6_addr, format_ipv6_addr,
is_address_in_network, is_address_in_network,
is_bridge_member, is_bridge_member,
is_ipv6_disabled,
) )
from charmhelpers.contrib.openstack.utils import ( from charmhelpers.contrib.openstack.utils import (
config_flags_parser, config_flags_parser,
@ -109,6 +111,7 @@ except ImportError:
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
ADDRESS_TYPES = ['admin', 'internal', 'public'] ADDRESS_TYPES = ['admin', 'internal', 'public']
HAPROXY_RUN_DIR = '/var/run/haproxy/'
def ensure_packages(packages): def ensure_packages(packages):
@ -534,6 +537,8 @@ class HAProxyContext(OSContextGenerator):
"""Provides half a context for the haproxy template, which describes """Provides half a context for the haproxy template, which describes
all peers to be included in the cluster. Each charm needs to include all peers to be included in the cluster. Each charm needs to include
its own context generator that describes the port mapping. its own context generator that describes the port mapping.
:side effect: mkdir is called on HAPROXY_RUN_DIR
""" """
interfaces = ['cluster'] interfaces = ['cluster']
@ -541,6 +546,8 @@ class HAProxyContext(OSContextGenerator):
self.singlenode_mode = singlenode_mode self.singlenode_mode = singlenode_mode
def __call__(self): def __call__(self):
if not os.path.isdir(HAPROXY_RUN_DIR):
mkdir(path=HAPROXY_RUN_DIR)
if not relation_ids('cluster') and not self.singlenode_mode: if not relation_ids('cluster') and not self.singlenode_mode:
return {} return {}
@ -1221,22 +1228,54 @@ class BindHostContext(OSContextGenerator):
return {'bind_host': '0.0.0.0'} return {'bind_host': '0.0.0.0'}
MAX_DEFAULT_WORKERS = 4
DEFAULT_MULTIPLIER = 2
def _calculate_workers():
'''
Determine the number of worker processes based on the CPU
count of the unit containing the application.
Workers will be limited to MAX_DEFAULT_WORKERS in
container environments where no worker-multipler configuration
option been set.
@returns int: number of worker processes to use
'''
multiplier = config('worker-multiplier') or DEFAULT_MULTIPLIER
count = int(_num_cpus() * multiplier)
if multiplier > 0 and count == 0:
count = 1
if config('worker-multiplier') is None and is_container():
# NOTE(jamespage): Limit unconfigured worker-multiplier
# to MAX_DEFAULT_WORKERS to avoid insane
# worker configuration in LXD containers
# on large servers
# Reference: https://pad.lv/1665270
count = min(count, MAX_DEFAULT_WORKERS)
return count
def _num_cpus():
'''
Compatibility wrapper for calculating the number of CPU's
a unit has.
@returns: int: number of CPU cores detected
'''
try:
return psutil.cpu_count()
except AttributeError:
return psutil.NUM_CPUS
class WorkerConfigContext(OSContextGenerator): class WorkerConfigContext(OSContextGenerator):
@property
def num_cpus(self):
# NOTE: use cpu_count if present (16.04 support)
if hasattr(psutil, 'cpu_count'):
return psutil.cpu_count()
else:
return psutil.NUM_CPUS
def __call__(self): def __call__(self):
multiplier = config('worker-multiplier') or 0 ctxt = {"workers": _calculate_workers()}
count = int(self.num_cpus * multiplier)
if multiplier > 0 and count == 0:
count = 1
ctxt = {"workers": count}
return ctxt return ctxt
@ -1244,7 +1283,7 @@ class WSGIWorkerConfigContext(WorkerConfigContext):
def __init__(self, name=None, script=None, admin_script=None, def __init__(self, name=None, script=None, admin_script=None,
public_script=None, process_weight=1.00, public_script=None, process_weight=1.00,
admin_process_weight=0.75, public_process_weight=0.25): admin_process_weight=0.25, public_process_weight=0.75):
self.service_name = name self.service_name = name
self.user = name self.user = name
self.group = name self.group = name
@ -1256,8 +1295,7 @@ class WSGIWorkerConfigContext(WorkerConfigContext):
self.public_process_weight = public_process_weight self.public_process_weight = public_process_weight
def __call__(self): def __call__(self):
multiplier = config('worker-multiplier') or 1 total_processes = _calculate_workers()
total_processes = self.num_cpus * multiplier
ctxt = { ctxt = {
"service_name": self.service_name, "service_name": self.service_name,
"user": self.user, "user": self.user,
@ -1588,7 +1626,7 @@ class MemcacheContext(OSContextGenerator):
"""Memcache context """Memcache context
This context provides options for configuring a local memcache client and This context provides options for configuring a local memcache client and
server server for both IPv4 and IPv6
""" """
def __init__(self, package=None): def __init__(self, package=None):
@ -1606,13 +1644,24 @@ class MemcacheContext(OSContextGenerator):
# Trusty version of memcached does not support ::1 as a listen # Trusty version of memcached does not support ::1 as a listen
# address so use host file entry instead # address so use host file entry instead
release = lsb_release()['DISTRIB_CODENAME'].lower() release = lsb_release()['DISTRIB_CODENAME'].lower()
if CompareHostReleases(release) > 'trusty': if is_ipv6_disabled():
ctxt['memcache_server'] = '::1' if CompareHostReleases(release) > 'trusty':
ctxt['memcache_server'] = '127.0.0.1'
else:
ctxt['memcache_server'] = 'localhost'
ctxt['memcache_server_formatted'] = '127.0.0.1'
ctxt['memcache_port'] = '11211'
ctxt['memcache_url'] = '{}:{}'.format(
ctxt['memcache_server_formatted'],
ctxt['memcache_port'])
else: else:
ctxt['memcache_server'] = 'ip6-localhost' if CompareHostReleases(release) > 'trusty':
ctxt['memcache_server_formatted'] = '[::1]' ctxt['memcache_server'] = '::1'
ctxt['memcache_port'] = '11211' else:
ctxt['memcache_url'] = 'inet6:{}:{}'.format( ctxt['memcache_server'] = 'ip6-localhost'
ctxt['memcache_server_formatted'], ctxt['memcache_server_formatted'] = '[::1]'
ctxt['memcache_port']) ctxt['memcache_port'] = '11211'
ctxt['memcache_url'] = 'inet6:{}:{}'.format(
ctxt['memcache_server_formatted'],
ctxt['memcache_port'])
return ctxt return ctxt

View File

@ -5,6 +5,8 @@ global
user haproxy user haproxy
group haproxy group haproxy
spread-checks 0 spread-checks 0
stats socket /var/run/haproxy/admin.sock mode 600 level admin
stats timeout 2m
defaults defaults
log global log global
@ -58,6 +60,15 @@ frontend tcp-in_{{ service }}
{% for frontend in frontends -%} {% for frontend in frontends -%}
backend {{ service }}_{{ frontend }} backend {{ service }}_{{ frontend }}
balance leastconn balance leastconn
{% if backend_options -%}
{% if backend_options[service] -%}
{% for option in backend_options[service] -%}
{% for key, value in option.items() -%}
{{ key }} {{ value }}
{% endfor -%}
{% endfor -%}
{% endif -%}
{% endif -%}
{% for unit, address in frontends[frontend]['backends'].items() -%} {% for unit, address in frontends[frontend]['backends'].items() -%}
server {{ unit }} {{ address }}:{{ ports[1] }} check server {{ unit }} {{ address }}:{{ ports[1] }} check
{% endfor %} {% endfor %}

View File

@ -191,7 +191,7 @@ def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d",
upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
sysv_file = os.path.join(initd_dir, service_name) sysv_file = os.path.join(initd_dir, service_name)
if init_is_systemd(): if init_is_systemd():
service('disable', service_name) service('mask', service_name)
elif os.path.exists(upstart_file): elif os.path.exists(upstart_file):
override_path = os.path.join( override_path = os.path.join(
init_dir, '{}.override'.format(service_name)) init_dir, '{}.override'.format(service_name))
@ -224,7 +224,7 @@ def service_resume(service_name, init_dir="/etc/init",
upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
sysv_file = os.path.join(initd_dir, service_name) sysv_file = os.path.join(initd_dir, service_name)
if init_is_systemd(): if init_is_systemd():
service('enable', service_name) service('unmask', service_name)
elif os.path.exists(upstart_file): elif os.path.exists(upstart_file):
override_path = os.path.join( override_path = os.path.join(
init_dir, '{}.override'.format(service_name)) init_dir, '{}.override'.format(service_name))

View File

@ -11,15 +11,17 @@ requests==2.6.0
# Liberty client lower constraints # Liberty client lower constraints
amulet>=1.14.3,<2.0 amulet>=1.14.3,<2.0
bundletester>=0.6.1,<1.0 bundletester>=0.6.1,<1.0
python-ceilometerclient>=1.5.0,<2.0 python-ceilometerclient>=1.5.0
python-cinderclient>=1.4.0,<2.0 python-cinderclient>=1.4.0
python-glanceclient>=1.1.0,<2.0 python-glanceclient>=1.1.0
python-heatclient>=0.8.0,<1.0 python-heatclient>=0.8.0
python-keystoneclient>=1.7.1,<2.0 python-keystoneclient>=1.7.1
python-neutronclient>=3.1.0,<4.0 python-neutronclient>=3.1.0
python-novaclient>=2.30.1,<3.0 python-novaclient>=2.30.1
python-openstackclient>=1.7.0,<2.0 python-openstackclient>=1.7.0
python-swiftclient>=2.6.0,<3.0 python-swiftclient>=2.6.0
pika>=0.10.0,<1.0 pika>=0.10.0,<1.0
distro-info distro-info
# END: Amulet OpenStack Charm Helper Requirements # END: Amulet OpenStack Charm Helper Requirements
# NOTE: workaround for 14.04 pip/tox
pytz

View File

@ -16,10 +16,7 @@
import amulet import amulet
import keystoneclient import keystoneclient
import subprocess
import swiftclient import swiftclient
import json
import time
from charmhelpers.contrib.openstack.amulet.deployment import ( from charmhelpers.contrib.openstack.amulet.deployment import (
OpenStackAmuletDeployment OpenStackAmuletDeployment
) )
@ -124,32 +121,6 @@ class CephRadosGwBasicDeployment(OpenStackAmuletDeployment):
'ceph': ceph_config} 'ceph': ceph_config}
super(CephRadosGwBasicDeployment, self)._configure_services(configs) super(CephRadosGwBasicDeployment, self)._configure_services(configs)
def _run_action(self, unit_id, action, *args):
command = ["juju", "action", "do", "--format=json", unit_id, action]
command.extend(args)
print("Running command: %s\n" % " ".join(command))
output = subprocess.check_output(command)
output_json = output.decode(encoding="UTF-8")
data = json.loads(output_json)
action_id = data[u'Action queued with id']
return action_id
def _wait_on_action(self, action_id):
command = ["juju", "action", "fetch", "--format=json", action_id]
while True:
try:
output = subprocess.check_output(command)
except Exception as e:
print(e)
return False
output_json = output.decode(encoding="UTF-8")
data = json.loads(output_json)
if data[u"status"] == "completed":
return True
elif data[u"status"] == "failed":
return False
time.sleep(2)
def _initialize_tests(self): def _initialize_tests(self):
"""Perform final initialization before tests get run.""" """Perform final initialization before tests get run."""
# Access the sentries for inspecting service units # Access the sentries for inspecting service units
@ -715,16 +686,15 @@ class CephRadosGwBasicDeployment(OpenStackAmuletDeployment):
"""The services can be paused and resumed. """ """The services can be paused and resumed. """
u.log.debug('Checking pause and resume actions...') u.log.debug('Checking pause and resume actions...')
unit = self.ceph_radosgw_sentry unit = self.ceph_radosgw_sentry
unit_name = unit.info['unit_name']
assert u.status_get(unit)[0] == "active" assert u.status_get(unit)[0] == "active"
action_id = self._run_action(unit_name, "pause") action_id = u.run_action(unit, "pause")
assert self._wait_on_action(action_id), "Pause action failed." assert u.wait_on_action(action_id), "Pause action failed."
assert u.status_get(unit)[0] == "maintenance" assert u.status_get(unit)[0] == "maintenance"
action_id = self._run_action(unit_name, "resume") action_id = u.run_action(unit, "resume")
assert self._wait_on_action(action_id), "Resume action failed." assert u.wait_on_action(action_id), "Resume action failed."
assert u.status_get(unit)[0] == "active" assert u.status_get(unit)[0] == "active"
u.log.debug('OK') u.log.debug('OK')

View File

@ -547,7 +547,7 @@ class OpenStackAmuletUtils(AmuletUtils):
"""Create the specified instance.""" """Create the specified instance."""
self.log.debug('Creating instance ' self.log.debug('Creating instance '
'({}|{}|{})'.format(instance_name, image_name, flavor)) '({}|{}|{})'.format(instance_name, image_name, flavor))
image = nova.images.find(name=image_name) image = nova.glance.find_image(image_name)
flavor = nova.flavors.find(name=flavor) flavor = nova.flavors.find(name=flavor)
instance = nova.servers.create(name=instance_name, image=image, instance = nova.servers.create(name=instance_name, image=image,
flavor=flavor) flavor=flavor)

View File

@ -191,7 +191,7 @@ def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d",
upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
sysv_file = os.path.join(initd_dir, service_name) sysv_file = os.path.join(initd_dir, service_name)
if init_is_systemd(): if init_is_systemd():
service('disable', service_name) service('mask', service_name)
elif os.path.exists(upstart_file): elif os.path.exists(upstart_file):
override_path = os.path.join( override_path = os.path.join(
init_dir, '{}.override'.format(service_name)) init_dir, '{}.override'.format(service_name))
@ -224,7 +224,7 @@ def service_resume(service_name, init_dir="/etc/init",
upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
sysv_file = os.path.join(initd_dir, service_name) sysv_file = os.path.join(initd_dir, service_name)
if init_is_systemd(): if init_is_systemd():
service('enable', service_name) service('unmask', service_name)
elif os.path.exists(upstart_file): elif os.path.exists(upstart_file):
override_path = os.path.join( override_path = os.path.join(
init_dir, '{}.override'.format(service_name)) init_dir, '{}.override'.format(service_name))

0
tests/gate-basic-zesty-ocata Normal file → Executable file
View File

View File

@ -37,6 +37,7 @@ class HAProxyContextTests(CharmTestCase):
self.relation_get.side_effect = self.test_relation.get self.relation_get.side_effect = self.test_relation.get
self.config.side_effect = self.test_config.get self.config.side_effect = self.test_config.get
@patch('charmhelpers.contrib.openstack.context.mkdir')
@patch('charmhelpers.contrib.openstack.context.unit_get') @patch('charmhelpers.contrib.openstack.context.unit_get')
@patch('charmhelpers.contrib.openstack.context.local_unit') @patch('charmhelpers.contrib.openstack.context.local_unit')
@patch('charmhelpers.contrib.openstack.context.get_host_ip') @patch('charmhelpers.contrib.openstack.context.get_host_ip')
@ -45,7 +46,7 @@ class HAProxyContextTests(CharmTestCase):
@patch('charmhelpers.contrib.openstack.context.relation_ids') @patch('charmhelpers.contrib.openstack.context.relation_ids')
@patch('charmhelpers.contrib.hahelpers.cluster.relation_ids') @patch('charmhelpers.contrib.hahelpers.cluster.relation_ids')
def test_ctxt(self, _harelation_ids, _ctxtrelation_ids, _haconfig, def test_ctxt(self, _harelation_ids, _ctxtrelation_ids, _haconfig,
_ctxtconfig, _get_host_ip, _local_unit, _unit_get): _ctxtconfig, _get_host_ip, _local_unit, _unit_get, _mkdir):
_get_host_ip.return_value = '10.0.0.10' _get_host_ip.return_value = '10.0.0.10'
_unit_get.return_value = '10.0.0.10' _unit_get.return_value = '10.0.0.10'
_ctxtconfig.side_effect = self.test_config.get _ctxtconfig.side_effect = self.test_config.get