diff --git a/.bzrignore b/.bzrignore
index a2c7a097..421e2bda 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -1,2 +1,3 @@
bin
.coverage
+tags
diff --git a/Makefile b/Makefile
index db4e2c25..6ca58a67 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
PYTHON := /usr/bin/env python
lint:
- @flake8 --exclude hooks/charmhelpers hooks unit_tests tests
+ @flake8 --exclude hooks/charmhelpers actions hooks unit_tests tests
@charm proof
unit_test:
@@ -15,7 +15,7 @@ bin/charm_helpers_sync.py:
> bin/charm_helpers_sync.py
sync: bin/charm_helpers_sync.py
- @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-sync.yaml
+ @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml
@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml
test:
@@ -25,7 +25,8 @@ test:
# https://bugs.launchpad.net/amulet/+bug/1320357
@juju test -v -p AMULET_HTTP_PROXY --timeout 900 \
00-setup 14-basic-precise-icehouse 15-basic-trusty-icehouse \
- 16-basic-trusty-juno
+ 16-basic-trusty-icehouse-git 17-basic-trusty-juno \
+ 18-basic-trusty-juno-git
publish: lint unit_test
bzr push lp:charms/neutron-openvswitch
diff --git a/README.md b/README.md
index 6318fa44..05d08869 100644
--- a/README.md
+++ b/README.md
@@ -41,3 +41,90 @@ This charm has a configuration option to allow users to disable any per-instance
...
These compute nodes could then be accessed by cloud users via use of host aggregates with specific flavors to target instances to hypervisors with no per-instance security.
+
+# Deploying from source
+
+The minimum openstack-origin-git config required to deploy from source is:
+
+ openstack-origin-git:
+ "repositories:
+ - {name: requirements,
+ repository: 'git://git.openstack.org/openstack/requirements',
+ branch: stable/juno}
+ - {name: neutron,
+ repository: 'git://git.openstack.org/openstack/neutron',
+ branch: stable/juno}"
+
+Note that there are only two 'name' values the charm knows about: 'requirements'
+and 'neutron'. These repositories must correspond to these 'name' values.
+Additionally, the requirements repository must be specified first and the
+neutron repository must be specified last. All other repostories are installed
+in the order in which they are specified.
+
+The following is a full list of current tip repos (may not be up-to-date):
+
+ openstack-origin-git:
+ "repositories:
+ - {name: requirements,
+ repository: 'git://git.openstack.org/openstack/requirements',
+ branch: master}
+ - {name: oslo-concurrency,
+ repository: 'git://git.openstack.org/openstack/oslo.concurrency',
+ branch: master}
+ - {name: oslo-config,
+ repository: 'git://git.openstack.org/openstack/oslo.config',
+ branch: master}
+ - {name: oslo-context,
+ repository: 'git://git.openstack.org/openstack/oslo.context.git',
+ branch: master}
+ - {name: oslo-db,
+ repository: 'git://git.openstack.org/openstack/oslo.db',
+ branch: master}
+ - {name: oslo-i18n,
+ repository: 'git://git.openstack.org/openstack/oslo.i18n',
+ branch: master}
+ - {name: oslo-messaging,
+ repository: 'git://git.openstack.org/openstack/oslo.messaging.git',
+ branch: master}
+ - {name: oslo-middleware,
+ repository': 'git://git.openstack.org/openstack/oslo.middleware.git',
+ branch: master}
+ - {name: oslo-rootwrap',
+ repository: 'git://git.openstack.org/openstack/oslo.rootwrap.git',
+ branch: master}
+ - {name: oslo-serialization,
+ repository: 'git://git.openstack.org/openstack/oslo.serialization',
+ branch: master}
+ - {name: oslo-utils,
+ repository: 'git://git.openstack.org/openstack/oslo.utils',
+ branch: master}
+ - {name: pbr,
+ repository: 'git://git.openstack.org/openstack-dev/pbr',
+ branch: master}
+ - {name: stevedore,
+ repository: 'git://git.openstack.org/openstack/stevedore.git',
+ branch: 'master'}
+ - {name: python-keystoneclient,
+ repository: 'git://git.openstack.org/openstack/python-keystoneclient',
+ branch: master}
+ - {name: python-neutronclient,
+ repository: 'git://git.openstack.org/openstack/python-neutronclient.git',
+ branch: master}
+ - {name: python-novaclient,
+ repository': 'git://git.openstack.org/openstack/python-novaclient.git',
+ branch: master}
+ - {name: keystonemiddleware,
+ repository: 'git://git.openstack.org/openstack/keystonemiddleware',
+ branch: master}
+ - {name: neutron-fwaas,
+ repository': 'git://git.openstack.org/openstack/neutron-fwaas.git',
+ branch: master}
+ - {name: neutron-lbaas,
+ repository: 'git://git.openstack.org/openstack/neutron-lbaas.git',
+ branch: master}
+ - {name: neutron-vpnaas,
+ repository: 'git://git.openstack.org/openstack/neutron-vpnaas.git',
+ branch: master}
+ - {name: neutron,
+ repository: 'git://git.openstack.org/openstack/neutron',
+ branch: master}"
diff --git a/actions.yaml b/actions.yaml
new file mode 100644
index 00000000..02fbf59a
--- /dev/null
+++ b/actions.yaml
@@ -0,0 +1,2 @@
+git-reinstall:
+ description: Reinstall neutron-openvswitch from the openstack-origin-git repositories.
diff --git a/actions/git-reinstall b/actions/git-reinstall
new file mode 120000
index 00000000..ff684984
--- /dev/null
+++ b/actions/git-reinstall
@@ -0,0 +1 @@
+git_reinstall.py
\ No newline at end of file
diff --git a/actions/git_reinstall.py b/actions/git_reinstall.py
new file mode 100755
index 00000000..849e6634
--- /dev/null
+++ b/actions/git_reinstall.py
@@ -0,0 +1,40 @@
+#!/usr/bin/python
+import sys
+import traceback
+
+sys.path.append('hooks/')
+
+from charmhelpers.contrib.openstack.utils import (
+ git_install_requested,
+)
+
+from charmhelpers.core.hookenv import (
+ action_set,
+ action_fail,
+ config,
+)
+
+from neutron_ovs_utils import (
+ git_install,
+)
+
+
+def git_reinstall():
+ """Reinstall from source and restart services.
+
+ If the openstack-origin-git config option was used to install openstack
+ from source git repositories, then this action can be used to reinstall
+ from updated git repositories, followed by a restart of services."""
+ if not git_install_requested():
+ action_fail('openstack-origin-git is not configured')
+ return
+
+ try:
+ git_install(config('openstack-origin-git'))
+ except:
+ action_set({'traceback': traceback.format_exc()})
+ action_fail('git-reinstall resulted in an unexpected error')
+
+
+if __name__ == '__main__':
+ git_reinstall()
diff --git a/charm-helpers-sync.yaml b/charm-helpers-hooks.yaml
similarity index 100%
rename from charm-helpers-sync.yaml
rename to charm-helpers-hooks.yaml
diff --git a/config.yaml b/config.yaml
index 41699d49..9f96a737 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,4 +1,21 @@
options:
+ openstack-origin-git:
+ default:
+ type: string
+ description: |
+ Specifies a YAML-formatted dictionary listing the git
+ repositories and branches from which to install OpenStack and
+ its dependencies.
+
+ When openstack-origin-git is specified, openstack-specific
+ packages will be installed from source rather than from the
+ the nova-compute charm's openstack-origin repository.
+
+ Note that the installed config files will be determined based on
+ the OpenStack release of the nova-compute charm's openstack-origin
+ option.
+
+ For more details see README.md.
rabbit-user:
default: neutron
type: string
@@ -60,3 +77,12 @@ options:
.
This network will be used for tenant network traffic in overlay
networks.
+ ext-port:
+ type: string
+ default:
+ description: |
+ A space-separated list of external ports to use for routing of instance
+ traffic to the external public network. Valid values are either MAC
+ addresses (in which case only MAC addresses for interfaces without an IP
+ address already assigned will be used), or interfaces (eth0)
+
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 90ac6d69..dd51bfbb 100644
--- a/hooks/charmhelpers/contrib/openstack/context.py
+++ b/hooks/charmhelpers/contrib/openstack/context.py
@@ -47,6 +47,7 @@ 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,
@@ -67,6 +68,7 @@ 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,
@@ -82,7 +84,6 @@ from charmhelpers.contrib.network.ip import (
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']
@@ -319,14 +320,15 @@ def db_ssl(rdata, ctxt, ssl_dir):
class IdentityServiceContext(OSContextGenerator):
- interfaces = ['identity-service']
- def __init__(self, service=None, service_user=None):
+ def __init__(self, service=None, service_user=None, rel_name='identity-service'):
self.service = service
self.service_user = service_user
+ self.rel_name = rel_name
+ self.interfaces = [self.rel_name]
def __call__(self):
- log('Generating template context for identity-service', level=DEBUG)
+ log('Generating template context for ' + self.rel_name, level=DEBUG)
ctxt = {}
if self.service and self.service_user:
@@ -340,7 +342,7 @@ class IdentityServiceContext(OSContextGenerator):
ctxt['signing_dir'] = cachedir
- for rid in relation_ids('identity-service'):
+ for rid in relation_ids(self.rel_name):
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
serv_host = rdata.get('service_host')
@@ -1162,3 +1164,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/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..5a12c9d6 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,103 @@ 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
+ http_proxy: http://squid.internal:3128
+ https_proxy: https://squid.internal:3128
+
+ The directory, http_proxy, and https_proxy keys are 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 'http_proxy' in projects.keys():
+ os.environ['http_proxy'] = projects['http_proxy']
- # 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)
+ if 'https_proxy' in projects.keys():
+ os.environ['https_proxy'] = projects['https_proxy']
- # 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))
+ if 'directory' in projects.keys():
+ parent_dir = projects['directory']
+
+ 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 +601,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/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/neutron_ovs_context.py b/hooks/neutron_ovs_context.py
index 4c6febf5..4ca582f9 100644
--- a/hooks/neutron_ovs_context.py
+++ b/hooks/neutron_ovs_context.py
@@ -1,80 +1,23 @@
+import os
+import uuid
from charmhelpers.core.hookenv import (
+ config,
+ relation_get,
relation_ids,
related_units,
- relation_get,
- config,
unit_get,
)
-from charmhelpers.core.strutils import bool_from_string
+from charmhelpers.contrib.openstack.ip import resolve_address
from charmhelpers.contrib.openstack import context
-from charmhelpers.core.host import (
- service_running,
- service_start,
- service_restart,
-)
-from charmhelpers.contrib.network.ovs import add_bridge, add_bridge_port
from charmhelpers.contrib.openstack.utils import get_host_ip
from charmhelpers.contrib.network.ip import get_address_in_network
+from charmhelpers.contrib.openstack.context import (
+ OSContextGenerator,
+ NeutronAPIContext,
+)
from charmhelpers.contrib.openstack.neutron import (
- parse_bridge_mappings,
- parse_data_port_mappings,
parse_vlan_range_mappings,
)
-from charmhelpers.core.host import (
- get_nic_hwaddr,
-)
-OVS_BRIDGE = 'br-int'
-
-
-def _neutron_api_settings():
- '''
- Inspects current neutron-plugin relation
- '''
- neutron_settings = {
- 'neutron_security_groups': False,
- 'l2_population': True,
- 'overlay_network_type': 'gre',
- }
-
- 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:
- neutron_settings.update({
- 'l2_population': bool_from_string(rdata['l2-population']),
- 'overlay_network_type': rdata['overlay-network-type'],
- 'neutron_security_groups':
- bool_from_string(rdata['neutron-security-groups'])
- })
-
- # Override with configuration if set to true
- if config('disable-security-groups'):
- neutron_settings['neutron_security_groups'] = False
-
- net_dev_mtu = rdata.get('network-device-mtu')
- if net_dev_mtu:
- neutron_settings['network_device_mtu'] = net_dev_mtu
-
- return neutron_settings
-
-
-class DataPortContext(context.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
- portmap.iteritems() if port in normalized.keys()}
-
- return None
class OVSPluginContext(context.NeutronContext):
@@ -90,27 +33,11 @@ class OVSPluginContext(context.NeutronContext):
@property
def neutron_security_groups(self):
- neutron_api_settings = _neutron_api_settings()
+ if config('disable-security-groups'):
+ return False
+ neutron_api_settings = NeutronAPIContext()()
return neutron_api_settings['neutron_security_groups']
- def _ensure_bridge(self):
- if not service_running('openvswitch-switch'):
- service_start('openvswitch-switch')
-
- add_bridge(OVS_BRIDGE)
-
- portmaps = DataPortContext()()
- bridgemaps = parse_bridge_mappings(config('bridge-mappings'))
- for provider, br in bridgemaps.iteritems():
- add_bridge(br)
-
- if not portmaps or br not in portmaps:
- continue
-
- add_bridge_port(br, portmaps[br], promisc=True)
-
- service_restart('os-charm-phy-nic-mtu')
-
def ovs_ctxt(self):
# In addition to generating config context, ensure the OVS service
# is running and the OVS bridge exists. Also need to ensure
@@ -119,15 +46,14 @@ class OVSPluginContext(context.NeutronContext):
if not ovs_ctxt:
return {}
- self._ensure_bridge()
-
conf = config()
ovs_ctxt['local_ip'] = \
get_address_in_network(config('os-data-network'),
get_host_ip(unit_get('private-address')))
- neutron_api_settings = _neutron_api_settings()
+ neutron_api_settings = NeutronAPIContext()()
ovs_ctxt['neutron_security_groups'] = self.neutron_security_groups
ovs_ctxt['l2_population'] = neutron_api_settings['l2_population']
+ ovs_ctxt['distributed_routing'] = neutron_api_settings['enable_dvr']
ovs_ctxt['overlay_network_type'] = \
neutron_api_settings['overlay_network_type']
# TODO: We need to sort out the syslog and debug/verbose options as a
@@ -157,18 +83,60 @@ class OVSPluginContext(context.NeutronContext):
return ovs_ctxt
-class PhyNICMTUContext(DataPortContext):
- """Context used to apply settings to neutron data-port devices"""
+class L3AgentContext(OSContextGenerator):
def __call__(self):
+ neutron_api_settings = NeutronAPIContext()()
ctxt = {}
- mappings = super(PhyNICMTUContext, self).__call__()
- if mappings and mappings.values():
- ports = mappings.values()
- neutron_api_settings = _neutron_api_settings()
- mtu = neutron_api_settings.get('network_device_mtu')
- if mtu:
- ctxt['devs'] = '\\n'.join(ports)
- ctxt['mtu'] = mtu
-
+ if neutron_api_settings['enable_dvr']:
+ ctxt['agent_mode'] = 'dvr'
+ else:
+ ctxt['agent_mode'] = 'legacy'
+ return ctxt
+
+
+SHARED_SECRET = "/etc/neutron/secret.txt"
+
+
+def get_shared_secret():
+ secret = None
+ if not os.path.exists(SHARED_SECRET):
+ secret = str(uuid.uuid4())
+ with open(SHARED_SECRET, 'w') as secret_file:
+ secret_file.write(secret)
+ else:
+ with open(SHARED_SECRET, 'r') as secret_file:
+ secret = secret_file.read().strip()
+ return secret
+
+
+class DVRSharedSecretContext(OSContextGenerator):
+
+ def __call__(self):
+ if NeutronAPIContext()()['enable_dvr']:
+ ctxt = {
+ 'shared_secret': get_shared_secret(),
+ 'local_ip': resolve_address(),
+ }
+ else:
+ ctxt = {}
+ return ctxt
+
+
+class APIIdentityServiceContext(context.IdentityServiceContext):
+
+ def __init__(self):
+ super(APIIdentityServiceContext,
+ self).__init__(rel_name='neutron-plugin-api')
+
+ def __call__(self):
+ ctxt = super(APIIdentityServiceContext, self).__call__()
+ if not ctxt:
+ return
+ for rid in relation_ids('neutron-plugin-api'):
+ for unit in related_units(rid):
+ rdata = relation_get(rid=rid, unit=unit)
+ ctxt['region'] = rdata.get('region')
+ if ctxt['region']:
+ return ctxt
return ctxt
diff --git a/hooks/neutron_ovs_hooks.py b/hooks/neutron_ovs_hooks.py
index eb53094d..887e3801 100755
--- a/hooks/neutron_ovs_hooks.py
+++ b/hooks/neutron_ovs_hooks.py
@@ -2,12 +2,18 @@
import sys
+from charmhelpers.contrib.openstack.utils import (
+ config_value_changed,
+ git_install_requested,
+)
+
from charmhelpers.core.hookenv import (
Hooks,
UnregisteredHookError,
config,
log,
relation_set,
+ relation_ids,
)
from charmhelpers.core.host import (
@@ -15,13 +21,24 @@ from charmhelpers.core.host import (
)
from charmhelpers.fetch import (
- apt_install, apt_update
+ apt_install, apt_update, apt_purge
+)
+
+from charmhelpers.contrib.openstack.utils import (
+ os_requires_version,
)
from neutron_ovs_utils import (
+ DVR_PACKAGES,
+ configure_ovs,
determine_packages,
+ git_install,
+ get_topics,
+ determine_dvr_packages,
+ get_shared_secret,
register_configs,
restart_map,
+ use_dvr,
)
hooks = Hooks()
@@ -35,13 +52,49 @@ def install():
for pkg in pkgs:
apt_install(pkg, fatal=True)
+ git_install(config('openstack-origin-git'))
+
@hooks.hook('neutron-plugin-relation-changed')
-@hooks.hook('neutron-plugin-api-relation-changed')
@hooks.hook('config-changed')
@restart_on_change(restart_map())
def config_changed():
+ if determine_dvr_packages():
+ apt_update()
+ apt_install(determine_dvr_packages(), fatal=True)
+
+ if git_install_requested():
+ if config_value_changed('openstack-origin-git'):
+ git_install(config('openstack-origin-git'))
+
+ configure_ovs()
CONFIGS.write_all()
+ for rid in relation_ids('zeromq-configuration'):
+ zeromq_configuration_relation_joined(rid)
+
+
+@hooks.hook('neutron-plugin-api-relation-changed')
+@restart_on_change(restart_map())
+def neutron_plugin_api_changed():
+ if use_dvr():
+ apt_update()
+ apt_install(DVR_PACKAGES, fatal=True)
+ else:
+ apt_purge(DVR_PACKAGES, fatal=True)
+ configure_ovs()
+ CONFIGS.write_all()
+ # If dvr setting has changed, need to pass that on
+ for rid in relation_ids('neutron-plugin'):
+ neutron_plugin_joined(relation_id=rid)
+
+
+@hooks.hook('neutron-plugin-relation-joined')
+def neutron_plugin_joined(relation_id=None):
+ secret = get_shared_secret() if use_dvr() else None
+ rel_data = {
+ 'metadata-shared-secret': secret,
+ }
+ relation_set(relation_id=relation_id, **rel_data)
@hooks.hook('amqp-relation-joined')
@@ -61,6 +114,20 @@ def amqp_changed():
CONFIGS.write_all()
+@hooks.hook('zeromq-configuration-relation-joined')
+@os_requires_version('kilo', 'neutron-common')
+def zeromq_configuration_relation_joined(relid=None):
+ relation_set(relation_id=relid,
+ topics=" ".join(get_topics()),
+ users="neutron")
+
+
+@hooks.hook('zeromq-configuration-relation-changed')
+@restart_on_change(restart_map(), stopstart=True)
+def zeromq_configuration_relation_changed():
+ CONFIGS.write_all()
+
+
def main():
try:
hooks.execute(sys.argv)
diff --git a/hooks/neutron_ovs_utils.py b/hooks/neutron_ovs_utils.py
index 5cc9871c..fb9d5dd6 100644
--- a/hooks/neutron_ovs_utils.py
+++ b/hooks/neutron_ovs_utils.py
@@ -1,18 +1,76 @@
+import os
+import shutil
+
from charmhelpers.contrib.openstack.neutron import neutron_plugin_attribute
from copy import deepcopy
from charmhelpers.contrib.openstack import context, templating
+from charmhelpers.contrib.openstack.utils import (
+ git_install_requested,
+ git_clone_and_install,
+ git_src_dir,
+)
from collections import OrderedDict
from charmhelpers.contrib.openstack.utils import (
os_release,
)
import neutron_ovs_context
+from charmhelpers.contrib.network.ovs import (
+ add_bridge,
+ add_bridge_port,
+ full_restart,
+)
+from charmhelpers.core.hookenv import (
+ config,
+)
+from charmhelpers.contrib.openstack.neutron import (
+ parse_bridge_mappings,
+)
+from charmhelpers.contrib.openstack.context import (
+ ExternalPortContext,
+ DataPortContext,
+)
+from charmhelpers.core.host import (
+ adduser,
+ add_group,
+ add_user_to_group,
+ mkdir,
+ service_restart,
+ service_running,
+ write_file,
+)
+
+from charmhelpers.core.templating import render
+
+BASE_GIT_PACKAGES = [
+ 'libxml2-dev',
+ 'libxslt1-dev',
+ 'openvswitch-switch',
+ 'python-dev',
+ 'python-pip',
+ 'python-setuptools',
+ 'zlib1g-dev',
+]
+
+# ubuntu packages that should not be installed when deploying from git
+GIT_PACKAGE_BLACKLIST = [
+ 'neutron-l3-agent',
+ 'neutron-metadata-agent',
+ 'neutron-server',
+ 'neutron-plugin-openvswitch',
+ 'neutron-plugin-openvswitch-agent',
+]
NOVA_CONF_DIR = "/etc/nova"
NEUTRON_CONF_DIR = "/etc/neutron"
NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
NEUTRON_DEFAULT = '/etc/default/neutron-server'
+NEUTRON_L3_AGENT_CONF = "/etc/neutron/l3_agent.ini"
+NEUTRON_FWAAS_CONF = "/etc/neutron/fwaas_driver.ini"
ML2_CONF = '%s/plugins/ml2/ml2_conf.ini' % NEUTRON_CONF_DIR
+EXT_PORT_CONF = '/etc/init/ext-port.conf'
+NEUTRON_METADATA_AGENT_CONF = "/etc/neutron/metadata_agent.ini"
+DVR_PACKAGES = ['neutron-l3-agent']
PHY_NIC_MTU_CONF = '/etc/init/os-charm-phy-nic-mtu.conf'
TEMPLATES = 'templates/'
@@ -20,7 +78,9 @@ BASE_RESOURCE_MAP = OrderedDict([
(NEUTRON_CONF, {
'services': ['neutron-plugin-openvswitch-agent'],
'contexts': [neutron_ovs_context.OVSPluginContext(),
- context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR)],
+ context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR),
+ context.ZeroMQContext(),
+ context.NotificationDriverContext()],
}),
(ML2_CONF, {
'services': ['neutron-plugin-openvswitch-agent'],
@@ -28,13 +88,53 @@ BASE_RESOURCE_MAP = OrderedDict([
}),
(PHY_NIC_MTU_CONF, {
'services': ['os-charm-phy-nic-mtu'],
- 'contexts': [neutron_ovs_context.PhyNICMTUContext()],
+ 'contexts': [context.PhyNICMTUContext()],
}),
])
+DVR_RESOURCE_MAP = OrderedDict([
+ (NEUTRON_L3_AGENT_CONF, {
+ 'services': ['neutron-l3-agent'],
+ 'contexts': [neutron_ovs_context.L3AgentContext()],
+ }),
+ (NEUTRON_FWAAS_CONF, {
+ 'services': ['neutron-l3-agent'],
+ 'contexts': [neutron_ovs_context.L3AgentContext()],
+ }),
+ (EXT_PORT_CONF, {
+ 'services': ['neutron-l3-agent'],
+ 'contexts': [context.ExternalPortContext()],
+ }),
+ (NEUTRON_METADATA_AGENT_CONF, {
+ 'services': ['neutron-metadata-agent'],
+ 'contexts': [neutron_ovs_context.DVRSharedSecretContext(),
+ neutron_ovs_context.APIIdentityServiceContext()],
+ }),
+])
+TEMPLATES = 'templates/'
+INT_BRIDGE = "br-int"
+EXT_BRIDGE = "br-ex"
+DATA_BRIDGE = 'br-data'
+
+
+def determine_dvr_packages():
+ if not git_install_requested():
+ if use_dvr():
+ return DVR_PACKAGES
+ return []
def determine_packages():
- return neutron_plugin_attribute('ovs', 'packages', 'neutron')
+ pkgs = neutron_plugin_attribute('ovs', 'packages', 'neutron')
+ pkgs.extend(determine_dvr_packages())
+
+ if git_install_requested():
+ pkgs.extend(BASE_GIT_PACKAGES)
+ # don't include packages that will be installed from git
+ for p in GIT_PACKAGE_BLACKLIST:
+ if p in pkgs:
+ pkgs.remove(p)
+
+ return pkgs
def register_configs(release=None):
@@ -52,6 +152,10 @@ def resource_map():
hook execution.
'''
resource_map = deepcopy(BASE_RESOURCE_MAP)
+ if use_dvr():
+ resource_map.update(DVR_RESOURCE_MAP)
+ dvr_services = ['neutron-metadata-agent', 'neutron-l3-agent']
+ resource_map[NEUTRON_CONF]['services'] += dvr_services
return resource_map
@@ -61,3 +165,134 @@ def restart_map():
state.
'''
return {k: v['services'] for k, v in resource_map().iteritems()}
+
+
+def get_topics():
+ topics = []
+ topics.append('q-agent-notifier-port-update')
+ topics.append('q-agent-notifier-network-delete')
+ topics.append('q-agent-notifier-tunnel-update')
+ topics.append('q-agent-notifier-security_group-update')
+ topics.append('q-agent-notifier-dvr-update')
+ if context.NeutronAPIContext()()['l2_population']:
+ topics.append('q-agent-notifier-l2population-update')
+ return topics
+
+
+def configure_ovs():
+ if not service_running('openvswitch-switch'):
+ full_restart()
+ add_bridge(INT_BRIDGE)
+ add_bridge(EXT_BRIDGE)
+ ext_port_ctx = None
+ if use_dvr():
+ ext_port_ctx = ExternalPortContext()()
+ if ext_port_ctx and ext_port_ctx['ext_port']:
+ add_bridge_port(EXT_BRIDGE, ext_port_ctx['ext_port'])
+
+ portmaps = DataPortContext()()
+ bridgemaps = parse_bridge_mappings(config('bridge-mappings'))
+ for provider, br in bridgemaps.iteritems():
+ add_bridge(br)
+ if not portmaps or br not in portmaps:
+ continue
+
+ add_bridge_port(br, portmaps[br], promisc=True)
+
+ # Ensure this runs so that mtu is applied to data-port interfaces if
+ # provided.
+ service_restart('os-charm-phy-nic-mtu')
+
+
+def get_shared_secret():
+ ctxt = neutron_ovs_context.DVRSharedSecretContext()()
+ if 'shared_secret' in ctxt:
+ return ctxt['shared_secret']
+
+
+def use_dvr():
+ return context.NeutronAPIContext()()['enable_dvr']
+
+
+def git_install(projects_yaml):
+ """Perform setup, and install git repos specified in yaml parameter."""
+ if git_install_requested():
+ git_pre_install()
+ git_clone_and_install(projects_yaml, core_project='neutron')
+ git_post_install(projects_yaml)
+
+
+def git_pre_install():
+ """Perform pre-install setup."""
+ dirs = [
+ '/etc/neutron',
+ '/etc/neutron/rootwrap.d',
+ '/var/lib/neutron',
+ '/var/lib/neutron/lock',
+ '/var/log/neutron',
+ ]
+
+ logs = [
+ '/var/log/neutron/server.log',
+ ]
+
+ adduser('neutron', shell='/bin/bash', system_user=True)
+ add_group('neutron', system_group=True)
+ add_user_to_group('neutron', 'neutron')
+
+ for d in dirs:
+ mkdir(d, owner='neutron', group='neutron', perms=0700, force=False)
+
+ for l in logs:
+ write_file(l, '', owner='neutron', group='neutron', perms=0600)
+
+
+def git_post_install(projects_yaml):
+ """Perform post-install setup."""
+ src_etc = os.path.join(git_src_dir(projects_yaml, 'neutron'), 'etc')
+ configs = {
+ 'debug-filters': {
+ 'src': os.path.join(src_etc, 'neutron/rootwrap.d/debug.filters'),
+ 'dest': '/etc/neutron/rootwrap.d/debug.filters',
+ },
+ 'policy': {
+ 'src': os.path.join(src_etc, 'policy.json'),
+ 'dest': '/etc/neutron/policy.json',
+ },
+ 'rootwrap': {
+ 'src': os.path.join(src_etc, 'rootwrap.conf'),
+ 'dest': '/etc/neutron/rootwrap.conf',
+ },
+ }
+
+ for conf, files in configs.iteritems():
+ shutil.copyfile(files['src'], files['dest'])
+
+ render('neutron_sudoers', '/etc/sudoers.d/neutron_sudoers', {},
+ perms=0o440)
+
+ neutron_ovs_agent_context = {
+ 'service_description': 'Neutron OpenvSwitch Plugin Agent',
+ 'charm_name': 'neutron-openvswitch',
+ 'process_name': 'neutron-openvswitch-agent',
+ 'cleanup_process_name': 'neutron-ovs-cleanup',
+ 'plugin_config': '/etc/neutron/plugins/ml2/ml2_conf.ini',
+ 'log_file': '/var/log/neutron/openvswitch-agent.log',
+ }
+
+ neutron_ovs_cleanup_context = {
+ 'service_description': 'Neutron OpenvSwitch Cleanup',
+ 'charm_name': 'neutron-openvswitch',
+ 'process_name': 'neutron-ovs-cleanup',
+ 'log_file': '/var/log/neutron/ovs-cleanup.log',
+ }
+
+ # NOTE(coreycb): Needs systemd support
+ render('upstart/neutron-plugin-openvswitch-agent.upstart',
+ '/etc/init/neutron-plugin-openvswitch-agent.conf',
+ neutron_ovs_agent_context, perms=0o644)
+ render('upstart/neutron-ovs-cleanup.upstart',
+ '/etc/init/neutron-ovs-cleanup.conf',
+ neutron_ovs_cleanup_context, perms=0o644)
+
+ service_restart('neutron-plugin-openvswitch-agent')
diff --git a/hooks/zeromq-configuration-relation-changed b/hooks/zeromq-configuration-relation-changed
new file mode 120000
index 00000000..55aa8e52
--- /dev/null
+++ b/hooks/zeromq-configuration-relation-changed
@@ -0,0 +1 @@
+neutron_ovs_hooks.py
\ No newline at end of file
diff --git a/hooks/zeromq-configuration-relation-joined b/hooks/zeromq-configuration-relation-joined
new file mode 120000
index 00000000..55aa8e52
--- /dev/null
+++ b/hooks/zeromq-configuration-relation-joined
@@ -0,0 +1 @@
+neutron_ovs_hooks.py
\ No newline at end of file
diff --git a/metadata.yaml b/metadata.yaml
index 0e840258..dfbefd7b 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -28,3 +28,7 @@ requires:
scope: container
neutron-plugin-api:
interface: neutron-plugin-api
+ zeromq-configuration:
+ interface: zeromq-configuration
+ scope: container
+
diff --git a/templates/ext-port.conf b/templates/ext-port.conf
new file mode 100644
index 00000000..1fad0a8b
--- /dev/null
+++ b/templates/ext-port.conf
@@ -0,0 +1,16 @@
+description "Enabling Neutron external networking port"
+
+start on runlevel [2345]
+
+task
+
+script
+ EXT_PORT="{{ ext_port }}"
+ MTU="{{ ext_port_mtu }}"
+ if [ -n "$EXT_PORT" ]; then
+ ip link set $EXT_PORT up
+ if [ -n "$MTU" ]; then
+ ip link set $EXT_PORT mtu $MTU
+ fi
+ fi
+end script
diff --git a/templates/icehouse/neutron.conf b/templates/icehouse/neutron.conf
index c7af465a..25004db5 100644
--- a/templates/icehouse/neutron.conf
+++ b/templates/icehouse/neutron.conf
@@ -12,15 +12,18 @@ state_path = /var/lib/neutron
lock_path = $state_path/lock
bind_host = 0.0.0.0
bind_port = 9696
+{% if network_device_mtu -%}
network_device_mtu = {{ network_device_mtu }}
-
+{% endif -%}
{% if core_plugin -%}
core_plugin = {{ core_plugin }}
{% endif -%}
api_paste_config = /etc/neutron/api-paste.ini
auth_strategy = keystone
+{% if notifications == 'True' -%}
notification_driver = neutron.openstack.common.notifier.rpc_notifier
+{% endif -%}
default_notification_level = INFO
notification_topics = notifications
@@ -35,4 +38,3 @@ root_helper = sudo neutron-rootwrap /etc/neutron/rootwrap.conf
[keystone_authtoken]
signing_dir = /var/lib/neutron/keystone-signing
-
diff --git a/templates/juno/fwaas_driver.ini b/templates/juno/fwaas_driver.ini
new file mode 100644
index 00000000..e64046dc
--- /dev/null
+++ b/templates/juno/fwaas_driver.ini
@@ -0,0 +1,7 @@
+###############################################################################
+# [ WARNING ]
+# Configuration file maintained by Juju. Local changes may be overwritten.
+###############################################################################
+[fwaas]
+driver = neutron.services.firewall.drivers.linux.iptables_fwaas.IptablesFwaasDriver
+enabled = True
diff --git a/templates/juno/l3_agent.ini b/templates/juno/l3_agent.ini
new file mode 100644
index 00000000..8e93c71a
--- /dev/null
+++ b/templates/juno/l3_agent.ini
@@ -0,0 +1,7 @@
+###############################################################################
+# [ WARNING ]
+# Configuration file maintained by Juju. Local changes may be overwritten.
+###############################################################################
+[DEFAULT]
+interface_driver = neutron.agent.linux.interface.OVSInterfaceDriver
+agent_mode = {{ agent_mode }}
diff --git a/templates/juno/metadata_agent.ini b/templates/juno/metadata_agent.ini
new file mode 100644
index 00000000..c64d057c
--- /dev/null
+++ b/templates/juno/metadata_agent.ini
@@ -0,0 +1,20 @@
+###############################################################################
+# [ WARNING ]
+# Configuration file maintained by Juju. Local changes may be overwritten.
+###############################################################################
+# Metadata service seems to cache neutron api url from keystone so trigger
+# restart if it changes: {{ quantum_url }}
+
+[DEFAULT]
+auth_url = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/v2.0
+auth_region = {{ region }}
+admin_tenant_name = {{ admin_tenant_name }}
+admin_user = {{ admin_user }}
+admin_password = {{ admin_password }}
+root_helper = sudo neutron-rootwrap /etc/neutron/rootwrap.conf
+state_path = /var/lib/neutron
+# Gateway runs a metadata API server locally
+#nova_metadata_ip = {{ local_ip }}
+nova_metadata_port = 8775
+metadata_proxy_shared_secret = {{ shared_secret }}
+cache_url = memory://?default_ttl=5
diff --git a/templates/juno/ml2_conf.ini b/templates/juno/ml2_conf.ini
new file mode 100644
index 00000000..f798463a
--- /dev/null
+++ b/templates/juno/ml2_conf.ini
@@ -0,0 +1,43 @@
+# juno
+###############################################################################
+# [ WARNING ]
+# Configuration file maintained by Juju. Local changes may be overwritten.
+# Config managed by neutron-openvswitch charm
+###############################################################################
+[ml2]
+type_drivers = gre,vxlan,vlan,flat
+tenant_network_types = gre,vxlan,vlan,flat
+mechanism_drivers = openvswitch,hyperv,l2population
+
+[ml2_type_gre]
+tunnel_id_ranges = 1:1000
+
+[ml2_type_vxlan]
+vni_ranges = 1001:2000
+
+[ml2_type_vlan]
+network_vlan_ranges = {{ vlan_ranges }}
+
+[ml2_type_flat]
+flat_networks = {{ network_providers }}
+
+[ovs]
+enable_tunneling = True
+local_ip = {{ local_ip }}
+bridge_mappings = {{ bridge_mappings }}
+
+[agent]
+tunnel_types = {{ overlay_network_type }}
+l2_population = {{ l2_population }}
+enable_distributed_routing = {{ distributed_routing }}
+{% if veth_mtu -%}
+veth_mtu = {{ veth_mtu }}
+{% endif %}
+
+[securitygroup]
+{% if neutron_security_groups -%}
+enable_security_group = True
+firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
+{% else -%}
+enable_security_group = False
+{% endif -%}
diff --git a/templates/kilo/fwaas_driver.ini b/templates/kilo/fwaas_driver.ini
new file mode 100644
index 00000000..b31a5008
--- /dev/null
+++ b/templates/kilo/fwaas_driver.ini
@@ -0,0 +1,8 @@
+# kilo
+###############################################################################
+# [ WARNING ]
+# Configuration file maintained by Juju. Local changes may be overwritten.
+###############################################################################
+[fwaas]
+driver = neutron_fwaas.services.firewall.drivers.linux.iptables_fwaas.IptablesFwaasDriver
+enabled = True
diff --git a/templates/kilo/neutron.conf b/templates/kilo/neutron.conf
new file mode 100644
index 00000000..7d5748de
--- /dev/null
+++ b/templates/kilo/neutron.conf
@@ -0,0 +1,42 @@
+# icehouse
+###############################################################################
+# [ WARNING ]
+# Configuration file maintained by Juju. Local changes may be overwritten.
+# Config managed by neutron-openvswitch charm
+###############################################################################
+[DEFAULT]
+verbose = {{ verbose }}
+debug = {{ debug }}
+use_syslog = {{ use_syslog }}
+state_path = /var/lib/neutron
+bind_host = 0.0.0.0
+bind_port = 9696
+{% if network_device_mtu -%}
+network_device_mtu = {{ network_device_mtu }}
+{% endif -%}
+{% if core_plugin -%}
+core_plugin = {{ core_plugin }}
+{% endif -%}
+
+api_paste_config = /etc/neutron/api-paste.ini
+auth_strategy = keystone
+notification_driver = neutron.openstack.common.notifier.rpc_notifier
+default_notification_level = INFO
+notification_topics = notifications
+
+{% include "section-zeromq" %}
+
+{% include "section-rabbitmq-oslo" %}
+
+[QUOTAS]
+
+[DEFAULT_SERVICETYPE]
+
+[AGENT]
+root_helper = sudo neutron-rootwrap /etc/neutron/rootwrap.conf
+
+[keystone_authtoken]
+signing_dir = /var/lib/neutron/keystone-signing
+
+[oslo_concurrency]
+lock_path = $state_path/lock
diff --git a/templates/neutron_sudoers b/templates/neutron_sudoers
new file mode 100644
index 00000000..d6fec647
--- /dev/null
+++ b/templates/neutron_sudoers
@@ -0,0 +1,4 @@
+Defaults:neutron !requiretty
+
+neutron ALL = (root) NOPASSWD: /usr/local/bin/neutron-rootwrap /etc/neutron/rootwrap.conf *
+
diff --git a/templates/upstart/neutron-ovs-cleanup.upstart b/templates/upstart/neutron-ovs-cleanup.upstart
new file mode 100644
index 00000000..cbbd0db2
--- /dev/null
+++ b/templates/upstart/neutron-ovs-cleanup.upstart
@@ -0,0 +1,17 @@
+description "{{ service_description }}"
+author "Juju {{ charm_name }} Charm "
+
+start on started openvswitch-switch
+stop on runlevel [!2345]
+
+pre-start script
+ mkdir -p /var/run/neutron
+ chown neutron:root /var/run/neutron
+end script
+
+pre-start script
+ [ ! -x /usr/bin/{{ process_name }} ] && exit 0
+ start-stop-daemon --start --chuid neutron --exec /usr/local/bin/{{ process_name }} -- \
+ --log-file /var/log/neutron/{{ log_file }} \
+ --config-file /etc/neutron/neutron.conf --verbose
+end script
diff --git a/templates/upstart/neutron-plugin-openvswitch-agent.upstart b/templates/upstart/neutron-plugin-openvswitch-agent.upstart
new file mode 100644
index 00000000..17a82edb
--- /dev/null
+++ b/templates/upstart/neutron-plugin-openvswitch-agent.upstart
@@ -0,0 +1,18 @@
+description "{{ service_description }}"
+author "Juju {{ charm_name }} Charm "
+
+start on runlevel [2345] and started {{ cleanup_process_name}}
+stop on runlevel [!2345]
+
+respawn
+
+chdir /var/run
+
+pre-start script
+ mkdir -p /var/run/neutron
+ chown neutron:root /var/run/neutron
+end script
+
+exec start-stop-daemon --start --chuid neutron --exec /usr/local/bin/{{ process_name }} -- \
+ --config-file=/etc/neutron/neutron.conf --config-file={{ plugin_config }} \
+ --log-file={{ log_file }}
diff --git a/tests/16-basic-trusty-icehouse-git b/tests/16-basic-trusty-icehouse-git
new file mode 100755
index 00000000..de2fec89
--- /dev/null
+++ b/tests/16-basic-trusty-icehouse-git
@@ -0,0 +1,9 @@
+#!/usr/bin/python
+
+"""Amulet tests on a basic neutron-openvswitch git deployment on trusty-icehouse."""
+
+from basic_deployment import NeutronOVSBasicDeployment
+
+if __name__ == '__main__':
+ deployment = NeutronOVSBasicDeployment(series='trusty', git=True)
+ deployment.run_tests()
diff --git a/tests/16-basic-trusty-juno b/tests/17-basic-trusty-juno
similarity index 100%
rename from tests/16-basic-trusty-juno
rename to tests/17-basic-trusty-juno
diff --git a/tests/18-basic-trusty-juno-git b/tests/18-basic-trusty-juno-git
new file mode 100755
index 00000000..618c9aa2
--- /dev/null
+++ b/tests/18-basic-trusty-juno-git
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+
+"""Amulet tests on a basic neutron-openvswitch git deployment on trusty-juno."""
+
+from basic_deployment import NeutronOVSBasicDeployment
+
+if __name__ == '__main__':
+ deployment = NeutronOVSBasicDeployment(series='trusty',
+ openstack='cloud:trusty-juno',
+ source='cloud:trusty-updates/juno',
+ git=True)
+ deployment.run_tests()
diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py
index 6fee544d..acde6898 100644
--- a/tests/basic_deployment.py
+++ b/tests/basic_deployment.py
@@ -2,6 +2,7 @@
import amulet
import time
+import yaml
from charmhelpers.contrib.openstack.amulet.deployment import (
OpenStackAmuletDeployment
@@ -24,10 +25,12 @@ u = OpenStackAmuletUtils(ERROR)
class NeutronOVSBasicDeployment(OpenStackAmuletDeployment):
"""Amulet tests on a basic neutron-openvswtich deployment."""
- def __init__(self, series, openstack=None, source=None, stable=False):
+ def __init__(self, series, openstack=None, source=None, git=False,
+ stable=False):
"""Deploy the entire test environment."""
super(NeutronOVSBasicDeployment, self).__init__(series, openstack,
source, stable)
+ self.git = git
self._add_services()
self._add_relations()
self._configure_services()
@@ -61,7 +64,24 @@ class NeutronOVSBasicDeployment(OpenStackAmuletDeployment):
def _configure_services(self):
"""Configure all of the services."""
- configs = {}
+ neutron_ovs_config = {}
+ if self.git:
+ branch = 'stable/' + self._get_openstack_release_string()
+ openstack_origin_git = {
+ 'repositories': [
+ {'name': 'requirements',
+ 'repository': 'git://git.openstack.org/openstack/requirements',
+ 'branch': branch},
+ {'name': 'neutron',
+ 'repository': 'git://git.openstack.org/openstack/neutron',
+ 'branch': branch},
+ ],
+ 'directory': '/mnt/openstack-git',
+ 'http_proxy': 'http://squid.internal:3128',
+ 'https_proxy': 'https://squid.internal:3128',
+ }
+ neutron_ovs_config['openstack-origin-git'] = yaml.dump(openstack_origin_git)
+ configs = {'neutron-openvswitch': neutron_ovs_config}
super(NeutronOVSBasicDeployment, self)._configure_services(configs)
def _initialize_tests(self):
@@ -76,7 +96,8 @@ class NeutronOVSBasicDeployment(OpenStackAmuletDeployment):
service units."""
commands = {
- self.compute_sentry: ['status nova-compute'],
+ self.compute_sentry: ['status nova-compute',
+ 'status neutron-plugin-openvswitch-agent'],
self.rabbitmq_sentry: ['service rabbitmq-server status'],
self.neutron_api_sentry: ['status neutron-server'],
}
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/__init__.py b/unit_tests/__init__.py
index 415b2110..43aa3614 100644
--- a/unit_tests/__init__.py
+++ b/unit_tests/__init__.py
@@ -1,2 +1,4 @@
import sys
+
+sys.path.append('actions/')
sys.path.append('hooks/')
diff --git a/unit_tests/test_actions_git_reinstall.py b/unit_tests/test_actions_git_reinstall.py
new file mode 100644
index 00000000..76da5218
--- /dev/null
+++ b/unit_tests/test_actions_git_reinstall.py
@@ -0,0 +1,85 @@
+from mock import patch
+
+with patch('charmhelpers.core.hookenv.config') as config:
+ config.return_value = 'neutron'
+ import neutron_ovs_utils as utils # noqa
+
+import git_reinstall
+
+from test_utils import (
+ CharmTestCase
+)
+
+TO_PATCH = [
+ 'config',
+]
+
+
+openstack_origin_git = \
+ """repositories:
+ - {name: requirements,
+ repository: 'git://git.openstack.org/openstack/requirements',
+ branch: stable/juno}
+ - {name: neutron,
+ repository: 'git://git.openstack.org/openstack/neutron',
+ branch: stable/juno}"""
+
+
+class TestNeutronOVSActions(CharmTestCase):
+
+ def setUp(self):
+ super(TestNeutronOVSActions, self).setUp(git_reinstall, TO_PATCH)
+ self.config.side_effect = self.test_config.get
+
+ @patch.object(git_reinstall, 'action_set')
+ @patch.object(git_reinstall, 'action_fail')
+ @patch.object(git_reinstall, 'git_install')
+ def test_git_reinstall(self, git_install, action_fail, action_set):
+ self.test_config.set('openstack-origin-git', openstack_origin_git)
+
+ git_reinstall.git_reinstall()
+
+ git_install.assert_called_with(openstack_origin_git)
+ self.assertTrue(git_install.called)
+ self.assertFalse(action_set.called)
+ self.assertFalse(action_fail.called)
+
+ @patch.object(git_reinstall, 'action_set')
+ @patch.object(git_reinstall, 'action_fail')
+ @patch.object(git_reinstall, 'git_install')
+ @patch('charmhelpers.contrib.openstack.utils.config')
+ def test_git_reinstall_not_configured(self, _config, git_install,
+ action_fail, action_set):
+ _config.return_value = None
+
+ git_reinstall.git_reinstall()
+
+ msg = 'openstack-origin-git is not configured'
+ action_fail.assert_called_with(msg)
+ self.assertFalse(git_install.called)
+ self.assertFalse(action_set.called)
+
+ @patch.object(git_reinstall, 'action_set')
+ @patch.object(git_reinstall, 'action_fail')
+ @patch.object(git_reinstall, 'git_install')
+ @patch('charmhelpers.contrib.openstack.utils.config')
+ def test_git_reinstall_exception(self, _config, git_install,
+ action_fail, action_set):
+ _config.return_value = openstack_origin_git
+ e = OSError('something bad happened')
+ git_install.side_effect = e
+ traceback = (
+ "Traceback (most recent call last):\n"
+ " File \"actions/git_reinstall.py\", line 33, in git_reinstall\n"
+ " git_install(config(\'openstack-origin-git\'))\n"
+ " File \"/usr/lib/python2.7/dist-packages/mock.py\", line 964, in __call__\n" # noqa
+ " return _mock_self._mock_call(*args, **kwargs)\n"
+ " File \"/usr/lib/python2.7/dist-packages/mock.py\", line 1019, in _mock_call\n" # noqa
+ " raise effect\n"
+ "OSError: something bad happened\n")
+
+ git_reinstall.git_reinstall()
+
+ msg = 'git-reinstall resulted in an unexpected error'
+ action_fail.assert_called_with(msg)
+ action_set.assert_called_with({'traceback': traceback})
diff --git a/unit_tests/test_neutron_ovs_context.py b/unit_tests/test_neutron_ovs_context.py
index a7da378b..72fb5543 100644
--- a/unit_tests/test_neutron_ovs_context.py
+++ b/unit_tests/test_neutron_ovs_context.py
@@ -1,27 +1,30 @@
from test_utils import CharmTestCase
+from test_utils import patch_open
from mock import patch
import neutron_ovs_context as context
import charmhelpers
+
TO_PATCH = [
- 'relation_get',
- 'relation_ids',
- 'related_units',
+ 'resolve_address',
'config',
'unit_get',
- 'add_bridge',
- 'add_bridge_port',
- 'service_running',
- 'service_start',
'get_host_ip',
]
+def fake_context(settings):
+ def outer():
+ def inner():
+ return settings
+ return inner
+ return outer
+
+
class OVSPluginContextTest(CharmTestCase):
def setUp(self):
super(OVSPluginContextTest, self).setUp(context, TO_PATCH)
- self.relation_get.side_effect = self.test_relation.get
self.config.side_effect = self.test_config.get
self.test_config.set('debug', True)
self.test_config.set('verbose', True)
@@ -30,49 +33,41 @@ class OVSPluginContextTest(CharmTestCase):
def tearDown(self):
super(OVSPluginContextTest, self).tearDown()
+ @patch('charmhelpers.contrib.openstack.context.config')
@patch('charmhelpers.contrib.openstack.context.NeutronPortContext.'
'resolve_ports')
- def test_data_port_name(self, mock_resolve_ports):
+ def test_data_port_name(self, mock_resolve_ports, config):
self.test_config.set('data-port', 'br-data:em1')
+ config.side_effect = self.test_config.get
mock_resolve_ports.side_effect = lambda ports: ports
- self.assertEquals(context.DataPortContext()(),
- {'br-data': 'em1'})
+ self.assertEquals(
+ charmhelpers.contrib.openstack.context.DataPortContext()(),
+ {'br-data': 'em1'}
+ )
- @patch.object(context, 'get_nic_hwaddr')
+ @patch('charmhelpers.contrib.openstack.context.config')
@patch('charmhelpers.contrib.openstack.context.get_nic_hwaddr')
@patch('charmhelpers.contrib.openstack.context.list_nics')
- def test_data_port_mac(self, list_nics, get_nic_hwaddr, get_nic_hwaddr2):
+ def test_data_port_mac(self, list_nics, get_nic_hwaddr, config):
machine_machs = {
'em1': 'aa:aa:aa:aa:aa:aa',
'eth0': 'bb:bb:bb:bb:bb:bb',
}
- get_nic_hwaddr2.side_effect = lambda nic: machine_machs[nic]
absent_mac = "cc:cc:cc:cc:cc:cc"
config_macs = ("br-d1:%s br-d2:%s" %
(absent_mac, machine_machs['em1']))
self.test_config.set('data-port', config_macs)
+ config.side_effect = self.test_config.get
list_nics.return_value = machine_machs.keys()
get_nic_hwaddr.side_effect = lambda nic: machine_machs[nic]
- self.assertEquals(context.DataPortContext()(),
- {'br-d2': 'em1'})
-
- @patch('charmhelpers.contrib.openstack.context.NeutronPortContext.'
- 'resolve_ports')
- def test_ensure_bridge_data_port_present(self, mock_resolve_ports):
- self.test_config.set('data-port', 'br-data:em1')
- self.test_config.set('bridge-mappings', 'phybr1:br-data')
-
- def add_port(bridge, port, promisc):
- if bridge == 'br-data' and port == 'em1' and promisc is True:
- self.bridge_added = True
- return
- self.bridge_added = False
-
- mock_resolve_ports.side_effect = lambda ports: ports
- self.add_bridge_port.side_effect = add_port
- context.OVSPluginContext()._ensure_bridge()
- self.assertEquals(self.bridge_added, True)
+ self.assertEquals(
+ charmhelpers.contrib.openstack.context.DataPortContext()(),
+ {'br-d2': 'em1'}
+ )
+ @patch.object(charmhelpers.contrib.openstack.context, 'relation_get')
+ @patch.object(charmhelpers.contrib.openstack.context, 'relation_ids')
+ @patch.object(charmhelpers.contrib.openstack.context, 'related_units')
@patch.object(charmhelpers.contrib.openstack.context, 'config')
@patch.object(charmhelpers.contrib.openstack.context, 'unit_get')
@patch.object(charmhelpers.contrib.openstack.context, 'is_clustered')
@@ -84,7 +79,7 @@ class OVSPluginContextTest(CharmTestCase):
@patch.object(charmhelpers.contrib.openstack.context, 'unit_private_ip')
def test_neutroncc_context_api_rel(self, _unit_priv_ip, _npa, _ens_pkgs,
_save_ff, _https, _is_clus, _unit_get,
- _config):
+ _config, _runits, _rids, _rget):
def mock_npa(plugin, section, manager):
if section == "driver":
return "neutron.randomdriver"
@@ -95,19 +90,22 @@ class OVSPluginContextTest(CharmTestCase):
_unit_get.return_value = '127.0.0.13'
_unit_priv_ip.return_value = '127.0.0.14'
_is_clus.return_value = False
- self.related_units.return_value = ['unit1']
- self.relation_ids.return_value = ['rid2']
- self.test_relation.set({'neutron-security-groups': 'True',
- 'l2-population': 'True',
- 'network-device-mtu': 1500,
- 'overlay-network-type': 'gre',
- })
+ _runits.return_value = ['unit1']
+ _rids.return_value = ['rid2']
+ rdata = {
+ 'neutron-security-groups': 'True',
+ 'l2-population': 'True',
+ 'network-device-mtu': 1500,
+ 'overlay-network-type': 'gre',
+ 'enable-dvr': 'True',
+ }
+ _rget.side_effect = lambda *args, **kwargs: rdata
self.get_host_ip.return_value = '127.0.0.15'
- self.service_running.return_value = False
napi_ctxt = context.OVSPluginContext()
expect = {
'neutron_alchemy_flags': {},
'neutron_security_groups': True,
+ 'distributed_routing': True,
'verbose': True,
'local_ip': '127.0.0.15',
'network_device_mtu': 1500,
@@ -126,8 +124,10 @@ class OVSPluginContextTest(CharmTestCase):
'vlan_ranges': 'physnet1:1000:2000',
}
self.assertEquals(expect, napi_ctxt())
- self.service_start.assertCalled()
+ @patch.object(charmhelpers.contrib.openstack.context, 'relation_get')
+ @patch.object(charmhelpers.contrib.openstack.context, 'relation_ids')
+ @patch.object(charmhelpers.contrib.openstack.context, 'related_units')
@patch.object(charmhelpers.contrib.openstack.context, 'config')
@patch.object(charmhelpers.contrib.openstack.context, 'unit_get')
@patch.object(charmhelpers.contrib.openstack.context, 'is_clustered')
@@ -142,7 +142,8 @@ class OVSPluginContextTest(CharmTestCase):
_ens_pkgs, _save_ff,
_https, _is_clus,
_unit_get,
- _config):
+ _config, _runits,
+ _rids, _rget):
def mock_npa(plugin, section, manager):
if section == "driver":
return "neutron.randomdriver"
@@ -155,17 +156,19 @@ class OVSPluginContextTest(CharmTestCase):
_unit_priv_ip.return_value = '127.0.0.14'
_is_clus.return_value = False
self.test_config.set('disable-security-groups', True)
- self.related_units.return_value = ['unit1']
- self.relation_ids.return_value = ['rid2']
- self.test_relation.set({'neutron-security-groups': 'True',
- 'l2-population': 'True',
- 'network-device-mtu': 1500,
- 'overlay-network-type': 'gre',
- })
+ _runits.return_value = ['unit1']
+ _rids.return_value = ['rid2']
+ rdata = {
+ 'neutron-security-groups': 'True',
+ 'l2-population': 'True',
+ 'network-device-mtu': 1500,
+ 'overlay-network-type': 'gre',
+ }
+ _rget.side_effect = lambda *args, **kwargs: rdata
self.get_host_ip.return_value = '127.0.0.15'
- self.service_running.return_value = False
napi_ctxt = context.OVSPluginContext()
expect = {
+ 'distributed_routing': False,
'neutron_alchemy_flags': {},
'neutron_security_groups': False,
'verbose': True,
@@ -186,4 +189,95 @@ class OVSPluginContextTest(CharmTestCase):
'vlan_ranges': 'physnet1:1000:2000',
}
self.assertEquals(expect, napi_ctxt())
- self.service_start.assertCalled()
+
+
+class L3AgentContextTest(CharmTestCase):
+
+ def setUp(self):
+ super(L3AgentContextTest, self).setUp(context, TO_PATCH)
+ self.config.side_effect = self.test_config.get
+
+ def tearDown(self):
+ super(L3AgentContextTest, self).tearDown()
+
+ @patch.object(charmhelpers.contrib.openstack.context, 'relation_get')
+ @patch.object(charmhelpers.contrib.openstack.context, 'relation_ids')
+ @patch.object(charmhelpers.contrib.openstack.context, 'related_units')
+ def test_dvr_enabled(self, _runits, _rids, _rget):
+ _runits.return_value = ['unit1']
+ _rids.return_value = ['rid2']
+ rdata = {
+ 'neutron-security-groups': 'True',
+ 'enable-dvr': 'True',
+ 'l2-population': 'True',
+ 'overlay-network-type': 'vxlan',
+ 'network-device-mtu': 1500,
+ }
+ _rget.side_effect = lambda *args, **kwargs: rdata
+ self.assertEquals(context.L3AgentContext()(), {'agent_mode': 'dvr'})
+
+ @patch.object(charmhelpers.contrib.openstack.context, 'relation_get')
+ @patch.object(charmhelpers.contrib.openstack.context, 'relation_ids')
+ @patch.object(charmhelpers.contrib.openstack.context, 'related_units')
+ def test_dvr_disabled(self, _runits, _rids, _rget):
+ _runits.return_value = ['unit1']
+ _rids.return_value = ['rid2']
+ rdata = {
+ 'neutron-security-groups': 'True',
+ 'enable-dvr': 'False',
+ 'l2-population': 'True',
+ 'overlay-network-type': 'vxlan',
+ 'network-device-mtu': 1500,
+ }
+ _rget.side_effect = lambda *args, **kwargs: rdata
+ self.assertEquals(context.L3AgentContext()(), {'agent_mode': 'legacy'})
+
+
+class DVRSharedSecretContext(CharmTestCase):
+
+ def setUp(self):
+ super(DVRSharedSecretContext, self).setUp(context,
+ TO_PATCH)
+ self.config.side_effect = self.test_config.get
+
+ @patch('os.path')
+ @patch('uuid.uuid4')
+ def test_secret_created_stored(self, _uuid4, _path):
+ _path.exists.return_value = False
+ _uuid4.return_value = 'secret_thing'
+ with patch_open() as (_open, _file):
+ self.assertEquals(context.get_shared_secret(),
+ 'secret_thing')
+ _open.assert_called_with(
+ context.SHARED_SECRET.format('quantum'), 'w')
+ _file.write.assert_called_with('secret_thing')
+
+ @patch('os.path')
+ def test_secret_retrieved(self, _path):
+ _path.exists.return_value = True
+ with patch_open() as (_open, _file):
+ _file.read.return_value = 'secret_thing\n'
+ self.assertEquals(context.get_shared_secret(),
+ 'secret_thing')
+ _open.assert_called_with(
+ context.SHARED_SECRET.format('quantum'), 'r')
+
+ @patch.object(context, 'NeutronAPIContext')
+ @patch.object(context, 'get_shared_secret')
+ def test_shared_secretcontext_dvr(self, _shared_secret,
+ _NeutronAPIContext):
+ _NeutronAPIContext.side_effect = fake_context({'enable_dvr': True})
+ _shared_secret.return_value = 'secret_thing'
+ self.resolve_address.return_value = '10.0.0.10'
+ self.assertEquals(context.DVRSharedSecretContext()(),
+ {'shared_secret': 'secret_thing',
+ 'local_ip': '10.0.0.10'})
+
+ @patch.object(context, 'NeutronAPIContext')
+ @patch.object(context, 'get_shared_secret')
+ def test_shared_secretcontext_nodvr(self, _shared_secret,
+ _NeutronAPIContext):
+ _NeutronAPIContext.side_effect = fake_context({'enable_dvr': False})
+ _shared_secret.return_value = 'secret_thing'
+ self.resolve_address.return_value = '10.0.0.10'
+ self.assertEquals(context.DVRSharedSecretContext()(), {})
diff --git a/unit_tests/test_neutron_ovs_hooks.py b/unit_tests/test_neutron_ovs_hooks.py
index d473086b..342b7c8c 100644
--- a/unit_tests/test_neutron_ovs_hooks.py
+++ b/unit_tests/test_neutron_ovs_hooks.py
@@ -1,7 +1,7 @@
-
from mock import MagicMock, patch, call
-from test_utils import CharmTestCase
+import yaml
+from test_utils import CharmTestCase
with patch('charmhelpers.core.hookenv.config') as config:
config.return_value = 'neutron'
@@ -21,11 +21,18 @@ utils.restart_map = _map
TO_PATCH = [
'apt_update',
'apt_install',
+ 'apt_purge',
'config',
'CONFIGS',
'determine_packages',
+ 'determine_dvr_packages',
+ 'get_shared_secret',
+ 'git_install',
'log',
+ 'relation_ids',
'relation_set',
+ 'configure_ovs',
+ 'use_dvr',
]
NEUTRON_CONF_DIR = "/etc/neutron"
@@ -44,7 +51,9 @@ class NeutronOVSHooksTests(CharmTestCase):
hooks.hooks.execute([
'hooks/{}'.format(hookname)])
- def test_install_hook(self):
+ @patch.object(hooks, 'git_install_requested')
+ def test_install_hook(self, git_requested):
+ git_requested.return_value = False
_pkgs = ['foo', 'bar']
self.determine_packages.return_value = [_pkgs]
self._call_hook('install')
@@ -53,9 +62,97 @@ class NeutronOVSHooksTests(CharmTestCase):
call(_pkgs, fatal=True),
])
- def test_config_changed(self):
+ @patch.object(hooks, 'git_install_requested')
+ def test_install_hook_git(self, git_requested):
+ git_requested.return_value = True
+ _pkgs = ['foo', 'bar']
+ self.determine_packages.return_value = _pkgs
+ openstack_origin_git = {
+ 'repositories': [
+ {'name': 'requirements',
+ 'repository': 'git://git.openstack.org/openstack/requirements', # noqa
+ 'branch': 'stable/juno'},
+ {'name': 'neutron',
+ 'repository': 'git://git.openstack.org/openstack/neutron',
+ 'branch': 'stable/juno'}
+ ],
+ 'directory': '/mnt/openstack-git',
+ }
+ projects_yaml = yaml.dump(openstack_origin_git)
+ self.test_config.set('openstack-origin-git', projects_yaml)
+ self._call_hook('install')
+ self.apt_update.assert_called_with()
+ self.assertTrue(self.determine_packages)
+ self.git_install.assert_called_with(projects_yaml)
+
+ @patch.object(hooks, 'git_install_requested')
+ def test_config_changed(self, git_requested):
+ git_requested.return_value = False
+ self.relation_ids.return_value = ['relid']
+ _zmq_joined = self.patch('zeromq_configuration_relation_joined')
self._call_hook('config-changed')
self.assertTrue(self.CONFIGS.write_all.called)
+ self.assertTrue(_zmq_joined.called_with('relid'))
+ self.configure_ovs.assert_called_with()
+
+ @patch.object(hooks, 'git_install_requested')
+ @patch.object(hooks, 'config_value_changed')
+ def test_config_changed_git(self, config_val_changed, git_requested):
+ git_requested.return_value = True
+ self.relation_ids.return_value = ['relid']
+ _zmq_joined = self.patch('zeromq_configuration_relation_joined')
+ openstack_origin_git = {
+ 'repositories': [
+ {'name': 'requirements',
+ 'repository':
+ 'git://git.openstack.org/openstack/requirements',
+ 'branch': 'stable/juno'},
+ {'name': 'neutron',
+ 'repository': 'git://git.openstack.org/openstack/neutron',
+ 'branch': 'stable/juno'}
+ ],
+ 'directory': '/mnt/openstack-git',
+ }
+ projects_yaml = yaml.dump(openstack_origin_git)
+ self.test_config.set('openstack-origin-git', projects_yaml)
+ self._call_hook('config-changed')
+ self.git_install.assert_called_with(projects_yaml)
+ self.assertTrue(self.CONFIGS.write_all.called)
+ self.assertTrue(_zmq_joined.called_with('relid'))
+ self.configure_ovs.assert_called_with()
+
+ @patch.object(hooks, 'git_install_requested')
+ def test_config_changed_dvr(self, git_requested):
+ git_requested.return_value = False
+ self.determine_dvr_packages.return_value = ['dvr']
+ self._call_hook('config-changed')
+ self.apt_update.assert_called_with()
+ self.assertTrue(self.CONFIGS.write_all.called)
+ self.apt_install.assert_has_calls([
+ call(['dvr'], fatal=True),
+ ])
+ self.configure_ovs.assert_called_with()
+
+ @patch.object(hooks, 'neutron_plugin_joined')
+ def test_neutron_plugin_api(self, _plugin_joined):
+ self.relation_ids.return_value = ['rid']
+ self._call_hook('neutron-plugin-api-relation-changed')
+ self.configure_ovs.assert_called_with()
+ self.assertTrue(self.CONFIGS.write_all.called)
+ _plugin_joined.assert_called_with(relation_id='rid')
+
+ @patch.object(hooks, 'git_install_requested')
+ def test_neutron_plugin_joined(self, git_requested):
+ git_requested.return_value = False
+ self.get_shared_secret.return_value = 'secret'
+ self._call_hook('neutron-plugin-relation-joined')
+ rel_data = {
+ 'metadata-shared-secret': 'secret',
+ }
+ self.relation_set.assert_called_with(
+ relation_id=None,
+ **rel_data
+ )
def test_amqp_joined(self):
self._call_hook('amqp-relation-joined')
diff --git a/unit_tests/test_neutron_ovs_utils.py b/unit_tests/test_neutron_ovs_utils.py
index 5d0a304b..c75d7915 100644
--- a/unit_tests/test_neutron_ovs_utils.py
+++ b/unit_tests/test_neutron_ovs_utils.py
@@ -1,11 +1,12 @@
-from mock import MagicMock, patch
+from mock import MagicMock, patch, call
from collections import OrderedDict
import charmhelpers.contrib.openstack.templating as templating
templating.OSConfigRenderer = MagicMock()
import neutron_ovs_utils as nutils
+import neutron_ovs_context
from test_utils import (
CharmTestCase,
@@ -15,12 +16,28 @@ import charmhelpers.core.hookenv as hookenv
TO_PATCH = [
+ 'add_bridge',
+ 'add_bridge_port',
+ 'config',
'os_release',
'neutron_plugin_attribute',
+ 'full_restart',
+ 'service_restart',
+ 'service_running',
+ 'ExternalPortContext',
]
head_pkg = 'linux-headers-3.15.0-5-generic'
+openstack_origin_git = \
+ """repositories:
+ - {name: requirements,
+ repository: 'git://git.openstack.org/openstack/requirements',
+ branch: stable/juno}
+ - {name: neutron,
+ repository: 'git://git.openstack.org/openstack/neutron',
+ branch: stable/juno}"""
+
def _mock_npa(plugin, attr, net_manager=None):
plugins = {
@@ -38,26 +55,42 @@ def _mock_npa(plugin, attr, net_manager=None):
return plugins[plugin][attr]
+class DummyContext():
+
+ def __init__(self, return_value):
+ self.return_value = return_value
+
+ def __call__(self):
+ return self.return_value
+
+
class TestNeutronOVSUtils(CharmTestCase):
def setUp(self):
super(TestNeutronOVSUtils, self).setUp(nutils, TO_PATCH)
self.neutron_plugin_attribute.side_effect = _mock_npa
+ self.config.side_effect = self.test_config.get
def tearDown(self):
# Reset cached cache
hookenv.cache = {}
+ @patch.object(nutils, 'use_dvr')
+ @patch.object(nutils, 'git_install_requested')
@patch.object(charmhelpers.contrib.openstack.neutron, 'os_release')
@patch.object(charmhelpers.contrib.openstack.neutron, 'headers_package')
- def test_determine_packages(self, _head_pkgs, _os_rel):
+ def test_determine_packages(self, _head_pkgs, _os_rel, _git_requested,
+ _use_dvr):
+ _git_requested.return_value = False
+ _use_dvr.return_value = False
_os_rel.return_value = 'trusty'
_head_pkgs.return_value = head_pkg
pkg_list = nutils.determine_packages()
expect = [['neutron-plugin-openvswitch-agent'], [head_pkg]]
self.assertItemsEqual(pkg_list, expect)
- def test_register_configs(self):
+ @patch.object(nutils, 'use_dvr')
+ def test_register_configs(self, _use_dvr):
class _mock_OSConfigRenderer():
def __init__(self, templates_dir=None, openstack_release=None):
self.configs = []
@@ -67,6 +100,7 @@ class TestNeutronOVSUtils(CharmTestCase):
self.configs.append(config)
self.ctxts.append(ctxt)
+ _use_dvr.return_value = False
self.os_release.return_value = 'trusty'
templating.OSConfigRenderer.side_effect = _mock_OSConfigRenderer
_regconfs = nutils.register_configs()
@@ -75,12 +109,28 @@ class TestNeutronOVSUtils(CharmTestCase):
'/etc/init/os-charm-phy-nic-mtu.conf']
self.assertItemsEqual(_regconfs.configs, confs)
- def test_resource_map(self):
+ @patch.object(nutils, 'use_dvr')
+ def test_resource_map(self, _use_dvr):
+ _use_dvr.return_value = False
_map = nutils.resource_map()
+ svcs = ['neutron-plugin-openvswitch-agent']
confs = [nutils.NEUTRON_CONF]
[self.assertIn(q_conf, _map.keys()) for q_conf in confs]
+ self.assertEqual(_map[nutils.NEUTRON_CONF]['services'], svcs)
- def test_restart_map(self):
+ @patch.object(nutils, 'use_dvr')
+ def test_resource_map_dvr(self, _use_dvr):
+ _use_dvr.return_value = True
+ _map = nutils.resource_map()
+ svcs = ['neutron-plugin-openvswitch-agent', 'neutron-metadata-agent',
+ 'neutron-l3-agent']
+ confs = [nutils.NEUTRON_CONF]
+ [self.assertIn(q_conf, _map.keys()) for q_conf in confs]
+ self.assertEqual(_map[nutils.NEUTRON_CONF]['services'], svcs)
+
+ @patch.object(nutils, 'use_dvr')
+ def test_restart_map(self, _use_dvr):
+ _use_dvr.return_value = False
_restart_map = nutils.restart_map()
ML2CONF = "/etc/neutron/plugins/ml2/ml2_conf.ini"
expect = OrderedDict([
@@ -92,3 +142,170 @@ class TestNeutronOVSUtils(CharmTestCase):
for item in _restart_map:
self.assertTrue(item in _restart_map)
self.assertTrue(expect[item] == _restart_map[item])
+
+ @patch.object(nutils, 'use_dvr')
+ @patch('charmhelpers.contrib.openstack.context.config')
+ def test_configure_ovs_ovs_data_port(self, mock_config, _use_dvr):
+ _use_dvr.return_value = False
+ mock_config.side_effect = self.test_config.get
+ self.config.side_effect = self.test_config.get
+ self.ExternalPortContext.return_value = \
+ DummyContext(return_value=None)
+ # Test back-compatibility i.e. port but no bridge (so br-data is
+ # assumed)
+ self.test_config.set('data-port', 'eth0')
+ nutils.configure_ovs()
+ self.add_bridge.assert_has_calls([
+ call('br-int'),
+ call('br-ex'),
+ call('br-data')
+ ])
+ self.assertTrue(self.add_bridge_port.called)
+
+ # Now test with bridge:port format
+ self.test_config.set('data-port', 'br-foo:eth0')
+ self.add_bridge.reset_mock()
+ self.add_bridge_port.reset_mock()
+ nutils.configure_ovs()
+ self.add_bridge.assert_has_calls([
+ call('br-int'),
+ call('br-ex'),
+ call('br-data')
+ ])
+ # Not called since we have a bogus bridge in data-ports
+ self.assertFalse(self.add_bridge_port.called)
+
+ @patch.object(nutils, 'use_dvr')
+ @patch('charmhelpers.contrib.openstack.context.config')
+ def test_configure_ovs_starts_service_if_required(self, mock_config,
+ _use_dvr):
+ _use_dvr.return_value = False
+ mock_config.side_effect = self.test_config.get
+ self.config.return_value = 'ovs'
+ self.service_running.return_value = False
+ nutils.configure_ovs()
+ self.assertTrue(self.full_restart.called)
+
+ @patch.object(nutils, 'use_dvr')
+ @patch('charmhelpers.contrib.openstack.context.config')
+ def test_configure_ovs_doesnt_restart_service(self, mock_config, _use_dvr):
+ _use_dvr.return_value = False
+ mock_config.side_effect = self.test_config.get
+ self.config.side_effect = self.test_config.get
+ self.service_running.return_value = True
+ nutils.configure_ovs()
+ self.assertFalse(self.full_restart.called)
+
+ @patch.object(nutils, 'use_dvr')
+ @patch('charmhelpers.contrib.openstack.context.config')
+ def test_configure_ovs_ovs_ext_port(self, mock_config, _use_dvr):
+ _use_dvr.return_value = True
+ mock_config.side_effect = self.test_config.get
+ self.config.side_effect = self.test_config.get
+ self.test_config.set('ext-port', 'eth0')
+ self.ExternalPortContext.return_value = \
+ DummyContext(return_value={'ext_port': 'eth0'})
+ nutils.configure_ovs()
+ self.add_bridge.assert_has_calls([
+ call('br-int'),
+ call('br-ex'),
+ call('br-data')
+ ])
+ self.add_bridge_port.assert_called_with('br-ex', 'eth0')
+
+ @patch.object(neutron_ovs_context, 'DVRSharedSecretContext')
+ def test_get_shared_secret(self, _dvr_secret_ctxt):
+ _dvr_secret_ctxt.return_value = \
+ DummyContext(return_value={'shared_secret': 'supersecret'})
+ self.assertEqual(nutils.get_shared_secret(), 'supersecret')
+
+ @patch.object(nutils, 'git_install_requested')
+ @patch.object(nutils, 'git_clone_and_install')
+ @patch.object(nutils, 'git_post_install')
+ @patch.object(nutils, 'git_pre_install')
+ def test_git_install(self, git_pre, git_post, git_clone_and_install,
+ git_requested):
+ projects_yaml = openstack_origin_git
+ git_requested.return_value = True
+ nutils.git_install(projects_yaml)
+ self.assertTrue(git_pre.called)
+ git_clone_and_install.assert_called_with(openstack_origin_git,
+ core_project='neutron')
+ self.assertTrue(git_post.called)
+
+ @patch.object(nutils, 'mkdir')
+ @patch.object(nutils, 'write_file')
+ @patch.object(nutils, 'add_user_to_group')
+ @patch.object(nutils, 'add_group')
+ @patch.object(nutils, 'adduser')
+ def test_git_pre_install(self, adduser, add_group, add_user_to_group,
+ write_file, mkdir):
+ nutils.git_pre_install()
+ adduser.assert_called_with('neutron', shell='/bin/bash',
+ system_user=True)
+ add_group.assert_called_with('neutron', system_group=True)
+ add_user_to_group.assert_called_with('neutron', 'neutron')
+ expected = [
+ call('/etc/neutron', owner='neutron',
+ group='neutron', perms=0700, force=False),
+ call('/etc/neutron/rootwrap.d', owner='neutron',
+ group='neutron', perms=0700, force=False),
+ call('/var/lib/neutron', owner='neutron',
+ group='neutron', perms=0700, force=False),
+ call('/var/lib/neutron/lock', owner='neutron',
+ group='neutron', perms=0700, force=False),
+ call('/var/log/neutron', owner='neutron',
+ group='neutron', perms=0700, force=False),
+ ]
+ self.assertEquals(mkdir.call_args_list, expected)
+ expected = [
+ call('/var/log/neutron/server.log', '', owner='neutron',
+ group='neutron', perms=0600),
+ ]
+ self.assertEquals(write_file.call_args_list, expected)
+
+ @patch.object(nutils, 'git_src_dir')
+ @patch.object(nutils, 'service_restart')
+ @patch.object(nutils, 'render')
+ @patch('os.path.join')
+ @patch('shutil.copyfile')
+ def test_git_post_install(self, copyfile, join, render,
+ service_restart, git_src_dir):
+ projects_yaml = openstack_origin_git
+ join.return_value = 'joined-string'
+ nutils.git_post_install(projects_yaml)
+ expected = [
+ call('joined-string', '/etc/neutron/rootwrap.d/debug.filters'),
+ call('joined-string', '/etc/neutron/policy.json'),
+ call('joined-string', '/etc/neutron/rootwrap.conf'),
+ ]
+ copyfile.assert_has_calls(expected, any_order=True)
+ neutron_ovs_agent_context = {
+ 'service_description': 'Neutron OpenvSwitch Plugin Agent',
+ 'charm_name': 'neutron-openvswitch',
+ 'process_name': 'neutron-openvswitch-agent',
+ 'cleanup_process_name': 'neutron-ovs-cleanup',
+ 'plugin_config': '/etc/neutron/plugins/ml2/ml2_conf.ini',
+ 'log_file': '/var/log/neutron/openvswitch-agent.log',
+ }
+ neutron_ovs_cleanup_context = {
+ 'service_description': 'Neutron OpenvSwitch Cleanup',
+ 'charm_name': 'neutron-openvswitch',
+ 'process_name': 'neutron-ovs-cleanup',
+ 'log_file': '/var/log/neutron/ovs-cleanup.log',
+ }
+ expected = [
+ call('neutron_sudoers', '/etc/sudoers.d/neutron_sudoers', {},
+ perms=0o440),
+ call('upstart/neutron-plugin-openvswitch-agent.upstart',
+ '/etc/init/neutron-plugin-openvswitch-agent.conf',
+ neutron_ovs_agent_context, perms=0o644),
+ call('upstart/neutron-ovs-cleanup.upstart',
+ '/etc/init/neutron-ovs-cleanup.conf',
+ neutron_ovs_cleanup_context, perms=0o644),
+ ]
+ self.assertEquals(render.call_args_list, expected)
+ expected = [
+ call('neutron-plugin-openvswitch-agent'),
+ ]
+ self.assertEquals(service_restart.call_args_list, expected)