diff --git a/config.yaml b/config.yaml
index 4ed6d7f1..91db1a8a 100644
--- a/config.yaml
+++ b/config.yaml
@@ -160,6 +160,13 @@ options:
order for this charm to function correctly, the privacy extension must be
disabled and a non-temporary address must be configured/available on
your network interface.
+ network-device-mtu:
+ type: int
+ default:
+ description: |
+ The MTU size for the interfaces managed by neutron. If unset or set to
+ 0, no value will be applied.
+ # Storage configuration options
libvirt-image-backend:
default:
type: string
@@ -182,9 +189,18 @@ options:
rbd pool has been created, changing this value will not have any
effect (although it can be changed in ceph by manually configuring
your ceph cluster).
+ # Other configuration options
sysctl:
type: string
default:
description: |
YAML formatted associative array of sysctl values, e.g.:
'{ kernel.pid_max : 4194303 }'
+ manage-neutron-plugin-legacy-mode:
+ type: boolean
+ default: True
+ description: |
+ If True nova-compute will install neutron packages for the plugin
+ stipulated by nova-cloud-controller. The option is only available for
+ backward compatibility for deployments which do not use the neutron-api
+ charm. Please do not enable this on new deployments.
diff --git a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py b/hooks/charmhelpers/contrib/openstack/amulet/deployment.py
index 0cfeaa4c..0e0db566 100644
--- a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py
+++ b/hooks/charmhelpers/contrib/openstack/amulet/deployment.py
@@ -15,6 +15,7 @@
# along with charm-helpers. If not, see .
import six
+from collections import OrderedDict
from charmhelpers.contrib.amulet.deployment import (
AmuletDeployment
)
@@ -100,12 +101,34 @@ class OpenStackAmuletDeployment(AmuletDeployment):
"""
(self.precise_essex, self.precise_folsom, self.precise_grizzly,
self.precise_havana, self.precise_icehouse,
- self.trusty_icehouse) = range(6)
+ self.trusty_icehouse, self.trusty_juno, self.trusty_kilo) = range(8)
releases = {
('precise', None): self.precise_essex,
('precise', 'cloud:precise-folsom'): self.precise_folsom,
('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
('precise', 'cloud:precise-havana'): self.precise_havana,
('precise', 'cloud:precise-icehouse'): self.precise_icehouse,
- ('trusty', None): self.trusty_icehouse}
+ ('trusty', None): self.trusty_icehouse,
+ ('trusty', 'cloud:trusty-juno'): self.trusty_juno,
+ ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo}
return releases[(self.series, self.openstack)]
+
+ def _get_openstack_release_string(self):
+ """Get openstack release string.
+
+ Return a string representing the openstack release.
+ """
+ releases = OrderedDict([
+ ('precise', 'essex'),
+ ('quantal', 'folsom'),
+ ('raring', 'grizzly'),
+ ('saucy', 'havana'),
+ ('trusty', 'icehouse'),
+ ('utopic', 'juno'),
+ ('vivid', 'kilo'),
+ ])
+ if self.openstack:
+ os_origin = self.openstack.split(':')[1]
+ return os_origin.split('%s-' % self.series)[1].split('/')[0]
+ else:
+ return releases[self.series]
diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py
index 2d9a95cd..45e65790 100644
--- a/hooks/charmhelpers/contrib/openstack/context.py
+++ b/hooks/charmhelpers/contrib/openstack/context.py
@@ -16,6 +16,7 @@
import json
import os
+import re
import time
from base64 import b64decode
from subprocess import check_call
@@ -46,8 +47,11 @@ from charmhelpers.core.hookenv import (
)
from charmhelpers.core.sysctl import create as sysctl_create
+from charmhelpers.core.strutils import bool_from_string
from charmhelpers.core.host import (
+ list_nics,
+ get_nic_hwaddr,
mkdir,
write_file,
)
@@ -64,16 +68,22 @@ from charmhelpers.contrib.hahelpers.apache import (
)
from charmhelpers.contrib.openstack.neutron import (
neutron_plugin_attribute,
+ parse_data_port_mappings,
+)
+from charmhelpers.contrib.openstack.ip import (
+ resolve_address,
+ INTERNAL,
)
from charmhelpers.contrib.network.ip import (
get_address_in_network,
+ get_ipv4_addr,
get_ipv6_addr,
get_netmask_for_address,
format_ipv6_addr,
is_address_in_network,
+ is_bridge_member,
)
from charmhelpers.contrib.openstack.utils import get_host_ip
-
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
ADDRESS_TYPES = ['admin', 'internal', 'public']
@@ -727,7 +737,14 @@ class ApacheSSLContext(OSContextGenerator):
'endpoints': [],
'ext_ports': []}
- for cn in self.canonical_names():
+ cns = self.canonical_names()
+ if cns:
+ for cn in cns:
+ self.configure_cert(cn)
+ else:
+ # Expect cert/key provided in config (currently assumed that ca
+ # uses ip for cn)
+ cn = resolve_address(endpoint_type=INTERNAL)
self.configure_cert(cn)
addresses = self.get_network_addresses()
@@ -883,6 +900,48 @@ class NeutronContext(OSContextGenerator):
return ctxt
+class NeutronPortContext(OSContextGenerator):
+ NIC_PREFIXES = ['eth', 'bond']
+
+ def resolve_ports(self, ports):
+ """Resolve NICs not yet bound to bridge(s)
+
+ If hwaddress provided then returns resolved hwaddress otherwise NIC.
+ """
+ if not ports:
+ return None
+
+ hwaddr_to_nic = {}
+ hwaddr_to_ip = {}
+ for nic in list_nics(self.NIC_PREFIXES):
+ hwaddr = get_nic_hwaddr(nic)
+ hwaddr_to_nic[hwaddr] = nic
+ addresses = get_ipv4_addr(nic, fatal=False)
+ addresses += get_ipv6_addr(iface=nic, fatal=False)
+ hwaddr_to_ip[hwaddr] = addresses
+
+ resolved = []
+ mac_regex = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I)
+ for entry in ports:
+ if re.match(mac_regex, entry):
+ # NIC is in known NICs and does NOT hace an IP address
+ if entry in hwaddr_to_nic and not hwaddr_to_ip[entry]:
+ # If the nic is part of a bridge then don't use it
+ if is_bridge_member(hwaddr_to_nic[entry]):
+ continue
+
+ # Entry is a MAC address for a valid interface that doesn't
+ # have an IP address assigned yet.
+ resolved.append(hwaddr_to_nic[entry])
+ else:
+ # If the passed entry is not a MAC address, assume it's a valid
+ # interface, and that the user put it there on purpose (we can
+ # trust it to be the real external network).
+ resolved.append(entry)
+
+ return resolved
+
+
class OSConfigFlagContext(OSContextGenerator):
"""Provides support for user-defined config flags.
@@ -1104,3 +1163,145 @@ class SysctlContext(OSContextGenerator):
sysctl_create(sysctl_dict,
'/etc/sysctl.d/50-{0}.conf'.format(charm_name()))
return {'sysctl': sysctl_dict}
+
+
+class NeutronAPIContext(OSContextGenerator):
+ '''
+ Inspects current neutron-plugin-api relation for neutron settings. Return
+ defaults if it is not present.
+ '''
+ interfaces = ['neutron-plugin-api']
+
+ def __call__(self):
+ self.neutron_defaults = {
+ 'l2_population': {
+ 'rel_key': 'l2-population',
+ 'default': False,
+ },
+ 'overlay_network_type': {
+ 'rel_key': 'overlay-network-type',
+ 'default': 'gre',
+ },
+ 'neutron_security_groups': {
+ 'rel_key': 'neutron-security-groups',
+ 'default': False,
+ },
+ 'network_device_mtu': {
+ 'rel_key': 'network-device-mtu',
+ 'default': None,
+ },
+ 'enable_dvr': {
+ 'rel_key': 'enable-dvr',
+ 'default': False,
+ },
+ 'enable_l3ha': {
+ 'rel_key': 'enable-l3ha',
+ 'default': False,
+ },
+ }
+ ctxt = self.get_neutron_options({})
+ for rid in relation_ids('neutron-plugin-api'):
+ for unit in related_units(rid):
+ rdata = relation_get(rid=rid, unit=unit)
+ if 'l2-population' in rdata:
+ ctxt.update(self.get_neutron_options(rdata))
+
+ return ctxt
+
+ def get_neutron_options(self, rdata):
+ settings = {}
+ for nkey in self.neutron_defaults.keys():
+ defv = self.neutron_defaults[nkey]['default']
+ rkey = self.neutron_defaults[nkey]['rel_key']
+ if rkey in rdata.keys():
+ if type(defv) is bool:
+ settings[nkey] = bool_from_string(rdata[rkey])
+ else:
+ settings[nkey] = rdata[rkey]
+ else:
+ settings[nkey] = defv
+ return settings
+
+
+class ExternalPortContext(NeutronPortContext):
+
+ def __call__(self):
+ ctxt = {}
+ ports = config('ext-port')
+ if ports:
+ ports = [p.strip() for p in ports.split()]
+ ports = self.resolve_ports(ports)
+ if ports:
+ ctxt = {"ext_port": ports[0]}
+ napi_settings = NeutronAPIContext()()
+ mtu = napi_settings.get('network_device_mtu')
+ if mtu:
+ ctxt['ext_port_mtu'] = mtu
+
+ return ctxt
+
+
+class DataPortContext(NeutronPortContext):
+
+ def __call__(self):
+ ports = config('data-port')
+ if ports:
+ portmap = parse_data_port_mappings(ports)
+ ports = portmap.values()
+ resolved = self.resolve_ports(ports)
+ normalized = {get_nic_hwaddr(port): port for port in resolved
+ if port not in ports}
+ normalized.update({port: port for port in resolved
+ if port in ports})
+ if resolved:
+ return {bridge: normalized[port] for bridge, port in
+ six.iteritems(portmap) if port in normalized.keys()}
+
+ return None
+
+
+class PhyNICMTUContext(DataPortContext):
+
+ def __call__(self):
+ ctxt = {}
+ mappings = super(PhyNICMTUContext, self).__call__()
+ if mappings and mappings.values():
+ ports = mappings.values()
+ napi_settings = NeutronAPIContext()()
+ mtu = napi_settings.get('network_device_mtu')
+ if mtu:
+ ctxt["devs"] = '\\n'.join(ports)
+ ctxt['mtu'] = mtu
+
+ return ctxt
+
+
+class NetworkServiceContext(OSContextGenerator):
+
+ def __init__(self, rel_name='quantum-network-service'):
+ self.rel_name = rel_name
+ self.interfaces = [rel_name]
+
+ def __call__(self):
+ for rid in relation_ids(self.rel_name):
+ for unit in related_units(rid):
+ rdata = relation_get(rid=rid, unit=unit)
+ ctxt = {
+ 'keystone_host': rdata.get('keystone_host'),
+ 'service_port': rdata.get('service_port'),
+ 'auth_port': rdata.get('auth_port'),
+ 'service_tenant': rdata.get('service_tenant'),
+ 'service_username': rdata.get('service_username'),
+ 'service_password': rdata.get('service_password'),
+ 'quantum_host': rdata.get('quantum_host'),
+ 'quantum_port': rdata.get('quantum_port'),
+ 'quantum_url': rdata.get('quantum_url'),
+ 'region': rdata.get('region'),
+ 'service_protocol':
+ rdata.get('service_protocol') or 'http',
+ 'auth_protocol':
+ rdata.get('auth_protocol') or 'http',
+ }
+ if context_complete(ctxt):
+ return ctxt
+ return {}
diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py
index 902757fe..f8851050 100644
--- a/hooks/charmhelpers/contrib/openstack/neutron.py
+++ b/hooks/charmhelpers/contrib/openstack/neutron.py
@@ -16,6 +16,7 @@
# Various utilies for dealing with Neutron and the renaming from Quantum.
+import six
from subprocess import check_output
from charmhelpers.core.hookenv import (
@@ -237,3 +238,72 @@ def network_manager():
else:
# ensure accurate naming for all releases post-H
return 'neutron'
+
+
+def parse_mappings(mappings):
+ parsed = {}
+ if mappings:
+ mappings = mappings.split(' ')
+ for m in mappings:
+ p = m.partition(':')
+ if p[1] == ':':
+ parsed[p[0].strip()] = p[2].strip()
+
+ return parsed
+
+
+def parse_bridge_mappings(mappings):
+ """Parse bridge mappings.
+
+ Mappings must be a space-delimited list of provider:bridge mappings.
+
+ Returns dict of the form {provider:bridge}.
+ """
+ return parse_mappings(mappings)
+
+
+def parse_data_port_mappings(mappings, default_bridge='br-data'):
+ """Parse data port mappings.
+
+ Mappings must be a space-delimited list of bridge:port mappings.
+
+ Returns dict of the form {bridge:port}.
+ """
+ _mappings = parse_mappings(mappings)
+ if not _mappings:
+ if not mappings:
+ return {}
+
+ # For backwards-compatibility we need to support port-only provided in
+ # config.
+ _mappings = {default_bridge: mappings.split(' ')[0]}
+
+ bridges = _mappings.keys()
+ ports = _mappings.values()
+ if len(set(bridges)) != len(bridges):
+ raise Exception("It is not allowed to have more than one port "
+ "configured on the same bridge")
+
+ if len(set(ports)) != len(ports):
+ raise Exception("It is not allowed to have the same port configured "
+ "on more than one bridge")
+
+ return _mappings
+
+
+def parse_vlan_range_mappings(mappings):
+ """Parse vlan range mappings.
+
+ Mappings must be a space-delimited list of provider:start:end mappings.
+
+ Returns dict of the form {provider: (start, end)}.
+ """
+ _mappings = parse_mappings(mappings)
+ if not _mappings:
+ return {}
+
+ mappings = {}
+ for p, r in six.iteritems(_mappings):
+ mappings[p] = tuple(r.split(':'))
+
+ return mappings
diff --git a/hooks/charmhelpers/contrib/openstack/templates/git.upstart b/hooks/charmhelpers/contrib/openstack/templates/git.upstart
new file mode 100644
index 00000000..da94ad12
--- /dev/null
+++ b/hooks/charmhelpers/contrib/openstack/templates/git.upstart
@@ -0,0 +1,13 @@
+description "{{ service_description }}"
+author "Juju {{ service_name }} Charm "
+
+start on runlevel [2345]
+stop on runlevel [!2345]
+
+respawn
+
+exec start-stop-daemon --start --chuid {{ user_name }} \
+ --chdir {{ start_dir }} --name {{ process_name }} \
+ --exec {{ executable_name }} -- \
+ --config-file={{ config_file }} \
+ --log-file={{ log_file }}
diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken b/hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken
new file mode 100644
index 00000000..2a37edd5
--- /dev/null
+++ b/hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken
@@ -0,0 +1,9 @@
+{% if auth_host -%}
+[keystone_authtoken]
+identity_uri = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/{{ auth_admin_prefix }}
+auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/{{ service_admin_prefix }}
+admin_tenant_name = {{ admin_tenant_name }}
+admin_user = {{ admin_user }}
+admin_password = {{ admin_password }}
+signing_dir = {{ signing_dir }}
+{% endif -%}
diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo b/hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo
new file mode 100644
index 00000000..b444c9c9
--- /dev/null
+++ b/hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo
@@ -0,0 +1,22 @@
+{% if rabbitmq_host or rabbitmq_hosts -%}
+[oslo_messaging_rabbit]
+rabbit_userid = {{ rabbitmq_user }}
+rabbit_virtual_host = {{ rabbitmq_virtual_host }}
+rabbit_password = {{ rabbitmq_password }}
+{% if rabbitmq_hosts -%}
+rabbit_hosts = {{ rabbitmq_hosts }}
+{% if rabbitmq_ha_queues -%}
+rabbit_ha_queues = True
+rabbit_durable_queues = False
+{% endif -%}
+{% else -%}
+rabbit_host = {{ rabbitmq_host }}
+{% endif -%}
+{% if rabbit_ssl_port -%}
+rabbit_use_ssl = True
+rabbit_port = {{ rabbit_ssl_port }}
+{% if rabbit_ssl_ca -%}
+kombu_ssl_ca_certs = {{ rabbit_ssl_ca }}
+{% endif -%}
+{% endif -%}
+{% endif -%}
diff --git a/hooks/charmhelpers/contrib/openstack/templates/zeromq b/hooks/charmhelpers/contrib/openstack/templates/section-zeromq
similarity index 66%
rename from hooks/charmhelpers/contrib/openstack/templates/zeromq
rename to hooks/charmhelpers/contrib/openstack/templates/section-zeromq
index 0695eef1..95f1a76c 100644
--- a/hooks/charmhelpers/contrib/openstack/templates/zeromq
+++ b/hooks/charmhelpers/contrib/openstack/templates/section-zeromq
@@ -3,12 +3,12 @@
rpc_backend = zmq
rpc_zmq_host = {{ zmq_host }}
{% if zmq_redis_address -%}
-rpc_zmq_matchmaker = oslo.messaging._drivers.matchmaker_redis.MatchMakerRedis
+rpc_zmq_matchmaker = redis
matchmaker_heartbeat_freq = 15
matchmaker_heartbeat_ttl = 30
[matchmaker_redis]
host = {{ zmq_redis_address }}
{% else -%}
-rpc_zmq_matchmaker = oslo.messaging._drivers.matchmaker_ring.MatchMakerRing
+rpc_zmq_matchmaker = ring
{% endif -%}
{% endif -%}
diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py
index 4f110c63..78c5e2df 100644
--- a/hooks/charmhelpers/contrib/openstack/utils.py
+++ b/hooks/charmhelpers/contrib/openstack/utils.py
@@ -30,6 +30,10 @@ import yaml
from charmhelpers.contrib.network import ip
+from charmhelpers.core import (
+ unitdata,
+)
+
from charmhelpers.core.hookenv import (
config,
log as juju_log,
@@ -330,6 +334,21 @@ def configure_installation_source(rel):
error_out("Invalid openstack-release specified: %s" % rel)
+def config_value_changed(option):
+ """
+ Determine if config value changed since last call to this function.
+ """
+ hook_data = unitdata.HookData()
+ with hook_data():
+ db = unitdata.kv()
+ current = config(option)
+ saved = db.get(option)
+ db.set(option, current)
+ if saved is None:
+ return False
+ return current != saved
+
+
def save_script_rc(script_path="scripts/scriptrc", **env_vars):
"""
Write an rc file in the charm-delivered directory containing
@@ -469,82 +488,95 @@ def os_requires_version(ostack_release, pkg):
def git_install_requested():
- """Returns true if openstack-origin-git is specified."""
- return config('openstack-origin-git') != "None"
+ """
+ Returns true if openstack-origin-git is specified.
+ """
+ return config('openstack-origin-git') is not 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
+def git_clone_and_install(projects_yaml, core_project):
+ """
+ Clone/install all specified OpenStack repositories.
- if file_name == "None":
+ The expected format of projects_yaml is:
+ repositories:
+ - {name: keystone,
+ repository: 'git://git.openstack.org/openstack/keystone.git',
+ branch: 'stable/icehouse'}
+ - {name: requirements,
+ repository: 'git://git.openstack.org/openstack/requirements.git',
+ branch: 'stable/icehouse'}
+ directory: /mnt/openstack-git
+
+ The directory key is optional.
+ """
+ global requirements_dir
+ parent_dir = '/mnt/openstack-git'
+
+ if not projects_yaml:
return
- yaml_file = os.path.join(charm_dir(), file_name)
+ projects = yaml.load(projects_yaml)
+ _git_validate_projects_yaml(projects, core_project)
- # 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')
+ if 'directory' in projects.keys():
+ parent_dir = projects['directory']
- # 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))
+ for p in projects['repositories']:
+ repo = p['repository']
+ branch = p['branch']
+ if p['name'] == 'requirements':
+ repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
+ update_requirements=False)
+ requirements_dir = repo_dir
+ else:
+ repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
+ update_requirements=True)
-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 = []
+def _git_validate_projects_yaml(projects, core_project):
+ """
+ Validate the projects yaml.
+ """
+ _git_ensure_key_exists('repositories', projects)
- 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
+ for project in projects['repositories']:
+ _git_ensure_key_exists('name', project.keys())
+ _git_ensure_key_exists('repository', project.keys())
+ _git_ensure_key_exists('branch', project.keys())
+
+ if projects['repositories'][0]['name'] != 'requirements':
+ error_out('{} git repo must be specified first'.format('requirements'))
+
+ if projects['repositories'][-1]['name'] != core_project:
+ error_out('{} git repo must be specified last'.format(core_project))
-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))
+def _git_ensure_key_exists(key, keys):
+ """
+ Ensure that key exists in keys.
+ """
+ if key not in keys:
+ error_out('openstack-origin-git key \'{}\' is missing'.format(key))
- 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)
+
+def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements):
+ """
+ Clone and install a single git repository.
+ """
+ dest_dir = os.path.join(parent_dir, os.path.basename(repo))
+
+ if not os.path.exists(parent_dir):
+ juju_log('Directory already exists at {}. '
+ 'No need to create directory.'.format(parent_dir))
+ os.mkdir(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)
+ repo_dir = install_remote(repo, dest=parent_dir, branch=branch)
else:
repo_dir = dest_dir
@@ -561,16 +593,39 @@ def _git_clone_and_install_single(repo, branch, update_requirements=False):
def _git_update_requirements(package_dir, reqs_dir):
- """Update from global requirements.
+ """
+ Update from global requirements.
- Update an OpenStack git directory's requirements.txt and
- test-requirements.txt from global-requirements.txt."""
+ 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)
+ cmd = ['python', 'update.py', package_dir]
try:
- subprocess.check_call(cmd.split(' '))
+ subprocess.check_call(cmd)
except subprocess.CalledProcessError:
package = os.path.basename(package_dir)
error_out("Error updating {} from global-requirements.txt".format(package))
os.chdir(orig_dir)
+
+
+def git_src_dir(projects_yaml, project):
+ """
+ Return the directory where the specified project's source is located.
+ """
+ parent_dir = '/mnt/openstack-git'
+
+ if not projects_yaml:
+ return
+
+ projects = yaml.load(projects_yaml)
+
+ if 'directory' in projects.keys():
+ parent_dir = projects['directory']
+
+ for p in projects['repositories']:
+ if p['name'] == project:
+ return os.path.join(parent_dir, os.path.basename(p['repository']))
+
+ return None
diff --git a/hooks/charmhelpers/contrib/python/debug.py b/hooks/charmhelpers/contrib/python/debug.py
new file mode 100644
index 00000000..960770f2
--- /dev/null
+++ b/hooks/charmhelpers/contrib/python/debug.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+# Copyright 2014-2015 Canonical Limited.
+#
+# This file is part of charm-helpers.
+#
+# charm-helpers is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# charm-helpers is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with charm-helpers. If not, see .
+
+from __future__ import print_function
+
+__author__ = "Jorge Niedbalski "
+
+import atexit
+import sys
+
+from charmhelpers.contrib.python.rpdb import Rpdb
+from charmhelpers.core.hookenv import (
+ open_port,
+ close_port,
+ ERROR,
+ log
+)
+
+DEFAULT_ADDR = "0.0.0.0"
+DEFAULT_PORT = 4444
+
+
+def _error(message):
+ log(message, level=ERROR)
+
+
+def set_trace(addr=DEFAULT_ADDR, port=DEFAULT_PORT):
+ """
+ Set a trace point using the remote debugger
+ """
+ atexit.register(close_port, port)
+ try:
+ log("Starting a remote python debugger session on %s:%s" % (addr,
+ port))
+ open_port(port)
+ debugger = Rpdb(addr=addr, port=port)
+ debugger.set_trace(sys._getframe().f_back)
+ except:
+ _error("Cannot start a remote debug session on %s:%s" % (addr,
+ port))
diff --git a/hooks/charmhelpers/contrib/python/rpdb.py b/hooks/charmhelpers/contrib/python/rpdb.py
new file mode 100644
index 00000000..80bf66aa
--- /dev/null
+++ b/hooks/charmhelpers/contrib/python/rpdb.py
@@ -0,0 +1,58 @@
+# Copyright 2014-2015 Canonical Limited.
+#
+# This file is part of charm-helpers.
+#
+# charm-helpers is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# charm-helpers is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with charm-helpers. If not, see .
+
+"""Remote Python Debugger (pdb wrapper)."""
+
+__author__ = "Bertrand Janin "
+__version__ = "0.1.3"
+
+import pdb
+import socket
+import sys
+
+
+class Rpdb(pdb.Pdb):
+
+ def __init__(self, addr="127.0.0.1", port=4444):
+ """Initialize the socket and initialize pdb."""
+
+ # Backup stdin and stdout before replacing them by the socket handle
+ self.old_stdout = sys.stdout
+ self.old_stdin = sys.stdin
+
+ # Open a 'reusable' socket to let the webapp reload on the same port
+ self.skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
+ self.skt.bind((addr, port))
+ self.skt.listen(1)
+ (clientsocket, address) = self.skt.accept()
+ handle = clientsocket.makefile('rw')
+ pdb.Pdb.__init__(self, completekey='tab', stdin=handle, stdout=handle)
+ sys.stdout = sys.stdin = handle
+
+ def shutdown(self):
+ """Revert stdin and stdout, close the socket."""
+ sys.stdout = self.old_stdout
+ sys.stdin = self.old_stdin
+ self.skt.close()
+ self.set_continue()
+
+ def do_continue(self, arg):
+ """Stop all operation on ``continue``."""
+ self.shutdown()
+ return 1
+
+ do_EOF = do_quit = do_exit = do_c = do_cont = do_continue
diff --git a/hooks/charmhelpers/contrib/python/version.py b/hooks/charmhelpers/contrib/python/version.py
new file mode 100644
index 00000000..6dc11296
--- /dev/null
+++ b/hooks/charmhelpers/contrib/python/version.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+# Copyright 2014-2015 Canonical Limited.
+#
+# This file is part of charm-helpers.
+#
+# charm-helpers is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# charm-helpers is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with charm-helpers. If not, see .
+
+__author__ = "Jorge Niedbalski "
+
+import sys
+
+
+def current_version():
+ """Current system python version"""
+ return sys.version_info
+
+
+def current_version_string():
+ """Current system python version as string major.minor.micro"""
+ return "{0}.{1}.{2}".format(sys.version_info.major,
+ sys.version_info.minor,
+ sys.version_info.micro)
diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py
index cf552b39..715dd4c5 100644
--- a/hooks/charmhelpers/core/hookenv.py
+++ b/hooks/charmhelpers/core/hookenv.py
@@ -566,3 +566,29 @@ class Hooks(object):
def charm_dir():
"""Return the root directory of the current charm"""
return os.environ.get('CHARM_DIR')
+
+
+@cached
+def action_get(key=None):
+ """Gets the value of an action parameter, or all key/value param pairs"""
+ cmd = ['action-get']
+ if key is not None:
+ cmd.append(key)
+ cmd.append('--format=json')
+ action_data = json.loads(subprocess.check_output(cmd).decode('UTF-8'))
+ return action_data
+
+
+def action_set(values):
+ """Sets the values to be returned after the action finishes"""
+ cmd = ['action-set']
+ for k, v in list(values.items()):
+ cmd.append('{}={}'.format(k, v))
+ subprocess.check_call(cmd)
+
+
+def action_fail(message):
+ """Sets the action status to failed and sets the error message.
+
+ The results set by action_set are preserved."""
+ subprocess.check_call(['action-fail', message])
diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py
index b771c611..830822af 100644
--- a/hooks/charmhelpers/core/host.py
+++ b/hooks/charmhelpers/core/host.py
@@ -339,12 +339,16 @@ def lsb_release():
def pwgen(length=None):
"""Generate a random pasword."""
if length is None:
+ # A random length is ok to use a weak PRNG
length = random.choice(range(35, 45))
alphanumeric_chars = [
l for l in (string.ascii_letters + string.digits)
if l not in 'l0QD1vAEIOUaeiou']
+ # Use a crypto-friendly PRNG (e.g. /dev/urandom) for making the
+ # actual password
+ random_generator = random.SystemRandom()
random_chars = [
- random.choice(alphanumeric_chars) for _ in range(length)]
+ random_generator.choice(alphanumeric_chars) for _ in range(length)]
return(''.join(random_chars))
diff --git a/hooks/charmhelpers/core/services/helpers.py b/hooks/charmhelpers/core/services/helpers.py
index 15b21664..3eb5fb44 100644
--- a/hooks/charmhelpers/core/services/helpers.py
+++ b/hooks/charmhelpers/core/services/helpers.py
@@ -139,7 +139,7 @@ class MysqlRelation(RelationContext):
def __init__(self, *args, **kwargs):
self.required_keys = ['host', 'user', 'password', 'database']
- super(HttpRelation).__init__(self, *args, **kwargs)
+ RelationContext.__init__(self, *args, **kwargs)
class HttpRelation(RelationContext):
@@ -154,7 +154,7 @@ class HttpRelation(RelationContext):
def __init__(self, *args, **kwargs):
self.required_keys = ['host', 'port']
- super(HttpRelation).__init__(self, *args, **kwargs)
+ RelationContext.__init__(self, *args, **kwargs)
def provide_data(self):
return {
diff --git a/hooks/charmhelpers/core/unitdata.py b/hooks/charmhelpers/core/unitdata.py
index 3000134a..406a35c5 100644
--- a/hooks/charmhelpers/core/unitdata.py
+++ b/hooks/charmhelpers/core/unitdata.py
@@ -443,7 +443,7 @@ class HookData(object):
data = hookenv.execution_environment()
self.conf = conf_delta = self.kv.delta(data['conf'], 'config')
self.rels = rels_delta = self.kv.delta(data['rels'], 'rels')
- self.kv.set('env', data['env'])
+ self.kv.set('env', dict(data['env']))
self.kv.set('unit', data['unit'])
self.kv.set('relid', data.get('relid'))
return conf_delta, rels_delta
diff --git a/hooks/nova_compute_context.py b/hooks/nova_compute_context.py
index 0049437a..5281cddd 100644
--- a/hooks/nova_compute_context.py
+++ b/hooks/nova_compute_context.py
@@ -1,5 +1,5 @@
import uuid
-
+import platform
from charmhelpers.contrib.openstack import context
from charmhelpers.core.host import service_running, service_start
from charmhelpers.fetch import apt_install, filter_installed_packages
@@ -104,6 +104,9 @@ class NovaComputeLibvirtContext(context.OSContextGenerator):
'listen_tls': 0,
}
+ # get the processor architecture to use in the nova.conf template
+ ctxt['arch'] = platform.machine()
+
# enable tcp listening if configured for live migration.
if config('enable-live-migration'):
ctxt['libvirtd_opts'] += ' -l'
@@ -125,6 +128,16 @@ class NovaComputeLibvirtContext(context.OSContextGenerator):
return ctxt
+class NovaComputeLibvirtOverrideContext(context.OSContextGenerator):
+ """Provides overrides to the libvirt-bin service"""
+ interfaces = []
+
+ def __call__(self):
+ ctxt = {}
+ ctxt['overrides'] = "limit nofile 65535 65535"
+ return ctxt
+
+
class NovaComputeVirtContext(context.OSContextGenerator):
interfaces = []
@@ -341,6 +354,10 @@ class CloudComputeContext(context.OSContextGenerator):
ctxt['network_manager'] = self.network_manager
ctxt['network_manager_config'] = net_manager
+ net_dev_mtu = config('network-device-mtu')
+ if net_dev_mtu:
+ ctxt['network_device_mtu'] = net_dev_mtu
+
vol_service = self.volume_context()
if vol_service:
ctxt['volume_service'] = vol_service
@@ -386,6 +403,19 @@ class InstanceConsoleContext(context.OSContextGenerator):
return ctxt
+class MetadataServiceContext(context.OSContextGenerator):
+
+ def __call__(self):
+ ctxt = {}
+ for rid in relation_ids('neutron-plugin'):
+ for unit in related_units(rid):
+ rdata = relation_get(rid=rid, unit=unit)
+ if 'metadata-shared-secret' in rdata:
+ ctxt['metadata_shared_secret'] = \
+ rdata['metadata-shared-secret']
+ return ctxt
+
+
class NeutronComputeContext(context.NeutronContext):
interfaces = []
diff --git a/hooks/nova_compute_hooks.py b/hooks/nova_compute_hooks.py
index f3364589..9d99acf2 100755
--- a/hooks/nova_compute_hooks.py
+++ b/hooks/nova_compute_hooks.py
@@ -17,10 +17,12 @@ from charmhelpers.core.hookenv import (
)
from charmhelpers.core.host import (
restart_on_change,
+ service_restart,
)
from charmhelpers.fetch import (
apt_install,
+ apt_purge,
apt_update,
filter_installed_packages,
)
@@ -46,7 +48,6 @@ from nova_compute_utils import (
initialize_ssh_keys,
migration_enabled,
network_manager,
- neutron_plugin,
do_openstack_upgrade,
public_ssh_key,
restart_map,
@@ -59,6 +60,7 @@ from nova_compute_utils import (
fix_path_ownership,
get_topics,
assert_charm_supports_ipv6,
+ manage_ovs,
)
from charmhelpers.contrib.network.ip import (
@@ -142,10 +144,10 @@ def amqp_changed():
return
CONFIGS.write(NOVA_CONF)
# No need to write NEUTRON_CONF if neutron-plugin is managing it
- if not relation_ids('neutron-plugin'):
- if network_manager() == 'quantum' and neutron_plugin() == 'ovs':
+ if manage_ovs():
+ if network_manager() == 'quantum':
CONFIGS.write(QUANTUM_CONF)
- if network_manager() == 'neutron' and neutron_plugin() == 'ovs':
+ if network_manager() == 'neutron':
CONFIGS.write(NEUTRON_CONF)
@@ -239,6 +241,8 @@ def compute_changed():
@restart_on_change(restart_map())
def ceph_joined():
apt_install(filter_installed_packages(['ceph-common']), fatal=True)
+ # Bug 1427660
+ service_restart('libvirt-bin')
@hooks.hook('ceph-relation-changed')
@@ -342,6 +346,18 @@ def update_nrpe_config():
nrpe_setup.write()
+@hooks.hook('neutron-plugin-relation-changed')
+@restart_on_change(restart_map())
+def neutron_plugin_changed():
+ settings = relation_get()
+ if 'metadata-shared-secret' in settings:
+ apt_update()
+ apt_install('nova-api-metadata', fatal=True)
+ else:
+ apt_purge('nova-api-metadata', fatal=True)
+ CONFIGS.write(NOVA_CONF)
+
+
def main():
try:
hooks.execute(sys.argv)
diff --git a/hooks/nova_compute_utils.py b/hooks/nova_compute_utils.py
index 24f0873c..b3dbeb51 100644
--- a/hooks/nova_compute_utils.py
+++ b/hooks/nova_compute_utils.py
@@ -39,7 +39,9 @@ from charmhelpers.contrib.openstack.utils import (
from nova_compute_context import (
CloudComputeContext,
+ MetadataServiceContext,
NovaComputeLibvirtContext,
+ NovaComputeLibvirtOverrideContext,
NovaComputeCephContext,
NeutronComputeContext,
InstanceConsoleContext,
@@ -62,6 +64,7 @@ NOVA_CONF_DIR = "/etc/nova"
QEMU_CONF = '/etc/libvirt/qemu.conf'
LIBVIRTD_CONF = '/etc/libvirt/libvirtd.conf'
LIBVIRT_BIN = '/etc/default/libvirt-bin'
+LIBVIRT_BIN_OVERRIDES = '/etc/init/libvirt-bin.override'
NOVA_CONF = '%s/nova.conf' % NOVA_CONF_DIR
BASE_RESOURCE_MAP = {
@@ -77,6 +80,10 @@ BASE_RESOURCE_MAP = {
'services': ['libvirt-bin'],
'contexts': [NovaComputeLibvirtContext()],
},
+ LIBVIRT_BIN_OVERRIDES: {
+ 'services': ['libvirt-bin'],
+ 'contexts': [NovaComputeLibvirtOverrideContext()],
+ },
NOVA_CONF: {
'services': ['nova-compute'],
'contexts': [context.AMQPContext(ssl_dir=NOVA_CONF_DIR),
@@ -96,6 +103,7 @@ BASE_RESOURCE_MAP = {
InstanceConsoleContext(),
context.ZeroMQContext(),
context.NotificationDriverContext(),
+ MetadataServiceContext(),
HostIPContext()],
},
}
@@ -118,7 +126,7 @@ QUANTUM_RESOURCES = {
'contexts': [NeutronComputeContext(),
context.AMQPContext(ssl_dir=QUANTUM_CONF_DIR),
context.SyslogContext()],
- }
+ },
}
NEUTRON_CONF_DIR = "/etc/neutron"
@@ -130,7 +138,7 @@ NEUTRON_RESOURCES = {
'contexts': [NeutronComputeContext(),
context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR),
context.SyslogContext()],
- }
+ },
}
@@ -175,7 +183,9 @@ def resource_map():
# depending on the plugin used.
# NOTE(james-page): only required for ovs plugin right now
if net_manager in ['neutron', 'quantum']:
- if not relation_ids('neutron-plugin') and plugin == 'ovs':
+ # This stanza supports the legacy case of ovs supported within
+ # compute charm code (now moved to neutron-openvswitch subordinate)
+ if manage_ovs():
if net_manager == 'quantum':
nm_rsc = QUANTUM_RESOURCES
if net_manager == 'neutron':
@@ -203,6 +213,8 @@ def resource_map():
}
resource_map.update(CEPH_RESOURCES)
+ if enable_nova_metadata():
+ resource_map[NOVA_CONF]['services'].append('nova-api-metadata')
return resource_map
@@ -256,7 +268,8 @@ def determine_packages():
if (net_manager in ['flatmanager', 'flatdhcpmanager'] and
config('multi-host').lower() == 'yes'):
packages.extend(['nova-api', 'nova-network'])
- elif net_manager in ['quantum', 'neutron']:
+ elif (net_manager in ['quantum', 'neutron']
+ and neutron_plugin_legacy_mode()):
plugin = neutron_plugin()
pkg_lists = neutron_plugin_attribute(plugin, 'packages', net_manager)
for pkg_list in pkg_lists:
@@ -271,6 +284,8 @@ def determine_packages():
except KeyError:
log('Unsupported virt-type configured: %s' % virt_type)
raise
+ if enable_nova_metadata():
+ packages.append('nova-api-metadata')
return packages
@@ -492,3 +507,21 @@ def assert_charm_supports_ipv6():
if lsb_release()['DISTRIB_CODENAME'].lower() < "trusty":
raise Exception("IPv6 is not supported in the charms for Ubuntu "
"versions less than Trusty 14.04")
+
+
+def enable_nova_metadata():
+ ctxt = MetadataServiceContext()()
+ return 'metadata_shared_secret' in ctxt
+
+
+def neutron_plugin_legacy_mode():
+ # If a charm is attatched to the neutron-plugin relation then its managing
+ # neutron
+ if relation_ids('neutron-plugin'):
+ return False
+ else:
+ return config('manage-neutron-plugin-legacy-mode')
+
+
+def manage_ovs():
+ return neutron_plugin_legacy_mode() and neutron_plugin() == 'ovs'
diff --git a/templates/havana/nova.conf b/templates/havana/nova.conf
index 20aa1ec0..e88b92c5 100644
--- a/templates/havana/nova.conf
+++ b/templates/havana/nova.conf
@@ -13,7 +13,14 @@ logdir=/var/log/nova
state_path=/var/lib/nova
lock_path=/var/lock/nova
force_dhcp_release=True
+
+{% if arch == 'aarch64' -%}
+libvirt_use_virtio_for_bridges=False
+libvirt_disk_prefix=vd
+{% else -%}
libvirt_use_virtio_for_bridges=True
+{% endif -%}
+
verbose=True
use_syslog = {{ use_syslog }}
ec2_private_dns_show_ip=True
@@ -64,6 +71,11 @@ security_group_api = neutron
firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% endif -%}
+{% if neutron_plugin and neutron_plugin == 'Calico' -%}
+security_group_api = neutron
+firewall_driver = nova.virt.firewall.NoopFirewallDriver
+{% endif -%}
+
{% if network_manager_config -%}
{% for key, value in network_manager_config.iteritems() -%}
{{ key }} = {{ value }}
@@ -76,6 +88,10 @@ network_api_class = nova.network.neutronv2.api.API
network_manager = nova.network.manager.FlatDHCPManager
{% endif -%}
+{% if network_device_mtu -%}
+network_device_mtu = {{ network_device_mtu }}
+{% endif -%}
+
{% if volume_service -%}
volume_api_class = nova.volume.cinder.API
{% endif -%}
diff --git a/templates/juno/nova.conf b/templates/juno/nova.conf
index 1b7cbcf9..65fb19f9 100644
--- a/templates/juno/nova.conf
+++ b/templates/juno/nova.conf
@@ -13,7 +13,14 @@ logdir=/var/log/nova
state_path=/var/lib/nova
lock_path=/var/lock/nova
force_dhcp_release=True
+
+{% if arch == 'aarch64' -%}
+libvirt_use_virtio_for_bridges=False
+libvirt_disk_prefix=vd
+{% else -%}
libvirt_use_virtio_for_bridges=True
+{% endif -%}
+
verbose=True
use_syslog = {{ use_syslog }}
ec2_private_dns_show_ip=True
@@ -31,6 +38,11 @@ my_ip = {{ host_ip }}
glance_api_servers = {{ glance_api_servers }}
{% endif -%}
+{% if metadata_shared_secret -%}
+neutron_metadata_proxy_shared_secret = {{ metadata_shared_secret }}
+service_neutron_metadata_proxy=True
+{% endif -%}
+
{% if console_vnc_type -%}
vnc_enabled = True
novnc_enabled = True
@@ -64,6 +76,11 @@ security_group_api = neutron
firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% endif -%}
+{% if neutron_plugin and neutron_plugin == 'Calico' -%}
+security_group_api = neutron
+firewall_driver = nova.virt.firewall.NoopFirewallDriver
+{% endif -%}
+
{% if network_manager_config -%}
{% for key, value in network_manager_config.iteritems() -%}
{{ key }} = {{ value }}
@@ -96,8 +113,6 @@ instances_path = {{ instances_path }}
{% endfor -%}
{% endif -%}
-{% include "zeromq" %}
-
{% if console_access_protocol == 'spice' -%}
[spice]
agent_enabled = True
diff --git a/templates/kilo/nova.conf b/templates/kilo/nova.conf
index 61245ed1..385106cc 100644
--- a/templates/kilo/nova.conf
+++ b/templates/kilo/nova.conf
@@ -11,7 +11,6 @@ dhcpbridge_flagfile=/etc/nova/nova.conf
dhcpbridge=/usr/bin/nova-dhcpbridge
logdir=/var/log/nova
state_path=/var/lib/nova
-lock_path=/var/lock/nova
force_dhcp_release=True
verbose=True
use_syslog = {{ use_syslog }}
@@ -22,9 +21,15 @@ auth_strategy=keystone
compute_driver=libvirt.LibvirtDriver
my_ip = {{ host_ip }}
-{% include "parts/database" %}
+{% if arch == 'aarch64' -%}
+libvirt_use_virtio_for_bridges=False
+libvirt_disk_prefix=vd
+{% endif -%}
-{% include "parts/rabbitmq" %}
+{% if metadata_shared_secret -%}
+neutron_metadata_proxy_shared_secret = {{ metadata_shared_secret }}
+service_neutron_metadata_proxy=True
+{% endif -%}
{% if console_vnc_type -%}
vnc_enabled = True
@@ -59,6 +64,11 @@ security_group_api = neutron
firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% endif -%}
+{% if neutron_plugin and neutron_plugin == 'Calico' -%}
+security_group_api = neutron
+firewall_driver = nova.virt.firewall.NoopFirewallDriver
+{% endif -%}
+
{% if network_manager != 'neutron' and network_manager_config -%}
{% for key, value in network_manager_config.iteritems() -%}
{{ key }} = {{ value }}
@@ -91,7 +101,7 @@ instances_path = {{ instances_path }}
{% endfor -%}
{% endif -%}
-{% include "zeromq" %}
+{% include "section-zeromq" %}
{% if network_manager == 'neutron' and network_manager_config -%}
[neutron]
@@ -140,3 +150,10 @@ live_migration_uri = {{ live_migration_uri }}
{% if disk_cachemodes -%}
disk_cachemodes = {{ disk_cachemodes }}
{% endif -%}
+
+{% include "parts/section-database" %}
+
+{% include "section-rabbitmq-oslo" %}
+
+[oslo_concurrency]
+lock_path=/var/lock/nova
diff --git a/templates/kilo/zeromq b/templates/kilo/zeromq
deleted file mode 100644
index 873be80e..00000000
--- a/templates/kilo/zeromq
+++ /dev/null
@@ -1,14 +0,0 @@
-{% if zmq_host -%}
-# ZeroMQ configuration (restart-nonce: {{ zmq_nonce }})
-rpc_backend = zmq
-rpc_zmq_host = {{ zmq_host }}
-{% if zmq_redis_address -%}
-rpc_zmq_matchmaker = oslo_messaging._drivers.matchmaker_redis.MatchMakerRedis
-matchmaker_heartbeat_freq = 15
-matchmaker_heartbeat_ttl = 30
-[matchmaker_redis]
-host = {{ zmq_redis_address }}
-{% else -%}
-rpc_zmq_matchmaker = oslo_messaging._drivers.matchmaker_ring.MatchMakerRing
-{% endif -%}
-{% endif -%}
diff --git a/templates/libvirt-bin.override b/templates/libvirt-bin.override
new file mode 100644
index 00000000..d29009c5
--- /dev/null
+++ b/templates/libvirt-bin.override
@@ -0,0 +1,3 @@
+{% if overrides -%}
+{{ overrides }}
+{% endif -%}
diff --git a/templates/parts/section-database b/templates/parts/section-database
new file mode 100644
index 00000000..ae4a5ba0
--- /dev/null
+++ b/templates/parts/section-database
@@ -0,0 +1,4 @@
+{% if database_host -%}
+[database]
+connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
+{% endif -%}
diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py
index 776da875..aeb2fafe 100644
--- a/tests/basic_deployment.py
+++ b/tests/basic_deployment.py
@@ -409,7 +409,7 @@ class NovaBasicDeployment(OpenStackAmuletDeployment):
found = True
if instance.status != 'ACTIVE':
msg = "cirros instance is not active"
- amulet.raise_status(amulet.FAIL, msg=message)
+ amulet.raise_status(amulet.FAIL, msg=msg)
if not found:
message = "nova cirros instance does not exist"
diff --git a/tests/charmhelpers/contrib/openstack/amulet/deployment.py b/tests/charmhelpers/contrib/openstack/amulet/deployment.py
index 0cfeaa4c..0e0db566 100644
--- a/tests/charmhelpers/contrib/openstack/amulet/deployment.py
+++ b/tests/charmhelpers/contrib/openstack/amulet/deployment.py
@@ -15,6 +15,7 @@
# along with charm-helpers. If not, see .
import six
+from collections import OrderedDict
from charmhelpers.contrib.amulet.deployment import (
AmuletDeployment
)
@@ -100,12 +101,34 @@ class OpenStackAmuletDeployment(AmuletDeployment):
"""
(self.precise_essex, self.precise_folsom, self.precise_grizzly,
self.precise_havana, self.precise_icehouse,
- self.trusty_icehouse) = range(6)
+ self.trusty_icehouse, self.trusty_juno, self.trusty_kilo) = range(8)
releases = {
('precise', None): self.precise_essex,
('precise', 'cloud:precise-folsom'): self.precise_folsom,
('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
('precise', 'cloud:precise-havana'): self.precise_havana,
('precise', 'cloud:precise-icehouse'): self.precise_icehouse,
- ('trusty', None): self.trusty_icehouse}
+ ('trusty', None): self.trusty_icehouse,
+ ('trusty', 'cloud:trusty-juno'): self.trusty_juno,
+ ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo}
return releases[(self.series, self.openstack)]
+
+ def _get_openstack_release_string(self):
+ """Get openstack release string.
+
+ Return a string representing the openstack release.
+ """
+ releases = OrderedDict([
+ ('precise', 'essex'),
+ ('quantal', 'folsom'),
+ ('raring', 'grizzly'),
+ ('saucy', 'havana'),
+ ('trusty', 'icehouse'),
+ ('utopic', 'juno'),
+ ('vivid', 'kilo'),
+ ])
+ if self.openstack:
+ os_origin = self.openstack.split(':')[1]
+ return os_origin.split('%s-' % self.series)[1].split('/')[0]
+ else:
+ return releases[self.series]
diff --git a/unit_tests/test_nova_compute_contexts.py b/unit_tests/test_nova_compute_contexts.py
index 956d28b2..e5987de3 100644
--- a/unit_tests/test_nova_compute_contexts.py
+++ b/unit_tests/test_nova_compute_contexts.py
@@ -1,3 +1,5 @@
+import platform
+
from mock import patch
from test_utils import CharmTestCase
@@ -179,6 +181,7 @@ class NovaComputeContextTests(CharmTestCase):
self.assertEqual(
{'libvirtd_opts': '-d',
+ 'arch': platform.machine(),
'listen_tls': 0,
'host_uuid': 'e46e530d-18ae-4a67-9ff0-e6e2ba7c60a7'}, libvirt())
@@ -190,6 +193,7 @@ class NovaComputeContextTests(CharmTestCase):
self.assertEqual(
{'libvirtd_opts': '-d -l',
+ 'arch': platform.machine(),
'listen_tls': 0,
'host_uuid': 'e46e530d-18ae-4a67-9ff0-e6e2ba7c60a7'}, libvirt())
@@ -201,9 +205,10 @@ class NovaComputeContextTests(CharmTestCase):
self.assertEqual(
{'libvirtd_opts': '-d',
+ 'disk_cachemodes': 'file=unsafe,block=none',
+ 'arch': platform.machine(),
'listen_tls': 0,
- 'host_uuid': 'e46e530d-18ae-4a67-9ff0-e6e2ba7c60a7',
- 'disk_cachemodes': 'file=unsafe,block=none'}, libvirt())
+ 'host_uuid': 'e46e530d-18ae-4a67-9ff0-e6e2ba7c60a7'}, libvirt())
@patch.object(context.NeutronComputeContext, 'network_manager')
@patch.object(context.NeutronComputeContext, 'plugin')
@@ -228,3 +233,11 @@ class NovaComputeContextTests(CharmTestCase):
self.assertEquals(
{'host_ip': '172.24.0.79'}, host_ip())
self.unit_get.assert_called_with('private-address')
+
+ def test_metadata_service_ctxt(self):
+ self.relation_ids.return_value = 'neutron-plugin:0'
+ self.related_units.return_value = 'neutron-openvswitch/0'
+ self.test_relation.set({'metadata-shared-secret': 'shared_secret'})
+ metadatactxt = context.MetadataServiceContext()
+ self.assertEqual(metadatactxt(), {'metadata_shared_secret':
+ 'shared_secret'})
diff --git a/unit_tests/test_nova_compute_hooks.py b/unit_tests/test_nova_compute_hooks.py
index 7fac3a1b..2d171789 100644
--- a/unit_tests/test_nova_compute_hooks.py
+++ b/unit_tests/test_nova_compute_hooks.py
@@ -27,6 +27,7 @@ TO_PATCH = [
'apt_update',
'filter_installed_packages',
'restart_on_change',
+ 'service_restart',
# charmhelpers.contrib.openstack.utils
'configure_installation_source',
'openstack_upgrade_available',
@@ -41,7 +42,7 @@ TO_PATCH = [
'migration_enabled',
'do_openstack_upgrade',
'network_manager',
- 'neutron_plugin',
+ 'manage_ovs',
'public_ssh_key',
'register_configs',
'disable_shell',
@@ -169,7 +170,6 @@ class NovaComputeRelationsTests(CharmTestCase):
configs.write = MagicMock()
if quantum:
self.network_manager.return_value = 'quantum'
- self.neutron_plugin.return_value = 'ovs'
hooks.amqp_changed()
@patch.object(hooks, 'CONFIGS')
@@ -188,6 +188,7 @@ class NovaComputeRelationsTests(CharmTestCase):
@patch.object(hooks, 'CONFIGS')
def test_amqp_changed_with_data_and_quantum_api(self, configs):
+ self.manage_ovs.return_value = False
self.relation_ids.return_value = ['neutron-plugin:0']
self._amqp_test(configs, quantum=True)
self.assertEquals([call('/etc/nova/nova.conf')],
@@ -348,6 +349,7 @@ class NovaComputeRelationsTests(CharmTestCase):
def test_ceph_joined(self):
hooks.ceph_joined()
self.apt_install.assert_called_with(['ceph-common'], fatal=True)
+ self.service_restart.assert_called_with('libvirt-bin')
@patch.object(hooks, 'CONFIGS')
def test_ceph_changed_missing_relation_data(self, configs):
@@ -384,3 +386,12 @@ class NovaComputeRelationsTests(CharmTestCase):
call('/etc/nova/nova.conf'),
]
self.assertEquals(ex, configs.write.call_args_list)
+
+ @patch.object(hooks, 'CONFIGS')
+ def test_neutron_plugin_changed(self, configs):
+ self.relation_get.return_value = {'metadata-shared-secret':
+ 'sharedsecret'}
+ hooks.neutron_plugin_changed()
+ self.assertTrue(self.apt_update.called)
+ self.apt_install.assert_called_with('nova-api-metadata', fatal=True)
+ configs.write.assert_called_with('/etc/nova/nova.conf')
diff --git a/unit_tests/test_nova_compute_utils.py b/unit_tests/test_nova_compute_utils.py
index bdc26b76..6e9c180b 100644
--- a/unit_tests/test_nova_compute_utils.py
+++ b/unit_tests/test_nova_compute_utils.py
@@ -24,7 +24,8 @@ TO_PATCH = [
'relation_ids',
'relation_get',
'mkdir',
- 'install_alternative'
+ 'install_alternative',
+ 'MetadataServiceContext',
]
OVS_PKGS = [
@@ -41,8 +42,10 @@ class NovaComputeUtilsTests(CharmTestCase):
super(NovaComputeUtilsTests, self).setUp(utils, TO_PATCH)
self.config.side_effect = self.test_config.get
+ @patch.object(utils, 'enable_nova_metadata')
@patch.object(utils, 'network_manager')
- def test_determine_packages_nova_network(self, net_man):
+ def test_determine_packages_nova_network(self, net_man, en_meta):
+ en_meta.return_value = False
net_man.return_value = 'flatdhcpmanager'
self.relation_ids.return_value = []
result = utils.determine_packages()
@@ -53,9 +56,11 @@ class NovaComputeUtilsTests(CharmTestCase):
]
self.assertEquals(ex, result)
+ @patch.object(utils, 'enable_nova_metadata')
@patch.object(utils, 'neutron_plugin')
@patch.object(utils, 'network_manager')
- def test_determine_packages_quantum(self, net_man, n_plugin):
+ def test_determine_packages_quantum(self, net_man, n_plugin, en_meta):
+ en_meta.return_value = False
self.neutron_plugin_attribute.return_value = OVS_PKGS
net_man.return_value = 'quantum'
n_plugin.return_value = 'ovs'
@@ -64,9 +69,30 @@ class NovaComputeUtilsTests(CharmTestCase):
ex = utils.BASE_PACKAGES + OVS_PKGS_FLAT + ['nova-compute-kvm']
self.assertEquals(ex, result)
+ @patch.object(utils, 'neutron_plugin_legacy_mode')
+ @patch.object(utils, 'enable_nova_metadata')
@patch.object(utils, 'neutron_plugin')
@patch.object(utils, 'network_manager')
- def test_determine_packages_quantum_ceph(self, net_man, n_plugin):
+ def test_determine_packages_quantum_legacy_off(self, net_man, n_plugin,
+ en_meta, leg_mode):
+ en_meta.return_value = False
+ leg_mode.return_value = False
+ self.neutron_plugin_attribute.return_value = OVS_PKGS
+ net_man.return_value = 'quantum'
+ n_plugin.return_value = 'ovs'
+ self.relation_ids.return_value = []
+ result = utils.determine_packages()
+ ex = utils.BASE_PACKAGES + ['nova-compute-kvm']
+ self.assertEquals(ex, result)
+
+ @patch.object(utils, 'neutron_plugin_legacy_mode')
+ @patch.object(utils, 'enable_nova_metadata')
+ @patch.object(utils, 'neutron_plugin')
+ @patch.object(utils, 'network_manager')
+ def test_determine_packages_quantum_ceph(self, net_man, n_plugin, en_meta,
+ leg_mode):
+ en_meta.return_value = False
+ leg_mode.return_value = True
self.neutron_plugin_attribute.return_value = OVS_PKGS
net_man.return_value = 'quantum'
n_plugin.return_value = 'ovs'
@@ -76,6 +102,18 @@ class NovaComputeUtilsTests(CharmTestCase):
['ceph-common', 'nova-compute-kvm'])
self.assertEquals(ex, result)
+ @patch.object(utils, 'enable_nova_metadata')
+ @patch.object(utils, 'neutron_plugin')
+ @patch.object(utils, 'network_manager')
+ def test_determine_packages_metadata(self, net_man, n_plugin, en_meta):
+ en_meta.return_value = True
+ self.neutron_plugin_attribute.return_value = OVS_PKGS
+ net_man.return_value = 'bob'
+ n_plugin.return_value = 'ovs'
+ self.relation_ids.return_value = []
+ result = utils.determine_packages()
+ self.assertTrue('nova-api-metadata' in result)
+
@patch.object(utils, 'network_manager')
def test_resource_map_nova_network_no_multihost(self, net_man):
self.skipTest('skipped until contexts are properly mocked')
@@ -172,6 +210,17 @@ class NovaComputeUtilsTests(CharmTestCase):
result = utils.resource_map()
self.assertTrue('/etc/neutron/neutron.conf' not in result)
+ @patch.object(utils, 'enable_nova_metadata')
+ @patch.object(utils, 'neutron_plugin')
+ @patch.object(utils, 'network_manager')
+ def test_resource_map_metadata(self, net_man, _plugin, _metadata):
+ _metadata.return_value = True
+ net_man.return_value = 'bob'
+ _plugin.return_value = 'ovs'
+ self.relation_ids.return_value = []
+ result = utils.resource_map()['/etc/nova/nova.conf']['services']
+ self.assertTrue('nova-api-metadata' in result)
+
def fake_user(self, username='foo'):
user = MagicMock()
user.pw_dir = '/home/' + username
@@ -375,3 +424,54 @@ class NovaComputeUtilsTests(CharmTestCase):
'secret-set-value', '--secret',
compute_context.CEPH_SECRET_UUID,
'--base64', key])
+
+ def test_enable_nova_metadata(self):
+ class DummyContext():
+
+ def __init__(self, return_value):
+ self.return_value = return_value
+
+ def __call__(self):
+ return self.return_value
+
+ self.MetadataServiceContext.return_value = \
+ DummyContext(return_value={'metadata_shared_secret':
+ 'sharedsecret'})
+ self.assertEqual(utils.enable_nova_metadata(), True)
+
+ def test_neutron_plugin_legacy_mode_plugin(self):
+ self.relation_ids.return_value = ['neutron-plugin:0']
+ self.assertFalse(utils.neutron_plugin_legacy_mode())
+
+ def test_neutron_plugin_legacy_mode_legacy_off(self):
+ self.relation_ids.return_value = []
+ self.test_config.set('manage-neutron-plugin-legacy-mode', False)
+ self.assertFalse(utils.neutron_plugin_legacy_mode())
+
+ def test_neutron_plugin_legacy_mode_legacy_on(self):
+ self.relation_ids.return_value = []
+ self.test_config.set('manage-neutron-plugin-legacy-mode', True)
+ self.assertTrue(utils.neutron_plugin_legacy_mode())
+
+ @patch.object(utils, 'neutron_plugin_legacy_mode')
+ def test_manage_ovs_legacy_mode_legacy_off(self,
+ _neutron_plugin_legacy_mode):
+ _neutron_plugin_legacy_mode.return_value = False
+ self.assertFalse(utils.manage_ovs())
+
+ @patch.object(utils, 'neutron_plugin')
+ @patch.object(utils, 'neutron_plugin_legacy_mode')
+ def test_manage_ovs_legacy_mode_legacy_on(self,
+ _neutron_plugin_legacy_mode,
+ _neutron_plugin):
+ _neutron_plugin_legacy_mode.return_value = True
+ _neutron_plugin.return_value = 'ovs'
+ self.assertTrue(utils.manage_ovs())
+
+ @patch.object(utils, 'neutron_plugin')
+ @patch.object(utils, 'neutron_plugin_legacy_mode')
+ def test_manage_ovs_legacy_mode_not_ovs(self, _neutron_plugin_legacy_mode,
+ _neutron_plugin):
+ _neutron_plugin_legacy_mode.return_value = True
+ _neutron_plugin.return_value = 'bobvs'
+ self.assertFalse(utils.manage_ovs())