Deploy from source

This commit is contained in:
Corey Bryant 2015-04-02 20:35:37 +00:00
commit 529c148ec8
43 changed files with 1699 additions and 247 deletions

View File

@ -1,2 +1,3 @@
bin bin
.coverage .coverage
tags

View File

@ -2,7 +2,7 @@
PYTHON := /usr/bin/env python PYTHON := /usr/bin/env python
lint: lint:
@flake8 --exclude hooks/charmhelpers hooks unit_tests tests @flake8 --exclude hooks/charmhelpers actions hooks unit_tests tests
@charm proof @charm proof
unit_test: unit_test:
@ -15,7 +15,7 @@ bin/charm_helpers_sync.py:
> bin/charm_helpers_sync.py > bin/charm_helpers_sync.py
sync: 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 @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml
test: test:
@ -25,7 +25,8 @@ test:
# https://bugs.launchpad.net/amulet/+bug/1320357 # https://bugs.launchpad.net/amulet/+bug/1320357
@juju test -v -p AMULET_HTTP_PROXY --timeout 900 \ @juju test -v -p AMULET_HTTP_PROXY --timeout 900 \
00-setup 14-basic-precise-icehouse 15-basic-trusty-icehouse \ 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 publish: lint unit_test
bzr push lp:charms/neutron-openvswitch bzr push lp:charms/neutron-openvswitch

View File

@ -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. 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}"

2
actions.yaml Normal file
View File

@ -0,0 +1,2 @@
git-reinstall:
description: Reinstall neutron-openvswitch from the openstack-origin-git repositories.

1
actions/git-reinstall Symbolic link
View File

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

40
actions/git_reinstall.py Executable file
View File

@ -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()

View File

@ -1,4 +1,21 @@
options: 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: rabbit-user:
default: neutron default: neutron
type: string type: string
@ -60,3 +77,12 @@ options:
. .
This network will be used for tenant network traffic in overlay This network will be used for tenant network traffic in overlay
networks. 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)

View File

@ -15,6 +15,7 @@
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
import six import six
from collections import OrderedDict
from charmhelpers.contrib.amulet.deployment import ( from charmhelpers.contrib.amulet.deployment import (
AmuletDeployment AmuletDeployment
) )
@ -100,12 +101,34 @@ class OpenStackAmuletDeployment(AmuletDeployment):
""" """
(self.precise_essex, self.precise_folsom, self.precise_grizzly, (self.precise_essex, self.precise_folsom, self.precise_grizzly,
self.precise_havana, self.precise_icehouse, self.precise_havana, self.precise_icehouse,
self.trusty_icehouse) = range(6) self.trusty_icehouse, self.trusty_juno, self.trusty_kilo) = range(8)
releases = { releases = {
('precise', None): self.precise_essex, ('precise', None): self.precise_essex,
('precise', 'cloud:precise-folsom'): self.precise_folsom, ('precise', 'cloud:precise-folsom'): self.precise_folsom,
('precise', 'cloud:precise-grizzly'): self.precise_grizzly, ('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
('precise', 'cloud:precise-havana'): self.precise_havana, ('precise', 'cloud:precise-havana'): self.precise_havana,
('precise', 'cloud:precise-icehouse'): self.precise_icehouse, ('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)] 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]

View File

@ -47,6 +47,7 @@ from charmhelpers.core.hookenv import (
) )
from charmhelpers.core.sysctl import create as sysctl_create from charmhelpers.core.sysctl import create as sysctl_create
from charmhelpers.core.strutils import bool_from_string
from charmhelpers.core.host import ( from charmhelpers.core.host import (
list_nics, list_nics,
@ -67,6 +68,7 @@ from charmhelpers.contrib.hahelpers.apache import (
) )
from charmhelpers.contrib.openstack.neutron import ( from charmhelpers.contrib.openstack.neutron import (
neutron_plugin_attribute, neutron_plugin_attribute,
parse_data_port_mappings,
) )
from charmhelpers.contrib.openstack.ip import ( from charmhelpers.contrib.openstack.ip import (
resolve_address, resolve_address,
@ -82,7 +84,6 @@ from charmhelpers.contrib.network.ip import (
is_bridge_member, is_bridge_member,
) )
from charmhelpers.contrib.openstack.utils import get_host_ip from charmhelpers.contrib.openstack.utils import get_host_ip
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
ADDRESS_TYPES = ['admin', 'internal', 'public'] ADDRESS_TYPES = ['admin', 'internal', 'public']
@ -319,14 +320,15 @@ def db_ssl(rdata, ctxt, ssl_dir):
class IdentityServiceContext(OSContextGenerator): 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 = service
self.service_user = service_user self.service_user = service_user
self.rel_name = rel_name
self.interfaces = [self.rel_name]
def __call__(self): def __call__(self):
log('Generating template context for identity-service', level=DEBUG) log('Generating template context for ' + self.rel_name, level=DEBUG)
ctxt = {} ctxt = {}
if self.service and self.service_user: if self.service and self.service_user:
@ -340,7 +342,7 @@ class IdentityServiceContext(OSContextGenerator):
ctxt['signing_dir'] = cachedir 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): for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit) rdata = relation_get(rid=rid, unit=unit)
serv_host = rdata.get('service_host') serv_host = rdata.get('service_host')
@ -1162,3 +1164,145 @@ class SysctlContext(OSContextGenerator):
sysctl_create(sysctl_dict, sysctl_create(sysctl_dict,
'/etc/sysctl.d/50-{0}.conf'.format(charm_name())) '/etc/sysctl.d/50-{0}.conf'.format(charm_name()))
return {'sysctl': sysctl_dict} 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 {}

View File

@ -0,0 +1,13 @@
description "{{ service_description }}"
author "Juju {{ service_name }} Charm <juju@localhost>"
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 }}

View File

@ -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 -%}

View File

@ -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 -%}

View File

@ -3,12 +3,12 @@
rpc_backend = zmq rpc_backend = zmq
rpc_zmq_host = {{ zmq_host }} rpc_zmq_host = {{ zmq_host }}
{% if zmq_redis_address -%} {% if zmq_redis_address -%}
rpc_zmq_matchmaker = oslo.messaging._drivers.matchmaker_redis.MatchMakerRedis rpc_zmq_matchmaker = redis
matchmaker_heartbeat_freq = 15 matchmaker_heartbeat_freq = 15
matchmaker_heartbeat_ttl = 30 matchmaker_heartbeat_ttl = 30
[matchmaker_redis] [matchmaker_redis]
host = {{ zmq_redis_address }} host = {{ zmq_redis_address }}
{% else -%} {% else -%}
rpc_zmq_matchmaker = oslo.messaging._drivers.matchmaker_ring.MatchMakerRing rpc_zmq_matchmaker = ring
{% endif -%} {% endif -%}
{% endif -%} {% endif -%}

View File

@ -30,6 +30,10 @@ import yaml
from charmhelpers.contrib.network import ip from charmhelpers.contrib.network import ip
from charmhelpers.core import (
unitdata,
)
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
config, config,
log as juju_log, log as juju_log,
@ -330,6 +334,21 @@ def configure_installation_source(rel):
error_out("Invalid openstack-release specified: %s" % 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): def save_script_rc(script_path="scripts/scriptrc", **env_vars):
""" """
Write an rc file in the charm-delivered directory containing 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(): 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 requirements_dir = None
def git_clone_and_install(file_name, core_project): def git_clone_and_install(projects_yaml, core_project):
"""Clone/install all OpenStack repos specified in yaml config file.""" """
global requirements_dir 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 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 if 'http_proxy' in projects.keys():
installed = _git_clone_and_install_subset(yaml_file, os.environ['http_proxy'] = projects['http_proxy']
whitelist=['requirements'])
if 'requirements' not in installed:
error_out('requirements git repository must be specified')
# clone/install all other projects except requirements and the core project if 'https_proxy' in projects.keys():
blacklist = ['requirements', core_project] os.environ['https_proxy'] = projects['https_proxy']
_git_clone_and_install_subset(yaml_file, blacklist=blacklist,
update_requirements=True)
# clone/install the core project if 'directory' in projects.keys():
whitelist = [core_project] parent_dir = projects['directory']
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']:
def _git_clone_and_install_subset(yaml_file, whitelist=[], blacklist=[], repo = p['repository']
update_requirements=False): branch = p['branch']
"""Clone/install subset of OpenStack repos specified in yaml config file.""" if p['name'] == 'requirements':
global requirements_dir repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
installed = [] update_requirements=False)
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 requirements_dir = repo_dir
installed.append(proj) else:
return installed repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
update_requirements=True)
def _git_clone_and_install_single(repo, branch, update_requirements=False): def _git_validate_projects_yaml(projects, core_project):
"""Clone and install a single git repository.""" """
dest_parent_dir = "/mnt/openstack-git/" Validate the projects yaml.
dest_dir = os.path.join(dest_parent_dir, os.path.basename(repo)) """
_git_ensure_key_exists('repositories', projects)
if not os.path.exists(dest_parent_dir): for project in projects['repositories']:
juju_log('Host dir not mounted at {}. ' _git_ensure_key_exists('name', project.keys())
'Creating directory there instead.'.format(dest_parent_dir)) _git_ensure_key_exists('repository', project.keys())
os.mkdir(dest_parent_dir) _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_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))
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): if not os.path.exists(dest_dir):
juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch)) 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: else:
repo_dir = dest_dir 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): 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 Update an OpenStack git directory's requirements.txt and
test-requirements.txt from global-requirements.txt.""" test-requirements.txt from global-requirements.txt.
"""
orig_dir = os.getcwd() orig_dir = os.getcwd()
os.chdir(reqs_dir) os.chdir(reqs_dir)
cmd = "python update.py {}".format(package_dir) cmd = ['python', 'update.py', package_dir]
try: try:
subprocess.check_call(cmd.split(' ')) subprocess.check_call(cmd)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
package = os.path.basename(package_dir) package = os.path.basename(package_dir)
error_out("Error updating {} from global-requirements.txt".format(package)) error_out("Error updating {} from global-requirements.txt".format(package))
os.chdir(orig_dir) 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

View File

@ -443,7 +443,7 @@ class HookData(object):
data = hookenv.execution_environment() data = hookenv.execution_environment()
self.conf = conf_delta = self.kv.delta(data['conf'], 'config') self.conf = conf_delta = self.kv.delta(data['conf'], 'config')
self.rels = rels_delta = self.kv.delta(data['rels'], 'rels') 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('unit', data['unit'])
self.kv.set('relid', data.get('relid')) self.kv.set('relid', data.get('relid'))
return conf_delta, rels_delta return conf_delta, rels_delta

View File

@ -1,80 +1,23 @@
import os
import uuid
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
config,
relation_get,
relation_ids, relation_ids,
related_units, related_units,
relation_get,
config,
unit_get, 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.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.openstack.utils import get_host_ip
from charmhelpers.contrib.network.ip import get_address_in_network from charmhelpers.contrib.network.ip import get_address_in_network
from charmhelpers.contrib.openstack.context import (
OSContextGenerator,
NeutronAPIContext,
)
from charmhelpers.contrib.openstack.neutron import ( from charmhelpers.contrib.openstack.neutron import (
parse_bridge_mappings,
parse_data_port_mappings,
parse_vlan_range_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): class OVSPluginContext(context.NeutronContext):
@ -90,27 +33,11 @@ class OVSPluginContext(context.NeutronContext):
@property @property
def neutron_security_groups(self): 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'] 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): def ovs_ctxt(self):
# In addition to generating config context, ensure the OVS service # In addition to generating config context, ensure the OVS service
# is running and the OVS bridge exists. Also need to ensure # is running and the OVS bridge exists. Also need to ensure
@ -119,15 +46,14 @@ class OVSPluginContext(context.NeutronContext):
if not ovs_ctxt: if not ovs_ctxt:
return {} return {}
self._ensure_bridge()
conf = config() conf = config()
ovs_ctxt['local_ip'] = \ ovs_ctxt['local_ip'] = \
get_address_in_network(config('os-data-network'), get_address_in_network(config('os-data-network'),
get_host_ip(unit_get('private-address'))) 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['neutron_security_groups'] = self.neutron_security_groups
ovs_ctxt['l2_population'] = neutron_api_settings['l2_population'] ovs_ctxt['l2_population'] = neutron_api_settings['l2_population']
ovs_ctxt['distributed_routing'] = neutron_api_settings['enable_dvr']
ovs_ctxt['overlay_network_type'] = \ ovs_ctxt['overlay_network_type'] = \
neutron_api_settings['overlay_network_type'] neutron_api_settings['overlay_network_type']
# TODO: We need to sort out the syslog and debug/verbose options as a # 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 return ovs_ctxt
class PhyNICMTUContext(DataPortContext): class L3AgentContext(OSContextGenerator):
"""Context used to apply settings to neutron data-port devices"""
def __call__(self): def __call__(self):
neutron_api_settings = NeutronAPIContext()()
ctxt = {} ctxt = {}
mappings = super(PhyNICMTUContext, self).__call__() if neutron_api_settings['enable_dvr']:
if mappings and mappings.values(): ctxt['agent_mode'] = 'dvr'
ports = mappings.values() else:
neutron_api_settings = _neutron_api_settings() ctxt['agent_mode'] = 'legacy'
mtu = neutron_api_settings.get('network_device_mtu') return ctxt
if mtu:
ctxt['devs'] = '\\n'.join(ports)
ctxt['mtu'] = mtu 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 return ctxt

View File

@ -2,12 +2,18 @@
import sys import sys
from charmhelpers.contrib.openstack.utils import (
config_value_changed,
git_install_requested,
)
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
Hooks, Hooks,
UnregisteredHookError, UnregisteredHookError,
config, config,
log, log,
relation_set, relation_set,
relation_ids,
) )
from charmhelpers.core.host import ( from charmhelpers.core.host import (
@ -15,13 +21,24 @@ from charmhelpers.core.host import (
) )
from charmhelpers.fetch 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 ( from neutron_ovs_utils import (
DVR_PACKAGES,
configure_ovs,
determine_packages, determine_packages,
git_install,
get_topics,
determine_dvr_packages,
get_shared_secret,
register_configs, register_configs,
restart_map, restart_map,
use_dvr,
) )
hooks = Hooks() hooks = Hooks()
@ -35,13 +52,49 @@ def install():
for pkg in pkgs: for pkg in pkgs:
apt_install(pkg, fatal=True) apt_install(pkg, fatal=True)
git_install(config('openstack-origin-git'))
@hooks.hook('neutron-plugin-relation-changed') @hooks.hook('neutron-plugin-relation-changed')
@hooks.hook('neutron-plugin-api-relation-changed')
@hooks.hook('config-changed') @hooks.hook('config-changed')
@restart_on_change(restart_map()) @restart_on_change(restart_map())
def config_changed(): 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() 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') @hooks.hook('amqp-relation-joined')
@ -61,6 +114,20 @@ def amqp_changed():
CONFIGS.write_all() 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(): def main():
try: try:
hooks.execute(sys.argv) hooks.execute(sys.argv)

View File

@ -1,18 +1,76 @@
import os
import shutil
from charmhelpers.contrib.openstack.neutron import neutron_plugin_attribute from charmhelpers.contrib.openstack.neutron import neutron_plugin_attribute
from copy import deepcopy from copy import deepcopy
from charmhelpers.contrib.openstack import context, templating 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 collections import OrderedDict
from charmhelpers.contrib.openstack.utils import ( from charmhelpers.contrib.openstack.utils import (
os_release, os_release,
) )
import neutron_ovs_context 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" NOVA_CONF_DIR = "/etc/nova"
NEUTRON_CONF_DIR = "/etc/neutron" NEUTRON_CONF_DIR = "/etc/neutron"
NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
NEUTRON_DEFAULT = '/etc/default/neutron-server' 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 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' PHY_NIC_MTU_CONF = '/etc/init/os-charm-phy-nic-mtu.conf'
TEMPLATES = 'templates/' TEMPLATES = 'templates/'
@ -20,7 +78,9 @@ BASE_RESOURCE_MAP = OrderedDict([
(NEUTRON_CONF, { (NEUTRON_CONF, {
'services': ['neutron-plugin-openvswitch-agent'], 'services': ['neutron-plugin-openvswitch-agent'],
'contexts': [neutron_ovs_context.OVSPluginContext(), '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, { (ML2_CONF, {
'services': ['neutron-plugin-openvswitch-agent'], 'services': ['neutron-plugin-openvswitch-agent'],
@ -28,13 +88,53 @@ BASE_RESOURCE_MAP = OrderedDict([
}), }),
(PHY_NIC_MTU_CONF, { (PHY_NIC_MTU_CONF, {
'services': ['os-charm-phy-nic-mtu'], '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(): 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): def register_configs(release=None):
@ -52,6 +152,10 @@ def resource_map():
hook execution. hook execution.
''' '''
resource_map = deepcopy(BASE_RESOURCE_MAP) 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 return resource_map
@ -61,3 +165,134 @@ def restart_map():
state. state.
''' '''
return {k: v['services'] for k, v in resource_map().iteritems()} 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')

View File

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

View File

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

View File

@ -28,3 +28,7 @@ requires:
scope: container scope: container
neutron-plugin-api: neutron-plugin-api:
interface: neutron-plugin-api interface: neutron-plugin-api
zeromq-configuration:
interface: zeromq-configuration
scope: container

16
templates/ext-port.conf Normal file
View File

@ -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

View File

@ -12,15 +12,18 @@ state_path = /var/lib/neutron
lock_path = $state_path/lock lock_path = $state_path/lock
bind_host = 0.0.0.0 bind_host = 0.0.0.0
bind_port = 9696 bind_port = 9696
{% if network_device_mtu -%}
network_device_mtu = {{ network_device_mtu }} network_device_mtu = {{ network_device_mtu }}
{% endif -%}
{% if core_plugin -%} {% if core_plugin -%}
core_plugin = {{ core_plugin }} core_plugin = {{ core_plugin }}
{% endif -%} {% endif -%}
api_paste_config = /etc/neutron/api-paste.ini api_paste_config = /etc/neutron/api-paste.ini
auth_strategy = keystone auth_strategy = keystone
{% if notifications == 'True' -%}
notification_driver = neutron.openstack.common.notifier.rpc_notifier notification_driver = neutron.openstack.common.notifier.rpc_notifier
{% endif -%}
default_notification_level = INFO default_notification_level = INFO
notification_topics = notifications notification_topics = notifications
@ -35,4 +38,3 @@ root_helper = sudo neutron-rootwrap /etc/neutron/rootwrap.conf
[keystone_authtoken] [keystone_authtoken]
signing_dir = /var/lib/neutron/keystone-signing signing_dir = /var/lib/neutron/keystone-signing

View File

@ -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

View File

@ -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 }}

View File

@ -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

View File

@ -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 -%}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,4 @@
Defaults:neutron !requiretty
neutron ALL = (root) NOPASSWD: /usr/local/bin/neutron-rootwrap /etc/neutron/rootwrap.conf *

View File

@ -0,0 +1,17 @@
description "{{ service_description }}"
author "Juju {{ charm_name }} Charm <juju@localhost>"
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

View File

@ -0,0 +1,18 @@
description "{{ service_description }}"
author "Juju {{ charm_name }} Charm <juju@localhost>"
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 }}

View File

@ -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()

12
tests/18-basic-trusty-juno-git Executable file
View File

@ -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()

View File

@ -2,6 +2,7 @@
import amulet import amulet
import time import time
import yaml
from charmhelpers.contrib.openstack.amulet.deployment import ( from charmhelpers.contrib.openstack.amulet.deployment import (
OpenStackAmuletDeployment OpenStackAmuletDeployment
@ -24,10 +25,12 @@ u = OpenStackAmuletUtils(ERROR)
class NeutronOVSBasicDeployment(OpenStackAmuletDeployment): class NeutronOVSBasicDeployment(OpenStackAmuletDeployment):
"""Amulet tests on a basic neutron-openvswtich deployment.""" """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.""" """Deploy the entire test environment."""
super(NeutronOVSBasicDeployment, self).__init__(series, openstack, super(NeutronOVSBasicDeployment, self).__init__(series, openstack,
source, stable) source, stable)
self.git = git
self._add_services() self._add_services()
self._add_relations() self._add_relations()
self._configure_services() self._configure_services()
@ -61,7 +64,24 @@ class NeutronOVSBasicDeployment(OpenStackAmuletDeployment):
def _configure_services(self): def _configure_services(self):
"""Configure all of the services.""" """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) super(NeutronOVSBasicDeployment, self)._configure_services(configs)
def _initialize_tests(self): def _initialize_tests(self):
@ -76,7 +96,8 @@ class NeutronOVSBasicDeployment(OpenStackAmuletDeployment):
service units.""" service units."""
commands = { 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.rabbitmq_sentry: ['service rabbitmq-server status'],
self.neutron_api_sentry: ['status neutron-server'], self.neutron_api_sentry: ['status neutron-server'],
} }

View File

@ -15,6 +15,7 @@
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
import six import six
from collections import OrderedDict
from charmhelpers.contrib.amulet.deployment import ( from charmhelpers.contrib.amulet.deployment import (
AmuletDeployment AmuletDeployment
) )
@ -100,12 +101,34 @@ class OpenStackAmuletDeployment(AmuletDeployment):
""" """
(self.precise_essex, self.precise_folsom, self.precise_grizzly, (self.precise_essex, self.precise_folsom, self.precise_grizzly,
self.precise_havana, self.precise_icehouse, self.precise_havana, self.precise_icehouse,
self.trusty_icehouse) = range(6) self.trusty_icehouse, self.trusty_juno, self.trusty_kilo) = range(8)
releases = { releases = {
('precise', None): self.precise_essex, ('precise', None): self.precise_essex,
('precise', 'cloud:precise-folsom'): self.precise_folsom, ('precise', 'cloud:precise-folsom'): self.precise_folsom,
('precise', 'cloud:precise-grizzly'): self.precise_grizzly, ('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
('precise', 'cloud:precise-havana'): self.precise_havana, ('precise', 'cloud:precise-havana'): self.precise_havana,
('precise', 'cloud:precise-icehouse'): self.precise_icehouse, ('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)] 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]

View File

@ -1,2 +1,4 @@
import sys import sys
sys.path.append('actions/')
sys.path.append('hooks/') sys.path.append('hooks/')

View File

@ -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})

View File

@ -1,27 +1,30 @@
from test_utils import CharmTestCase from test_utils import CharmTestCase
from test_utils import patch_open
from mock import patch from mock import patch
import neutron_ovs_context as context import neutron_ovs_context as context
import charmhelpers import charmhelpers
TO_PATCH = [ TO_PATCH = [
'relation_get', 'resolve_address',
'relation_ids',
'related_units',
'config', 'config',
'unit_get', 'unit_get',
'add_bridge',
'add_bridge_port',
'service_running',
'service_start',
'get_host_ip', 'get_host_ip',
] ]
def fake_context(settings):
def outer():
def inner():
return settings
return inner
return outer
class OVSPluginContextTest(CharmTestCase): class OVSPluginContextTest(CharmTestCase):
def setUp(self): def setUp(self):
super(OVSPluginContextTest, self).setUp(context, TO_PATCH) 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.config.side_effect = self.test_config.get
self.test_config.set('debug', True) self.test_config.set('debug', True)
self.test_config.set('verbose', True) self.test_config.set('verbose', True)
@ -30,49 +33,41 @@ class OVSPluginContextTest(CharmTestCase):
def tearDown(self): def tearDown(self):
super(OVSPluginContextTest, self).tearDown() super(OVSPluginContextTest, self).tearDown()
@patch('charmhelpers.contrib.openstack.context.config')
@patch('charmhelpers.contrib.openstack.context.NeutronPortContext.' @patch('charmhelpers.contrib.openstack.context.NeutronPortContext.'
'resolve_ports') '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') self.test_config.set('data-port', 'br-data:em1')
config.side_effect = self.test_config.get
mock_resolve_ports.side_effect = lambda ports: ports mock_resolve_ports.side_effect = lambda ports: ports
self.assertEquals(context.DataPortContext()(), self.assertEquals(
{'br-data': 'em1'}) 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.get_nic_hwaddr')
@patch('charmhelpers.contrib.openstack.context.list_nics') @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 = { machine_machs = {
'em1': 'aa:aa:aa:aa:aa:aa', 'em1': 'aa:aa:aa:aa:aa:aa',
'eth0': 'bb:bb:bb:bb:bb:bb', '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" absent_mac = "cc:cc:cc:cc:cc:cc"
config_macs = ("br-d1:%s br-d2:%s" % config_macs = ("br-d1:%s br-d2:%s" %
(absent_mac, machine_machs['em1'])) (absent_mac, machine_machs['em1']))
self.test_config.set('data-port', config_macs) self.test_config.set('data-port', config_macs)
config.side_effect = self.test_config.get
list_nics.return_value = machine_machs.keys() list_nics.return_value = machine_machs.keys()
get_nic_hwaddr.side_effect = lambda nic: machine_machs[nic] get_nic_hwaddr.side_effect = lambda nic: machine_machs[nic]
self.assertEquals(context.DataPortContext()(), self.assertEquals(
{'br-d2': 'em1'}) charmhelpers.contrib.openstack.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)
@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, 'config')
@patch.object(charmhelpers.contrib.openstack.context, 'unit_get') @patch.object(charmhelpers.contrib.openstack.context, 'unit_get')
@patch.object(charmhelpers.contrib.openstack.context, 'is_clustered') @patch.object(charmhelpers.contrib.openstack.context, 'is_clustered')
@ -84,7 +79,7 @@ class OVSPluginContextTest(CharmTestCase):
@patch.object(charmhelpers.contrib.openstack.context, 'unit_private_ip') @patch.object(charmhelpers.contrib.openstack.context, 'unit_private_ip')
def test_neutroncc_context_api_rel(self, _unit_priv_ip, _npa, _ens_pkgs, def test_neutroncc_context_api_rel(self, _unit_priv_ip, _npa, _ens_pkgs,
_save_ff, _https, _is_clus, _unit_get, _save_ff, _https, _is_clus, _unit_get,
_config): _config, _runits, _rids, _rget):
def mock_npa(plugin, section, manager): def mock_npa(plugin, section, manager):
if section == "driver": if section == "driver":
return "neutron.randomdriver" return "neutron.randomdriver"
@ -95,19 +90,22 @@ class OVSPluginContextTest(CharmTestCase):
_unit_get.return_value = '127.0.0.13' _unit_get.return_value = '127.0.0.13'
_unit_priv_ip.return_value = '127.0.0.14' _unit_priv_ip.return_value = '127.0.0.14'
_is_clus.return_value = False _is_clus.return_value = False
self.related_units.return_value = ['unit1'] _runits.return_value = ['unit1']
self.relation_ids.return_value = ['rid2'] _rids.return_value = ['rid2']
self.test_relation.set({'neutron-security-groups': 'True', rdata = {
'neutron-security-groups': 'True',
'l2-population': 'True', 'l2-population': 'True',
'network-device-mtu': 1500, 'network-device-mtu': 1500,
'overlay-network-type': 'gre', '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.get_host_ip.return_value = '127.0.0.15'
self.service_running.return_value = False
napi_ctxt = context.OVSPluginContext() napi_ctxt = context.OVSPluginContext()
expect = { expect = {
'neutron_alchemy_flags': {}, 'neutron_alchemy_flags': {},
'neutron_security_groups': True, 'neutron_security_groups': True,
'distributed_routing': True,
'verbose': True, 'verbose': True,
'local_ip': '127.0.0.15', 'local_ip': '127.0.0.15',
'network_device_mtu': 1500, 'network_device_mtu': 1500,
@ -126,8 +124,10 @@ class OVSPluginContextTest(CharmTestCase):
'vlan_ranges': 'physnet1:1000:2000', 'vlan_ranges': 'physnet1:1000:2000',
} }
self.assertEquals(expect, napi_ctxt()) 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, 'config')
@patch.object(charmhelpers.contrib.openstack.context, 'unit_get') @patch.object(charmhelpers.contrib.openstack.context, 'unit_get')
@patch.object(charmhelpers.contrib.openstack.context, 'is_clustered') @patch.object(charmhelpers.contrib.openstack.context, 'is_clustered')
@ -142,7 +142,8 @@ class OVSPluginContextTest(CharmTestCase):
_ens_pkgs, _save_ff, _ens_pkgs, _save_ff,
_https, _is_clus, _https, _is_clus,
_unit_get, _unit_get,
_config): _config, _runits,
_rids, _rget):
def mock_npa(plugin, section, manager): def mock_npa(plugin, section, manager):
if section == "driver": if section == "driver":
return "neutron.randomdriver" return "neutron.randomdriver"
@ -155,17 +156,19 @@ class OVSPluginContextTest(CharmTestCase):
_unit_priv_ip.return_value = '127.0.0.14' _unit_priv_ip.return_value = '127.0.0.14'
_is_clus.return_value = False _is_clus.return_value = False
self.test_config.set('disable-security-groups', True) self.test_config.set('disable-security-groups', True)
self.related_units.return_value = ['unit1'] _runits.return_value = ['unit1']
self.relation_ids.return_value = ['rid2'] _rids.return_value = ['rid2']
self.test_relation.set({'neutron-security-groups': 'True', rdata = {
'neutron-security-groups': 'True',
'l2-population': 'True', 'l2-population': 'True',
'network-device-mtu': 1500, 'network-device-mtu': 1500,
'overlay-network-type': 'gre', 'overlay-network-type': 'gre',
}) }
_rget.side_effect = lambda *args, **kwargs: rdata
self.get_host_ip.return_value = '127.0.0.15' self.get_host_ip.return_value = '127.0.0.15'
self.service_running.return_value = False
napi_ctxt = context.OVSPluginContext() napi_ctxt = context.OVSPluginContext()
expect = { expect = {
'distributed_routing': False,
'neutron_alchemy_flags': {}, 'neutron_alchemy_flags': {},
'neutron_security_groups': False, 'neutron_security_groups': False,
'verbose': True, 'verbose': True,
@ -186,4 +189,95 @@ class OVSPluginContextTest(CharmTestCase):
'vlan_ranges': 'physnet1:1000:2000', 'vlan_ranges': 'physnet1:1000:2000',
} }
self.assertEquals(expect, napi_ctxt()) 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()(), {})

View File

@ -1,7 +1,7 @@
from mock import MagicMock, patch, call 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: with patch('charmhelpers.core.hookenv.config') as config:
config.return_value = 'neutron' config.return_value = 'neutron'
@ -21,11 +21,18 @@ utils.restart_map = _map
TO_PATCH = [ TO_PATCH = [
'apt_update', 'apt_update',
'apt_install', 'apt_install',
'apt_purge',
'config', 'config',
'CONFIGS', 'CONFIGS',
'determine_packages', 'determine_packages',
'determine_dvr_packages',
'get_shared_secret',
'git_install',
'log', 'log',
'relation_ids',
'relation_set', 'relation_set',
'configure_ovs',
'use_dvr',
] ]
NEUTRON_CONF_DIR = "/etc/neutron" NEUTRON_CONF_DIR = "/etc/neutron"
@ -44,7 +51,9 @@ class NeutronOVSHooksTests(CharmTestCase):
hooks.hooks.execute([ hooks.hooks.execute([
'hooks/{}'.format(hookname)]) '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'] _pkgs = ['foo', 'bar']
self.determine_packages.return_value = [_pkgs] self.determine_packages.return_value = [_pkgs]
self._call_hook('install') self._call_hook('install')
@ -53,9 +62,97 @@ class NeutronOVSHooksTests(CharmTestCase):
call(_pkgs, fatal=True), 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._call_hook('config-changed')
self.assertTrue(self.CONFIGS.write_all.called) 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): def test_amqp_joined(self):
self._call_hook('amqp-relation-joined') self._call_hook('amqp-relation-joined')

View File

@ -1,11 +1,12 @@
from mock import MagicMock, patch from mock import MagicMock, patch, call
from collections import OrderedDict from collections import OrderedDict
import charmhelpers.contrib.openstack.templating as templating import charmhelpers.contrib.openstack.templating as templating
templating.OSConfigRenderer = MagicMock() templating.OSConfigRenderer = MagicMock()
import neutron_ovs_utils as nutils import neutron_ovs_utils as nutils
import neutron_ovs_context
from test_utils import ( from test_utils import (
CharmTestCase, CharmTestCase,
@ -15,12 +16,28 @@ import charmhelpers.core.hookenv as hookenv
TO_PATCH = [ TO_PATCH = [
'add_bridge',
'add_bridge_port',
'config',
'os_release', 'os_release',
'neutron_plugin_attribute', 'neutron_plugin_attribute',
'full_restart',
'service_restart',
'service_running',
'ExternalPortContext',
] ]
head_pkg = 'linux-headers-3.15.0-5-generic' 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): def _mock_npa(plugin, attr, net_manager=None):
plugins = { plugins = {
@ -38,26 +55,42 @@ def _mock_npa(plugin, attr, net_manager=None):
return plugins[plugin][attr] 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): class TestNeutronOVSUtils(CharmTestCase):
def setUp(self): def setUp(self):
super(TestNeutronOVSUtils, self).setUp(nutils, TO_PATCH) super(TestNeutronOVSUtils, self).setUp(nutils, TO_PATCH)
self.neutron_plugin_attribute.side_effect = _mock_npa self.neutron_plugin_attribute.side_effect = _mock_npa
self.config.side_effect = self.test_config.get
def tearDown(self): def tearDown(self):
# Reset cached cache # Reset cached cache
hookenv.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, 'os_release')
@patch.object(charmhelpers.contrib.openstack.neutron, 'headers_package') @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' _os_rel.return_value = 'trusty'
_head_pkgs.return_value = head_pkg _head_pkgs.return_value = head_pkg
pkg_list = nutils.determine_packages() pkg_list = nutils.determine_packages()
expect = [['neutron-plugin-openvswitch-agent'], [head_pkg]] expect = [['neutron-plugin-openvswitch-agent'], [head_pkg]]
self.assertItemsEqual(pkg_list, expect) 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(): class _mock_OSConfigRenderer():
def __init__(self, templates_dir=None, openstack_release=None): def __init__(self, templates_dir=None, openstack_release=None):
self.configs = [] self.configs = []
@ -67,6 +100,7 @@ class TestNeutronOVSUtils(CharmTestCase):
self.configs.append(config) self.configs.append(config)
self.ctxts.append(ctxt) self.ctxts.append(ctxt)
_use_dvr.return_value = False
self.os_release.return_value = 'trusty' self.os_release.return_value = 'trusty'
templating.OSConfigRenderer.side_effect = _mock_OSConfigRenderer templating.OSConfigRenderer.side_effect = _mock_OSConfigRenderer
_regconfs = nutils.register_configs() _regconfs = nutils.register_configs()
@ -75,12 +109,28 @@ class TestNeutronOVSUtils(CharmTestCase):
'/etc/init/os-charm-phy-nic-mtu.conf'] '/etc/init/os-charm-phy-nic-mtu.conf']
self.assertItemsEqual(_regconfs.configs, confs) 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() _map = nutils.resource_map()
svcs = ['neutron-plugin-openvswitch-agent']
confs = [nutils.NEUTRON_CONF] confs = [nutils.NEUTRON_CONF]
[self.assertIn(q_conf, _map.keys()) for q_conf in confs] [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() _restart_map = nutils.restart_map()
ML2CONF = "/etc/neutron/plugins/ml2/ml2_conf.ini" ML2CONF = "/etc/neutron/plugins/ml2/ml2_conf.ini"
expect = OrderedDict([ expect = OrderedDict([
@ -92,3 +142,170 @@ class TestNeutronOVSUtils(CharmTestCase):
for item in _restart_map: for item in _restart_map:
self.assertTrue(item in _restart_map) self.assertTrue(item in _restart_map)
self.assertTrue(expect[item] == _restart_map[item]) 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)