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/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/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/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_hooks.py b/hooks/neutron_ovs_hooks.py
index 15323ebf..2f2bd9e9 100755
--- a/hooks/neutron_ovs_hooks.py
+++ b/hooks/neutron_ovs_hooks.py
@@ -19,10 +19,15 @@ from charmhelpers.fetch import (
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,
+ get_topics,
determine_dvr_packages,
get_shared_secret,
register_configs,
@@ -51,6 +56,8 @@ def config_changed():
apt_install(determine_dvr_packages(), fatal=True)
configure_ovs()
CONFIGS.write_all()
+ for rid in relation_ids('zeromq-configuration'):
+ zeromq_configuration_relation_joined(rid)
@hooks.hook('neutron-plugin-api-relation-changed')
@@ -94,6 +101,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 233e5313..2adf8e56 100644
--- a/hooks/neutron_ovs_utils.py
+++ b/hooks/neutron_ovs_utils.py
@@ -44,7 +44,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'],
@@ -122,6 +124,18 @@ def restart_map():
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()
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/icehouse/neutron.conf b/templates/icehouse/neutron.conf
index b0e35f9a..25004db5 100644
--- a/templates/icehouse/neutron.conf
+++ b/templates/icehouse/neutron.conf
@@ -21,7 +21,9 @@ core_plugin = {{ core_plugin }}
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
@@ -36,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/kilo/neutron.conf b/templates/kilo/neutron.conf
index b4703bef..7d5748de 100644
--- a/templates/kilo/neutron.conf
+++ b/templates/kilo/neutron.conf
@@ -24,6 +24,8 @@ notification_driver = neutron.openstack.common.notifier.rpc_notifier
default_notification_level = INFO
notification_topics = notifications
+{% include "section-zeromq" %}
+
{% include "section-rabbitmq-oslo" %}
[QUOTAS]
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_neutron_ovs_hooks.py b/unit_tests/test_neutron_ovs_hooks.py
index d651d00f..6266a523 100644
--- a/unit_tests/test_neutron_ovs_hooks.py
+++ b/unit_tests/test_neutron_ovs_hooks.py
@@ -59,8 +59,11 @@ class NeutronOVSHooksTests(CharmTestCase):
])
def test_config_changed(self):
+ 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()
def test_config_changed_dvr(self):