Move chassis code to layer-ovn
The two charms ``ovn-chassis`` and ``ovn-dedicated-chassis`` are apart from metadata identical. Move the shared code to layer. Add missing LXD profile. Reasoning behind updates to functional test bundles: The `ovn-central` charm has been changed to disable openvswitch components, and consequently it is no longer suitable as being a principle charm for testing the ``ovn-chassis`` subordinate charm. I chose to use the ``magpie`` charm as a principle charm for the test as we regularily use it in our teams testing already and it is most likely to stay on the bleeding edge wrt. series support. Change-Id: If58c870481876eeac26127ca2459d34b6617cb6e
This commit is contained in:
parent
3253c42659
commit
42f8f3c003
|
@ -1,67 +1 @@
|
|||
options:
|
||||
interface-bridge-mappings:
|
||||
type: string
|
||||
default:
|
||||
description: >
|
||||
A space-delimited list of key-value pairs that map a network interface
|
||||
MAC address or name to a local ovs bridge to which it should be
|
||||
connected.
|
||||
|
||||
Note: MAC addresses of physical interfaces that belong to a bond will be
|
||||
resolved to the bond name and the bond will be added to the ovs bridge.
|
||||
|
||||
Bridges referenced here must be mentioned in the `ovn-bridge-mappings`
|
||||
configuration option.
|
||||
|
||||
If a match is found the bridge will be created if it does not already
|
||||
exist, the matched interface will be added to it and the mapping found in
|
||||
`ovn-bridge-mappings` will be added to the local OVSDB under the
|
||||
`external_ids:ovn-bridge-mappings` key in the Open_vSwitch table.
|
||||
|
||||
An example value mapping two network interface mac address to two ovs
|
||||
bridges would be:
|
||||
|
||||
00:00:5e:00:00:42:br-internet enp3s0f0:br-provider
|
||||
|
||||
|
||||
Note: OVN gives you distributed East/West and highly available
|
||||
North/South routing by default. You do not need to add provider networks
|
||||
for use with external Layer3 connectivity to all chassis.
|
||||
|
||||
Doing so will create a scaling problem at the physical network layer
|
||||
that needs to be resolved with globally shared Layer2 (does not scale) or
|
||||
tunneling at the top-of-rack switch layer (adds complexity) and is
|
||||
generally not a recommended configuration.
|
||||
|
||||
Add provider networks for use with external Layer3 connectivity to
|
||||
individual chassis located near the datacenter border gateways by adding
|
||||
the MAC address of the physical interfaces of those units.
|
||||
ovn-bridge-mappings:
|
||||
type: string
|
||||
default:
|
||||
description: >
|
||||
A space-delimited list of key-value pairs that map a physical network
|
||||
name to a local ovs bridge that provides connectivity to that network.
|
||||
|
||||
The physical network name can be referenced when the administrator
|
||||
programs the OVN logical flows either by talking directly to the
|
||||
Northbound database or by interfacing with a Cloud Management System
|
||||
(CMS).
|
||||
|
||||
Each charm unit will evaluate each key-value pair and determine if the
|
||||
configuration is relevant for the host it is running on based on matches
|
||||
found in the `interface-bridge-mappings` configuration option.
|
||||
|
||||
If a match is found the bridge will be created if it does not already
|
||||
exist, the matched interface will be added to it and the mapping will be
|
||||
added to the local OVSDB under the `external_ids:ovn-bridge-mappings` key
|
||||
in the Open_vSwitch table.
|
||||
|
||||
An example value mapping two physical network names to two ovs bridges
|
||||
would be:
|
||||
|
||||
physnet1:br-internet physnet2:br-provider
|
||||
|
||||
NOTE: Values in this configuration option will only have effect for units
|
||||
that have a interface referenced in the `interface-bridge-mappings`
|
||||
configuration option.
|
||||
options: {}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
includes:
|
||||
- layer:openstack
|
||||
- layer:ovn
|
||||
- interface:ovsdb
|
||||
- interface:neutron-plugin
|
||||
options:
|
||||
|
@ -10,6 +10,7 @@ repo: https://github.com/openstack/charm-ovn-controller
|
|||
config:
|
||||
deletes:
|
||||
- debug
|
||||
- source
|
||||
- ssl_ca
|
||||
- ssl_cert
|
||||
- ssl_key
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2019 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
|
@ -1,211 +0,0 @@
|
|||
import collections
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
import charms.reactive as reactive
|
||||
|
||||
import charmhelpers.core as ch_core
|
||||
import charmhelpers.contrib.openstack.context as os_context
|
||||
|
||||
import charms_openstack.adapters
|
||||
import charms_openstack.charm
|
||||
|
||||
import charm.ovsdb as ovsdb
|
||||
|
||||
|
||||
OVS_ETCDIR = '/etc/openvswitch'
|
||||
|
||||
|
||||
@charms_openstack.adapters.config_property
|
||||
def ovn_key(cls):
|
||||
return os.path.join(OVS_ETCDIR, 'key_host')
|
||||
|
||||
|
||||
@charms_openstack.adapters.config_property
|
||||
def ovn_cert(cls):
|
||||
return os.path.join(OVS_ETCDIR, 'cert_host')
|
||||
|
||||
|
||||
@charms_openstack.adapters.config_property
|
||||
def ovn_ca_cert(cls):
|
||||
return os.path.join(OVS_ETCDIR,
|
||||
'{}.crt'.format(cls.charm_instance.name))
|
||||
|
||||
|
||||
class NeutronPluginRelationAdapter(
|
||||
charms_openstack.adapters.OpenStackRelationAdapter):
|
||||
|
||||
@property
|
||||
def metadata_shared_secret(self):
|
||||
return self.relation.get_or_create_shared_secret()
|
||||
|
||||
|
||||
class OVNChassisCharmRelationAdapters(
|
||||
charms_openstack.adapters.OpenStackRelationAdapters):
|
||||
relation_adapters = {
|
||||
'nova_compute': NeutronPluginRelationAdapter,
|
||||
}
|
||||
|
||||
|
||||
class OVNChassisCharm(charms_openstack.charm.OpenStackCharm):
|
||||
# OpenvSwitch and OVN is distributed as part of the Ubuntu Cloud Archive
|
||||
# Pockets get their name from OpenStack releases
|
||||
release = 'train'
|
||||
package_codenames = {
|
||||
'ovn-host': collections.OrderedDict([
|
||||
('2.12', 'train'),
|
||||
]),
|
||||
}
|
||||
name = 'ovn-chassis'
|
||||
packages = ['ovn-host']
|
||||
services = ['ovn-host']
|
||||
adapters_class = OVNChassisCharmRelationAdapters
|
||||
required_relations = ['certificates', 'ovsdb']
|
||||
restart_map = {
|
||||
'/etc/default/ovn-host': ['ovn-host'],
|
||||
}
|
||||
python_version = 3
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if reactive.is_flag_set('charm.ovn-chassis.enable-openstack-metadata'):
|
||||
metadata_agent = 'networking-ovn-metadata-agent'
|
||||
self.packages.extend(['networking-ovn-metadata-agent', 'haproxy'])
|
||||
self.services.append(metadata_agent)
|
||||
self.restart_map.update({
|
||||
'/etc/neutron/'
|
||||
'networking_ovn_metadata_agent.ini': [metadata_agent],
|
||||
})
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def run(self, *args):
|
||||
cp = subprocess.run(
|
||||
args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True,
|
||||
universal_newlines=True)
|
||||
ch_core.hookenv.log(cp, level=ch_core.hookenv.INFO)
|
||||
|
||||
def configure_tls(self, certificates_interface=None):
|
||||
"""Override default handler prepare certs per OVNs taste."""
|
||||
# The default handler in ``OpenStackCharm`` class does the CA only
|
||||
tls_objects = self.get_certs_and_keys(
|
||||
certificates_interface=certificates_interface)
|
||||
|
||||
for tls_object in tls_objects:
|
||||
with open(ovn_ca_cert(self.adapters_instance), 'w') as crt:
|
||||
chain = tls_object.get('chain')
|
||||
if chain:
|
||||
crt.write(tls_object['ca'] + os.linesep + chain)
|
||||
else:
|
||||
crt.write(tls_object['ca'])
|
||||
|
||||
self.configure_cert(OVS_ETCDIR,
|
||||
tls_object['cert'],
|
||||
tls_object['key'],
|
||||
cn='host')
|
||||
break
|
||||
|
||||
def configure_ovs(self, ovsdb_interface):
|
||||
self.run('ovs-vsctl',
|
||||
'set-ssl',
|
||||
ovn_key(self.adapters_instance),
|
||||
ovn_cert(self.adapters_instance),
|
||||
ovn_ca_cert(self.adapters_instance))
|
||||
self.run('ovs-vsctl',
|
||||
'set', 'open', '.',
|
||||
'external-ids:ovn-encap-type=geneve', '--',
|
||||
'set', 'open', '.',
|
||||
'external-ids:ovn-encap-ip={}'
|
||||
.format(ovsdb_interface.cluster_local_addr), '--',
|
||||
'set', 'open', '.',
|
||||
'external-ids:system-id={}'
|
||||
.format(
|
||||
socket.getfqdn(ovsdb_interface.cluster_local_addr)))
|
||||
self.run('ovs-vsctl',
|
||||
'set',
|
||||
'open',
|
||||
'.',
|
||||
'external-ids:ovn-remote={}'
|
||||
.format(','.join(ovsdb_interface.db_sb_connection_strs)))
|
||||
self.restart_all()
|
||||
|
||||
def configure_bridges(self):
|
||||
# we use the resolve_port method of NeutronPortContext to translate
|
||||
# MAC addresses into interface names
|
||||
npc = os_context.NeutronPortContext()
|
||||
|
||||
# build map of bridge config with existing interfaces on host
|
||||
ifbridges = collections.defaultdict(list)
|
||||
config_ifbm = self.config['interface-bridge-mappings'] or ''
|
||||
for pair in config_ifbm.split():
|
||||
ifname_or_mac, bridge = pair.rsplit(':', 1)
|
||||
ifbridges[bridge].append(ifname_or_mac)
|
||||
for br in ifbridges.keys():
|
||||
# resolve mac addresses to interface names
|
||||
ifbridges[br] = npc.resolve_ports(ifbridges[br])
|
||||
# remove empty bridges
|
||||
ifbridges = {k: v for k, v in ifbridges.items() if len(v) > 0}
|
||||
|
||||
# build map of bridges to ovn networks with existing if-mapping on host
|
||||
# and at the same time build ovn-bridge-mappings string
|
||||
ovn_br_map_str = ''
|
||||
ovnbridges = collections.defaultdict(list)
|
||||
config_obm = self.config['ovn-bridge-mappings'] or ''
|
||||
for pair in sorted(config_obm.split()):
|
||||
network, bridge = pair.split(':', 1)
|
||||
if bridge in ifbridges:
|
||||
ovnbridges[bridge].append(network)
|
||||
if ovn_br_map_str:
|
||||
ovn_br_map_str += ','
|
||||
ovn_br_map_str += '{}:{}'.format(network, bridge)
|
||||
|
||||
bridges = ovsdb.SimpleOVSDB('ovs-vsctl', 'bridge')
|
||||
ports = ovsdb.SimpleOVSDB('ovs-vsctl', 'port')
|
||||
for bridge in bridges.find('external_ids:charm-ovn-chassis=managed'):
|
||||
# remove bridges and ports that are managed by us and no longer in
|
||||
# config
|
||||
if bridge['name'] not in ifbridges:
|
||||
ch_core.hookenv.log('removing bridge "{}" as it is no longer'
|
||||
'present in configuration for this unit.'
|
||||
.format(bridge['name']),
|
||||
level=ch_core.hookenv.DEBUG)
|
||||
ovsdb.del_br(bridge['name'])
|
||||
else:
|
||||
for port in ports.find('external_ids:charm-ovn-chassis={}'
|
||||
.format(bridge['name'])):
|
||||
if port['name'] not in ifbridges[bridge['name']]:
|
||||
ch_core.hookenv.log('removing port "{}" from bridge '
|
||||
'"{}" as it is no longer present '
|
||||
'in configuration for this unit.'
|
||||
.format(port['name'],
|
||||
bridge['name']),
|
||||
level=ch_core.hookenv.DEBUG)
|
||||
ovsdb.del_port(bridge['name'], port['name'])
|
||||
for br in ifbridges.keys():
|
||||
if br not in ovnbridges:
|
||||
continue
|
||||
try:
|
||||
next(bridges.find('name={}'.format(br)))
|
||||
except StopIteration:
|
||||
ovsdb.add_br(br, ('charm-ovn-chassis', 'managed'))
|
||||
else:
|
||||
ch_core.hookenv.log('skip adding already existing bridge "{}"'
|
||||
.format(br), level=ch_core.hookenv.DEBUG)
|
||||
for port in ifbridges[br]:
|
||||
if port not in ovsdb.list_ports(br):
|
||||
ovsdb.add_port(br, port, ('charm-ovn-chassis', br))
|
||||
else:
|
||||
ch_core.hookenv.log('skip adding already existing port '
|
||||
'"{}" to bridge "{}"'
|
||||
.format(port, br),
|
||||
level=ch_core.hookenv.DEBUG)
|
||||
|
||||
opvs = ovsdb.SimpleOVSDB('ovs-vsctl', 'Open_vSwitch')
|
||||
if ovn_br_map_str:
|
||||
opvs.set('.', 'external_ids:ovn-bridge-mappings', ovn_br_map_str)
|
||||
# NOTE(fnordahl): Workaround for LP: #1848757
|
||||
opvs.set('.', 'external_ids:ovn-cms-options',
|
||||
'enable-chassis-as-gw')
|
||||
else:
|
||||
opvs.remove('.', 'external_ids', 'ovn-bridge-mappings')
|
||||
# NOTE(fnordahl): Workaround for LP: #1848757
|
||||
opvs.remove('.', 'external_ids', 'ovn-cms-options')
|
|
@ -1,154 +0,0 @@
|
|||
# TODO: much of this code is shared with the ``ovn-dedicated-chassis`` and
|
||||
# ``ovn-central`` charms and we should move this to a layer or library.
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
|
||||
def _run(*args):
|
||||
"""Run a process, check result, capture decoded output from STDERR/STDOUT.
|
||||
|
||||
:param args: Command and arguments to run
|
||||
:type args: Union
|
||||
:returns: Information about the completed process
|
||||
:rtype: subprocess.CompletedProcess
|
||||
:raises subprocess.CalledProcessError
|
||||
"""
|
||||
return subprocess.run(
|
||||
args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True,
|
||||
universal_newlines=True)
|
||||
|
||||
|
||||
def add_br(bridge, external_id=None):
|
||||
"""Add bridge and optionally attach a external_id to bridge.
|
||||
|
||||
:param bridge: Name of bridge to create
|
||||
:type bridge: str
|
||||
:param external_id: Key-value pair
|
||||
:type external_id: Option[None,Union[str,str]]
|
||||
:raises: subprocess.CalledProcessError
|
||||
"""
|
||||
cmd = ['ovs-vsctl', 'add-br', bridge, '--', 'set', 'bridge', bridge,
|
||||
'protocols=OpenFlow13']
|
||||
if external_id:
|
||||
cmd.extend(('--', 'br-set-external-id', bridge))
|
||||
cmd.extend(external_id)
|
||||
_run(*cmd)
|
||||
|
||||
|
||||
def del_br(bridge):
|
||||
"""Remove bridge.
|
||||
|
||||
:param bridge: Name of bridge to remove
|
||||
:type bridge: str
|
||||
:raises: subprocess.CalledProcessError
|
||||
"""
|
||||
_run('ovs-vsctl', 'del-br', bridge)
|
||||
|
||||
|
||||
def add_port(bridge, port, external_id=None):
|
||||
"""Add port to bridge and optionally attach a external_id to port.
|
||||
|
||||
:param bridge: Name of bridge to attach port to
|
||||
:type bridge: str
|
||||
:param port: Name of port as represented in netdev
|
||||
:type port: str
|
||||
:param external_id: Key-value pair
|
||||
:type external_id: Option[None,Union[str,str]]
|
||||
:raises: subprocess.CalledProcessError
|
||||
"""
|
||||
_run('ip', 'link', 'set', port, 'up')
|
||||
_run('ovs-vsctl', 'add-port', bridge, port)
|
||||
if external_id:
|
||||
ports = SimpleOVSDB('ovs-vsctl', 'port')
|
||||
for port in ports.find('name={}'.format(port)):
|
||||
ports.set(port['_uuid'],
|
||||
'external_ids:{}'.format(external_id[0]),
|
||||
external_id[1])
|
||||
|
||||
|
||||
def del_port(bridge, port):
|
||||
"""Remove port from bridge.
|
||||
|
||||
:param bridge: Name of bridge to remove port from
|
||||
:type bridge: str
|
||||
:param port: Name of port to remove
|
||||
:type port: str
|
||||
:raises: subprocess.CalledProcessError
|
||||
"""
|
||||
_run('ovs-vsctl', 'del-port', bridge, port)
|
||||
|
||||
|
||||
def list_ports(bridge):
|
||||
"""List ports on a bridge.
|
||||
|
||||
:param bridge: Name of bridge to list ports on
|
||||
:type bridge: str
|
||||
:returns: List of ports
|
||||
:rtype: List
|
||||
"""
|
||||
cp = _run('ovs-vsctl', 'list-ports', bridge)
|
||||
return cp.stdout.splitlines()
|
||||
|
||||
|
||||
class SimpleOVSDB(object):
|
||||
"""Simple interface to OVSDB through the use of command line tools.
|
||||
|
||||
OVS and OVN is managed through a set of databases. These databases have
|
||||
similar command line tools to manage them. We make use of the similarity
|
||||
to provide a generic class that can be used to manage them.
|
||||
|
||||
The OpenvSwitch project does provide a Python API, but on the surface it
|
||||
appears to be a bit too involved for our simple use case.
|
||||
|
||||
Examples:
|
||||
chassis = SimpleOVSDB('ovn-sbctl', 'chassis')
|
||||
for chs in chassis:
|
||||
print(chs)
|
||||
|
||||
bridges = SimpleOVSDB('ovs-vsctl', 'bridge')
|
||||
for br in bridges:
|
||||
if br['name'] == 'br-test':
|
||||
bridges.set(br['uuid'], 'external_ids:charm', 'managed')
|
||||
"""
|
||||
|
||||
def __init__(self, tool, table):
|
||||
"""SimpleOVSDB constructor
|
||||
|
||||
:param tool: Which tool with database commands to operate on.
|
||||
Usually one of `ovs-vsctl`, `ovn-nbctl`, `ovn-sbctl`
|
||||
:type tool: str
|
||||
:param table: Which table to operate on
|
||||
:type table: str
|
||||
"""
|
||||
self.tool = tool
|
||||
self.tbl = table
|
||||
|
||||
def _find_tbl(self, condition=None):
|
||||
cmd = [self.tool, '-f', 'json', 'find', self.tbl]
|
||||
if condition:
|
||||
cmd.append(condition)
|
||||
cp = _run(*cmd)
|
||||
data = json.loads(cp.stdout)
|
||||
for row in data['data']:
|
||||
values = []
|
||||
for col in row:
|
||||
if isinstance(col, list):
|
||||
values.append(col[1])
|
||||
else:
|
||||
values.append(col)
|
||||
yield dict(zip(data['headings'], values))
|
||||
|
||||
def __iter__(self):
|
||||
return self._find_tbl()
|
||||
|
||||
def clear(self, rec, col):
|
||||
_run(self.tool, 'clear', self.tbl, rec, col)
|
||||
|
||||
def find(self, condition):
|
||||
return self._find_tbl(condition=condition)
|
||||
|
||||
def remove(self, rec, col, value):
|
||||
_run(self.tool, 'remove', self.tbl, rec, col, value)
|
||||
|
||||
def set(self, rec, col, value):
|
||||
_run(self.tool, 'set', self.tbl, rec, '{}={}'.format(col, value))
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2019 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
|
@ -0,0 +1,21 @@
|
|||
# Copyright 2019 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import charms.ovn_charm
|
||||
|
||||
|
||||
class OVNChassisCharm(charms.ovn_charm.BaseOVNChassisCharm):
|
||||
# OpenvSwitch and OVN is distributed as part of the Ubuntu Cloud Archive
|
||||
# Pockets get their name from OpenStack releases
|
||||
release = 'train'
|
||||
name = 'ovn-chassis'
|
|
@ -0,0 +1,2 @@
|
|||
config:
|
||||
linux.kernel_modules: openvswitch
|
|
@ -1,63 +1,22 @@
|
|||
import charmhelpers.core as ch_core
|
||||
|
||||
# Copyright 2019 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import charms.reactive as reactive
|
||||
|
||||
import charms_openstack.bus
|
||||
import charms_openstack.charm as charm
|
||||
from . import ovn_chassis_charm_handlers
|
||||
|
||||
|
||||
charms_openstack.bus.discover()
|
||||
|
||||
# Use the charms.openstack defaults for common states and hooks
|
||||
charm.use_defaults(
|
||||
'charm.installed',
|
||||
'config.changed',
|
||||
'update-status',
|
||||
'upgrade-charm',
|
||||
'certificates.available',
|
||||
)
|
||||
|
||||
|
||||
@reactive.when_not('nova-compute.connected')
|
||||
def disable_metadata():
|
||||
reactive.clear_flag('charm.ovn-chassis.enable-openstack-metadata')
|
||||
|
||||
|
||||
@reactive.when('nova-compute.connected')
|
||||
def enable_metadata():
|
||||
reactive.set_flag('charm.ovn-chassis.enable-openstack-metadata')
|
||||
nova_compute = reactive.endpoint_from_flag('nova-compute.connected')
|
||||
nova_compute.publish_shared_secret()
|
||||
with charm.provide_charm_instance() as charm_instance:
|
||||
charm_instance.install()
|
||||
charm_instance.assess_status()
|
||||
|
||||
|
||||
@reactive.when('charm.installed')
|
||||
@reactive.when_any('config.changed.ovn-bridge-mappings',
|
||||
'config.changed.interface-bridge-mappings',
|
||||
'run-default-upgrade-charm')
|
||||
def configure_bridges():
|
||||
with charm.provide_charm_instance() as charm_instance:
|
||||
charm_instance.configure_bridges()
|
||||
reactive.clear_flag('config.changed.ovn-bridge-mappings')
|
||||
reactive.clear_flag('config.changed.interface-bridge-mappings')
|
||||
charm_instance.assess_status()
|
||||
|
||||
|
||||
@reactive.when('ovsdb.available')
|
||||
def configure_ovs():
|
||||
ovsdb = reactive.endpoint_from_flag('ovsdb.available')
|
||||
with charm.provide_charm_instance() as charm_instance:
|
||||
ch_core.hookenv.log(
|
||||
'DEBUG: {} {} {} {}'
|
||||
.format(charm_instance,
|
||||
charm_instance.packages,
|
||||
charm_instance.services,
|
||||
charm_instance.restart_map),
|
||||
level=ch_core.hookenv.INFO)
|
||||
charm_instance.render_with_interfaces(
|
||||
charm.optional_interfaces((ovsdb,),
|
||||
'nova-compute.connected'))
|
||||
charm_instance.configure_ovs(ovsdb)
|
||||
charm_instance.assess_status()
|
||||
@reactive.when_not(ovn_chassis_charm_handlers.OVN_CHASSIS_ENABLE_HANDLERS_FLAG)
|
||||
def enable_ovn_chassis_handlers():
|
||||
reactive.set_flag(
|
||||
ovn_chassis_charm_handlers.OVN_CHASSIS_ENABLE_HANDLERS_FLAG)
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
###############################################################################
|
||||
# [ WARNING ]
|
||||
# Configuration file maintained by Juju. Local changes may be overwritten.
|
||||
# Configuration managed by neutron-openvswitch charm
|
||||
###############################################################################
|
||||
[DEFAULT]
|
||||
metadata_proxy_shared_secret={{ nova_compute.metadata_shared_secret }}
|
||||
|
||||
[ovs]
|
||||
ovsdb_connection=unix:/var/run/openvswitch/db.sock
|
||||
|
||||
[ovn]
|
||||
ovn_sb_connection={{ ','.join(ovsdb.db_sb_connection_strs) }}
|
||||
ovn_sb_private_key={{ options.ovn_key }}
|
||||
ovn_sb_certificate={{ options.ovn_cert }}
|
||||
ovn_sb_ca_cert={{ options.ovn_ca_cert }}
|
|
@ -1,12 +0,0 @@
|
|||
# This is a POSIX shell fragment -*- sh -*-
|
||||
|
||||
###############################################################################
|
||||
# [ WARNING ]
|
||||
# Configuration file maintained by Juju. Local changes may be overwritten.
|
||||
# Configuration managed by neutron-openvswitch charm
|
||||
###############################################################################
|
||||
|
||||
# OVN_CTL_OPTS: Extra options to pass to ovs-ctl. This is, for example,
|
||||
# a suitable place to specify --ovn-controller-wrapper=valgrind.
|
||||
# OVN_CTL_OPTS=
|
||||
|
|
@ -4,7 +4,7 @@ relations:
|
|||
- mysql:shared-db
|
||||
- - ovn-central:certificates
|
||||
- vault:certificates
|
||||
- - ovn-central:juju-info
|
||||
- - magpie:juju-info
|
||||
- ovn-chassis:juju-info
|
||||
- - ovn-chassis:ovsdb
|
||||
- ovn-central:ovsdb
|
||||
|
@ -22,6 +22,9 @@ applications:
|
|||
num_units: 3
|
||||
options:
|
||||
source: cloud:bionic-train
|
||||
magpie:
|
||||
charm: cs:~admcleod/magpie
|
||||
num_units: 2
|
||||
ovn-chassis:
|
||||
series: bionic
|
||||
charm: cs:~openstack-charmers-next/ovn-chassis
|
||||
|
|
|
@ -4,7 +4,7 @@ relations:
|
|||
- mysql:shared-db
|
||||
- - ovn-central:certificates
|
||||
- vault:certificates
|
||||
- - ovn-central:juju-info
|
||||
- - magpie:juju-info
|
||||
- ovn-chassis:juju-info
|
||||
- - ovn-chassis:ovsdb
|
||||
- ovn-central:ovsdb
|
||||
|
@ -20,6 +20,9 @@ applications:
|
|||
ovn-central:
|
||||
charm: cs:~openstack-charmers-next/ovn-central
|
||||
num_units: 3
|
||||
magpie:
|
||||
charm: cs:~admcleod/magpie
|
||||
num_units: 2
|
||||
ovn-chassis:
|
||||
series: eoan
|
||||
charm: cs:~openstack-charmers-next/ovn-chassis
|
||||
|
|
|
@ -5,6 +5,8 @@ gate_bundles:
|
|||
smoke_bundles:
|
||||
- bionic
|
||||
target_deploy_status:
|
||||
magpie:
|
||||
workload-status-message: icmp ok
|
||||
ovn-central:
|
||||
workload-status: blocked
|
||||
workload-status-message: "'certificates' missing"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
This is not the unit tests you are looking for, take a look at `layer-ovn`.
|
|
@ -37,3 +37,9 @@ sys.modules['neutronclient'] = neutronclient
|
|||
sys.modules['neutronclient.v2_0'] = neutronclient.v2_0
|
||||
sys.modules['neutron_lib'] = neutron_lib
|
||||
sys.modules['neutron_lib.constants'] = neutron_lib.constants
|
||||
import reactive
|
||||
reactive.ovn_chassis_charm_handlers = mock.MagicMock()
|
||||
reactive.ovn_chassis_charm_handlers.OVN_CHASSIS_ENABLE_HANDLERS_FLAG = \
|
||||
'MOCKED_FLAG'
|
||||
sys.modules['reactive.ovn_chassis_charm_handlers'] = \
|
||||
reactive.ovn_chassis_charm_handlers
|
||||
|
|
|
@ -1,203 +0,0 @@
|
|||
# Copyright 2019 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import io
|
||||
import mock
|
||||
import os
|
||||
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
||||
import charm.openstack.ovn_chassis as ovn_chassis
|
||||
|
||||
|
||||
class TestOVNConfigProperties(test_utils.PatchHelper):
|
||||
|
||||
def test_ovn_key(self):
|
||||
self.assertEquals(ovn_chassis.ovn_key(None),
|
||||
os.path.join(ovn_chassis.OVS_ETCDIR, 'key_host'))
|
||||
|
||||
def test_ovn_cert(self):
|
||||
self.assertEquals(ovn_chassis.ovn_cert(None),
|
||||
os.path.join(ovn_chassis.OVS_ETCDIR, 'cert_host'))
|
||||
|
||||
def test_ovn_ca_cert(self):
|
||||
cls = mock.MagicMock()
|
||||
cls.charm_instance.name = mock.PropertyMock().return_value = 'name'
|
||||
self.assertEquals(ovn_chassis.ovn_ca_cert(cls),
|
||||
os.path.join(ovn_chassis.OVS_ETCDIR, 'name.crt'))
|
||||
|
||||
|
||||
class Helper(test_utils.PatchHelper):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.patch_release(ovn_chassis.OVNChassisCharm.release)
|
||||
self.patch_object(ovn_chassis.reactive, 'is_flag_set',
|
||||
return_value=False)
|
||||
self.target = ovn_chassis.OVNChassisCharm()
|
||||
# remove the 'is_flag_set' patch so the tests can use it
|
||||
self._patches['is_flag_set'].stop()
|
||||
setattr(self, 'is_flag_set', None)
|
||||
del(self._patches['is_flag_set'])
|
||||
del(self._patches_start['is_flag_set'])
|
||||
|
||||
def patch_target(self, attr, return_value=None):
|
||||
mocked = mock.patch.object(self.target, attr)
|
||||
self._patches[attr] = mocked
|
||||
started = mocked.start()
|
||||
started.return_value = return_value
|
||||
self._patches_start[attr] = started
|
||||
setattr(self, attr, started)
|
||||
|
||||
|
||||
class TestOVNChassisCharm(Helper):
|
||||
|
||||
def test_optional_openstack_metadata(self):
|
||||
self.assertEquals(self.target.packages, ['ovn-host'])
|
||||
self.assertEquals(self.target.services, ['ovn-host'])
|
||||
self.patch_object(ovn_chassis.reactive, 'is_flag_set',
|
||||
return_value=True)
|
||||
c = ovn_chassis.OVNChassisCharm()
|
||||
self.assertEquals(c.packages, [
|
||||
'ovn-host', 'networking-ovn-metadata-agent', 'haproxy'
|
||||
])
|
||||
self.assertEquals(c.services, [
|
||||
'ovn-host', 'networking-ovn-metadata-agent'])
|
||||
|
||||
def test_run(self):
|
||||
self.patch_object(ovn_chassis.subprocess, 'run')
|
||||
self.patch_object(ovn_chassis.ch_core.hookenv, 'log')
|
||||
self.target.run('some', 'args')
|
||||
self.run.assert_called_once_with(
|
||||
('some', 'args'),
|
||||
stdout=ovn_chassis.subprocess.PIPE,
|
||||
stderr=ovn_chassis.subprocess.STDOUT,
|
||||
check=True,
|
||||
universal_newlines=True)
|
||||
|
||||
def test_configure_tls(self):
|
||||
self.patch_target('get_certs_and_keys')
|
||||
self.get_certs_and_keys.return_value = [{
|
||||
'cert': 'fakecert',
|
||||
'key': 'fakekey',
|
||||
'cn': 'fakecn',
|
||||
'ca': 'fakeca',
|
||||
'chain': 'fakechain',
|
||||
}]
|
||||
with mock.patch('builtins.open', create=True) as mocked_open:
|
||||
mocked_file = mock.MagicMock(spec=io.FileIO)
|
||||
mocked_open.return_value = mocked_file
|
||||
self.target.configure_cert = mock.MagicMock()
|
||||
self.target.run = mock.MagicMock()
|
||||
self.target.configure_tls()
|
||||
mocked_open.assert_called_once_with(
|
||||
'/etc/openvswitch/ovn-chassis.crt', 'w')
|
||||
mocked_file.__enter__().write.assert_called_once_with(
|
||||
'fakeca\nfakechain')
|
||||
self.target.configure_cert.assert_called_once_with(
|
||||
ovn_chassis.OVS_ETCDIR,
|
||||
'fakecert',
|
||||
'fakekey',
|
||||
cn='host')
|
||||
|
||||
def test_configure_ovs(self):
|
||||
self.patch_target('run')
|
||||
self.patch_target('restart_all')
|
||||
self.patch_object(ovn_chassis, 'ovn_key')
|
||||
self.patch_object(ovn_chassis, 'ovn_cert')
|
||||
self.patch_object(ovn_chassis, 'ovn_ca_cert')
|
||||
ovsdb_interface = mock.MagicMock()
|
||||
db_sb_connection_strs = mock.PropertyMock().return_value = ['dbsbconn']
|
||||
ovsdb_interface.db_sb_connection_strs = db_sb_connection_strs
|
||||
cluster_local_addr = mock.PropertyMock().return_value = (
|
||||
'cluster_local_addr')
|
||||
ovsdb_interface.cluster_local_addr = cluster_local_addr
|
||||
self.target.configure_ovs(ovsdb_interface)
|
||||
self.run.assert_has_calls([
|
||||
mock.call('ovs-vsctl', 'set-ssl', mock.ANY, mock.ANY, mock.ANY),
|
||||
mock.call('ovs-vsctl', 'set', 'open', '.',
|
||||
'external-ids:ovn-encap-type=geneve', '--',
|
||||
'set', 'open', '.',
|
||||
'external-ids:ovn-encap-ip=cluster_local_addr', '--',
|
||||
'set', 'open', '.',
|
||||
'external-ids:system-id=cluster_local_addr'),
|
||||
mock.call('ovs-vsctl', 'set', 'open', '.',
|
||||
'external-ids:ovn-remote=dbsbconn'),
|
||||
])
|
||||
|
||||
def test_configure_bridges(self):
|
||||
self.patch_object(ovn_chassis.os_context, 'NeutronPortContext')
|
||||
npc = mock.MagicMock()
|
||||
|
||||
def _fake_resolve_ports(mac_or_if):
|
||||
result = []
|
||||
for entry in mac_or_if:
|
||||
if ':' in entry:
|
||||
result.append('eth0')
|
||||
continue
|
||||
result.append(entry)
|
||||
return result
|
||||
|
||||
npc.resolve_ports.side_effect = _fake_resolve_ports
|
||||
self.NeutronPortContext.return_value = npc
|
||||
self.patch_target('config')
|
||||
self.config.__getitem__.side_effect = [
|
||||
'00:01:02:03:04:05:br-provider eth5:br-other',
|
||||
'provider:br-provider other:br-other']
|
||||
self.patch_object(ovn_chassis.ovsdb, 'SimpleOVSDB')
|
||||
bridges = mock.MagicMock()
|
||||
bridges.find.side_effect = [
|
||||
[
|
||||
{'name': 'delete-bridge'},
|
||||
{'name': 'br-other'}
|
||||
],
|
||||
StopIteration,
|
||||
]
|
||||
ports = mock.MagicMock()
|
||||
ports.find.side_effect = [[{'name': 'delete-port'}]]
|
||||
opvs = mock.MagicMock()
|
||||
self.SimpleOVSDB.side_effect = [bridges, ports, opvs]
|
||||
self.patch_object(ovn_chassis.ovsdb, 'del_br')
|
||||
self.patch_object(ovn_chassis.ovsdb, 'del_port')
|
||||
self.patch_object(ovn_chassis.ovsdb, 'add_br')
|
||||
self.patch_object(ovn_chassis.ovsdb, 'list_ports')
|
||||
self.list_ports().__iter__.return_value = []
|
||||
self.patch_object(ovn_chassis.ovsdb, 'add_port')
|
||||
self.target.configure_bridges()
|
||||
npc.resolve_ports.assert_has_calls([
|
||||
mock.call(['00:01:02:03:04:05']),
|
||||
mock.call(['eth5']),
|
||||
], any_order=True)
|
||||
bridges.find.assert_has_calls([
|
||||
mock.call('name=br-provider'),
|
||||
mock.call('name=br-other'),
|
||||
], any_order=True)
|
||||
self.del_br.assert_called_once_with('delete-bridge')
|
||||
self.del_port.assert_called_once_with('br-other', 'delete-port')
|
||||
self.add_br.assert_has_calls([
|
||||
mock.call('br-provider', ('charm-ovn-chassis', 'managed')),
|
||||
mock.call('br-other', ('charm-ovn-chassis', 'managed')),
|
||||
], any_order=True)
|
||||
self.add_port.assert_has_calls([
|
||||
mock.call(
|
||||
'br-provider', 'eth0', ('charm-ovn-chassis', 'br-provider')),
|
||||
mock.call(
|
||||
'br-other', 'eth5', ('charm-ovn-chassis', 'br-other')),
|
||||
], any_order=True)
|
||||
opvs.set.assert_has_calls([
|
||||
mock.call('.', 'external_ids:ovn-bridge-mappings',
|
||||
'other:br-other,provider:br-provider'),
|
||||
mock.call('.', 'external_ids:ovn-cms-options',
|
||||
'enable-chassis-as-gw'),
|
||||
])
|
|
@ -1,141 +0,0 @@
|
|||
import mock
|
||||
import subprocess
|
||||
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
||||
import charm.ovsdb as ovsdb
|
||||
|
||||
VSCTL_BRIDGE_TBL = '''
|
||||
{"data":[[["uuid","1e21ba48-61ff-4b32-b35e-cb80411da351"],["set",[]],["set",[]],"0000a0369fdd3890","","<unknown>",["map",[["charm-ovn-chassis","managed"],["other","value"]]],["set",[]],["set",[]],["map",[]],["set",[]],false,["set",[]],"br-test",["set",[]],["map",[]],["set",[["uuid","617f9359-77e2-41be-8af6-4c44e7a6bcc3"],["uuid","da840476-8809-4107-8733-591f4696f056"]]],["set",[]],false,["map",[]],["set",[]],["map",[]],false],[["uuid","bb685b0f-a383-40a1-b7a5-b5c2066bfa42"],["set",[]],["set",[]],"00000e5b68bba140","","<unknown>",["map",[]],"secure",["set",[]],["map",[]],["set",[]],false,["set",[]],"br-int",["set",[]],["map",[["disable-in-band","true"]]],["set",[["uuid","07f4c231-9fd2-49b0-a558-5b69d657fdb0"],["uuid","8bbd2441-866f-4317-a284-09491702776c"],["uuid","d9e9c081-6482-4006-b7d6-239182b56c2e"]]],["set",[]],false,["map",[]],["set",[]],["map",[]],false]],"headings":["_uuid","auto_attach","controller","datapath_id","datapath_type","datapath_version","external_ids","fail_mode","flood_vlans","flow_tables","ipfix","mcast_snooping_enable","mirrors","name","netflow","other_config","ports","protocols","rstp_enable","rstp_status","sflow","status","stp_enable"]}
|
||||
'''
|
||||
|
||||
|
||||
class TestOVSDB(test_utils.PatchHelper):
|
||||
|
||||
def test__run(self):
|
||||
self.patch_object(ovsdb.subprocess, 'run')
|
||||
self.run.return_value = 'aReturn'
|
||||
self.assertEquals(ovsdb._run('aArg'), 'aReturn')
|
||||
self.run.assert_called_once_with(
|
||||
('aArg',), stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
check=True, universal_newlines=True)
|
||||
|
||||
def test_add_br(self):
|
||||
self.patch_object(ovsdb, '_run')
|
||||
ovsdb.add_br('br-x')
|
||||
self._run.assert_called_once_with(
|
||||
'ovs-vsctl', 'add-br', 'br-x', '--', 'set', 'bridge', 'br-x',
|
||||
'protocols=OpenFlow13')
|
||||
self._run.reset_mock()
|
||||
ovsdb.add_br('br-x', ('charm', 'managed'))
|
||||
self._run.assert_called_once_with(
|
||||
'ovs-vsctl', 'add-br', 'br-x', '--', 'set', 'bridge', 'br-x',
|
||||
'protocols=OpenFlow13', '--',
|
||||
'br-set-external-id', 'br-x', 'charm', 'managed')
|
||||
|
||||
def test_del_br(self):
|
||||
self.patch_object(ovsdb, '_run')
|
||||
ovsdb.del_br('br-x')
|
||||
self._run.assert_called_once_with(
|
||||
'ovs-vsctl', 'del-br', 'br-x')
|
||||
|
||||
def test_add_port(self):
|
||||
self.patch_object(ovsdb, '_run')
|
||||
ovsdb.add_port('br-x', 'enp3s0f0')
|
||||
self._run.assert_has_calls([
|
||||
mock.call('ip', 'link', 'set', 'enp3s0f0', 'up'),
|
||||
mock.call('ovs-vsctl', 'add-port', 'br-x', 'enp3s0f0'),
|
||||
])
|
||||
|
||||
def test_list_ports(self):
|
||||
self.patch_object(ovsdb, '_run')
|
||||
ovsdb.list_ports('someBridge')
|
||||
self._run.assert_called_once_with('ovs-vsctl', 'list-ports',
|
||||
'someBridge')
|
||||
|
||||
|
||||
class Helper(test_utils.PatchHelper):
|
||||
|
||||
def patch_target(self, attr, return_value=None):
|
||||
mocked = mock.patch.object(self.target, attr)
|
||||
self._patches[attr] = mocked
|
||||
started = mocked.start()
|
||||
started.return_value = return_value
|
||||
self._patches_start[attr] = started
|
||||
setattr(self, attr, started)
|
||||
|
||||
|
||||
class TestSimpleOVSDB(Helper):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.target = ovsdb.SimpleOVSDB('atool', 'atable')
|
||||
|
||||
def test__find_tbl(self):
|
||||
self.patch_object(ovsdb, '_run')
|
||||
cp = mock.MagicMock()
|
||||
cp.stdout = mock.PropertyMock().return_value = VSCTL_BRIDGE_TBL
|
||||
self._run.return_value = cp
|
||||
self.maxDiff = None
|
||||
expect = {
|
||||
'_uuid': '1e21ba48-61ff-4b32-b35e-cb80411da351',
|
||||
'auto_attach': [],
|
||||
'controller': [],
|
||||
'datapath_id': '0000a0369fdd3890',
|
||||
'datapath_type': '',
|
||||
'datapath_version': '<unknown>',
|
||||
'external_ids': [['charm-ovn-chassis', 'managed'],
|
||||
['other', 'value']],
|
||||
'fail_mode': [],
|
||||
'flood_vlans': [],
|
||||
'flow_tables': [],
|
||||
'ipfix': [],
|
||||
'mcast_snooping_enable': False,
|
||||
'mirrors': [],
|
||||
'name': 'br-test',
|
||||
'netflow': [],
|
||||
'other_config': [],
|
||||
'ports': [['uuid', '617f9359-77e2-41be-8af6-4c44e7a6bcc3'],
|
||||
['uuid', 'da840476-8809-4107-8733-591f4696f056']],
|
||||
'protocols': [],
|
||||
'rstp_enable': False,
|
||||
'rstp_status': [],
|
||||
'sflow': [],
|
||||
'status': [],
|
||||
'stp_enable': False}
|
||||
# this in effect also tests the __iter__ front end method
|
||||
for el in self.target:
|
||||
self.assertDictEqual(el, expect)
|
||||
break
|
||||
self._run.assert_called_once_with(
|
||||
'atool', '-f', 'json', 'find', 'atable')
|
||||
self._run.reset_mock()
|
||||
# this in effect also tests the find front end method
|
||||
for el in self.target.find(condition='name=br-test'):
|
||||
break
|
||||
self._run.assert_called_once_with(
|
||||
'atool', '-f', 'json', 'find', 'atable', 'name=br-test')
|
||||
|
||||
def test_clear(self):
|
||||
self.patch_object(ovsdb, '_run')
|
||||
self.target.clear('1e21ba48-61ff-4b32-b35e-cb80411da351',
|
||||
'external_ids')
|
||||
self._run.assert_called_once_with(
|
||||
'atool', 'clear', 'atable',
|
||||
'1e21ba48-61ff-4b32-b35e-cb80411da351', 'external_ids')
|
||||
|
||||
def test_remove(self):
|
||||
self.patch_object(ovsdb, '_run')
|
||||
self.target.remove('1e21ba48-61ff-4b32-b35e-cb80411da351',
|
||||
'external_ids', 'other')
|
||||
self._run.assert_called_once_with(
|
||||
'atool', 'remove', 'atable',
|
||||
'1e21ba48-61ff-4b32-b35e-cb80411da351', 'external_ids', 'other')
|
||||
|
||||
def test_set(self):
|
||||
self.patch_object(ovsdb, '_run')
|
||||
self.target.set('1e21ba48-61ff-4b32-b35e-cb80411da351',
|
||||
'external_ids:other', 'value')
|
||||
self._run.assert_called_once_with(
|
||||
'atool', 'set', 'atable',
|
||||
'1e21ba48-61ff-4b32-b35e-cb80411da351', 'external_ids:other=value')
|
|
@ -12,8 +12,6 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import mock
|
||||
|
||||
import reactive.ovn_chassis_handlers as handlers
|
||||
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
@ -21,69 +19,23 @@ import charms_openstack.test_utils as test_utils
|
|||
|
||||
class TestRegisteredHooks(test_utils.TestRegisteredHooks):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
def test_hooks(self):
|
||||
defaults = [
|
||||
'charm.installed',
|
||||
'config.changed',
|
||||
'update-status',
|
||||
'upgrade-charm',
|
||||
'certificates.available',
|
||||
]
|
||||
hook_set = {
|
||||
'when': {
|
||||
'configure_ovs': ('ovsdb.available',),
|
||||
'enable_metadata': ('nova-compute.connected',),
|
||||
'configure_bridges': ('charm.installed',),
|
||||
},
|
||||
'when_not': {
|
||||
'disable_metadata': ('nova-compute.connected',),
|
||||
},
|
||||
'when_any': {
|
||||
'configure_bridges': (
|
||||
'config.changed.ovn-bridge-mappings',
|
||||
'config.changed.interface-bridge-mappings',
|
||||
'run-default-upgrade-charm',),
|
||||
'enable_ovn_chassis_handlers': ('MOCKED_FLAG',),
|
||||
},
|
||||
}
|
||||
# test that the hooks were registered via the
|
||||
# reactive.ovn_handlers
|
||||
self.registered_hooks_test_helper(handlers, hook_set, defaults)
|
||||
self.registered_hooks_test_helper(handlers, hook_set, {})
|
||||
|
||||
|
||||
class TestOvnHandlers(test_utils.PatchHelper):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.charm = mock.MagicMock()
|
||||
self.patch_object(handlers.charm, 'provide_charm_instance',
|
||||
new=mock.MagicMock())
|
||||
self.provide_charm_instance().__enter__.return_value = \
|
||||
self.charm
|
||||
self.provide_charm_instance().__exit__.return_value = None
|
||||
|
||||
def test_disable_metadata(self):
|
||||
self.patch_object(handlers.reactive, 'clear_flag')
|
||||
handlers.disable_metadata()
|
||||
self.clear_flag.assert_called_once_with(
|
||||
'charm.ovn-chassis.enable-openstack-metadata')
|
||||
|
||||
def test_enable_metadata(self):
|
||||
self.patch_object(handlers.reactive, 'endpoint_from_flag')
|
||||
def test_enable_ovn_chassis_handlers(self):
|
||||
self.patch_object(handlers.reactive, 'set_flag')
|
||||
nova_compute = mock.MagicMock()
|
||||
self.endpoint_from_flag.return_value = nova_compute
|
||||
handlers.enable_metadata()
|
||||
self.set_flag.assert_called_once_with(
|
||||
'charm.ovn-chassis.enable-openstack-metadata')
|
||||
nova_compute.publish_shared_secret.assert_called_once_with()
|
||||
self.charm.install.assert_called_once_with()
|
||||
self.charm.assess_status.assert_called_once_with()
|
||||
|
||||
def configure_ovs(self):
|
||||
self.patch_object(handlers.reactive, 'endpoint_from_flag')
|
||||
ovsdb = mock.MagicMock()
|
||||
self.endpoint_from_flag.return_value = ovsdb
|
||||
self.charm.render_with_interfaces.assert_called_once_with(
|
||||
self.charm.optional_interfaces((ovsdb,), 'nova-compute.connected'))
|
||||
self.charm.configure_ovs.assert_called_once_with(ovsdb)
|
||||
self.charm.assess_status.assert_called_once_with()
|
||||
handlers.enable_ovn_chassis_handlers()
|
||||
self.set_flag.assert_called_once_with('MOCKED_FLAG')
|
||||
|
|
Loading…
Reference in New Issue