Merged next in

This commit is contained in:
Liam Young 2014-12-19 10:25:03 +00:00
commit dac6bbed30
30 changed files with 630 additions and 70 deletions

@ -10,3 +10,4 @@ include:
- payload.execd
- contrib.network.ip
- contrib.peerstorage
- contrib.python.packages

@ -0,0 +1,22 @@
# Bootstrap charm-helpers, installing its dependencies if necessary using
# only standard libraries.
import subprocess
import sys
try:
import six # flake8: noqa
except ImportError:
if sys.version_info.major == 2:
subprocess.check_call(['apt-get', 'install', '-y', 'python-six'])
else:
subprocess.check_call(['apt-get', 'install', '-y', 'python3-six'])
import six # flake8: noqa
try:
import yaml # flake8: noqa
except ImportError:
if sys.version_info.major == 2:
subprocess.check_call(['apt-get', 'install', '-y', 'python-yaml'])
else:
subprocess.check_call(['apt-get', 'install', '-y', 'python3-yaml'])
import yaml # flake8: noqa

@ -13,6 +13,7 @@ clustering-related helpers.
import subprocess
import os
from socket import gethostname as get_unit_hostname
import six
@ -28,12 +29,19 @@ from charmhelpers.core.hookenv import (
WARNING,
unit_get,
)
from charmhelpers.core.decorators import (
retry_on_exception,
)
class HAIncompleteConfig(Exception):
pass
class CRMResourceNotFound(Exception):
pass
def is_elected_leader(resource):
"""
Returns True if the charm executing this is the elected cluster leader.
@ -68,24 +76,30 @@ def is_clustered():
return False
def is_crm_leader(resource):
@retry_on_exception(5, base_delay=2, exc_type=CRMResourceNotFound)
def is_crm_leader(resource, retry=False):
"""
Returns True if the charm calling this is the elected corosync leader,
as returned by calling the external "crm" command.
We allow this operation to be retried to avoid the possibility of getting a
false negative. See LP #1396246 for more info.
"""
cmd = [
"crm", "resource",
"show", resource
]
cmd = ['crm', 'resource', 'show', resource]
try:
status = subprocess.check_output(cmd).decode('UTF-8')
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
else:
if get_unit_hostname() in status:
return True
else:
return False
status = None
if status and get_unit_hostname() in status:
return True
if status and "resource %s is NOT running" % (resource) in status:
raise CRMResourceNotFound("CRM resource %s not found" % (resource))
return False
def is_leader(resource):

@ -21,11 +21,15 @@ from charmhelpers.core.hookenv import (
relation_set,
unit_get,
unit_private_ip,
charm_name,
DEBUG,
INFO,
WARNING,
ERROR,
)
from charmhelpers.core.sysctl import create as sysctl_create
from charmhelpers.core.host import (
mkdir,
write_file,
@ -1015,3 +1019,14 @@ class NotificationDriverContext(OSContextGenerator):
ctxt['notifications'] = "True"
return ctxt
class SysctlContext(OSContextGenerator):
"""This context check if the 'sysctl' option exists on configuration
then creates a file with the loaded contents"""
def __call__(self):
sysctl_dict = config('sysctl')
if sysctl_dict:
sysctl_create(sysctl_dict,
'/etc/sysctl.d/50-{0}.conf'.format(charm_name()))
return {'sysctl': sysctl_dict}

@ -2,21 +2,19 @@ from charmhelpers.core.hookenv import (
config,
unit_get,
)
from charmhelpers.contrib.network.ip import (
get_address_in_network,
is_address_in_network,
is_ipv6,
get_ipv6_addr,
)
from charmhelpers.contrib.hahelpers.cluster import is_clustered
PUBLIC = 'public'
INTERNAL = 'int'
ADMIN = 'admin'
_address_map = {
ADDRESS_MAP = {
PUBLIC: {
'config': 'os-public-network',
'fallback': 'public-address'
@ -33,16 +31,14 @@ _address_map = {
def canonical_url(configs, endpoint_type=PUBLIC):
'''
Returns the correct HTTP URL to this host given the state of HTTPS
"""Returns the correct HTTP URL to this host given the state of HTTPS
configuration, hacluster and charm configuration.
:configs OSTemplateRenderer: A config tempating object to inspect for
a complete https context.
:endpoint_type str: The endpoint type to resolve.
:returns str: Base URL for services on the current service unit.
'''
:param configs: OSTemplateRenderer config templating object to inspect
for a complete https context.
:param endpoint_type: str endpoint type to resolve.
:param returns: str base URL for services on the current service unit.
"""
scheme = 'http'
if 'https' in configs.complete_contexts():
scheme = 'https'
@ -53,27 +49,45 @@ def canonical_url(configs, endpoint_type=PUBLIC):
def resolve_address(endpoint_type=PUBLIC):
"""Return unit address depending on net config.
If unit is clustered with vip(s) and has net splits defined, return vip on
correct network. If clustered with no nets defined, return primary vip.
If not clustered, return unit address ensuring address is on configured net
split if one is configured.
:param endpoint_type: Network endpoing type
"""
resolved_address = None
if is_clustered():
if config(_address_map[endpoint_type]['config']) is None:
# Assume vip is simple and pass back directly
resolved_address = config('vip')
vips = config('vip')
if vips:
vips = vips.split()
net_type = ADDRESS_MAP[endpoint_type]['config']
net_addr = config(net_type)
net_fallback = ADDRESS_MAP[endpoint_type]['fallback']
clustered = is_clustered()
if clustered:
if not net_addr:
# If no net-splits defined, we expect a single vip
resolved_address = vips[0]
else:
for vip in config('vip').split():
if is_address_in_network(
config(_address_map[endpoint_type]['config']),
vip):
for vip in vips:
if is_address_in_network(net_addr, vip):
resolved_address = vip
break
else:
if config('prefer-ipv6'):
fallback_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
fallback_addr = get_ipv6_addr(exc_list=vips)[0]
else:
fallback_addr = unit_get(_address_map[endpoint_type]['fallback'])
resolved_address = get_address_in_network(
config(_address_map[endpoint_type]['config']), fallback_addr)
fallback_addr = unit_get(net_fallback)
resolved_address = get_address_in_network(net_addr, fallback_addr)
if resolved_address is None:
raise ValueError('Unable to resolve a suitable IP address'
' based on charm state and configuration')
else:
return resolved_address
raise ValueError("Unable to resolve a suitable IP address based on "
"charm state and configuration. (net_type=%s, "
"clustered=%s)" % (net_type, clustered))
return resolved_address

@ -11,12 +11,12 @@ import socket
import sys
import six
import yaml
from charmhelpers.core.hookenv import (
config,
log as juju_log,
charm_dir,
ERROR,
INFO,
relation_ids,
relation_set
@ -33,7 +33,8 @@ from charmhelpers.contrib.network.ip import (
)
from charmhelpers.core.host import lsb_release, mounts, umount
from charmhelpers.fetch import apt_install, apt_cache
from charmhelpers.fetch import apt_install, apt_cache, install_remote
from charmhelpers.contrib.python.packages import pip_install
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
@ -353,8 +354,8 @@ def ensure_block_device(block_device):
'''
_none = ['None', 'none', None]
if (block_device in _none):
error_out('prepare_storage(): Missing required input: '
'block_device=%s.' % block_device, level=ERROR)
error_out('prepare_storage(): Missing required input: block_device=%s.'
% block_device)
if block_device.startswith('/dev/'):
bdev = block_device
@ -370,8 +371,7 @@ def ensure_block_device(block_device):
bdev = '/dev/%s' % block_device
if not is_block_device(bdev):
error_out('Failed to locate valid block device at %s' % bdev,
level=ERROR)
error_out('Failed to locate valid block device at %s' % bdev)
return bdev
@ -509,3 +509,111 @@ def os_requires_version(ostack_release, pkg):
f(*args)
return wrapped_f
return wrap
def git_install_requested():
"""Returns true if openstack-origin-git is specified."""
return config('openstack-origin-git') != "None"
requirements_dir = None
def git_clone_and_install(file_name, core_project):
"""Clone/install all OpenStack repos specified in yaml config file."""
global requirements_dir
if file_name == "None":
return
yaml_file = os.path.join(charm_dir(), file_name)
# clone/install the requirements project first
installed = _git_clone_and_install_subset(yaml_file,
whitelist=['requirements'])
if 'requirements' not in installed:
error_out('requirements git repository must be specified')
# clone/install all other projects except requirements and the core project
blacklist = ['requirements', core_project]
_git_clone_and_install_subset(yaml_file, blacklist=blacklist,
update_requirements=True)
# clone/install the core project
whitelist = [core_project]
installed = _git_clone_and_install_subset(yaml_file, whitelist=whitelist,
update_requirements=True)
if core_project not in installed:
error_out('{} git repository must be specified'.format(core_project))
def _git_clone_and_install_subset(yaml_file, whitelist=[], blacklist=[],
update_requirements=False):
"""Clone/install subset of OpenStack repos specified in yaml config file."""
global requirements_dir
installed = []
with open(yaml_file, 'r') as fd:
projects = yaml.load(fd)
for proj, val in projects.items():
# The project subset is chosen based on the following 3 rules:
# 1) If project is in blacklist, we don't clone/install it, period.
# 2) If whitelist is empty, we clone/install everything else.
# 3) If whitelist is not empty, we clone/install everything in the
# whitelist.
if proj in blacklist:
continue
if whitelist and proj not in whitelist:
continue
repo = val['repository']
branch = val['branch']
repo_dir = _git_clone_and_install_single(repo, branch,
update_requirements)
if proj == 'requirements':
requirements_dir = repo_dir
installed.append(proj)
return installed
def _git_clone_and_install_single(repo, branch, update_requirements=False):
"""Clone and install a single git repository."""
dest_parent_dir = "/mnt/openstack-git/"
dest_dir = os.path.join(dest_parent_dir, os.path.basename(repo))
if not os.path.exists(dest_parent_dir):
juju_log('Host dir not mounted at {}. '
'Creating directory there instead.'.format(dest_parent_dir))
os.mkdir(dest_parent_dir)
if not os.path.exists(dest_dir):
juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))
repo_dir = install_remote(repo, dest=dest_parent_dir, branch=branch)
else:
repo_dir = dest_dir
if update_requirements:
if not requirements_dir:
error_out('requirements repo must be cloned before '
'updating from global requirements.')
_git_update_requirements(repo_dir, requirements_dir)
juju_log('Installing git repo from dir: {}'.format(repo_dir))
pip_install(repo_dir)
return repo_dir
def _git_update_requirements(package_dir, reqs_dir):
"""Update from global requirements.
Update an OpenStack git directory's requirements.txt and
test-requirements.txt from global-requirements.txt."""
orig_dir = os.getcwd()
os.chdir(reqs_dir)
cmd = "python update.py {}".format(package_dir)
try:
subprocess.check_call(cmd.split(' '))
except subprocess.CalledProcessError:
package = os.path.basename(package_dir)
error_out("Error updating {} from global-requirements.txt".format(package))
os.chdir(orig_dir)

@ -0,0 +1,77 @@
#!/usr/bin/env python
# coding: utf-8
__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
from charmhelpers.fetch import apt_install, apt_update
from charmhelpers.core.hookenv import log
try:
from pip import main as pip_execute
except ImportError:
apt_update()
apt_install('python-pip')
from pip import main as pip_execute
def parse_options(given, available):
"""Given a set of options, check if available"""
for key, value in sorted(given.items()):
if key in available:
yield "--{0}={1}".format(key, value)
def pip_install_requirements(requirements, **options):
"""Install a requirements file """
command = ["install"]
available_options = ('proxy', 'src', 'log', )
for option in parse_options(options, available_options):
command.append(option)
command.append("-r {0}".format(requirements))
log("Installing from file: {} with options: {}".format(requirements,
command))
pip_execute(command)
def pip_install(package, fatal=False, **options):
"""Install a python package"""
command = ["install"]
available_options = ('proxy', 'src', 'log', "index-url", )
for option in parse_options(options, available_options):
command.append(option)
if isinstance(package, list):
command.extend(package)
else:
command.append(package)
log("Installing {} package with options: {}".format(package,
command))
pip_execute(command)
def pip_uninstall(package, **options):
"""Uninstall a python package"""
command = ["uninstall", "-q", "-y"]
available_options = ('proxy', 'log', )
for option in parse_options(options, available_options):
command.append(option)
if isinstance(package, list):
command.extend(package)
else:
command.append(package)
log("Uninstalling {} package with options: {}".format(package,
command))
pip_execute(command)
def pip_list():
"""Returns the list of current python installed packages
"""
return pip_execute(["list"])

@ -372,3 +372,46 @@ def ceph_version():
return None
else:
return None
class CephBrokerRq(object):
"""Ceph broker request.
Multiple operations can be added to a request and sent to the Ceph broker
to be executed.
Request is json-encoded for sending over the wire.
The API is versioned and defaults to version 1.
"""
def __init__(self, api_version=1):
self.api_version = api_version
self.ops = []
def add_op_create_pool(self, name, replica_count=3):
self.ops.append({'op': 'create-pool', 'name': name,
'replicas': replica_count})
@property
def request(self):
return json.dumps({'api-version': self.api_version, 'ops': self.ops})
class CephBrokerRsp(object):
"""Ceph broker response.
Response is json-decoded and contents provided as methods/properties.
The API is versioned and defaults to version 1.
"""
def __init__(self, encoded_rsp):
self.api_version = None
self.rsp = json.loads(encoded_rsp)
@property
def exit_code(self):
return self.rsp.get('exit-code')
@property
def exit_msg(self):
return self.rsp.get('stderr')

@ -0,0 +1,41 @@
#
# Copyright 2014 Canonical Ltd.
#
# Authors:
# Edward Hope-Morley <opentastic@gmail.com>
#
import time
from charmhelpers.core.hookenv import (
log,
INFO,
)
def retry_on_exception(num_retries, base_delay=0, exc_type=Exception):
"""If the decorated function raises exception exc_type, allow num_retries
retry attempts before raise the exception.
"""
def _retry_on_exception_inner_1(f):
def _retry_on_exception_inner_2(*args, **kwargs):
retries = num_retries
multiplier = 1
while True:
try:
return f(*args, **kwargs)
except exc_type:
if not retries:
raise
delay = base_delay * multiplier
multiplier += 1
log("Retrying '%s' %d more times (delay=%s)" %
(f.__name__, retries, delay), level=INFO)
retries -= 1
if delay:
time.sleep(delay)
return _retry_on_exception_inner_2
return _retry_on_exception_inner_1

@ -68,6 +68,8 @@ def log(message, level=None):
command = ['juju-log']
if level:
command += ['-l', level]
if not isinstance(message, six.string_types):
message = repr(message)
command += [message]
subprocess.call(command)
@ -393,21 +395,31 @@ def relations_of_type(reltype=None):
return relation_data
@cached
def metadata():
"""Get the current charm metadata.yaml contents as a python object"""
with open(os.path.join(charm_dir(), 'metadata.yaml')) as md:
return yaml.safe_load(md)
@cached
def relation_types():
"""Get a list of relation types supported by this charm"""
charmdir = os.environ.get('CHARM_DIR', '')
mdf = open(os.path.join(charmdir, 'metadata.yaml'))
md = yaml.safe_load(mdf)
rel_types = []
md = metadata()
for key in ('provides', 'requires', 'peers'):
section = md.get(key)
if section:
rel_types.extend(section.keys())
mdf.close()
return rel_types
@cached
def charm_name():
"""Get the name of the current charm as is specified on metadata.yaml"""
return metadata().get('name')
@cached
def relations():
"""Get a nested dictionary of relation data for all related units"""

@ -101,6 +101,26 @@ def adduser(username, password=None, shell='/bin/bash', system_user=False):
return user_info
def add_group(group_name, system_group=False):
"""Add a group to the system"""
try:
group_info = grp.getgrnam(group_name)
log('group {0} already exists!'.format(group_name))
except KeyError:
log('creating group {0}'.format(group_name))
cmd = ['addgroup']
if system_group:
cmd.append('--system')
else:
cmd.extend([
'--group',
])
cmd.append(group_name)
subprocess.check_call(cmd)
group_info = grp.getgrnam(group_name)
return group_info
def add_user_to_group(username, group):
"""Add a user to a group"""
cmd = [
@ -142,13 +162,16 @@ def mkdir(path, owner='root', group='root', perms=0o555, force=False):
uid = pwd.getpwnam(owner).pw_uid
gid = grp.getgrnam(group).gr_gid
realpath = os.path.abspath(path)
if os.path.exists(realpath):
if force and not os.path.isdir(realpath):
path_exists = os.path.exists(realpath)
if path_exists and force:
if not os.path.isdir(realpath):
log("Removing non-directory file {} prior to mkdir()".format(path))
os.unlink(realpath)
else:
os.makedirs(realpath, perms)
os.chown(realpath, uid, gid)
elif not path_exists:
os.makedirs(realpath, perms)
os.chown(realpath, uid, gid)
os.chown(realpath, uid, gid)
def write_file(path, content, owner='root', group='root', perms=0o444):
@ -368,8 +391,8 @@ def cmp_pkgrevno(package, revno, pkgcache=None):
'''
import apt_pkg
from charmhelpers.fetch import apt_cache
if not pkgcache:
from charmhelpers.fetch import apt_cache
pkgcache = apt_cache()
pkg = pkgcache[package]
return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)

@ -48,5 +48,5 @@ def render(source, target, context, owner='root', group='root',
level=hookenv.ERROR)
raise e
content = template.render(context)
host.mkdir(os.path.dirname(target))
host.mkdir(os.path.dirname(target), owner, group)
host.write_file(target, content, owner, group, perms)

@ -34,11 +34,14 @@ class GitUrlFetchHandler(BaseFetchHandler):
repo = Repo.clone_from(source, dest)
repo.git.checkout(branch)
def install(self, source, branch="master"):
def install(self, source, branch="master", dest=None):
url_parts = self.parse_url(source)
branch_name = url_parts.path.strip("/").split("/")[-1]
dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
branch_name)
if dest:
dest_dir = os.path.join(dest, branch_name)
else:
dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
branch_name)
if not os.path.exists(dest_dir):
mkdir(dest_dir, perms=0o755)
try:

@ -0,0 +1 @@
nova_cc_hooks.py

@ -0,0 +1 @@
nova_cc_hooks.py

@ -0,0 +1 @@
nova_cc_hooks.py

@ -0,0 +1 @@
nova_cc_hooks.py

@ -1,19 +1,36 @@
from charmhelpers.core.hookenv import (
config, relation_ids, relation_set, log, ERROR,
unit_get, related_units, relation_get)
from charmhelpers.fetch import apt_install, filter_installed_packages
from charmhelpers.contrib.openstack import context, neutron, utils
config,
relation_ids,
relation_set,
log,
ERROR,
unit_get,
related_units,
relations_for_id,
relation_get,
)
from charmhelpers.fetch import (
apt_install,
filter_installed_packages,
)
from charmhelpers.contrib.openstack import (
context,
neutron,
utils,
)
from charmhelpers.contrib.hahelpers.cluster import (
determine_apache_port,
determine_api_port,
https,
is_clustered
is_clustered,
)
from charmhelpers.contrib.network.ip import (
get_ipv6_addr
get_ipv6_addr,
format_ipv6_addr,
)
from charmhelpers.contrib.openstack.ip import (
resolve_address,
INTERNAL,
)
@ -283,6 +300,8 @@ class NovaConfigContext(context.WorkerConfigContext):
ctxt = super(NovaConfigContext, self).__call__()
ctxt['cpu_allocation_ratio'] = config('cpu-allocation-ratio')
ctxt['ram_allocation_ratio'] = config('ram-allocation-ratio')
addr = resolve_address(INTERNAL)
ctxt['host_ip'] = format_ipv6_addr(addr) or addr
return ctxt
@ -291,3 +310,24 @@ class NovaIPv6Context(context.BindHostContext):
ctxt = super(NovaIPv6Context, self).__call__()
ctxt['use_ipv6'] = config('prefer-ipv6')
return ctxt
class InstanceConsoleContext(context.OSContextGenerator):
interfaces = []
def __call__(self):
ctxt = {}
servers = []
try:
for rid in relation_ids('memcache'):
for rel in relations_for_id(rid):
priv_addr = rel['private-address']
# Format it as IPv6 address if needed
priv_addr = format_ipv6_addr(priv_addr) or priv_addr
servers.append("%s:%s" % (priv_addr, rel['port']))
except Exception as ex:
log("Could not get memcache servers: %s" % (ex), level='WARNING')
servers = []
ctxt['memcached_servers'] = ','.join(servers)
return ctxt

@ -849,6 +849,15 @@ def neutron_api_relation_broken():
quantum_joined(rid=rid)
@hooks.hook('memcache-relation-joined',
'memcache-relation-departed',
'memcache-relation-changed',
'memcache-relation-broken')
@restart_on_change(restart_map())
def memcached_joined():
CONFIGS.write(NOVA_CONF)
def main():
try:
hooks.execute(sys.argv)

@ -67,7 +67,9 @@ BASE_PACKAGES = [
'python-mysqldb',
'python-psycopg2',
'python-psutil',
'python-six',
'uuid',
'python-memcache',
]
BASE_SERVICES = [
@ -123,7 +125,8 @@ BASE_RESOURCE_MAP = OrderedDict([
nova_cc_context.VolumeServiceContext(),
nova_cc_context.NovaIPv6Context(),
nova_cc_context.NeutronCCContext(),
nova_cc_context.NovaConfigContext()],
nova_cc_context.NovaConfigContext(),
nova_cc_context.InstanceConsoleContext()],
}),
(NOVA_API_PASTE, {
'services': [s for s in BASE_SERVICES if 'api' in s],

@ -40,6 +40,8 @@ requires:
nova-vmware:
interface: nova-vmware
scope: container
memcache:
interface: memcache
peers:
cluster:
interface: nova-ha

@ -21,6 +21,11 @@ volumes_path=/var/lib/nova/volumes
enabled_apis=ec2,osapi_compute,metadata
auth_strategy=keystone
compute_driver=libvirt.LibvirtDriver
{% if memcached_servers %}
memcached_servers = {{ memcached_servers }}
{% endif %}
{% if keystone_ec2_url -%}
keystone_ec2_url = {{ keystone_ec2_url }}
{% endif -%}

@ -20,6 +20,11 @@ volumes_path=/var/lib/nova/volumes
enabled_apis=ec2,osapi_compute,metadata
auth_strategy=keystone
compute_driver=libvirt.LibvirtDriver
{% if memcached_servers %}
memcached_servers = {{ memcached_servers }}
{% endif %}
{% if keystone_ec2_url -%}
keystone_ec2_url = {{ keystone_ec2_url }}
{% endif -%}

@ -25,6 +25,11 @@ ec2_workers = {{ workers }}
scheduler_default_filters = RetryFilter,AvailabilityZoneFilter,CoreFilter,RamFilter,ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,ServerGroupAntiAffinityFilter,ServerGroupAffinityFilter
cpu_allocation_ratio = {{ cpu_allocation_ratio }}
use_syslog={{ use_syslog }}
my_ip = {{ host_ip }}
{% if memcached_servers %}
memcached_servers = {{ memcached_servers }}
{% endif %}
{% if keystone_ec2_url -%}
keystone_ec2_url = {{ keystone_ec2_url }}

@ -24,7 +24,6 @@ auth_strategy=keystone
compute_driver=libvirt.LibvirtDriver
use_ipv6 = {{ use_ipv6 }}
osapi_compute_listen = {{ bind_host }}
my_ip = {{ bind_host }}
metadata_host = {{ bind_host }}
s3_listen = {{ bind_host }}
ec2_listen = {{ bind_host }}
@ -37,6 +36,11 @@ cpu_allocation_ratio = {{ cpu_allocation_ratio }}
ram_allocation_ratio = {{ ram_allocation_ratio }}
use_syslog={{ use_syslog }}
my_ip = {{ host_ip }}
{% if memcached_servers %}
memcached_servers = {{ memcached_servers }}
{% endif %}
{% if keystone_ec2_url -%}
keystone_ec2_url = {{ keystone_ec2_url }}

@ -19,7 +19,7 @@ u = OpenStackAmuletUtils(ERROR)
class NovaCCBasicDeployment(OpenStackAmuletDeployment):
"""Amulet tests on a basic nova cloud controller deployment."""
def __init__(self, series=None, openstack=None, source=None, stable=False):
def __init__(self, series=None, openstack=None, source=None, stable=True):
"""Deploy the entire test environment."""
super(NovaCCBasicDeployment, self).__init__(series, openstack, source, stable)
self._add_services()

@ -0,0 +1,22 @@
# Bootstrap charm-helpers, installing its dependencies if necessary using
# only standard libraries.
import subprocess
import sys
try:
import six # flake8: noqa
except ImportError:
if sys.version_info.major == 2:
subprocess.check_call(['apt-get', 'install', '-y', 'python-six'])
else:
subprocess.check_call(['apt-get', 'install', '-y', 'python3-six'])
import six # flake8: noqa
try:
import yaml # flake8: noqa
except ImportError:
if sys.version_info.major == 2:
subprocess.check_call(['apt-get', 'install', '-y', 'python-yaml'])
else:
subprocess.check_call(['apt-get', 'install', '-y', 'python3-yaml'])
import yaml # flake8: noqa

@ -0,0 +1,87 @@
from __future__ import print_function
import mock
#####
# NOTE(freyes): this is a workaround to patch config() function imported by
# nova_cc_utils before it gets a reference to the actual config() provided by
# hookenv module.
from charmhelpers.core import hookenv
_conf = hookenv.config
hookenv.config = mock.MagicMock()
import nova_cc_utils as _utils
# this assert is a double check + to avoid pep8 warning
assert _utils.config == hookenv.config
hookenv.config = _conf
#####
import nova_cc_context as context
from charmhelpers.contrib.openstack import utils
from test_utils import CharmTestCase
TO_PATCH = [
'apt_install',
'filter_installed_packages',
'relation_ids',
'relation_get',
'related_units',
'config',
'log',
'unit_get',
'relations_for_id',
]
def fake_log(msg, level=None):
level = level or 'INFO'
print('[juju test log (%s)] %s' % (level, msg))
class NovaComputeContextTests(CharmTestCase):
def setUp(self):
super(NovaComputeContextTests, self).setUp(context, TO_PATCH)
self.relation_get.side_effect = self.test_relation.get
self.config.side_effect = self.test_config.get
self.log.side_effect = fake_log
@mock.patch.object(utils, 'os_release')
@mock.patch('charmhelpers.contrib.network.ip.log')
def test_instance_console_context_without_memcache(self, os_release, log_):
self.unit_get.return_value = '127.0.0.1'
self.relation_ids.return_value = 'cache:0'
self.related_units.return_value = 'memcached/0'
instance_console = context.InstanceConsoleContext()
os_release.return_value = 'icehouse'
self.assertEqual({'memcached_servers': ''},
instance_console())
@mock.patch.object(utils, 'os_release')
@mock.patch('charmhelpers.contrib.network.ip.log')
def test_instance_console_context_with_memcache(self, os_release, log_):
self.check_instance_console_context_with_memcache(os_release,
'127.0.1.1',
'127.0.1.1')
@mock.patch.object(utils, 'os_release')
@mock.patch('charmhelpers.contrib.network.ip.log')
def test_instance_console_context_with_memcache_ipv6(self, os_release,
log_):
self.check_instance_console_context_with_memcache(os_release, '::1',
'[::1]')
def check_instance_console_context_with_memcache(self, os_release, ip,
formated_ip):
memcached_servers = [{'private-address': formated_ip,
'port': '11211'}]
self.unit_get.return_value = ip
self.relation_ids.return_value = ['cache:0']
self.relations_for_id.return_value = memcached_servers
self.related_units.return_value = 'memcached/0'
instance_console = context.InstanceConsoleContext()
os_release.return_value = 'icehouse'
self.maxDiff = None
self.assertEqual({'memcached_servers': "%s:11211" % (formated_ip, )},
instance_console())

@ -1,6 +1,7 @@
from mock import MagicMock, patch, call
from test_utils import CharmTestCase, patch_open
import os
with patch('charmhelpers.core.hookenv.config') as config:
config.return_value = 'neutron'
import nova_cc_utils as utils