Rebase, resync

This commit is contained in:
James Page 2015-03-30 17:57:53 +01:00
commit 300247b84a
24 changed files with 738 additions and 195 deletions

View File

@ -25,8 +25,10 @@ options:
type: string type: string
default: default:
description: | description: |
The data port will be added to br-data and will allow usage of flat or VLAN Space-delimited list of bridge:port mappings. Ports will be added to
network types their corresponding bridge. The bridges will allow usage of flat or
VLAN network types with Neutron and should match this defined in
bridge-mappings.
disable-security-groups: disable-security-groups:
type: boolean type: boolean
default: false default: false
@ -36,6 +38,17 @@ options:
. .
BE CAREFUL - this option allows you to disable all port level security within BE CAREFUL - this option allows you to disable all port level security within
an OpenStack cloud. an OpenStack cloud.
bridge-mappings:
type: string
default: 'physnet1:br-data'
description: |
Space-delimited 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 # Network configuration options
# by default all access is over 'private-address' # by default all access is over 'private-address'
os-data-network: os-data-network:

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

@ -16,6 +16,7 @@
import json import json
import os import os
import re
import time import time
from base64 import b64decode from base64 import b64decode
from subprocess import check_call 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.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,
get_nic_hwaddr,
mkdir, mkdir,
write_file, write_file,
) )
@ -64,16 +68,22 @@ 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 (
resolve_address,
INTERNAL,
) )
from charmhelpers.contrib.network.ip import ( from charmhelpers.contrib.network.ip import (
get_address_in_network, get_address_in_network,
get_ipv4_addr,
get_ipv6_addr, get_ipv6_addr,
get_netmask_for_address, get_netmask_for_address,
format_ipv6_addr, format_ipv6_addr,
is_address_in_network, is_address_in_network,
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']
@ -727,7 +737,14 @@ class ApacheSSLContext(OSContextGenerator):
'endpoints': [], 'endpoints': [],
'ext_ports': []} '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) self.configure_cert(cn)
addresses = self.get_network_addresses() addresses = self.get_network_addresses()
@ -883,6 +900,48 @@ class NeutronContext(OSContextGenerator):
return ctxt 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): class OSConfigFlagContext(OSContextGenerator):
"""Provides support for user-defined config flags. """Provides support for user-defined config flags.
@ -1104,3 +1163,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

@ -16,6 +16,7 @@
# Various utilies for dealing with Neutron and the renaming from Quantum. # Various utilies for dealing with Neutron and the renaming from Quantum.
import six
from subprocess import check_output from subprocess import check_output
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
@ -237,3 +238,72 @@ def network_manager():
else: else:
# ensure accurate naming for all releases post-H # ensure accurate naming for all releases post-H
return 'neutron' 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_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,95 @@ 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
The directory key is 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 'directory' in projects.keys():
installed = _git_clone_and_install_subset(yaml_file, parent_dir = projects['directory']
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 for p in projects['repositories']:
blacklist = ['requirements', core_project] repo = p['repository']
_git_clone_and_install_subset(yaml_file, blacklist=blacklist, branch = p['branch']
update_requirements=True) if p['name'] == 'requirements':
repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
# clone/install the core project update_requirements=False)
whitelist = [core_project] requirements_dir = repo_dir
installed = _git_clone_and_install_subset(yaml_file, whitelist=whitelist, else:
update_requirements=True) repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
if core_project not in installed: update_requirements=True)
error_out('{} git repository must be specified'.format(core_project))
def _git_clone_and_install_subset(yaml_file, whitelist=[], blacklist=[], def _git_validate_projects_yaml(projects, core_project):
update_requirements=False): """
"""Clone/install subset of OpenStack repos specified in yaml config file.""" Validate the projects yaml.
global requirements_dir """
installed = [] _git_ensure_key_exists('repositories', projects)
with open(yaml_file, 'r') as fd: for project in projects['repositories']:
projects = yaml.load(fd) _git_ensure_key_exists('name', project.keys())
for proj, val in projects.items(): _git_ensure_key_exists('repository', project.keys())
# The project subset is chosen based on the following 3 rules: _git_ensure_key_exists('branch', project.keys())
# 1) If project is in blacklist, we don't clone/install it, period.
# 2) If whitelist is empty, we clone/install everything else. if projects['repositories'][0]['name'] != 'requirements':
# 3) If whitelist is not empty, we clone/install everything in the error_out('{} git repo must be specified first'.format('requirements'))
# whitelist.
if proj in blacklist: if projects['repositories'][-1]['name'] != core_project:
continue error_out('{} git repo must be specified last'.format(core_project))
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
def _git_clone_and_install_single(repo, branch, update_requirements=False): def _git_ensure_key_exists(key, keys):
"""Clone and install a single git repository.""" """
dest_parent_dir = "/mnt/openstack-git/" Ensure that key exists in keys.
dest_dir = os.path.join(dest_parent_dir, os.path.basename(repo)) """
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 {}. ' def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements):
'Creating directory there instead.'.format(dest_parent_dir)) """
os.mkdir(dest_parent_dir) 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 +593,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

@ -566,3 +566,29 @@ class Hooks(object):
def charm_dir(): def charm_dir():
"""Return the root directory of the current charm""" """Return the root directory of the current charm"""
return os.environ.get('CHARM_DIR') 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): def pwgen(length=None):
"""Generate a random pasword.""" """Generate a random pasword."""
if length is None: if length is None:
# A random length is ok to use a weak PRNG
length = random.choice(range(35, 45)) length = random.choice(range(35, 45))
alphanumeric_chars = [ alphanumeric_chars = [
l for l in (string.ascii_letters + string.digits) l for l in (string.ascii_letters + string.digits)
if l not in 'l0QD1vAEIOUaeiou'] 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_chars = [
random.choice(alphanumeric_chars) for _ in range(length)] random_generator.choice(alphanumeric_chars) for _ in range(length)]
return(''.join(random_chars)) return(''.join(random_chars))

View File

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

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,49 +1,25 @@
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
relation_ids,
related_units,
relation_get,
config, config,
unit_get, unit_get,
) )
from charmhelpers.core.host import list_nics, get_nic_hwaddr
from charmhelpers.core.strutils import bool_from_string
from charmhelpers.contrib.openstack import context from charmhelpers.contrib.openstack import context
from charmhelpers.core.host import service_running, service_start 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.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
import re from charmhelpers.contrib.openstack.context import (
NeutronAPIContext,
DataPortContext,
)
from charmhelpers.contrib.openstack.neutron import (
parse_bridge_mappings,
parse_vlan_range_mappings,
)
OVS_BRIDGE = 'br-int' OVS_BRIDGE = 'br-int'
DATA_BRIDGE = 'br-data'
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' not in rdata:
continue
neutron_settings = {
'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
return neutron_settings
return neutron_settings
class OVSPluginContext(context.NeutronContext): class OVSPluginContext(context.NeutronContext):
@ -59,34 +35,28 @@ class OVSPluginContext(context.NeutronContext):
@property @property
def neutron_security_groups(self): def neutron_security_groups(self):
napi_settings = neutron_api_settings() if config('disable-security-groups'):
return napi_settings['neutron_security_groups'] return False
neutron_api_settings = NeutronAPIContext()()
def get_data_port(self): return neutron_api_settings['neutron_security_groups']
data_ports = config('data-port')
if not data_ports:
return None
hwaddrs = {}
for nic in list_nics(['eth', 'bond']):
hwaddrs[get_nic_hwaddr(nic).lower()] = nic
mac_regex = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I)
for entry in data_ports.split():
entry = entry.strip().lower()
if re.match(mac_regex, entry):
if entry in hwaddrs:
return hwaddrs[entry]
else:
return entry
return None
def _ensure_bridge(self): def _ensure_bridge(self):
if not service_running('openvswitch-switch'): if not service_running('openvswitch-switch'):
service_start('openvswitch-switch') service_start('openvswitch-switch')
add_bridge(OVS_BRIDGE) add_bridge(OVS_BRIDGE)
add_bridge(DATA_BRIDGE)
data_port = self.get_data_port() portmaps = DataPortContext()()
if data_port: bridgemaps = parse_bridge_mappings(config('bridge-mappings'))
add_bridge_port(DATA_BRIDGE, data_port, promisc=True) 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
@ -102,14 +72,33 @@ class OVSPluginContext(context.NeutronContext):
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')))
napi_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'] = napi_settings['l2_population'] ovs_ctxt['l2_population'] = neutron_api_settings['l2_population']
ovs_ctxt['overlay_network_type'] = \ ovs_ctxt['overlay_network_type'] = \
napi_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
# general context helper # general context helper
ovs_ctxt['use_syslog'] = conf['use-syslog'] ovs_ctxt['use_syslog'] = conf['use-syslog']
ovs_ctxt['verbose'] = conf['verbose'] ovs_ctxt['verbose'] = conf['verbose']
ovs_ctxt['debug'] = conf['debug'] ovs_ctxt['debug'] = conf['debug']
net_dev_mtu = neutron_api_settings.get('network_device_mtu')
if net_dev_mtu:
# neutron.conf
ovs_ctxt['network_device_mtu'] = net_dev_mtu
# ml2 conf
ovs_ctxt['veth_mtu'] = net_dev_mtu
mappings = config('bridge-mappings')
if mappings:
ovs_ctxt['bridge_mappings'] = mappings
vlan_ranges = config('vlan-ranges')
vlan_range_mappings = parse_vlan_range_mappings(config('vlan-ranges'))
if vlan_ranges:
providers = vlan_range_mappings.keys()
ovs_ctxt['network_providers'] = ' '.join(providers)
ovs_ctxt['vlan_ranges'] = vlan_ranges
return ovs_ctxt return ovs_ctxt

View File

@ -13,6 +13,8 @@ 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'
ML2_CONF = '%s/plugins/ml2/ml2_conf.ini' % NEUTRON_CONF_DIR ML2_CONF = '%s/plugins/ml2/ml2_conf.ini' % NEUTRON_CONF_DIR
PHY_NIC_MTU_CONF = '/etc/init/os-charm-phy-nic-mtu.conf'
TEMPLATES = 'templates/'
BASE_RESOURCE_MAP = OrderedDict([ BASE_RESOURCE_MAP = OrderedDict([
(NEUTRON_CONF, { (NEUTRON_CONF, {
@ -26,8 +28,11 @@ BASE_RESOURCE_MAP = OrderedDict([
'services': ['neutron-plugin-openvswitch-agent'], 'services': ['neutron-plugin-openvswitch-agent'],
'contexts': [neutron_ovs_context.OVSPluginContext()], 'contexts': [neutron_ovs_context.OVSPluginContext()],
}), }),
(PHY_NIC_MTU_CONF, {
'services': ['os-charm-phy-nic-mtu'],
'contexts': [context.PhyNICMTUContext()],
}),
]) ])
TEMPLATES = 'templates/'
def determine_packages(): def determine_packages():

View File

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

View File

@ -12,7 +12,9 @@ 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 }}
{% endif -%}
{% if core_plugin -%} {% if core_plugin -%}
core_plugin = {{ core_plugin }} core_plugin = {{ core_plugin }}
{% endif -%} {% endif -%}

View File

@ -27,8 +27,6 @@ notification_topics = notifications
{% include "parts/rabbitmq" %} {% include "parts/rabbitmq" %}
{% include "zeromq" %}
[QUOTAS] [QUOTAS]
[DEFAULT_SERVICETYPE] [DEFAULT_SERVICETYPE]

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

@ -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/>. # 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

@ -4,18 +4,14 @@ 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',
'relation_ids',
'related_units',
'config', 'config',
'unit_get', 'unit_get',
'add_bridge', 'add_bridge',
'add_bridge_port', 'add_bridge_port',
'service_running', 'service_running',
'service_start', 'service_start',
'service_restart',
'get_host_ip', 'get_host_ip',
'get_nic_hwaddr',
'list_nics',
] ]
@ -23,7 +19,6 @@ 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)
@ -32,38 +27,57 @@ class OVSPluginContextTest(CharmTestCase):
def tearDown(self): def tearDown(self):
super(OVSPluginContextTest, self).tearDown() super(OVSPluginContextTest, self).tearDown()
def test_data_port_name(self): @patch('charmhelpers.contrib.openstack.context.config')
self.test_config.set('data-port', 'em1') @patch('charmhelpers.contrib.openstack.context.NeutronPortContext.'
self.assertEquals(context.OVSPluginContext().get_data_port(), 'em1') 'resolve_ports')
def test_data_port_name(self, mock_resolve_ports, config):
self.test_config.set('data-port', 'br-data:em1')
config.side_effect = self.test_config.get
mock_resolve_ports.side_effect = lambda ports: ports
self.assertEquals(context.DataPortContext()(),
{'br-data': 'em1'})
def test_data_port_mac(self): @patch('charmhelpers.contrib.openstack.context.config')
@patch('charmhelpers.contrib.openstack.context.get_nic_hwaddr')
@patch('charmhelpers.contrib.openstack.context.list_nics')
def test_data_port_mac(self, list_nics, get_nic_hwaddr, 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',
} }
absent_mac = "cc:cc:cc:cc:cc:cc" absent_mac = "cc:cc:cc:cc:cc:cc"
config_macs = "%s %s" % (absent_mac, machine_machs['em1']) config_macs = ("br-d1:%s br-d2:%s" %
(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()
get_nic_hwaddr.side_effect = lambda nic: machine_machs[nic]
self.assertEquals(context.DataPortContext()(),
{'br-d2': 'em1'})
def get_hwaddr(eth): @patch('charmhelpers.contrib.openstack.context.config')
return machine_machs[eth] @patch('charmhelpers.contrib.openstack.context.NeutronPortContext.'
self.get_nic_hwaddr.side_effect = get_hwaddr 'resolve_ports')
self.list_nics.return_value = machine_machs.keys() def test_ensure_bridge_data_port_present(self, mock_resolve_ports, config):
self.assertEquals(context.OVSPluginContext().get_data_port(), 'em1') self.test_config.set('data-port', 'br-data:em1')
self.test_config.set('bridge-mappings', 'phybr1:br-data')
config.side_effect = self.test_config.get
@patch.object(context.OVSPluginContext, 'get_data_port')
def test_ensure_bridge_data_port_present(self, get_data_port):
def add_port(bridge, port, promisc): def add_port(bridge, port, promisc):
if bridge == 'br-data' and port == 'em1' and promisc is True: if bridge == 'br-data' and port == 'em1' and promisc is True:
self.bridge_added = True self.bridge_added = True
return return
self.bridge_added = False self.bridge_added = False
get_data_port.return_value = 'em1' mock_resolve_ports.side_effect = lambda ports: ports
self.add_bridge_port.side_effect = add_port self.add_bridge_port.side_effect = add_port
context.OVSPluginContext()._ensure_bridge() context.OVSPluginContext()._ensure_bridge()
self.assertEquals(self.bridge_added, True) 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')
@ -75,7 +89,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"
@ -86,12 +100,15 @@ 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 = {
'l2-population': 'True', 'neutron-security-groups': 'True',
'overlay-network-type': 'gre', 'l2-population': 'True',
}) 'network-device-mtu': 1500,
'overlay-network-type': 'gre',
}
_rget.side_effect = lambda *args, **kwargs: rdata
self.get_host_ip.return_value = '127.0.0.15' self.get_host_ip.return_value = '127.0.0.15'
self.service_running.return_value = False self.service_running.return_value = False
napi_ctxt = context.OVSPluginContext() napi_ctxt = context.OVSPluginContext()
@ -100,6 +117,8 @@ class OVSPluginContextTest(CharmTestCase):
'neutron_security_groups': True, 'neutron_security_groups': True,
'verbose': True, 'verbose': True,
'local_ip': '127.0.0.15', 'local_ip': '127.0.0.15',
'network_device_mtu': 1500,
'veth_mtu': 1500,
'config': 'neutron.randomconfig', 'config': 'neutron.randomconfig',
'use_syslog': True, 'use_syslog': True,
'network_manager': 'neutron', 'network_manager': 'neutron',
@ -109,10 +128,16 @@ class OVSPluginContextTest(CharmTestCase):
'neutron_url': 'https://127.0.0.13:9696', 'neutron_url': 'https://127.0.0.13:9696',
'l2_population': True, 'l2_population': True,
'overlay_network_type': 'gre', 'overlay_network_type': 'gre',
'network_providers': 'physnet1',
'bridge_mappings': 'physnet1:br-data',
'vlan_ranges': 'physnet1:1000:2000',
} }
self.assertEquals(expect, napi_ctxt()) self.assertEquals(expect, napi_ctxt())
self.service_start.assertCalled() 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')
@ -127,24 +152,29 @@ 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"
if section == "config": if section == "config":
return "neutron.randomconfig" return "neutron.randomconfig"
_npa.side_effect = mock_npa _npa.side_effect = mock_npa
_config.return_value = 'ovs' _config.return_value = 'ovs'
_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.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 = {
'l2-population': 'True', 'neutron-security-groups': 'True',
'overlay-network-type': 'gre', 'l2-population': 'True',
}) 'network-device-mtu': 1500,
'overlay-network-type': 'gre',
}
_rget.side_effect = lambda *args, **kwargs: rdata
self.get_host_ip.return_value = '127.0.0.15' self.get_host_ip.return_value = '127.0.0.15'
self.service_running.return_value = False self.service_running.return_value = False
napi_ctxt = context.OVSPluginContext() napi_ctxt = context.OVSPluginContext()
@ -153,6 +183,8 @@ class OVSPluginContextTest(CharmTestCase):
'neutron_security_groups': False, 'neutron_security_groups': False,
'verbose': True, 'verbose': True,
'local_ip': '127.0.0.15', 'local_ip': '127.0.0.15',
'veth_mtu': 1500,
'network_device_mtu': 1500,
'config': 'neutron.randomconfig', 'config': 'neutron.randomconfig',
'use_syslog': True, 'use_syslog': True,
'network_manager': 'neutron', 'network_manager': 'neutron',
@ -162,6 +194,9 @@ class OVSPluginContextTest(CharmTestCase):
'neutron_url': 'https://127.0.0.13:9696', 'neutron_url': 'https://127.0.0.13:9696',
'l2_population': True, 'l2_population': True,
'overlay_network_type': 'gre', 'overlay_network_type': 'gre',
'network_providers': 'physnet1',
'bridge_mappings': 'physnet1:br-data',
'vlan_ranges': 'physnet1:1000:2000',
} }
self.assertEquals(expect, napi_ctxt()) self.assertEquals(expect, napi_ctxt())
self.service_start.assertCalled() self.service_start.assertCalled()

View File

@ -71,7 +71,8 @@ class TestNeutronOVSUtils(CharmTestCase):
templating.OSConfigRenderer.side_effect = _mock_OSConfigRenderer templating.OSConfigRenderer.side_effect = _mock_OSConfigRenderer
_regconfs = nutils.register_configs() _regconfs = nutils.register_configs()
confs = ['/etc/neutron/neutron.conf', confs = ['/etc/neutron/neutron.conf',
'/etc/neutron/plugins/ml2/ml2_conf.ini'] '/etc/neutron/plugins/ml2/ml2_conf.ini',
'/etc/init/os-charm-phy-nic-mtu.conf']
self.assertItemsEqual(_regconfs.configs, confs) self.assertItemsEqual(_regconfs.configs, confs)
def test_resource_map(self): def test_resource_map(self):
@ -85,8 +86,9 @@ class TestNeutronOVSUtils(CharmTestCase):
expect = OrderedDict([ expect = OrderedDict([
(nutils.NEUTRON_CONF, ['neutron-plugin-openvswitch-agent']), (nutils.NEUTRON_CONF, ['neutron-plugin-openvswitch-agent']),
(ML2CONF, ['neutron-plugin-openvswitch-agent']), (ML2CONF, ['neutron-plugin-openvswitch-agent']),
(nutils.PHY_NIC_MTU_CONF, ['os-charm-phy-nic-mtu'])
]) ])
self.assertTrue(len(expect) == len(_restart_map)) self.assertEqual(expect, _restart_map)
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])