Rebase, resync
This commit is contained in:
commit
33e01d8cf6
@ -8,5 +8,6 @@ include:
|
||||
- contrib.network
|
||||
- contrib.python.packages
|
||||
- contrib.storage.linux
|
||||
- contrib.python
|
||||
- payload.execd
|
||||
- contrib.charmsupport
|
||||
|
20
config.yaml
20
config.yaml
@ -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.
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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 {}
|
||||
|
@ -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
|
||||
|
13
hooks/charmhelpers/contrib/openstack/templates/git.upstart
Normal file
13
hooks/charmhelpers/contrib/openstack/templates/git.upstart
Normal 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 }}
|
@ -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 -%}
|
@ -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 -%}
|
@ -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 -%}
|
@ -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
|
||||
|
56
hooks/charmhelpers/contrib/python/debug.py
Normal file
56
hooks/charmhelpers/contrib/python/debug.py
Normal 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))
|
58
hooks/charmhelpers/contrib/python/rpdb.py
Normal file
58
hooks/charmhelpers/contrib/python/rpdb.py
Normal 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
|
34
hooks/charmhelpers/contrib/python/version.py
Normal file
34
hooks/charmhelpers/contrib/python/version.py
Normal 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)
|
@ -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])
|
||||
|
@ -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))
|
||||
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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():
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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 %}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
25
templates/juno/l3_agent.ini
Normal file
25
templates/juno/l3_agent.ini
Normal 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 }}
|
36
templates/juno/ml2_conf.ini
Normal file
36
templates/juno/ml2_conf.ini
Normal 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
|
@ -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" %}
|
@ -1,3 +1,4 @@
|
||||
# kilo
|
||||
###############################################################################
|
||||
# [ WARNING ]
|
||||
# Configuration file maintained by Juju. Local changes may be overwritten.
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -1,3 +1,4 @@
|
||||
# kilo
|
||||
###############################################################################
|
||||
# [ WARNING ]
|
||||
# Configuration file maintained by Juju. Local changes may be overwritten.
|
||||
|
@ -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 -%}
|
22
templates/os-charm-phy-nic-mtu.conf
Normal file
22
templates/os-charm-phy-nic-mtu.conf
Normal 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
|
@ -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]
|
||||
|
@ -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', })
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user