Rebase, resync

This commit is contained in:
james.page@ubuntu.com 2015-03-31 16:13:53 +01:00
commit 6d16a34043
38 changed files with 1016 additions and 463 deletions

View File

@ -8,5 +8,6 @@ include:
- contrib.network
- contrib.python.packages
- contrib.storage.linux
- contrib.python
- payload.execd
- contrib.charmsupport

View File

@ -13,7 +13,7 @@ options:
type: string
default:
description: |
A space-separated list of external ports to use for routing of instance
Space-delimited 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)
@ -21,8 +21,10 @@ options:
type: string
default:
description: |
The data port will be added to br-data and will allow usage of flat or VLAN
network types with Neutron.
Space-delimited list of bridge:port mappings. Ports will be added to
their corresponding bridge. The bridges will allow usage of flat or
VLAN network types with Neutron and should match this defined in
bridge-mappings.
openstack-origin:
type: string
default: distro
@ -120,6 +122,17 @@ options:
description: |
A comma-separated list of nagios servicegroups.
If left empty, the nagios_context will be used as the servicegroup
bridge-mappings:
type: string
default: 'physnet1:br-data'
description: |
Space-separated list of ML2 data bridge mappings with format
<provider>:<bridge>.
vlan-ranges:
type: string
default: "physnet1:1000:2000"
description: |
Space-delimited list of network provider vlan id ranges.
# Network configuration options
# by default all access is over 'private-address'
os-data-network:
@ -160,4 +173,3 @@ options:
description: |
Default multicast port number that will be used to communicate between
HA Cluster nodes.

View File

@ -15,6 +15,7 @@
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
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]

View File

@ -16,6 +16,7 @@
import json
import os
import re
import time
from base64 import b64decode
from subprocess import check_call
@ -46,8 +47,11 @@ from charmhelpers.core.hookenv import (
)
from charmhelpers.core.sysctl import create as sysctl_create
from charmhelpers.core.strutils import bool_from_string
from charmhelpers.core.host import (
list_nics,
get_nic_hwaddr,
mkdir,
write_file,
)
@ -64,16 +68,22 @@ from charmhelpers.contrib.hahelpers.apache import (
)
from charmhelpers.contrib.openstack.neutron import (
neutron_plugin_attribute,
parse_data_port_mappings,
)
from charmhelpers.contrib.openstack.ip import (
resolve_address,
INTERNAL,
)
from charmhelpers.contrib.network.ip import (
get_address_in_network,
get_ipv4_addr,
get_ipv6_addr,
get_netmask_for_address,
format_ipv6_addr,
is_address_in_network,
is_bridge_member,
)
from charmhelpers.contrib.openstack.utils import get_host_ip
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
ADDRESS_TYPES = ['admin', 'internal', 'public']
@ -727,7 +737,14 @@ class ApacheSSLContext(OSContextGenerator):
'endpoints': [],
'ext_ports': []}
for cn in self.canonical_names():
cns = self.canonical_names()
if cns:
for cn in cns:
self.configure_cert(cn)
else:
# Expect cert/key provided in config (currently assumed that ca
# uses ip for cn)
cn = resolve_address(endpoint_type=INTERNAL)
self.configure_cert(cn)
addresses = self.get_network_addresses()
@ -883,6 +900,48 @@ class NeutronContext(OSContextGenerator):
return ctxt
class NeutronPortContext(OSContextGenerator):
NIC_PREFIXES = ['eth', 'bond']
def resolve_ports(self, ports):
"""Resolve NICs not yet bound to bridge(s)
If hwaddress provided then returns resolved hwaddress otherwise NIC.
"""
if not ports:
return None
hwaddr_to_nic = {}
hwaddr_to_ip = {}
for nic in list_nics(self.NIC_PREFIXES):
hwaddr = get_nic_hwaddr(nic)
hwaddr_to_nic[hwaddr] = nic
addresses = get_ipv4_addr(nic, fatal=False)
addresses += get_ipv6_addr(iface=nic, fatal=False)
hwaddr_to_ip[hwaddr] = addresses
resolved = []
mac_regex = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I)
for entry in ports:
if re.match(mac_regex, entry):
# NIC is in known NICs and does NOT hace an IP address
if entry in hwaddr_to_nic and not hwaddr_to_ip[entry]:
# If the nic is part of a bridge then don't use it
if is_bridge_member(hwaddr_to_nic[entry]):
continue
# Entry is a MAC address for a valid interface that doesn't
# have an IP address assigned yet.
resolved.append(hwaddr_to_nic[entry])
else:
# If the passed entry is not a MAC address, assume it's a valid
# interface, and that the user put it there on purpose (we can
# trust it to be the real external network).
resolved.append(entry)
return resolved
class OSConfigFlagContext(OSContextGenerator):
"""Provides support for user-defined config flags.
@ -1104,3 +1163,145 @@ class SysctlContext(OSContextGenerator):
sysctl_create(sysctl_dict,
'/etc/sysctl.d/50-{0}.conf'.format(charm_name()))
return {'sysctl': sysctl_dict}
class NeutronAPIContext(OSContextGenerator):
'''
Inspects current neutron-plugin-api relation for neutron settings. Return
defaults if it is not present.
'''
interfaces = ['neutron-plugin-api']
def __call__(self):
self.neutron_defaults = {
'l2_population': {
'rel_key': 'l2-population',
'default': False,
},
'overlay_network_type': {
'rel_key': 'overlay-network-type',
'default': 'gre',
},
'neutron_security_groups': {
'rel_key': 'neutron-security-groups',
'default': False,
},
'network_device_mtu': {
'rel_key': 'network-device-mtu',
'default': None,
},
'enable_dvr': {
'rel_key': 'enable-dvr',
'default': False,
},
'enable_l3ha': {
'rel_key': 'enable-l3ha',
'default': False,
},
}
ctxt = self.get_neutron_options({})
for rid in relation_ids('neutron-plugin-api'):
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
if 'l2-population' in rdata:
ctxt.update(self.get_neutron_options(rdata))
return ctxt
def get_neutron_options(self, rdata):
settings = {}
for nkey in self.neutron_defaults.keys():
defv = self.neutron_defaults[nkey]['default']
rkey = self.neutron_defaults[nkey]['rel_key']
if rkey in rdata.keys():
if type(defv) is bool:
settings[nkey] = bool_from_string(rdata[rkey])
else:
settings[nkey] = rdata[rkey]
else:
settings[nkey] = defv
return settings
class ExternalPortContext(NeutronPortContext):
def __call__(self):
ctxt = {}
ports = config('ext-port')
if ports:
ports = [p.strip() for p in ports.split()]
ports = self.resolve_ports(ports)
if ports:
ctxt = {"ext_port": ports[0]}
napi_settings = NeutronAPIContext()()
mtu = napi_settings.get('network_device_mtu')
if mtu:
ctxt['ext_port_mtu'] = mtu
return ctxt
class DataPortContext(NeutronPortContext):
def __call__(self):
ports = config('data-port')
if ports:
portmap = parse_data_port_mappings(ports)
ports = portmap.values()
resolved = self.resolve_ports(ports)
normalized = {get_nic_hwaddr(port): port for port in resolved
if port not in ports}
normalized.update({port: port for port in resolved
if port in ports})
if resolved:
return {bridge: normalized[port] for bridge, port in
six.iteritems(portmap) if port in normalized.keys()}
return None
class PhyNICMTUContext(DataPortContext):
def __call__(self):
ctxt = {}
mappings = super(PhyNICMTUContext, self).__call__()
if mappings and mappings.values():
ports = mappings.values()
napi_settings = NeutronAPIContext()()
mtu = napi_settings.get('network_device_mtu')
if mtu:
ctxt["devs"] = '\\n'.join(ports)
ctxt['mtu'] = mtu
return ctxt
class NetworkServiceContext(OSContextGenerator):
def __init__(self, rel_name='quantum-network-service'):
self.rel_name = rel_name
self.interfaces = [rel_name]
def __call__(self):
for rid in relation_ids(self.rel_name):
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
ctxt = {
'keystone_host': rdata.get('keystone_host'),
'service_port': rdata.get('service_port'),
'auth_port': rdata.get('auth_port'),
'service_tenant': rdata.get('service_tenant'),
'service_username': rdata.get('service_username'),
'service_password': rdata.get('service_password'),
'quantum_host': rdata.get('quantum_host'),
'quantum_port': rdata.get('quantum_port'),
'quantum_url': rdata.get('quantum_url'),
'region': rdata.get('region'),
'service_protocol':
rdata.get('service_protocol') or 'http',
'auth_protocol':
rdata.get('auth_protocol') or 'http',
}
if context_complete(ctxt):
return ctxt
return {}

View File

@ -16,6 +16,7 @@
# Various utilies for dealing with Neutron and the renaming from Quantum.
import six
from subprocess import check_output
from charmhelpers.core.hookenv import (
@ -237,3 +238,72 @@ def network_manager():
else:
# ensure accurate naming for all releases post-H
return 'neutron'
def parse_mappings(mappings):
parsed = {}
if mappings:
mappings = mappings.split(' ')
for m in mappings:
p = m.partition(':')
if p[1] == ':':
parsed[p[0].strip()] = p[2].strip()
return parsed
def parse_bridge_mappings(mappings):
"""Parse bridge mappings.
Mappings must be a space-delimited list of provider:bridge mappings.
Returns dict of the form {provider:bridge}.
"""
return parse_mappings(mappings)
def parse_data_port_mappings(mappings, default_bridge='br-data'):
"""Parse data port mappings.
Mappings must be a space-delimited list of bridge:port mappings.
Returns dict of the form {bridge:port}.
"""
_mappings = parse_mappings(mappings)
if not _mappings:
if not mappings:
return {}
# For backwards-compatibility we need to support port-only provided in
# config.
_mappings = {default_bridge: mappings.split(' ')[0]}
bridges = _mappings.keys()
ports = _mappings.values()
if len(set(bridges)) != len(bridges):
raise Exception("It is not allowed to have more than one port "
"configured on the same bridge")
if len(set(ports)) != len(ports):
raise Exception("It is not allowed to have the same port configured "
"on more than one bridge")
return _mappings
def parse_vlan_range_mappings(mappings):
"""Parse vlan range mappings.
Mappings must be a space-delimited list of provider:start:end mappings.
Returns dict of the form {provider: (start, end)}.
"""
_mappings = parse_mappings(mappings)
if not _mappings:
return {}
mappings = {}
for p, r in six.iteritems(_mappings):
mappings[p] = tuple(r.split(':'))
return mappings

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

View File

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

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright 2014-2015 Canonical Limited.
#
# This file is part of charm-helpers.
#
# charm-helpers is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3 as
# published by the Free Software Foundation.
#
# charm-helpers is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
import atexit
import sys
from charmhelpers.contrib.python.rpdb import Rpdb
from charmhelpers.core.hookenv import (
open_port,
close_port,
ERROR,
log
)
__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
DEFAULT_ADDR = "0.0.0.0"
DEFAULT_PORT = 4444
def _error(message):
log(message, level=ERROR)
def set_trace(addr=DEFAULT_ADDR, port=DEFAULT_PORT):
"""
Set a trace point using the remote debugger
"""
atexit.register(close_port, port)
try:
log("Starting a remote python debugger session on %s:%s" % (addr,
port))
open_port(port)
debugger = Rpdb(addr=addr, port=port)
debugger.set_trace(sys._getframe().f_back)
except:
_error("Cannot start a remote debug session on %s:%s" % (addr,
port))

View File

@ -0,0 +1,58 @@
# Copyright 2014-2015 Canonical Limited.
#
# This file is part of charm-helpers.
#
# charm-helpers is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3 as
# published by the Free Software Foundation.
#
# charm-helpers is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
"""Remote Python Debugger (pdb wrapper)."""
import pdb
import socket
import sys
__author__ = "Bertrand Janin <b@janin.com>"
__version__ = "0.1.3"
class Rpdb(pdb.Pdb):
def __init__(self, addr="127.0.0.1", port=4444):
"""Initialize the socket and initialize pdb."""
# Backup stdin and stdout before replacing them by the socket handle
self.old_stdout = sys.stdout
self.old_stdin = sys.stdin
# Open a 'reusable' socket to let the webapp reload on the same port
self.skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
self.skt.bind((addr, port))
self.skt.listen(1)
(clientsocket, address) = self.skt.accept()
handle = clientsocket.makefile('rw')
pdb.Pdb.__init__(self, completekey='tab', stdin=handle, stdout=handle)
sys.stdout = sys.stdin = handle
def shutdown(self):
"""Revert stdin and stdout, close the socket."""
sys.stdout = self.old_stdout
sys.stdin = self.old_stdin
self.skt.close()
self.set_continue()
def do_continue(self, arg):
"""Stop all operation on ``continue``."""
self.shutdown()
return 1
do_EOF = do_quit = do_exit = do_c = do_cont = do_continue

View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright 2014-2015 Canonical Limited.
#
# This file is part of charm-helpers.
#
# charm-helpers is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3 as
# published by the Free Software Foundation.
#
# charm-helpers is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
import sys
__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
def current_version():
"""Current system python version"""
return sys.version_info
def current_version_string():
"""Current system python version as string major.minor.micro"""
return "{0}.{1}.{2}".format(sys.version_info.major,
sys.version_info.minor,
sys.version_info.micro)

View File

@ -566,3 +566,29 @@ class Hooks(object):
def charm_dir():
"""Return the root directory of the current charm"""
return os.environ.get('CHARM_DIR')
@cached
def action_get(key=None):
"""Gets the value of an action parameter, or all key/value param pairs"""
cmd = ['action-get']
if key is not None:
cmd.append(key)
cmd.append('--format=json')
action_data = json.loads(subprocess.check_output(cmd).decode('UTF-8'))
return action_data
def action_set(values):
"""Sets the values to be returned after the action finishes"""
cmd = ['action-set']
for k, v in list(values.items()):
cmd.append('{}={}'.format(k, v))
subprocess.check_call(cmd)
def action_fail(message):
"""Sets the action status to failed and sets the error message.
The results set by action_set are preserved."""
subprocess.check_call(['action-fail', message])

View File

@ -339,12 +339,16 @@ def lsb_release():
def pwgen(length=None):
"""Generate a random pasword."""
if length is None:
# A random length is ok to use a weak PRNG
length = random.choice(range(35, 45))
alphanumeric_chars = [
l for l in (string.ascii_letters + string.digits)
if l not in 'l0QD1vAEIOUaeiou']
# Use a crypto-friendly PRNG (e.g. /dev/urandom) for making the
# actual password
random_generator = random.SystemRandom()
random_chars = [
random.choice(alphanumeric_chars) for _ in range(length)]
random_generator.choice(alphanumeric_chars) for _ in range(length)]
return(''.join(random_chars))

View File

@ -139,7 +139,7 @@ class MysqlRelation(RelationContext):
def __init__(self, *args, **kwargs):
self.required_keys = ['host', 'user', 'password', 'database']
super(HttpRelation).__init__(self, *args, **kwargs)
RelationContext.__init__(self, *args, **kwargs)
class HttpRelation(RelationContext):
@ -154,7 +154,7 @@ class HttpRelation(RelationContext):
def __init__(self, *args, **kwargs):
self.required_keys = ['host', 'port']
super(HttpRelation).__init__(self, *args, **kwargs)
RelationContext.__init__(self, *args, **kwargs)
def provide_data(self):
return {

View File

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

View File

@ -2,15 +2,8 @@
import os
import uuid
import socket
from charmhelpers.core.host import (
list_nics,
get_nic_hwaddr
)
from charmhelpers.core.hookenv import (
config,
relation_ids,
related_units,
relation_get,
unit_get,
cached
)
@ -19,7 +12,7 @@ from charmhelpers.fetch import (
)
from charmhelpers.contrib.openstack.context import (
OSContextGenerator,
context_complete,
NeutronAPIContext,
)
from charmhelpers.contrib.openstack.utils import (
get_os_codename_install_source
@ -27,12 +20,11 @@ from charmhelpers.contrib.openstack.utils import (
from charmhelpers.contrib.hahelpers.cluster import(
eligible_leader
)
import re
from charmhelpers.contrib.network.ip import (
get_address_in_network,
get_ipv4_addr,
get_ipv6_addr,
is_bridge_member,
)
from charmhelpers.contrib.openstack.neutron import (
parse_vlan_range_mappings,
)
DB_USER = "quantum"
@ -104,60 +96,10 @@ def core_plugin():
return CORE_PLUGIN[networking_name()][plugin]
def neutron_api_settings():
'''
Inspects current neutron-plugin-api relation for neutron settings. Return
defaults if it is not present
'''
neutron_settings = {
'l2_population': False,
'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' not in rdata:
continue
neutron_settings = {
'l2_population': rdata['l2-population'],
'overlay_network_type': rdata['overlay-network-type'],
}
return neutron_settings
return neutron_settings
class NetworkServiceContext(OSContextGenerator):
interfaces = ['quantum-network-service']
def __call__(self):
for rid in relation_ids('quantum-network-service'):
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 {}
class L3AgentContext(OSContextGenerator):
def __call__(self):
api_settings = NeutronAPIContext()()
ctxt = {}
if config('run-internal-router') == 'leader':
ctxt['handle_internal_only_router'] = eligible_leader(None)
@ -170,68 +112,19 @@ class L3AgentContext(OSContextGenerator):
if config('external-network-id'):
ctxt['ext_net_id'] = config('external-network-id')
if config('plugin'):
ctxt['plugin'] = config('plugin')
if api_settings['enable_dvr']:
ctxt['agent_mode'] = 'dvr_snat'
else:
ctxt['agent_mode'] = 'legacy'
return ctxt
class NeutronPortContext(OSContextGenerator):
def _resolve_port(self, config_key):
if not config(config_key):
return None
hwaddr_to_nic = {}
hwaddr_to_ip = {}
for nic in list_nics(['eth', 'bond']):
hwaddr = get_nic_hwaddr(nic)
hwaddr_to_nic[hwaddr] = nic
addresses = get_ipv4_addr(nic, fatal=False) + \
get_ipv6_addr(iface=nic, fatal=False)
hwaddr_to_ip[hwaddr] = addresses
mac_regex = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I)
for entry in config(config_key).split():
entry = entry.strip()
if re.match(mac_regex, entry):
if entry in hwaddr_to_nic and len(hwaddr_to_ip[entry]) == 0:
# If the nic is part of a bridge then don't use it
if is_bridge_member(hwaddr_to_nic[entry]):
continue
# Entry is a MAC address for a valid interface that doesn't
# have an IP address assigned yet.
return hwaddr_to_nic[entry]
else:
# If the passed entry is not a MAC address, assume it's a valid
# interface, and that the user put it there on purpose (we can
# trust it to be the real external network).
return entry
return None
class ExternalPortContext(NeutronPortContext):
def __call__(self):
port = self._resolve_port('ext-port')
if port:
return {"ext_port": port}
else:
return None
class DataPortContext(NeutronPortContext):
def __call__(self):
port = self._resolve_port('data-port')
if port:
return {"data_port": port}
else:
return None
class QuantumGatewayContext(OSContextGenerator):
def __call__(self):
napi_settings = neutron_api_settings()
api_settings = NeutronAPIContext()()
ctxt = {
'shared_secret': get_shared_secret(),
'local_ip':
@ -242,10 +135,29 @@ class QuantumGatewayContext(OSContextGenerator):
'debug': config('debug'),
'verbose': config('verbose'),
'instance_mtu': config('instance-mtu'),
'l2_population': napi_settings['l2_population'],
'l2_population': api_settings['l2_population'],
'enable_dvr': api_settings['enable_dvr'],
'enable_l3ha': api_settings['enable_l3ha'],
'overlay_network_type':
napi_settings['overlay_network_type'],
api_settings['overlay_network_type'],
}
mappings = config('bridge-mappings')
if mappings:
ctxt['bridge_mappings'] = mappings
vlan_ranges = config('vlan-ranges')
vlan_range_mappings = parse_vlan_range_mappings(vlan_ranges)
if vlan_range_mappings:
providers = sorted(vlan_range_mappings.keys())
ctxt['network_providers'] = ' '.join(providers)
ctxt['vlan_ranges'] = vlan_ranges
net_dev_mtu = api_settings['network_device_mtu']
if net_dev_mtu:
ctxt['network_device_mtu'] = net_dev_mtu
ctxt['veth_mtu'] = net_dev_mtu
return ctxt

View File

@ -41,6 +41,7 @@ from charmhelpers.contrib.charmsupport import nrpe
import sys
from quantum_utils import (
L3HA_PACKAGES,
register_configs,
restart_map,
services,
@ -58,7 +59,8 @@ from quantum_utils import (
install_legacy_ha_files,
cleanup_ovs_netns,
reassign_agent_resources,
stop_neutron_ha_monitor_daemon
stop_neutron_ha_monitor_daemon,
use_l3ha,
)
hooks = Hooks()
@ -197,13 +199,23 @@ def amqp_departed():
'pgsql-db-relation-changed',
'amqp-relation-changed',
'cluster-relation-changed',
'cluster-relation-joined',
'neutron-plugin-api-relation-changed')
'cluster-relation-joined')
@restart_on_change(restart_map())
def db_amqp_changed():
CONFIGS.write_all()
@hooks.hook('neutron-plugin-api-relation-changed')
@restart_on_change(restart_map())
def neutron_plugin_api_changed():
if use_l3ha():
apt_update()
apt_install(L3HA_PACKAGES, fatal=True)
else:
apt_purge(L3HA_PACKAGES, fatal=True)
CONFIGS.write_all()
@hooks.hook('quantum-network-service-relation-changed')
@restart_on_change(restart_map())
def nm_changed():

View File

@ -41,7 +41,12 @@ from charmhelpers.contrib.openstack.neutron import (
import charmhelpers.contrib.openstack.context as context
from charmhelpers.contrib.openstack.context import (
SyslogContext
SyslogContext,
NeutronAPIContext,
NetworkServiceContext,
ExternalPortContext,
PhyNICMTUContext,
DataPortContext,
)
import charmhelpers.contrib.openstack.templating as templating
from charmhelpers.contrib.openstack.neutron import headers_package
@ -50,11 +55,11 @@ from quantum_contexts import (
NEUTRON, QUANTUM,
networking_name,
QuantumGatewayContext,
NetworkServiceContext,
L3AgentContext,
ExternalPortContext,
DataPortContext,
remap_plugin
remap_plugin,
)
from charmhelpers.contrib.openstack.neutron import (
parse_bridge_mappings,
)
from copy import deepcopy
@ -167,6 +172,7 @@ LEGACY_FILES_MAP = {
},
}
LEGACY_RES_MAP = ['res_monitor']
L3HA_PACKAGES = ['keepalived']
def get_early_packages():
@ -197,16 +203,28 @@ def get_packages():
packages.append('openswan')
if source >= 'kilo':
packages.append('python-neutron-fwaas')
packages.extend(determine_l3ha_packages())
return packages
def determine_l3ha_packages():
if use_l3ha():
return L3HA_PACKAGES
return []
def get_common_package():
if get_os_codename_package('quantum-common', fatal=False) is not None:
return 'quantum-common'
else:
return 'neutron-common'
def use_l3ha():
return NeutronAPIContext()()['enable_l3ha']
EXT_PORT_CONF = '/etc/init/ext-port.conf'
PHY_NIC_MTU_CONF = '/etc/init/os-charm-phy-nic-mtu.conf'
TEMPLATES = 'templates'
QUANTUM_CONF = "/etc/quantum/quantum.conf"
@ -293,7 +311,11 @@ QUANTUM_OVS_CONFIG_FILES = {
},
EXT_PORT_CONF: {
'hook_contexts': [ExternalPortContext()],
'services': []
'services': ['ext-port']
},
PHY_NIC_MTU_CONF: {
'hook_contexts': [PhyNICMTUContext()],
'services': ['os-charm-phy-nic-mtu']
}
}
QUANTUM_OVS_CONFIG_FILES.update(QUANTUM_SHARED_CONFIG_FILES)
@ -319,7 +341,7 @@ NEUTRON_OVS_CONFIG_FILES = {
'hook_contexts': [NetworkServiceContext(),
L3AgentContext(),
QuantumGatewayContext()],
'services': ['neutron-l3-agent']
'services': ['neutron-l3-agent', 'neutron-vpn-agent']
},
NEUTRON_METERING_AGENT_CONF: {
'hook_contexts': [QuantumGatewayContext()],
@ -337,7 +359,7 @@ NEUTRON_OVS_CONFIG_FILES = {
},
NEUTRON_FWAAS_CONF: {
'hook_contexts': [QuantumGatewayContext()],
'services': ['neutron-l3-agent']
'services': ['neutron-l3-agent', 'neutron-vpn-agent']
},
NEUTRON_OVS_PLUGIN_CONF: {
'hook_contexts': [QuantumGatewayContext()],
@ -349,7 +371,11 @@ NEUTRON_OVS_CONFIG_FILES = {
},
EXT_PORT_CONF: {
'hook_contexts': [ExternalPortContext()],
'services': []
'services': ['ext-port']
},
PHY_NIC_MTU_CONF: {
'hook_contexts': [PhyNICMTUContext()],
'services': ['os-charm-phy-nic-mtu']
}
}
NEUTRON_OVS_CONFIG_FILES.update(NEUTRON_SHARED_CONFIG_FILES)
@ -471,7 +497,6 @@ def restart_map():
INT_BRIDGE = "br-int"
EXT_BRIDGE = "br-ex"
DATA_BRIDGE = 'br-data'
DHCP_AGENT = "DHCP Agent"
L3_AGENT = "L3 Agent"
@ -603,11 +628,20 @@ def configure_ovs():
ext_port_ctx = ExternalPortContext()()
if ext_port_ctx and ext_port_ctx['ext_port']:
add_bridge_port(EXT_BRIDGE, ext_port_ctx['ext_port'])
add_bridge(DATA_BRIDGE)
data_port_ctx = DataPortContext()()
if data_port_ctx and data_port_ctx['data_port']:
add_bridge_port(DATA_BRIDGE, data_port_ctx['data_port'],
promisc=True)
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 copy_file(src, dst, perms=None, force=False):

View File

@ -5,5 +5,12 @@ start on runlevel [2345]
task
script
ip link set {{ ext_port }} up
end 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

@ -7,3 +7,8 @@ local_ip = {{ local_ip }}
tenant_network_type = gre
enable_tunneling = True
tunnel_id_ranges = 1:1000
[agent]
{% if veth_mtu -%}
veth_mtu = {{ veth_mtu }}
{% endif %}

View File

@ -14,19 +14,22 @@ tunnel_id_ranges = 1:1000
vni_ranges = 1001:2000
[ml2_type_vlan]
network_vlan_ranges = physnet1:1000:2000
network_vlan_ranges = {{ vlan_ranges }}
[ml2_type_flat]
flat_networks = physnet1
flat_networks = {{ network_providers }}
[ovs]
enable_tunneling = True
local_ip = {{ local_ip }}
bridge_mappings = physnet1:br-data
bridge_mappings = {{ bridge_mappings }}
[agent]
tunnel_types = {{ overlay_network_type }}
l2_population = {{ l2_population }}
{% if veth_mtu -%}
veth_mtu = {{ veth_mtu }}
{% endif %}
[securitygroup]
firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver

View File

@ -9,9 +9,10 @@ lock_path = /var/lock/neutron
core_plugin = {{ core_plugin }}
{% include "parts/rabbitmq" %}
control_exchange = neutron
{% if notifications == 'True' -%}
notification_driver = neutron.openstack.common.notifier.list_notifier
list_notifier_drivers = neutron.openstack.common.notifier.rabbit_notifier
{% if network_device_mtu -%}
network_device_mtu = {{ network_device_mtu }}
{% endif -%}
[agent]
root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf

View File

@ -0,0 +1,25 @@
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
interface_driver = neutron.agent.linux.interface.OVSInterfaceDriver
auth_url = {{ service_protocol }}://{{ keystone_host }}:{{ service_port }}/v2.0
auth_region = {{ region }}
admin_tenant_name = {{ service_tenant }}
admin_user = {{ service_username }}
admin_password = {{ service_password }}
root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf
handle_internal_only_routers = {{ handle_internal_only_router }}
{% if plugin == 'n1kv' %}
l3_agent_manager = neutron.agent.l3_agent.L3NATAgentWithStateReport
external_network_bridge = br-int
ovs_use_veth = False
use_namespaces = True
{% else %}
ovs_use_veth = True
{% endif %}
{% if ext_net_id -%}
gateway_external_network_id = {{ ext_net_id }}
{% endif -%}
agent_mode = {{ agent_mode }}

View File

@ -0,0 +1,36 @@
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[ml2]
type_drivers = gre,vxlan,vlan,flat
tenant_network_types = gre,vxlan,vlan,flat
mechanism_drivers = openvswitch,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 = {{ enable_dvr }}
{% if veth_mtu -%}
veth_mtu = {{ veth_mtu }}
{% endif %}
[securitygroup]
firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver

View File

@ -1,29 +0,0 @@
# juno
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
logdir=/var/log/nova
state_path=/var/lib/nova
lock_path=/var/lock/nova
root_helper=sudo nova-rootwrap /etc/nova/rootwrap.conf
verbose= {{ verbose }}
use_syslog = {{ use_syslog }}
api_paste_config=/etc/nova/api-paste.ini
enabled_apis=metadata
multi_host=True
{% include "parts/database" %}
neutron_metadata_proxy_shared_secret={{ shared_secret }}
service_neutron_metadata_proxy=True
# Access to message bus
{% include "parts/rabbitmq" %}
# Access to neutron API services
network_api_class=nova.network.neutronv2.api.API
neutron_auth_strategy=keystone
neutron_url={{ quantum_url }}
neutron_admin_tenant_name={{ service_tenant }}
neutron_admin_username={{ service_username }}
neutron_admin_password={{ service_password }}
neutron_admin_auth_url={{ service_protocol }}://{{ keystone_host }}:{{ service_port }}/v2.0
{% include "zeromq" %}

View File

@ -1,3 +1,4 @@
# kilo
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.

View File

@ -1,3 +1,8 @@
# kilo
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
periodic_interval = 10
interface_driver = neutron.agent.linux.interface.OVSInterfaceDriver

View File

@ -1,3 +1,4 @@
# kilo
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
@ -5,15 +6,20 @@
[DEFAULT]
verbose = {{ verbose }}
debug = {{ debug }}
lock_path = /var/lock/neutron
core_plugin = {{ core_plugin }}
{% include "parts/rabbitmq" %}
core_plugin = {{ core_plugin }}
control_exchange = neutron
{% if notifications == 'True' -%}
notification_driver = neutron.openstack.common.notifier.list_notifier
list_notifier_drivers = neutron.openstack.common.notifier.rabbit_notifier
{% if network_device_mtu -%}
network_device_mtu = {{ network_device_mtu }}
{% endif -%}
{% include "zeromq" %}
{% include "section-zeromq" %}
[agent]
root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf
{% include "section-rabbitmq-oslo" %}
[oslo_concurrency]
lock_path = /var/lock/neutron

View File

@ -6,19 +6,17 @@
[DEFAULT]
logdir=/var/log/nova
state_path=/var/lib/nova
lock_path=/var/lock/nova
root_helper=sudo nova-rootwrap /etc/nova/rootwrap.conf
verbose= {{ verbose }}
use_syslog = {{ use_syslog }}
api_paste_config=/etc/nova/api-paste.ini
enabled_apis=metadata
multi_host=True
{% include "parts/database" %}
# Access to message bus
{% include "parts/rabbitmq" %}
# Access to neutron API services
network_api_class=nova.network.neutronv2.api.API
{% include "zeromq" %}
{% include "section-zeromq" %}
[neutron]
auth_strategy=keystone
url={{ quantum_url }}
@ -28,3 +26,8 @@ admin_password={{ service_password }}
admin_auth_url={{ service_protocol }}://{{ keystone_host }}:{{ service_port }}/v2.0
service_metadata_proxy=True
metadata_proxy_shared_secret={{ shared_secret }}
{% include "section-rabbitmq-oslo" %}
[oslo_concurrency]
lock_path=/var/lock/nova

View File

@ -1,3 +1,4 @@
# kilo
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.

View File

@ -1,14 +0,0 @@
{% if zmq_host -%}
# ZeroMQ configuration (restart-nonce: {{ zmq_nonce }})
rpc_backend = zmq
rpc_zmq_host = {{ zmq_host }}
{% if zmq_redis_address -%}
rpc_zmq_matchmaker = oslo_messaging._drivers.matchmaker_redis.MatchMakerRedis
matchmaker_heartbeat_freq = 15
matchmaker_heartbeat_ttl = 30
[matchmaker_redis]
host = {{ zmq_redis_address }}
{% else -%}
rpc_zmq_matchmaker = oslo_messaging._drivers.matchmaker_ring.MatchMakerRing
{% endif -%}
{% endif -%}

View File

@ -0,0 +1,22 @@
description "Enabling Quantum external networking port"
start on runlevel [2345]
task
script
devs="{{ devs }}"
mtu="{{ mtu }}"
tmpfile=`mktemp`
echo $devs > $tmpfile
if [ -n "$mtu" ]; then
while read -r dev; do
[ -n "$dev" ] || continue
rc=0
# Try all devices before exiting with error
ip link set $dev mtu $mtu || rc=$?
done < $tmpfile
rm $tmpfile
[ $rc = 0 ] || exit $rc
fi
end script

View File

@ -15,6 +15,7 @@
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
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]

View File

@ -14,16 +14,8 @@ from test_utils import (
TO_PATCH = [
'apt_install',
'config',
'context_complete',
'eligible_leader',
'get_ipv4_addr',
'get_ipv6_addr',
'get_nic_hwaddr',
'get_os_codename_install_source',
'list_nics',
'relation_get',
'relation_ids',
'related_units',
'unit_get',
]
@ -46,138 +38,13 @@ def patch_open():
yield mock_open, mock_file
class _TestQuantumContext(CharmTestCase):
class DummyNeutronAPIContext():
def setUp(self):
super(_TestQuantumContext, self).setUp(quantum_contexts, TO_PATCH)
self.config.side_effect = self.test_config.get
def __init__(self, return_value):
self.return_value = return_value
def test_not_related(self):
self.relation_ids.return_value = []
self.assertEquals(self.context(), {})
def test_no_units(self):
self.relation_ids.return_value = []
self.relation_ids.return_value = ['foo']
self.related_units.return_value = []
self.assertEquals(self.context(), {})
def test_no_data(self):
self.relation_ids.return_value = ['foo']
self.related_units.return_value = ['bar']
self.relation_get.side_effect = self.test_relation.get
self.context_complete.return_value = False
self.assertEquals(self.context(), {})
def test_data_multi_unit(self):
self.relation_ids.return_value = ['foo']
self.related_units.return_value = ['bar', 'baz']
self.context_complete.return_value = True
self.relation_get.side_effect = self.test_relation.get
self.assertEquals(self.context(), self.data_result)
def test_data_single_unit(self):
self.relation_ids.return_value = ['foo']
self.related_units.return_value = ['bar']
self.context_complete.return_value = True
self.relation_get.side_effect = self.test_relation.get
self.assertEquals(self.context(), self.data_result)
class TestNetworkServiceContext(_TestQuantumContext):
def setUp(self):
super(TestNetworkServiceContext, self).setUp()
self.context = quantum_contexts.NetworkServiceContext()
self.test_relation.set(
{'keystone_host': '10.5.0.1',
'service_port': '5000',
'auth_port': '20000',
'service_tenant': 'tenant',
'service_username': 'username',
'service_password': 'password',
'quantum_host': '10.5.0.2',
'quantum_port': '9696',
'quantum_url': 'http://10.5.0.2:9696/v2',
'region': 'aregion'}
)
self.data_result = {
'keystone_host': '10.5.0.1',
'service_port': '5000',
'auth_port': '20000',
'service_tenant': 'tenant',
'service_username': 'username',
'service_password': 'password',
'quantum_host': '10.5.0.2',
'quantum_port': '9696',
'quantum_url': 'http://10.5.0.2:9696/v2',
'region': 'aregion',
'service_protocol': 'http',
'auth_protocol': 'http',
}
class TestNeutronPortContext(CharmTestCase):
def setUp(self):
super(TestNeutronPortContext, self).setUp(quantum_contexts,
TO_PATCH)
self.machine_macs = {
'eth0': 'fe:c5:ce:8e:2b:00',
'eth1': 'fe:c5:ce:8e:2b:01',
'eth2': 'fe:c5:ce:8e:2b:02',
'eth3': 'fe:c5:ce:8e:2b:03',
}
self.machine_nics = {
'eth0': ['192.168.0.1'],
'eth1': ['192.168.0.2'],
'eth2': [],
'eth3': [],
}
self.absent_macs = "aa:a5:ae:ae:ab:a4 "
def test_no_ext_port(self):
self.config.return_value = None
self.assertIsNone(quantum_contexts.ExternalPortContext()())
def test_ext_port_eth(self):
self.config.return_value = 'eth1010'
self.assertEquals(quantum_contexts.ExternalPortContext()(),
{'ext_port': 'eth1010'})
def _fake_get_hwaddr(self, arg):
return self.machine_macs[arg]
def _fake_get_ipv4(self, arg, fatal=False):
return self.machine_nics[arg]
def test_ext_port_mac(self):
config_macs = self.absent_macs + " " + self.machine_macs['eth2']
self.get_ipv4_addr.side_effect = self._fake_get_ipv4
self.get_ipv6_addr.return_value = []
self.config.return_value = config_macs
self.list_nics.return_value = self.machine_macs.keys()
self.get_nic_hwaddr.side_effect = self._fake_get_hwaddr
self.assertEquals(quantum_contexts.ExternalPortContext()(),
{'ext_port': 'eth2'})
self.config.return_value = self.absent_macs
self.assertIsNone(quantum_contexts.ExternalPortContext()())
def test_ext_port_mac_one_used_nic(self):
config_macs = self.machine_macs['eth1'] + " " + \
self.machine_macs['eth2']
self.get_ipv4_addr.side_effect = self._fake_get_ipv4
self.get_ipv6_addr.return_value = []
self.config.return_value = config_macs
self.list_nics.return_value = self.machine_macs.keys()
self.get_nic_hwaddr.side_effect = self._fake_get_hwaddr
self.assertEquals(quantum_contexts.ExternalPortContext()(),
{'ext_port': 'eth2'})
def test_data_port_eth(self):
self.config.return_value = 'eth1010'
self.assertEquals(quantum_contexts.DataPortContext()(),
{'data_port': 'eth1010'})
def __call__(self):
return self.return_value
class TestL3AgentContext(CharmTestCase):
@ -187,32 +54,51 @@ class TestL3AgentContext(CharmTestCase):
TO_PATCH)
self.config.side_effect = self.test_config.get
def test_no_ext_netid(self):
@patch('quantum_contexts.NeutronAPIContext')
def test_no_ext_netid(self, _NeutronAPIContext):
_NeutronAPIContext.return_value = \
DummyNeutronAPIContext(return_value={'enable_dvr': False})
self.test_config.set('run-internal-router', 'none')
self.test_config.set('external-network-id', '')
self.eligible_leader.return_value = False
self.assertEquals(quantum_contexts.L3AgentContext()(),
{'handle_internal_only_router': False,
{'agent_mode': 'legacy',
'handle_internal_only_router': False,
'plugin': 'ovs'})
def test_hior_leader(self):
@patch('quantum_contexts.NeutronAPIContext')
def test_hior_leader(self, _NeutronAPIContext):
_NeutronAPIContext.return_value = \
DummyNeutronAPIContext(return_value={'enable_dvr': False})
self.test_config.set('run-internal-router', 'leader')
self.test_config.set('external-network-id', 'netid')
self.eligible_leader.return_value = True
self.assertEquals(quantum_contexts.L3AgentContext()(),
{'handle_internal_only_router': True,
{'agent_mode': 'legacy',
'handle_internal_only_router': True,
'ext_net_id': 'netid',
'plugin': 'ovs'})
def test_hior_all(self):
@patch('quantum_contexts.NeutronAPIContext')
def test_hior_all(self, _NeutronAPIContext):
_NeutronAPIContext.return_value = \
DummyNeutronAPIContext(return_value={'enable_dvr': False})
self.test_config.set('run-internal-router', 'all')
self.test_config.set('external-network-id', 'netid')
self.eligible_leader.return_value = True
self.assertEquals(quantum_contexts.L3AgentContext()(),
{'handle_internal_only_router': True,
{'agent_mode': 'legacy',
'handle_internal_only_router': True,
'ext_net_id': 'netid',
'plugin': 'ovs'})
@patch('quantum_contexts.NeutronAPIContext')
def test_dvr(self, _NeutronAPIContext):
_NeutronAPIContext.return_value = \
DummyNeutronAPIContext(return_value={'enable_dvr': True})
self.assertEquals(quantum_contexts.L3AgentContext()()['agent_mode'],
'dvr_snat')
class TestQuantumGatewayContext(CharmTestCase):
@ -220,19 +106,37 @@ class TestQuantumGatewayContext(CharmTestCase):
super(TestQuantumGatewayContext, self).setUp(quantum_contexts,
TO_PATCH)
self.config.side_effect = self.test_config.get
self.maxDiff = None
@patch('charmhelpers.contrib.openstack.context.relation_get')
@patch('charmhelpers.contrib.openstack.context.related_units')
@patch('charmhelpers.contrib.openstack.context.relation_ids')
@patch.object(quantum_contexts, 'get_shared_secret')
@patch.object(quantum_contexts, 'get_host_ip')
def test_all(self, _host_ip, _secret):
def test_all(self, _host_ip, _secret, _rids, _runits, _rget):
rdata = {'l2-population': 'True',
'enable-dvr': 'True',
'overlay-network-type': 'gre',
'enable-l3ha': 'True',
'network-device-mtu': 9000}
self.test_config.set('plugin', 'ovs')
self.test_config.set('debug', False)
self.test_config.set('verbose', True)
self.test_config.set('instance-mtu', 1420)
self.test_config.set('vlan-ranges',
'physnet1:1000:2000 physnet2:2001:3000')
# Provided by neutron-api relation
_rids.return_value = ['neutron-plugin-api:0']
_runits.return_value = ['neutron-api/0']
_rget.side_effect = lambda *args, **kwargs: rdata
self.get_os_codename_install_source.return_value = 'folsom'
_host_ip.return_value = '10.5.0.1'
_secret.return_value = 'testsecret'
self.assertEquals(quantum_contexts.QuantumGatewayContext()(), {
ctxt = quantum_contexts.QuantumGatewayContext()()
self.assertEquals(ctxt, {
'shared_secret': 'testsecret',
'enable_dvr': True,
'enable_l3ha': True,
'local_ip': '10.5.0.1',
'instance_mtu': 1420,
'core_plugin': "quantum.plugins.openvswitch.ovs_quantum_plugin."
@ -240,8 +144,13 @@ class TestQuantumGatewayContext(CharmTestCase):
'plugin': 'ovs',
'debug': False,
'verbose': True,
'l2_population': False,
'l2_population': True,
'overlay_network_type': 'gre',
'bridge_mappings': 'physnet1:br-data',
'network_providers': 'physnet1 physnet2',
'vlan_ranges': 'physnet1:1000:2000 physnet2:2001:3000',
'network_device_mtu': 9000,
'veth_mtu': 9000,
})
@ -362,29 +271,3 @@ class TestMisc(CharmTestCase):
self.config.return_value = 'ovs'
self.assertEquals(quantum_contexts.core_plugin(),
quantum_contexts.NEUTRON_ML2_PLUGIN)
def test_neutron_api_settings(self):
self.relation_ids.return_value = ['foo']
self.related_units.return_value = ['bar']
self.test_relation.set({'l2-population': True,
'overlay-network-type': 'gre', })
self.relation_get.side_effect = self.test_relation.get
self.assertEquals(quantum_contexts.neutron_api_settings(),
{'l2_population': True,
'overlay_network_type': 'gre'})
def test_neutron_api_settings2(self):
self.relation_ids.return_value = ['foo']
self.related_units.return_value = ['bar']
self.test_relation.set({'l2-population': True,
'overlay-network-type': 'gre', })
self.relation_get.side_effect = self.test_relation.get
self.assertEquals(quantum_contexts.neutron_api_settings(),
{'l2_population': True,
'overlay_network_type': 'gre'})
def test_neutron_api_settings_no_apiplugin(self):
self.relation_ids.return_value = []
self.assertEquals(quantum_contexts.neutron_api_settings(),
{'l2_population': False,
'overlay_network_type': 'gre', })

View File

@ -47,7 +47,8 @@ TO_PATCH = [
'get_hacluster_config',
'remove_legacy_ha_files',
'cleanup_ovs_netns',
'stop_neutron_ha_monitor_daemon'
'stop_neutron_ha_monitor_daemon',
'use_l3ha',
]
@ -248,6 +249,12 @@ class TestQuantumHooks(CharmTestCase):
self.assertTrue(self.CONFIGS.write_all.called)
self.install_ca_cert.assert_called_with('cert')
def test_neutron_plugin_changed(self):
self.use_l3ha.return_value = True
self._call_hook('neutron-plugin-api-relation-changed')
self.apt_install.assert_called_with(['keepalived'], fatal=True)
self.assertTrue(self.CONFIGS.write_all.called)
def test_cluster_departed_nvp(self):
self.test_config.set('plugin', 'nvp')
self._call_hook('cluster-relation-departed')

View File

@ -36,7 +36,6 @@ TO_PATCH = [
'service_running',
'NetworkServiceContext',
'ExternalPortContext',
'DataPortContext',
'unit_private_ip',
'relations_of_type',
'service_stop',
@ -46,7 +45,8 @@ TO_PATCH = [
'is_relation_made',
'lsb_release',
'mkdir',
'copy2'
'copy2',
'NeutronAPIContext',
]
@ -139,7 +139,14 @@ class TestQuantumUtils(CharmTestCase):
self.get_os_codename_install_source.return_value = 'kilo'
self.assertTrue('python-neutron-fwaas' in quantum_utils.get_packages())
def test_configure_ovs_starts_service_if_required(self):
def test_get_packages_l3ha(self):
self.config.return_value = 'ovs'
self.get_os_codename_install_source.return_value = 'juno'
self.assertTrue('keepalived' in quantum_utils.get_packages())
@patch('charmhelpers.contrib.openstack.context.config')
def test_configure_ovs_starts_service_if_required(self, mock_config):
mock_config.side_effect = self.test_config.get
self.config.return_value = 'ovs'
self.service_running.return_value = False
quantum_utils.configure_ovs()
@ -150,14 +157,14 @@ class TestQuantumUtils(CharmTestCase):
quantum_utils.configure_ovs()
self.assertFalse(self.full_restart.called)
def test_configure_ovs_ovs_ext_port(self):
@patch('charmhelpers.contrib.openstack.context.config')
def test_configure_ovs_ovs_ext_port(self, mock_config):
mock_config.side_effect = self.test_config.get
self.config.side_effect = self.test_config.get
self.test_config.set('plugin', 'ovs')
self.test_config.set('ext-port', 'eth0')
self.ExternalPortContext.return_value = \
DummyExternalPortContext(return_value={'ext_port': 'eth0'})
self.DataPortContext.return_value = \
DummyExternalPortContext(return_value=None)
quantum_utils.configure_ovs()
self.add_bridge.assert_has_calls([
call('br-int'),
@ -166,22 +173,36 @@ class TestQuantumUtils(CharmTestCase):
])
self.add_bridge_port.assert_called_with('br-ex', 'eth0')
def test_configure_ovs_ovs_data_port(self):
@patch('charmhelpers.contrib.openstack.context.config')
def test_configure_ovs_ovs_data_port(self, mock_config):
mock_config.side_effect = self.test_config.get
self.config.side_effect = self.test_config.get
self.test_config.set('plugin', 'ovs')
self.test_config.set('data-port', 'eth0')
self.ExternalPortContext.return_value = \
DummyExternalPortContext(return_value=None)
self.DataPortContext.return_value = \
DummyExternalPortContext(return_value={'data_port': 'eth0'})
# Test back-compatibility i.e. port but no bridge (so br-data is
# assumed)
self.test_config.set('data-port', 'eth0')
quantum_utils.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-data', 'eth0',
promisc=True)
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()
quantum_utils.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)
def test_do_openstack_upgrade(self):
self.config.side_effect = self.test_config.get
@ -241,6 +262,7 @@ class TestQuantumUtils(CharmTestCase):
def test_restart_map_ovs(self):
self.config.return_value = 'ovs'
self.get_os_codename_install_source.return_value = 'havana'
ex_map = {
quantum_utils.NEUTRON_CONF: ['neutron-l3-agent',
'neutron-dhcp-agent',
@ -261,12 +283,16 @@ class TestQuantumUtils(CharmTestCase):
quantum_utils.NEUTRON_VPNAAS_AGENT_CONF: [
'neutron-plugin-vpn-agent',
'neutron-vpn-agent'],
quantum_utils.NEUTRON_L3_AGENT_CONF: ['neutron-l3-agent'],
quantum_utils.NEUTRON_L3_AGENT_CONF: ['neutron-l3-agent',
'neutron-vpn-agent'],
quantum_utils.NEUTRON_DHCP_AGENT_CONF: ['neutron-dhcp-agent'],
quantum_utils.NEUTRON_FWAAS_CONF: ['neutron-l3-agent'],
quantum_utils.NEUTRON_FWAAS_CONF: ['neutron-l3-agent',
'neutron-vpn-agent'],
quantum_utils.NEUTRON_METERING_AGENT_CONF:
['neutron-metering-agent', 'neutron-plugin-metering-agent'],
quantum_utils.NOVA_CONF: ['nova-api-metadata'],
quantum_utils.EXT_PORT_CONF: ['ext-port'],
quantum_utils.PHY_NIC_MTU_CONF: ['os-charm-phy-nic-mtu'],
}
self.assertDictEqual(quantum_utils.restart_map(), ex_map)