Add optional OpenStack metadata support
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
includes:
|
||||
- layer:openstack
|
||||
- interface:ovsdb
|
||||
- interface:neutron-plugin
|
||||
options:
|
||||
basic:
|
||||
use_venv: True
|
||||
|
||||
@@ -3,28 +3,13 @@ import subprocess
|
||||
|
||||
import charmhelpers.core as ch_core
|
||||
|
||||
import charms_openstack.adapters
|
||||
import charms_openstack.charm
|
||||
|
||||
|
||||
OVS_ETCDIR = '/etc/openvswitch'
|
||||
|
||||
|
||||
@charms_openstack.adapters.config_property
|
||||
def cluster_local_addr(cls):
|
||||
"""Address the ``ovsdb-server`` processes should be bound to.
|
||||
|
||||
:param cls: charms_openstack.adapters.ConfigurationAdapter derived class
|
||||
instance. Charm class instance is at cls.charm_instance.
|
||||
:type: cls: charms_openstack.adapters.ConfiguartionAdapter
|
||||
:returns: IP address selected for cluster communication on local unit.
|
||||
:rtype: str
|
||||
"""
|
||||
# XXX this should probably be made space aware
|
||||
# for addr in cls.charm_instance.get_local_addresses():
|
||||
# return addr
|
||||
return ch_core.hookenv.unit_get('private-address')
|
||||
|
||||
|
||||
@charms_openstack.adapters.config_property
|
||||
def ovn_key(cls):
|
||||
return os.path.join(OVS_ETCDIR, 'key_host')
|
||||
@@ -41,16 +26,67 @@ def ovn_ca_cert(cls):
|
||||
'{}.crt'.format(cls.charm_instance.name))
|
||||
|
||||
|
||||
class OVNControllerCharm(charms_openstack.charm.OpenStackCharm):
|
||||
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):
|
||||
release = 'stein'
|
||||
name = 'ovn-controller'
|
||||
name = 'ovn-chassis'
|
||||
packages = ['ovn-host']
|
||||
services = ['ovn-host']
|
||||
adapters_class = OVNChassisCharmRelationAdapters
|
||||
required_relations = ['certificates', 'ovsdb']
|
||||
restart_map = {
|
||||
'/etc/default/ovn-host': 'ovn-host',
|
||||
'/etc/default/ovn-host': ['ovn-host'],
|
||||
}
|
||||
python_version = 3
|
||||
# Name of unitdata key with information on whether to enable metadata
|
||||
metadata_kv_key = 'ovn-chassis-enable-metadata'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
enable_metadata = ch_core.unitdata.kv().get(
|
||||
self.metadata_kv_key, False)
|
||||
print(enable_metadata)
|
||||
if enable_metadata:
|
||||
# XXX for Train onwards, we should use the
|
||||
# ``networking-ovn-metadata-agent`` package
|
||||
metadata_agent = 'networking-ovn-metadata-agent'
|
||||
self.packages.extend(['python3-networking-ovn', 'haproxy'])
|
||||
self.services.append(metadata_agent)
|
||||
self.restart_map.update({
|
||||
'/etc/neutron/'
|
||||
'networking_ovn_metadata_agent.ini': [metadata_agent],
|
||||
'/etc/init.d/''networking-ovn-metadata-agent': [
|
||||
metadata_agent],
|
||||
'/lib/systemd/system/networking-ovn-metadata-agent.service': (
|
||||
[metadata_agent]),
|
||||
})
|
||||
self.permission_override_map = {
|
||||
'/etc/init.d/networking-ovn-metadata-agent': 0o755,
|
||||
}
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def disable_metadata(self):
|
||||
db = ch_core.unitdata.kv()
|
||||
db.unset(self.metadata_kv_key)
|
||||
db.flush()
|
||||
|
||||
def enable_metadata(self):
|
||||
db = ch_core.unitdata.kv()
|
||||
db.set(self.metadata_kv_key, True)
|
||||
db.flush()
|
||||
|
||||
def run(self, *args):
|
||||
cp = subprocess.run(
|
||||
@@ -61,7 +97,7 @@ class OVNControllerCharm(charms_openstack.charm.OpenStackCharm):
|
||||
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 = super().configure_tls(
|
||||
tls_objects = self.get_certs_and_keys(
|
||||
certificates_interface=certificates_interface)
|
||||
|
||||
for tls_object in tls_objects:
|
||||
@@ -92,5 +128,5 @@ class OVNControllerCharm(charms_openstack.charm.OpenStackCharm):
|
||||
'external-ids:ovn-encap-type=geneve')
|
||||
self.run('ovs-vsctl', 'set', 'open', '.',
|
||||
'external-ids:ovn-encap-ip={}'
|
||||
.format(cluster_local_addr(None)))
|
||||
.format(ovsdb_interface.cluster_local_addr))
|
||||
self.restart_all()
|
||||
@@ -1,4 +1,4 @@
|
||||
name: ovn-controller
|
||||
name: ovn-chassis
|
||||
summary: Open Virtual Network for Open vSwitch
|
||||
maintainer: OpenStack Charmers <openstack-charmers@lists.ubuntu.com>
|
||||
description: |
|
||||
@@ -10,6 +10,10 @@ series:
|
||||
- bionic
|
||||
- disco
|
||||
subordinate: true
|
||||
provides:
|
||||
nova-compute:
|
||||
interface: neutron-plugin
|
||||
scope: container
|
||||
requires:
|
||||
juju-info:
|
||||
interface: juju-info
|
||||
|
||||
68
src/reactive/ovn_chassis_handlers.py
Normal file
68
src/reactive/ovn_chassis_handlers.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import charmhelpers.core as ch_core
|
||||
|
||||
import charms.reactive as reactive
|
||||
|
||||
import charms_openstack.bus
|
||||
import charms_openstack.charm as charm
|
||||
|
||||
|
||||
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():
|
||||
with charm.provide_charm_instance() as charm_instance:
|
||||
charm_instance.disable_metadata()
|
||||
charm_instance.assess_status()
|
||||
|
||||
|
||||
@reactive.when('nova-compute.connected')
|
||||
def enable_metadata():
|
||||
nova_compute = reactive.endpoint_from_flag('nova-compute.connected')
|
||||
nova_compute.publish_shared_secret()
|
||||
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.enable_metadata()
|
||||
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.install()
|
||||
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()
|
||||
@@ -1,25 +0,0 @@
|
||||
import charms.reactive as reactive
|
||||
|
||||
import charms_openstack.bus
|
||||
import charms_openstack.charm as charm
|
||||
|
||||
|
||||
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('ovsdb.available')
|
||||
def configure_ovs():
|
||||
ovsdb = reactive.endpoint_from_flag('ovsdb.available')
|
||||
with charm.provide_charm_instance() as charm_instance:
|
||||
charm_instance.render_with_interfaces([ovsdb])
|
||||
charm_instance.configure_ovs(ovsdb)
|
||||
charm_instance.assess_status()
|
||||
236
src/templates/networking-ovn-metadata-agent
Executable file
236
src/templates/networking-ovn-metadata-agent
Executable file
@@ -0,0 +1,236 @@
|
||||
#!/bin/sh
|
||||
|
||||
###############################################################################
|
||||
# [ WARNING ]
|
||||
# Configuration file maintained by Juju. Local changes may be overwritten.
|
||||
# Configuration managed by neutron-openvswitch charm
|
||||
###############################################################################
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: networking-ovn-metadata-agent
|
||||
# Required-Start: $network $local_fs $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs
|
||||
# Should-Start:
|
||||
# Should-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: OpenStack OVN metadata agent
|
||||
# Description: OpenStack Metadata services of instances
|
||||
# provisioned using OVN.
|
||||
### END INIT INFO
|
||||
|
||||
# Author: James Page <james.page@ubuntu.com>
|
||||
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="Networking OVN Metadata Agent"
|
||||
PROJECT_NAME=neutron
|
||||
NAME=networking-ovn-metadata-agent
|
||||
DAEMON_ARGS="--config-file=/etc/neutron/networking_ovn_metadata_agent.ini"
|
||||
# The metadata-agent runs as root as it needs to:
|
||||
# a) interact with openvswitch locally via a root owned unix socket
|
||||
# b) manage veth pairs for actual metadataproxy processes
|
||||
# the actual proxy between VM and Nova will not run as root
|
||||
SYSTEM_USER=root
|
||||
SYSTEM_GROUP=root
|
||||
#!/bin/sh
|
||||
# The content after this line comes from openstack-pkg-tools
|
||||
# and has been automatically added to a .init.in script, which
|
||||
# contains only the descriptive part for the daemon. Everything
|
||||
# else is standardized as a single unique script.
|
||||
|
||||
# Author: Thomas Goirand <zigo@debian.org>
|
||||
# Author: Ondřej Nový <novy@ondrej.org>
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
|
||||
if [ -n "${UWSGI_PORT}" ] && [ -n "${UWSGI_INI_PATH}" ] && [ -n "${UWSGI_INI_APP}" ] ; then
|
||||
if ! [ -f "${UWSGI_INI_APP}" ] ; then
|
||||
exit 0
|
||||
fi
|
||||
if [ -d /etc/${PROJECT_NAME}/ssl/private ] ; then
|
||||
KEY_FILE=$(find /etc/${PROJECT_NAME}/ssl/private -type f -iname '*.pem' 2>/dev/null | head -n 1)
|
||||
fi
|
||||
|
||||
if [ -e /usr/local/share/ca-certificates/puppet_openstack.crt ] ; then
|
||||
# This is needed for puppet...
|
||||
CERT_FILE=/usr/local/share/ca-certificates/puppet_openstack.crt
|
||||
else
|
||||
if [ -d /etc/${PROJECT_NAME}/ssl/public ] ; then
|
||||
CERT_FILE=$(find /etc/${PROJECT_NAME}/ssl/public -type f -iname '*.crt' 2>/dev/null | head -n 1)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Sid doesn't have /usr/bin/uwsgi_python3, so we need
|
||||
# to search for a more specific daemon name. For stretch
|
||||
# /usr/bin/uwsgi_python3 is fine.
|
||||
for i in 3 35 36 37 38 39 ; do
|
||||
if [ -x /usr/bin/uwsgi_python${i} ] ; then
|
||||
DAEMON=/usr/bin/uwsgi_python${i}
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "${KEY_FILE}" ] && [ -n "${CERT_FILE}" ] ; then
|
||||
DAEMON_ARGS="--https-socket [::]:${UWSGI_PORT},${CERT_FILE},${KEY_FILE}"
|
||||
else
|
||||
DAEMON_ARGS="--http-socket [::]:${UWSGI_PORT}"
|
||||
fi
|
||||
|
||||
DAEMON_ARGS="${DAEMON_ARGS} --ini ${UWSGI_INI_PATH}"
|
||||
NO_OPENSTACK_CONFIG_FILE_DAEMON_ARG=yes
|
||||
NO_OPENSTACK_LOGFILE_DAEMON_ARG=yes
|
||||
fi
|
||||
|
||||
if [ -z "${DAEMON}" ] ; then
|
||||
DAEMON=/usr/bin/${NAME}
|
||||
fi
|
||||
PIDFILE=/var/run/${PROJECT_NAME}/${NAME}.pid
|
||||
if [ -z "${SCRIPTNAME}" ] ; then
|
||||
SCRIPTNAME=/etc/init.d/${NAME}
|
||||
fi
|
||||
if [ -z "${SYSTEM_USER}" ] ; then
|
||||
SYSTEM_USER=${PROJECT_NAME}
|
||||
fi
|
||||
if [ -z "${SYSTEM_GROUP}" ] ; then
|
||||
SYSTEM_GROUP=${PROJECT_NAME}
|
||||
fi
|
||||
if [ "${SYSTEM_USER}" != "root" ] ; then
|
||||
STARTDAEMON_CHUID="--chuid ${SYSTEM_USER}:${SYSTEM_GROUP}"
|
||||
fi
|
||||
if [ -z "${CONFIG_FILE}" ] ; then
|
||||
CONFIG_FILE=/etc/${PROJECT_NAME}/${PROJECT_NAME}.conf
|
||||
fi
|
||||
LOGFILE=/var/log/${PROJECT_NAME}/${NAME}.log
|
||||
if [ -z "${NO_OPENSTACK_CONFIG_FILE_DAEMON_ARG}" ] ; then
|
||||
DAEMON_ARGS="--config-file=${CONFIG_FILE} ${DAEMON_ARGS}"
|
||||
fi
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x $DAEMON ] || exit 0
|
||||
|
||||
# If ran as root, create /var/lock/X, /var/run/X and /var/cache/X as needed
|
||||
if [ `whoami` = "root" ] ; then
|
||||
for i in lock run cache ; do
|
||||
mkdir -p /var/$i/${PROJECT_NAME}
|
||||
chown ${SYSTEM_USER}:${SYSTEM_GROUP} /var/$i/${PROJECT_NAME}
|
||||
done
|
||||
fi
|
||||
|
||||
# This defines support functions which we use later on
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
RET=0
|
||||
|
||||
# Manage log options: logfile and/or syslog, depending on user's choosing
|
||||
[ -r /etc/default/openstack ] && . /etc/default/openstack
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
[ "x$USE_SYSLOG" = "xyes" ] && DAEMON_ARGS="$DAEMON_ARGS --use-syslog"
|
||||
if [ -z "${NO_OPENSTACK_LOGFILE_DAEMON_ARG}" ] ; then
|
||||
[ "x$USE_LOGFILE" != "xno" ] && DAEMON_ARGS="$DAEMON_ARGS --log-file=$LOGFILE"
|
||||
fi
|
||||
|
||||
do_start() {
|
||||
start-stop-daemon \
|
||||
--start \
|
||||
--quiet \
|
||||
--background ${STARTDAEMON_CHUID} \
|
||||
--make-pidfile --pidfile ${PIDFILE} \
|
||||
--chdir /var/lib/${PROJECT_NAME} \
|
||||
--startas $DAEMON \
|
||||
--test > /dev/null \
|
||||
|| return 1
|
||||
if [ -n "${PYARGV}" ] ; then
|
||||
start-stop-daemon \
|
||||
--start \
|
||||
--quiet \
|
||||
--background ${STARTDAEMON_CHUID} \
|
||||
--make-pidfile --pidfile ${PIDFILE} \
|
||||
--chdir /var/lib/${PROJECT_NAME} \
|
||||
--startas $DAEMON \
|
||||
-- $DAEMON_ARGS --pyargv "${PYARGV}" \
|
||||
|| return 2
|
||||
else
|
||||
start-stop-daemon \
|
||||
--start \
|
||||
--quiet \
|
||||
--background ${STARTDAEMON_CHUID} \
|
||||
--make-pidfile --pidfile ${PIDFILE} \
|
||||
--chdir /var/lib/${PROJECT_NAME} \
|
||||
--startas $DAEMON \
|
||||
-- $DAEMON_ARGS \
|
||||
|| return 2
|
||||
fi
|
||||
}
|
||||
|
||||
do_stop() {
|
||||
start-stop-daemon \
|
||||
--stop \
|
||||
--quiet \
|
||||
--retry=TERM/30/KILL/5 \
|
||||
--pidfile $PIDFILE
|
||||
RETVAL=$?
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
do_systemd_start() {
|
||||
if [ -n "${PYARGV}" ] ; then
|
||||
exec $DAEMON $DAEMON_ARGS --pyargv "${PYARGV}"
|
||||
else
|
||||
exec $DAEMON $DAEMON_ARGS
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
log_daemon_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case $? in
|
||||
0|1) log_end_msg 0 ; RET=$? ;;
|
||||
2) log_end_msg 1 ; RET=$? ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case $? in
|
||||
0|1) log_end_msg 0 ; RET=$? ;;
|
||||
2) log_end_msg 1 ; RET=$? ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME"
|
||||
RET=$?
|
||||
;;
|
||||
systemd-start)
|
||||
do_systemd_start
|
||||
;;
|
||||
show-args)
|
||||
if [ -n "${PYARGV}" ] ; then
|
||||
echo $DAEMON $DAEMON_ARGS --pyargv \"${PYARGV}\"
|
||||
else
|
||||
echo $DAEMON $DAEMON_ARGS
|
||||
fi
|
||||
;;
|
||||
restart|force-reload)
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case $? in
|
||||
0|1)
|
||||
do_start
|
||||
case $? in
|
||||
0) log_end_msg 0 ; RET=$? ;;
|
||||
1) log_end_msg 1 ; RET=$? ;; # Old process is still running
|
||||
*) log_end_msg 1 ; RET=$? ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*) log_end_msg 1 ; RET=$? ;; # Failed to stop
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload|systemd-start}" >&2
|
||||
RET=3
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $RET
|
||||
28
src/templates/networking-ovn-metadata-agent.service
Normal file
28
src/templates/networking-ovn-metadata-agent.service
Normal file
@@ -0,0 +1,28 @@
|
||||
###############################################################################
|
||||
# [ WARNING ]
|
||||
# Configuration file maintained by Juju. Local changes may be overwritten.
|
||||
# Configuration managed by neutron-openvswitch charm
|
||||
###############################################################################
|
||||
|
||||
[Unit]
|
||||
Description=Networking OVN Metadata Agent
|
||||
|
||||
|
||||
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
Type=simple
|
||||
WorkingDirectory=~
|
||||
RuntimeDirectory=neutron lock/neutron
|
||||
CacheDirectory=neutron
|
||||
ExecStart=/etc/init.d/networking-ovn-metadata-agent systemd-start
|
||||
Restart=on-failure
|
||||
LimitNOFILE=65535
|
||||
TimeoutStopSec=15
|
||||
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
16
src/templates/networking_ovn_metadata_agent.ini
Normal file
16
src/templates/networking_ovn_metadata_agent.ini
Normal file
@@ -0,0 +1,16 @@
|
||||
###############################################################################
|
||||
# [ 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 }}
|
||||
129
unit_tests/test_lib_charm_openstack_ovn_chassis.py
Normal file
129
unit_tests/test_lib_charm_openstack_ovn_chassis.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# 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.target = ovn_chassis.OVNChassisCharm()
|
||||
|
||||
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_disable_metadata(self):
|
||||
self.patch_object(ovn_chassis.ch_core.unitdata, 'kv')
|
||||
db = mock.MagicMock()
|
||||
self.kv.return_value = db
|
||||
self.target.disable_metadata()
|
||||
db.unset.assert_called_once_with(self.target.metadata_kv_key)
|
||||
|
||||
def test_enable_metadata(self):
|
||||
self.patch_object(ovn_chassis.ch_core.unitdata, 'kv')
|
||||
db = mock.MagicMock()
|
||||
self.kv.return_value = db
|
||||
self.target.enable_metadata()
|
||||
db.set.assert_called_once_with(self.target.metadata_kv_key, True)
|
||||
|
||||
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()
|
||||
ovsdb_interface.db_sb_connection_strs = mock.PropertyMock(
|
||||
).return_value = ['dbsbconn']
|
||||
ovsdb_interface.cluster_local_addr = mock.PropertyMock(
|
||||
).return_value = '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-remote=dbsbconn'),
|
||||
mock.call('ovs-vsctl', 'set', 'open', '.',
|
||||
'external-ids:ovn-encap-type=geneve'),
|
||||
mock.call('ovs-vsctl', 'set', 'open', '.',
|
||||
'external-ids:ovn-encap-ip=cluster_local_addr'),
|
||||
])
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
import mock
|
||||
|
||||
import reactive.ovn_controller_handlers as handlers
|
||||
import reactive.ovn_chassis_handlers as handlers
|
||||
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
||||
@@ -32,6 +32,10 @@ class TestRegisteredHooks(test_utils.TestRegisteredHooks):
|
||||
hook_set = {
|
||||
'when': {
|
||||
'configure_ovs': ('ovsdb.available',),
|
||||
'enable_metadata': ('nova-compute.connected',),
|
||||
},
|
||||
'when_not': {
|
||||
'disable_metadata': ('nova-compute.connected',),
|
||||
},
|
||||
}
|
||||
# test that the hooks were registered via the
|
||||
@@ -43,10 +47,33 @@ class TestOvnHandlers(test_utils.PatchHelper):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# self.patch_release(octavia.OctaviaCharm.release)
|
||||
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):
|
||||
handlers.disable_metadata()
|
||||
self.charm.disable_metadata.assert_called_once_with()
|
||||
self.charm.assess_status.assert_called_once_with()
|
||||
|
||||
def test_enable_metadata(self):
|
||||
self.patch_object(handlers.reactive, 'endpoint_from_flag')
|
||||
nova_compute = mock.MagicMock()
|
||||
self.endpoint_from_flag.return_value = nova_compute
|
||||
handlers.enable_metadata()
|
||||
nova_compute.publish_shared_secret.assert_called_once_with()
|
||||
self.charm.enable_metadata.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()
|
||||
Reference in New Issue
Block a user