Merge pull request #30 from thedac/feature/dragent
Neutron dynamic routing testing
This commit is contained in:
@@ -14,6 +14,7 @@ install_require = [
|
||||
'juju',
|
||||
'juju-wait',
|
||||
'PyYAML',
|
||||
'tenacity',
|
||||
]
|
||||
|
||||
tests_require = [
|
||||
|
||||
@@ -24,6 +24,7 @@ python-keystoneclient
|
||||
python-neutronclient
|
||||
python-novaclient
|
||||
python-swiftclient
|
||||
tenacity
|
||||
distro-info
|
||||
paramiko
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ class TestCharmLifecycleDeploy(ut_utils.BaseTestCase):
|
||||
self.patch_object(lc_deploy.juju_wait, 'wait')
|
||||
lc_deploy.deploy('bun.yaml', 'newmodel')
|
||||
self.deploy_bundle.assert_called_once_with('bun.yaml', 'newmodel')
|
||||
self.wait.assert_called_once_with()
|
||||
self.wait.assert_called_once_with(wait_for_workload=True)
|
||||
|
||||
def test_deploy_nowait(self):
|
||||
self.patch_object(lc_deploy, 'deploy_bundle')
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import mock
|
||||
import unit_tests.utils as ut_utils
|
||||
from zaza.utilities import _local_utils
|
||||
|
||||
|
||||
class TestLocalUtils(ut_utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLocalUtils, self).setUp()
|
||||
|
||||
def test_get_yaml_config(self):
|
||||
self.patch("builtins.open",
|
||||
new_callable=mock.mock_open(),
|
||||
name="_open")
|
||||
_yaml = "data: somedata"
|
||||
_yaml_dict = {"data": "somedata"}
|
||||
_filename = "filename"
|
||||
_fileobj = mock.MagicMock()
|
||||
_fileobj.read.return_value = _yaml
|
||||
self._open.return_value = _fileobj
|
||||
|
||||
self.assertEqual(_local_utils.get_yaml_config(_filename),
|
||||
_yaml_dict)
|
||||
self._open.assert_called_once_with(_filename, "r")
|
||||
|
||||
def test_get_undercloud_env_vars(self):
|
||||
self.patch_object(_local_utils.os.environ, "get")
|
||||
|
||||
def _get_env(key):
|
||||
return _env.get(key)
|
||||
self.get.side_effect = _get_env
|
||||
|
||||
# OSCI backward compatible env vars
|
||||
_env = {"NET_ID": "netid",
|
||||
"NAMESERVER": "10.0.0.10",
|
||||
"GATEWAY": "10.0.0.1",
|
||||
"CIDR_EXT": "10.0.0.0/24",
|
||||
"FIP_RANGE": "10.0.200.0:10.0.200.254"}
|
||||
_expected_result = {}
|
||||
_expected_result["net_id"] = _env["NET_ID"]
|
||||
_expected_result["external_dns"] = _env["NAMESERVER"]
|
||||
_expected_result["default_gateway"] = _env["GATEWAY"]
|
||||
_expected_result["external_net_cidr"] = _env["CIDR_EXT"]
|
||||
_expected_result["start_floating_ip"] = _env["FIP_RANGE"].split(":")[0]
|
||||
_expected_result["end_floating_ip"] = _env["FIP_RANGE"].split(":")[1]
|
||||
self.assertEqual(_local_utils.get_undercloud_env_vars(),
|
||||
_expected_result)
|
||||
|
||||
# Overriding configure.network named variables
|
||||
_override = {"start_floating_ip": "10.100.50.0",
|
||||
"end_floating_ip": "10.100.50.254",
|
||||
"default_gateway": "10.100.0.1",
|
||||
"external_net_cidr": "10.100.0.0/16"}
|
||||
_env.update(_override)
|
||||
_expected_result.update(_override)
|
||||
self.assertEqual(_local_utils.get_undercloud_env_vars(),
|
||||
_expected_result)
|
||||
|
||||
def test_get_net_info(self):
|
||||
self.patch_object(_local_utils.os.path, "exists")
|
||||
self.patch_object(_local_utils, "get_yaml_config")
|
||||
self.patch_object(_local_utils, "get_undercloud_env_vars")
|
||||
net_topology = "topo"
|
||||
_data = {net_topology: {"network": "DATA"}}
|
||||
self.get_yaml_config.return_value = _data
|
||||
|
||||
# YAML file does not exist
|
||||
self.exists.return_value = False
|
||||
with self.assertRaises(Exception):
|
||||
_local_utils.get_net_info(net_topology)
|
||||
|
||||
# No environmental variables
|
||||
self.exists.return_value = True
|
||||
self.assertEqual(
|
||||
_local_utils.get_net_info(net_topology, ignore_env_vars=True),
|
||||
_data[net_topology])
|
||||
self.get_yaml_config.assert_called_once_with("network.yaml")
|
||||
self.get_undercloud_env_vars.assert_not_called()
|
||||
|
||||
# Update with environmental variables
|
||||
_more_data = {"network": "NEW",
|
||||
"other": "DATA"}
|
||||
self.get_undercloud_env_vars.return_value = _more_data
|
||||
_data[net_topology].update(_more_data)
|
||||
self.assertEqual(
|
||||
_local_utils.get_net_info(net_topology),
|
||||
_data[net_topology])
|
||||
self.get_undercloud_env_vars.assert_called_once_with()
|
||||
@@ -1,3 +1,4 @@
|
||||
import copy
|
||||
import mock
|
||||
import unit_tests.utils as ut_utils
|
||||
from zaza.utilities import openstack_utils
|
||||
@@ -7,20 +8,41 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOpenStackUtils, self).setUp()
|
||||
self.port_name = 'port_name'
|
||||
self.net_uuid = 'uuid'
|
||||
self.port_name = "port_name"
|
||||
self.net_uuid = "net_uuid"
|
||||
self.project_id = "project_uuid"
|
||||
self.ext_net = "ext_net"
|
||||
self.private_net = "private_net"
|
||||
self.port = {
|
||||
'port': {'id': 'port_id',
|
||||
'name': self.port_name,
|
||||
'network_id': self.net_uuid}}
|
||||
|
||||
self.ports = {'ports': [self.port['port']]}
|
||||
"port": {"id": "port_id",
|
||||
"name": self.port_name,
|
||||
"network_id": self.net_uuid}}
|
||||
self.ports = {"ports": [self.port["port"]]}
|
||||
self.floatingip = {
|
||||
'floatingip': {'id': 'floatingip_id',
|
||||
'floating_network_id': self.net_uuid,
|
||||
'port_id': 'port_id'}}
|
||||
"floatingip": {"id": "floatingip_id",
|
||||
"floating_network_id": self.net_uuid,
|
||||
"port_id": "port_id"}}
|
||||
self.floatingips = {"floatingips": [self.floatingip["floatingip"]]}
|
||||
self.address_scope_name = "address_scope_name"
|
||||
self.address_scope = {
|
||||
"address_scope": {"id": "address_scope_id",
|
||||
"name": self.address_scope_name,
|
||||
"shared": True,
|
||||
"ip_version": 4,
|
||||
"tenant_id": self.project_id}}
|
||||
self.address_scopes = {
|
||||
"address_scopes": [self.address_scope["address_scope"]]}
|
||||
|
||||
self.floatingips = {'floatingips': [self.floatingip['floatingip']]}
|
||||
self.network = {
|
||||
"network": {"id": "network_id",
|
||||
"name": self.ext_net,
|
||||
"tenant_id": self.project_id,
|
||||
"router:external": True,
|
||||
"provider:physical_network": "physnet1",
|
||||
"provider:network_type": "flat"}}
|
||||
|
||||
self.networks = {
|
||||
"networks": [self.network["network"]]}
|
||||
|
||||
self.neutronclient = mock.MagicMock()
|
||||
self.neutronclient.list_ports.return_value = self.ports
|
||||
@@ -28,42 +50,124 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
|
||||
self.neutronclient.list_floatingips.return_value = self.floatingips
|
||||
self.neutronclient.create_floatingip.return_value = self.floatingip
|
||||
self.ext_net = 'ext_net'
|
||||
self.private_net = 'private_net'
|
||||
|
||||
self.neutronclient.list_address_scopes.return_value = (
|
||||
self.address_scopes)
|
||||
self.neutronclient.create_address_scope.return_value = (
|
||||
self.address_scope)
|
||||
|
||||
self.neutronclient.list_networks.return_value = self.networks
|
||||
self.neutronclient.create_network.return_value = self.network
|
||||
|
||||
def test_create_port(self):
|
||||
self.patch_object(openstack_utils, 'get_net_uuid')
|
||||
self.patch_object(openstack_utils, "get_net_uuid")
|
||||
self.get_net_uuid.return_value = self.net_uuid
|
||||
|
||||
# Already exists
|
||||
port = openstack_utils.create_port(
|
||||
self.neutronclient, self.port_name, self.private_net)
|
||||
self.assertEqual(port, self.port['port'])
|
||||
self.assertEqual(port, self.port["port"])
|
||||
self.neutronclient.create_port.assert_not_called()
|
||||
|
||||
# Does not yet exist
|
||||
self.neutronclient.list_ports.return_value = {'ports': []}
|
||||
self.port['port'].pop('id')
|
||||
self.neutronclient.list_ports.return_value = {"ports": []}
|
||||
self.port["port"].pop("id")
|
||||
port = openstack_utils.create_port(
|
||||
self.neutronclient, self.port_name, self.private_net)
|
||||
self.assertEqual(port, self.port['port'])
|
||||
self.assertEqual(port, self.port["port"])
|
||||
self.neutronclient.create_port.assert_called_once_with(self.port)
|
||||
|
||||
def test_create_floating_ip(self):
|
||||
self.patch_object(openstack_utils, 'get_net_uuid')
|
||||
self.patch_object(openstack_utils, "get_net_uuid")
|
||||
self.get_net_uuid.return_value = self.net_uuid
|
||||
|
||||
# Already exists
|
||||
floatingip = openstack_utils.create_floating_ip(
|
||||
self.neutronclient, self.ext_net, port=self.port['port'])
|
||||
self.assertEqual(floatingip, self.floatingip['floatingip'])
|
||||
self.neutronclient, self.ext_net, port=self.port["port"])
|
||||
self.assertEqual(floatingip, self.floatingip["floatingip"])
|
||||
self.neutronclient.create_floatingip.assert_not_called()
|
||||
|
||||
# Does not yet exist
|
||||
self.neutronclient.list_floatingips.return_value = {'floatingips': []}
|
||||
self.floatingip['floatingip'].pop('id')
|
||||
self.neutronclient.list_floatingips.return_value = {"floatingips": []}
|
||||
self.floatingip["floatingip"].pop("id")
|
||||
floatingip = openstack_utils.create_floating_ip(
|
||||
self.neutronclient, self.private_net, port=self.port['port'])
|
||||
self.assertEqual(floatingip, self.floatingip['floatingip'])
|
||||
self.neutronclient, self.private_net, port=self.port["port"])
|
||||
self.assertEqual(floatingip, self.floatingip["floatingip"])
|
||||
self.neutronclient.create_floatingip.assert_called_once_with(
|
||||
self.floatingip)
|
||||
|
||||
def test_create_address_scope(self):
|
||||
self.patch_object(openstack_utils, "get_net_uuid")
|
||||
self.get_net_uuid.return_value = self.net_uuid
|
||||
|
||||
# Already exists
|
||||
address_scope = openstack_utils.create_address_scope(
|
||||
self.neutronclient, self.project_id, self.address_scope_name)
|
||||
self.assertEqual(address_scope, self.address_scope["address_scope"])
|
||||
self.neutronclient.create_address_scope.assert_not_called()
|
||||
|
||||
# Does not yet exist
|
||||
self.neutronclient.list_address_scopes.return_value = {
|
||||
"address_scopes": []}
|
||||
address_scope_msg = copy.deepcopy(self.address_scope)
|
||||
address_scope_msg["address_scope"].pop("id")
|
||||
address_scope = openstack_utils.create_address_scope(
|
||||
self.neutronclient, self.project_id, self.address_scope_name)
|
||||
self.assertEqual(address_scope, self.address_scope["address_scope"])
|
||||
self.neutronclient.create_address_scope.assert_called_once_with(
|
||||
address_scope_msg)
|
||||
|
||||
def test_create_external_network(self):
|
||||
self.patch_object(openstack_utils, "get_net_uuid")
|
||||
self.get_net_uuid.return_value = self.net_uuid
|
||||
|
||||
# Already exists
|
||||
network = openstack_utils.create_external_network(
|
||||
self.neutronclient, self.project_id, False)
|
||||
self.assertEqual(network, self.network["network"])
|
||||
self.neutronclient.create_network.assert_not_called()
|
||||
|
||||
# Does not yet exist
|
||||
self.neutronclient.list_networks.return_value = {
|
||||
"networks": []}
|
||||
network_msg = copy.deepcopy(self.network)
|
||||
network_msg["network"].pop("id")
|
||||
network = openstack_utils.create_external_network(
|
||||
self.neutronclient, self.project_id, False)
|
||||
self.assertEqual(network, self.network["network"])
|
||||
self.neutronclient.create_network.assert_called_once_with(
|
||||
network_msg)
|
||||
|
||||
def test_get_keystone_scope(self):
|
||||
self.patch_object(openstack_utils, "get_current_os_versions")
|
||||
|
||||
# <= Liberty
|
||||
self.get_current_os_versions.return_value = {"keystone": "liberty"}
|
||||
self.assertEqual(openstack_utils.get_keystone_scope(), "DOMAIN")
|
||||
# > Liberty
|
||||
self.get_current_os_versions.return_value = {"keystone": "mitaka"}
|
||||
self.assertEqual(openstack_utils.get_keystone_scope(), "PROJECT")
|
||||
|
||||
def test_get_overcloud_keystone_session(self):
|
||||
self.patch_object(openstack_utils, "get_keystone_session")
|
||||
self.patch_object(openstack_utils, "get_keystone_scope")
|
||||
self.patch_object(openstack_utils, "get_overcloud_auth")
|
||||
_auth = "FAKE_AUTH"
|
||||
_scope = "PROJECT"
|
||||
self.get_keystone_scope.return_value = _scope
|
||||
self.get_overcloud_auth.return_value = _auth
|
||||
|
||||
openstack_utils.get_overcloud_keystone_session()
|
||||
self.get_keystone_session.assert_called_once_with(_auth, scope=_scope)
|
||||
|
||||
def test_get_undercloud_keystone_session(self):
|
||||
self.patch_object(openstack_utils, "get_keystone_session")
|
||||
self.patch_object(openstack_utils, "get_keystone_scope")
|
||||
self.patch_object(openstack_utils, "get_undercloud_auth")
|
||||
_auth = "FAKE_AUTH"
|
||||
_scope = "PROJECT"
|
||||
self.get_keystone_scope.return_value = _scope
|
||||
self.get_undercloud_auth.return_value = _auth
|
||||
|
||||
openstack_utils.get_undercloud_keystone_session()
|
||||
self.get_keystone_session.assert_called_once_with(_auth, scope=_scope)
|
||||
|
||||
@@ -224,7 +224,7 @@ def deploy(bundle, model, wait=True):
|
||||
if wait:
|
||||
logging.info("Waiting for environment to settle")
|
||||
utils.set_juju_model(model)
|
||||
juju_wait.wait()
|
||||
juju_wait.wait(wait_for_workload=True)
|
||||
|
||||
|
||||
def parse_args(args):
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from zaza.configure import (
|
||||
network,
|
||||
bgp_speaker,
|
||||
)
|
||||
from zaza.utilities import (
|
||||
_local_utils,
|
||||
openstack_utils,
|
||||
)
|
||||
|
||||
DEFAULT_PEER_APPLICATION_NAME = "quagga"
|
||||
|
||||
# The overcloud network configuration settings are declared.
|
||||
# These are the network configuration settings under test.
|
||||
OVERCLOUD_NETWORK_CONFIG = {
|
||||
"network_type": "gre",
|
||||
"router_name": "provider-router",
|
||||
"ip_version": "4",
|
||||
"address_scope": "public",
|
||||
"external_net_name": "ext_net",
|
||||
"external_subnet_name": "ext_net_subnet",
|
||||
"prefix_len": "24",
|
||||
"subnetpool_name": "pooled_subnets",
|
||||
"subnetpool_prefix": "192.168.0.0/16",
|
||||
}
|
||||
|
||||
# The undercloud network configuration settings are substrate specific to
|
||||
# the environment where the tests are being executed. These settings may be
|
||||
# overridden by environment variables. See the doc string documentation for
|
||||
# zaza.utilities._local_utils.get_overcloud_env_vars for the environment
|
||||
# variables required to be exported and available to zaza.
|
||||
# These are default settings provided as an example.
|
||||
DEFAULT_UNDERCLOUD_NETWORK_CONFIG = {
|
||||
"start_floating_ip": "10.5.150.0",
|
||||
"end_floating_ip": "10.5.150.254",
|
||||
"external_dns": "10.5.0.2",
|
||||
"external_net_cidr": "10.5.0.0/16",
|
||||
"default_gateway": "10.5.0.1",
|
||||
}
|
||||
|
||||
|
||||
def setup():
|
||||
"""Setup BGP networking
|
||||
|
||||
Configure the following:
|
||||
The overcloud network using subnet pools
|
||||
The overcloud BGP speaker
|
||||
The BGP peer
|
||||
Advertising of the FIPs via BGP
|
||||
Advertising of the project network(s) via BGP
|
||||
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
_local_utils.setup_logging()
|
||||
|
||||
# Get network configuration settings
|
||||
network_config = {}
|
||||
# Declared overcloud settings
|
||||
network_config.update(OVERCLOUD_NETWORK_CONFIG)
|
||||
# Default undercloud settings
|
||||
network_config.update(DEFAULT_UNDERCLOUD_NETWORK_CONFIG)
|
||||
# Environment specific settings
|
||||
network_config.update(_local_utils.get_undercloud_env_vars())
|
||||
|
||||
# Get keystone session
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
|
||||
# Confugre the overcloud network
|
||||
network.setup_sdn(network_config, keystone_session=keystone_session)
|
||||
# Configure BGP
|
||||
bgp_speaker.setup_bgp_speaker(
|
||||
peer_application_name=DEFAULT_PEER_APPLICATION_NAME,
|
||||
keystone_session=keystone_session)
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
import tenacity
|
||||
|
||||
from zaza import model
|
||||
from zaza.charm_lifecycle import utils as lifecycle_utils
|
||||
from zaza.utilities import (
|
||||
_local_utils,
|
||||
openstack_utils,
|
||||
)
|
||||
|
||||
|
||||
def test_bgp_routes(peer_application_name="quagga", keystone_session=None):
|
||||
"""Test BGP routes
|
||||
|
||||
:param peer_application_name: String name of BGP peer application
|
||||
:type peer_application_name: string
|
||||
:param keystone_session: Keystone session object for overcloud
|
||||
:type keystone_session: keystoneauth1.session.Session object
|
||||
:raises: AssertionError if expected BGP routes are not found
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
# If a session has not been provided, acquire one
|
||||
if not keystone_session:
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
|
||||
# Get authenticated clients
|
||||
neutron_client = openstack_utils.get_neutron_session_client(
|
||||
keystone_session)
|
||||
|
||||
# Get the peer unit
|
||||
peer_unit = model.get_units(
|
||||
lifecycle_utils.get_juju_model(), peer_application_name)[0].entity_id
|
||||
|
||||
# Get expected advertised routes
|
||||
private_cidr = neutron_client.list_subnets(
|
||||
name="private_subnet")["subnets"][0]["cidr"]
|
||||
floating_ip_cidr = "{}/32".format(
|
||||
neutron_client.list_floatingips()
|
||||
["floatingips"][0]["floating_ip_address"])
|
||||
|
||||
# This test may run immediately after configuration. It may take time for
|
||||
# routes to propogate via BGP. Do a binary backoff.
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
|
||||
reraise=True, stop=tenacity.stop_after_attempt(8))
|
||||
def _assert_cidr_in_peer_routing_table(peer_unit, cidr):
|
||||
logging.debug("Checking for {} on BGP peer {}"
|
||||
.format(cidr, peer_unit))
|
||||
# Run show ip route bgp on BGP peer
|
||||
routes = _local_utils.remote_run(
|
||||
peer_unit, remote_cmd='vtysh -c "show ip route bgp"')
|
||||
logging.debug(routes)
|
||||
assert cidr in routes, (
|
||||
"CIDR, {}, not found in BGP peer's routing table" .format(cidr))
|
||||
|
||||
_assert_cidr_in_peer_routing_table(peer_unit, private_cidr)
|
||||
logging.info("Private subnet CIDR, {}, found in routing table"
|
||||
.format(private_cidr))
|
||||
_assert_cidr_in_peer_routing_table(peer_unit, floating_ip_cidr)
|
||||
logging.info("Floating IP CIDR, {}, found in routing table"
|
||||
.format(floating_ip_cidr))
|
||||
|
||||
|
||||
def run_from_cli():
|
||||
"""Run test for BGP routes from CLI
|
||||
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
_local_utils.setup_logging()
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--peer-application", "-a",
|
||||
help="BGP Peer application name. Default: quagga",
|
||||
default="quagga")
|
||||
options = parser.parse_args()
|
||||
|
||||
peer_application_name = _local_utils.parse_arg(options,
|
||||
"peer_application")
|
||||
|
||||
test_bgp_routes(peer_application_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_from_cli())
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
|
||||
from zaza.utilities import _local_utils
|
||||
from zaza.charm_tests.dragent import test
|
||||
|
||||
|
||||
class DRAgentTest(unittest.TestCase):
|
||||
|
||||
BGP_PEER_APPLICATION = 'quagga'
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
_local_utils.setup_logging()
|
||||
|
||||
def test_bgp_routes(self):
|
||||
test.test_bgp_routes(peer_application_name=self.BGP_PEER_APPLICATION)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from zaza.utilities import _local_utils
|
||||
from zaza.utilities import openstack_utils
|
||||
|
||||
|
||||
EXT_NET = "ext_net"
|
||||
PRIVATE_NET = "private"
|
||||
FIP_TEST = "FIP TEST"
|
||||
|
||||
|
||||
def setup_bgp_speaker(peer_application_name, keystone_session=None):
|
||||
"""Setup BGP Speaker
|
||||
|
||||
:param peer_application_name: String name of BGP peer application
|
||||
:type peer_application_name: string
|
||||
:param keystone_session: Keystone session object for overcloud
|
||||
:type keystone_session: keystoneauth1.session.Session object
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
# If a session has not been provided, acquire one
|
||||
if not keystone_session:
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
|
||||
# Get authenticated clients
|
||||
neutron_client = openstack_utils.get_neutron_session_client(
|
||||
keystone_session)
|
||||
|
||||
# Create BGP speaker
|
||||
logging.info("Setting up BGP speaker")
|
||||
bgp_speaker = openstack_utils.create_bgp_speaker(
|
||||
neutron_client, local_as=12345)
|
||||
|
||||
# Add networks to bgp speaker
|
||||
logging.info("Advertising BGP routes")
|
||||
openstack_utils.add_network_to_bgp_speaker(
|
||||
neutron_client, bgp_speaker, EXT_NET)
|
||||
openstack_utils.add_network_to_bgp_speaker(
|
||||
neutron_client, bgp_speaker, PRIVATE_NET)
|
||||
logging.debug("Advertised routes: {}"
|
||||
.format(
|
||||
neutron_client.list_route_advertised_from_bgp_speaker(
|
||||
bgp_speaker["id"])))
|
||||
|
||||
# Create peer
|
||||
logging.info("Setting up BGP peer")
|
||||
bgp_peer = openstack_utils.create_bgp_peer(neutron_client,
|
||||
peer_application_name,
|
||||
remote_as=10000)
|
||||
# Add peer to bgp speaker
|
||||
logging.info("Adding BGP peer to BGP speaker")
|
||||
openstack_utils.add_peer_to_bgp_speaker(
|
||||
neutron_client, bgp_speaker, bgp_peer)
|
||||
|
||||
# Create Floating IP to advertise
|
||||
logging.info("Creating floating IP to advertise")
|
||||
port = openstack_utils.create_port(neutron_client, FIP_TEST, PRIVATE_NET)
|
||||
floating_ip = openstack_utils.create_floating_ip(neutron_client, EXT_NET,
|
||||
port=port)
|
||||
logging.info(
|
||||
"Advertised floating IP: {}".format(
|
||||
floating_ip["floating_ip_address"]))
|
||||
|
||||
|
||||
def run_from_cli():
|
||||
"""Run BGP Speaker setup from CLI
|
||||
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
_local_utils.setup_logging()
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--peer-application", "-a",
|
||||
help="BGP peer application name. Default: quagga",
|
||||
default="quagga")
|
||||
|
||||
options = parser.parse_args()
|
||||
peer_application_name = _local_utils.parse_arg(options,
|
||||
"peer_application")
|
||||
|
||||
setup_bgp_speaker(peer_application_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_from_cli())
|
||||
Executable
+254
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from zaza.utilities import _local_utils
|
||||
from zaza.utilities import openstack_utils
|
||||
|
||||
"""Configure network
|
||||
|
||||
For these network configuration functions there are two distinct sets of
|
||||
settings. There is the configuration of the overcloud's network, the network
|
||||
under test, settings which may vary from test to test. Then there is the
|
||||
configuration of the substrate specific undercloud which may vary from one test
|
||||
environment to another. All of this information is required to setup a valid
|
||||
test. For the purposes of using these functions please consider the following:
|
||||
|
||||
The overcloud network configuration settings are declared by the test caller.
|
||||
These are the network configuration settings under test and they may range from
|
||||
a simple GRE setup, to a DVR configuration, to subnetpools with address scopes.
|
||||
Each test caller may declare a slightly different set of configuration
|
||||
settings. Here is a simple GRE example:
|
||||
|
||||
EXAMPLE_OVERCLOUD_NETWORK_CONFIG = {
|
||||
"network_type": "gre",
|
||||
"router_name": "provider-router",
|
||||
"private_net_cidr": "192.168.21.0/24",
|
||||
"external_net_name": "ext_net",
|
||||
"external_subnet_name": "ext_net_subnet",
|
||||
}
|
||||
|
||||
The undercloud network configuration settings are substrate specific to the
|
||||
environment where the tests are being executed. They primarily focus on the
|
||||
provider network settings. These settings may be overridden by environment
|
||||
variables. See the doc string documentation for
|
||||
zaza.utilities._local_utils.get_overcloud_env_vars for the environment
|
||||
variables required to be exported and available to zaza. Here is an example of
|
||||
undercloud settings:
|
||||
EXAMPLE_DEFAULT_UNDERCLOUD_NETWORK_CONFIG = {
|
||||
"start_floating_ip": "10.5.150.0",
|
||||
"end_floating_ip": "10.5.150.254",
|
||||
"external_dns": "10.5.0.2",
|
||||
"external_net_cidr": "10.5.0.0/16",
|
||||
"default_gateway": "10.5.0.1",
|
||||
}
|
||||
|
||||
The network configuration functions take in a dictionary parameter called
|
||||
network_config and it is a combination of the above including environmental
|
||||
overrides. The recommended use case is as follows:
|
||||
|
||||
As a python module:
|
||||
|
||||
import zaza
|
||||
# Build network configuration settings
|
||||
network_config = {}
|
||||
# Declared overcloud settings for the network under test
|
||||
network_config.update(EXAMPLE_OVERCLOUD_NETWORK_CONFIG)
|
||||
# Default undercloud settings
|
||||
network_config.update(EXAMPLE_DEFAULT_UNDERCLOUD_NETWORK_CONFIG)
|
||||
# Environment specific settings
|
||||
network_config.update(
|
||||
zaza.utilities._local_utils.get_undercloud_env_vars())
|
||||
|
||||
# Configure the SDN network
|
||||
zaza.configure.network.setup_sdn(network_config)
|
||||
|
||||
|
||||
As a script from CLI with a YAML file of configuration:
|
||||
|
||||
./network toploogy_name -f network.yaml
|
||||
"""
|
||||
|
||||
|
||||
def setup_sdn(network_config, keystone_session=None):
|
||||
"""Setup Software Defined Network
|
||||
|
||||
:param network_config: Network configuration settings dictionary
|
||||
:type network_config: dict
|
||||
:param keystone_session: Keystone session object for overcloud
|
||||
:type keystone_session: keystoneauth1.session.Session object
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
# If a session has not been provided, acquire one
|
||||
if not keystone_session:
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
|
||||
# Get authenticated clients
|
||||
keystone_client = openstack_utils.get_keystone_session_client(
|
||||
keystone_session)
|
||||
neutron_client = openstack_utils.get_neutron_session_client(
|
||||
keystone_session)
|
||||
|
||||
# Resolve the project name from the overcloud openrc into a project id
|
||||
project_id = openstack_utils.get_project_id(
|
||||
keystone_client,
|
||||
"admin",
|
||||
)
|
||||
# Network Setup
|
||||
subnetpools = False
|
||||
if network_config.get("subnetpool_prefix"):
|
||||
subnetpools = True
|
||||
|
||||
logging.info("Configuring overcloud network")
|
||||
# Create the external network
|
||||
ext_network = openstack_utils.create_external_network(
|
||||
neutron_client,
|
||||
project_id,
|
||||
network_config.get("dvr_enabled", False),
|
||||
network_config["external_net_name"])
|
||||
openstack_utils.create_external_subnet(
|
||||
neutron_client,
|
||||
project_id,
|
||||
ext_network,
|
||||
network_config["default_gateway"],
|
||||
network_config["external_net_cidr"],
|
||||
network_config["start_floating_ip"],
|
||||
network_config["end_floating_ip"],
|
||||
network_config["external_subnet_name"])
|
||||
provider_router = (
|
||||
openstack_utils.create_provider_router(neutron_client, project_id))
|
||||
openstack_utils.plug_extnet_into_router(
|
||||
neutron_client,
|
||||
provider_router,
|
||||
ext_network)
|
||||
ip_version = network_config.get("ip_version") or 4
|
||||
subnetpool = None
|
||||
if subnetpools:
|
||||
address_scope = openstack_utils.create_address_scope(
|
||||
neutron_client,
|
||||
project_id,
|
||||
network_config.get("address_scope"),
|
||||
ip_version=ip_version)
|
||||
subnetpool = openstack_utils.create_subnetpool(
|
||||
neutron_client,
|
||||
project_id,
|
||||
network_config.get("subnetpool_name"),
|
||||
network_config.get("subnetpool_prefix"),
|
||||
address_scope)
|
||||
project_network = openstack_utils.create_project_network(
|
||||
neutron_client,
|
||||
project_id,
|
||||
shared=False,
|
||||
network_type=network_config["network_type"])
|
||||
project_subnet = openstack_utils.create_project_subnet(
|
||||
neutron_client,
|
||||
project_id,
|
||||
project_network,
|
||||
network_config.get("private_net_cidr"),
|
||||
subnetpool=subnetpool,
|
||||
ip_version=ip_version)
|
||||
openstack_utils.update_subnet_dns(
|
||||
neutron_client,
|
||||
project_subnet,
|
||||
network_config["external_dns"])
|
||||
openstack_utils.plug_subnet_into_router(
|
||||
neutron_client,
|
||||
network_config["router_name"],
|
||||
project_network,
|
||||
project_subnet)
|
||||
openstack_utils.add_neutron_secgroup_rules(neutron_client, project_id)
|
||||
|
||||
|
||||
def setup_gateway_ext_port(network_config, keystone_session=None):
|
||||
"""Setup external port on Neutron Gateway.
|
||||
For OpenStack on OpenStack scenarios
|
||||
|
||||
:param network_config: Network configuration dictionary
|
||||
:type network_config: dict
|
||||
:param keystone_session: Keystone session object for undercloud
|
||||
:type keystone_session: keystoneauth1.session.Session object
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
# If a session has not been provided, acquire one
|
||||
if not keystone_session:
|
||||
keystone_session = openstack_utils.get_undercloud_keystone_session()
|
||||
|
||||
# Get authenticated clients
|
||||
nova_client = openstack_utils.get_nova_session_client(keystone_session)
|
||||
neutron_client = openstack_utils.get_neutron_session_client(
|
||||
keystone_session)
|
||||
|
||||
# Add an interface to the neutron-gateway units and tell juju to use it
|
||||
# as the external port.
|
||||
if "net_id" in network_config.keys():
|
||||
net_id = network_config["net_id"]
|
||||
else:
|
||||
net_id = None
|
||||
|
||||
logging.info("Configuring network for OpenStack undercloud/provider")
|
||||
openstack_utils.configure_gateway_ext_port(
|
||||
nova_client,
|
||||
neutron_client,
|
||||
dvr_mode=network_config.get("dvr_enabled", False),
|
||||
net_id=net_id)
|
||||
|
||||
|
||||
def run_from_cli():
|
||||
"""Run network configurations from CLI
|
||||
|
||||
Use a YAML file of network configuration settings to configure the
|
||||
overcloud network. YAML file of the form:
|
||||
|
||||
topology_name:
|
||||
network_type: gre
|
||||
router_name: provider-router
|
||||
private_net_cidr: 192.168.21.0/24
|
||||
external_dns: 10.5.0.2
|
||||
external_net_cidr: 10.5.0.0/16
|
||||
external_net_name: ext_net
|
||||
external_subnet_name: ext_net_subnet
|
||||
default_gateway: 10.5.0.1
|
||||
start_floating_ip: 10.5.150.0
|
||||
end_floating_ip: 10.5.200.254
|
||||
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
_local_utils.setup_logging()
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("net_topology",
|
||||
help="network topology type, default is GRE",
|
||||
default="gre", nargs="?")
|
||||
parser.add_argument("--ignore_env_vars", "-i",
|
||||
help="do not override using environment variables",
|
||||
action="store_true",
|
||||
default=False)
|
||||
parser.add_argument("--net_topology_file", "-f",
|
||||
help="Network topology file location",
|
||||
default="network.yaml")
|
||||
# Handle CLI options
|
||||
options = parser.parse_args()
|
||||
net_topology = _local_utils.parse_arg(options, "net_topology")
|
||||
net_topology_file = _local_utils.parse_arg(options, "net_topology_file")
|
||||
ignore_env_vars = _local_utils.parse_arg(options, "ignore_env_vars")
|
||||
|
||||
logging.info("Setting up %s network" % (net_topology))
|
||||
network_config = _local_utils.get_network_config(
|
||||
net_topology, ignore_env_vars, net_topology_file)
|
||||
|
||||
# Handle network for Openstack-on-Openstack scenarios
|
||||
if _local_utils.get_provider_type() == "openstack":
|
||||
setup_gateway_ext_port(network_config)
|
||||
|
||||
setup_sdn(network_config)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_from_cli())
|
||||
@@ -18,65 +18,64 @@ from zaza import model
|
||||
from zaza.charm_lifecycle import utils as lifecycle_utils
|
||||
|
||||
|
||||
# XXX Tech Debt Begins Here
|
||||
def get_undercloud_env_vars():
|
||||
""" Get environment specific undercloud network configuration settings from
|
||||
environment variables.
|
||||
|
||||
def get_network_env_vars():
|
||||
"""Get environment variables with names which are consistent with
|
||||
network.yaml keys; Also get network environment variables as commonly
|
||||
used by openstack-charm-testing and ubuntu-openstack-ci automation.
|
||||
Return a dictionary compatible with openstack-mojo-specs network.yaml
|
||||
key structure.
|
||||
For each testing substrate, specific undercloud network configuration
|
||||
settings should be exported into the environment to enable testing on that
|
||||
substrate.
|
||||
|
||||
Note: *Overcloud* settings should be declared by the test caller and should
|
||||
not be overridden here.
|
||||
|
||||
Return a dictionary compatible with zaza.configure.network functions'
|
||||
expected key structure.
|
||||
|
||||
Example exported environment variables:
|
||||
export default_gateway="172.17.107.1"
|
||||
export external_net_cidr="172.17.107.0/24"
|
||||
export external_dns="10.5.0.2"
|
||||
export start_floating_ip="172.17.107.200"
|
||||
export end_floating_ip="172.17.107.249"
|
||||
|
||||
Example o-c-t & uosci non-standard environment variables:
|
||||
export NET_ID="a705dd0f-5571-4818-8c30-4132cc494668"
|
||||
export GATEWAY="172.17.107.1"
|
||||
export CIDR_EXT="172.17.107.0/24"
|
||||
export NAMESERVER="10.5.0.2"
|
||||
export FIP_RANGE="172.17.107.200:172.17.107.249"
|
||||
|
||||
:returns: Network environment variables
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
# Example o-c-t & uosci environment variables:
|
||||
# NET_ID="a705dd0f-5571-4818-8c30-4132cc494668"
|
||||
# GATEWAY="172.17.107.1"
|
||||
# CIDR_EXT="172.17.107.0/24"
|
||||
# CIDR_PRIV="192.168.121.0/24"
|
||||
# NAMESERVER="10.5.0.2"
|
||||
# FIP_RANGE="172.17.107.200:172.17.107.249"
|
||||
# AMULET_OS_VIP00="172.17.107.250"
|
||||
# AMULET_OS_VIP01="172.17.107.251"
|
||||
# AMULET_OS_VIP02="172.17.107.252"
|
||||
# AMULET_OS_VIP03="172.17.107.253"
|
||||
# Handle backward compatibile OSCI enviornment variables
|
||||
_vars = {}
|
||||
_vars['net_id'] = os.environ.get('NET_ID')
|
||||
_vars['external_dns'] = os.environ.get('NAMESERVER')
|
||||
_vars['default_gateway'] = os.environ.get('GATEWAY')
|
||||
_vars['external_net_cidr'] = os.environ.get('CIDR_EXT')
|
||||
_vars['private_net_cidr'] = os.environ.get('CIDR_PRIV')
|
||||
|
||||
# Take FIP_RANGE and create start and end floating ips
|
||||
_fip_range = os.environ.get('FIP_RANGE')
|
||||
if _fip_range and ':' in _fip_range:
|
||||
_vars['start_floating_ip'] = os.environ.get('FIP_RANGE').split(':')[0]
|
||||
_vars['end_floating_ip'] = os.environ.get('FIP_RANGE').split(':')[1]
|
||||
|
||||
_vips = [os.environ.get('AMULET_OS_VIP00'),
|
||||
os.environ.get('AMULET_OS_VIP01'),
|
||||
os.environ.get('AMULET_OS_VIP02'),
|
||||
os.environ.get('AMULET_OS_VIP03')]
|
||||
|
||||
# Env var naming consistent with network.yaml takes priority
|
||||
_keys = ['default_gateway'
|
||||
# Env var naming consistent with zaza.configure.network functions takes
|
||||
# priority. Override backward compatible settings.
|
||||
_keys = ['default_gateway',
|
||||
'start_floating_ip',
|
||||
'end_floating_ip',
|
||||
'external_dns',
|
||||
'external_net_cidr',
|
||||
'external_net_name',
|
||||
'external_subnet_name',
|
||||
'network_type',
|
||||
'private_net_cidr',
|
||||
'router_name']
|
||||
'external_net_cidr']
|
||||
for _key in _keys:
|
||||
_val = os.environ.get(_key)
|
||||
if _val:
|
||||
_vars[_key] = _val
|
||||
|
||||
# Remove keys and items with a None value
|
||||
_vars['vips'] = [_f for _f in _vips if _f]
|
||||
for k, v in list(_vars.items()):
|
||||
if not v:
|
||||
del _vars[k]
|
||||
@@ -112,9 +111,13 @@ def get_yaml_config(config_file):
|
||||
return yaml.load(open(config_file, 'r').read())
|
||||
|
||||
|
||||
def get_net_info(net_topology, ignore_env_vars=False):
|
||||
def get_net_info(net_topology, ignore_env_vars=False,
|
||||
net_topology_file="network.yaml"):
|
||||
"""Get network info from network.yaml, override the values if specific
|
||||
environment variables are set.
|
||||
environment variables are set for the undercloud.
|
||||
|
||||
This function may be used when running network configuration from CLI to
|
||||
pass in network configuration settings from a YAML file.
|
||||
|
||||
:param net_topology: Network topology name from network.yaml
|
||||
:type net_topology: string
|
||||
@@ -124,13 +127,18 @@ def get_net_info(net_topology, ignore_env_vars=False):
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
net_info = get_yaml_config('network.yaml')[net_topology]
|
||||
if os.path.exists(net_topology_file):
|
||||
net_info = get_yaml_config(net_topology_file)[net_topology]
|
||||
else:
|
||||
raise Exception("Network topology file: {} not found."
|
||||
.format(net_topology_file))
|
||||
|
||||
if not ignore_env_vars:
|
||||
logging.info('Consuming network environment variables as overrides.')
|
||||
net_info.update(get_network_env_vars())
|
||||
logging.info("Consuming network environment variables as overrides "
|
||||
"for the undercloud.")
|
||||
net_info.update(get_undercloud_env_vars())
|
||||
|
||||
logging.info('Network info: {}'.format(dict_to_yaml(net_info)))
|
||||
logging.info("Network info: {}".format(dict_to_yaml(net_info)))
|
||||
return net_info
|
||||
|
||||
|
||||
@@ -173,8 +181,8 @@ def remote_run(unit, remote_cmd, timeout=None, fatal=None):
|
||||
"""
|
||||
if fatal is None:
|
||||
fatal = True
|
||||
result = model.run_on_unit(unit,
|
||||
lifecycle_utils.get_juju_model(),
|
||||
result = model.run_on_unit(lifecycle_utils.get_juju_model(),
|
||||
unit,
|
||||
remote_cmd,
|
||||
timeout=timeout)
|
||||
if result:
|
||||
|
||||
@@ -139,6 +139,23 @@ def get_neutron_session_client(session):
|
||||
return neutronclient.Client(session=session)
|
||||
|
||||
|
||||
def get_keystone_scope():
|
||||
"""Return Keystone scope based on OpenStack release
|
||||
|
||||
:returns: String keystone scope
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
os_version = get_current_os_versions("keystone")["keystone"]
|
||||
# Keystone policy.json shipped the charm with liberty requires a domain
|
||||
# scoped token. Bug #1649106
|
||||
if os_version == "liberty":
|
||||
scope = "DOMAIN"
|
||||
else:
|
||||
scope = "PROJECT"
|
||||
return scope
|
||||
|
||||
|
||||
def get_keystone_session(opentackrc_creds, insecure=True, scope='PROJECT'):
|
||||
"""Return keystone session
|
||||
|
||||
@@ -160,6 +177,28 @@ def get_keystone_session(opentackrc_creds, insecure=True, scope='PROJECT'):
|
||||
return session.Session(auth=auth, verify=not insecure)
|
||||
|
||||
|
||||
def get_overcloud_keystone_session():
|
||||
"""Return Over cloud keystone session
|
||||
|
||||
:returns keystone_session: keystoneauth1.session.Session object
|
||||
:rtype: keystoneauth1.session.Session
|
||||
"""
|
||||
|
||||
return get_keystone_session(get_overcloud_auth(),
|
||||
scope=get_keystone_scope())
|
||||
|
||||
|
||||
def get_undercloud_keystone_session():
|
||||
"""Return Under cloud keystone session
|
||||
|
||||
:returns keystone_session: keystoneauth1.session.Session object
|
||||
:rtype: keystoneauth1.session.Session
|
||||
"""
|
||||
|
||||
return get_keystone_session(get_undercloud_auth(),
|
||||
scope=get_keystone_scope())
|
||||
|
||||
|
||||
def get_keystone_session_client(session):
|
||||
"""Return keystoneclient authenticated by keystone session
|
||||
|
||||
@@ -362,10 +401,6 @@ def configure_gateway_ext_port(novaclient, neutronclient,
|
||||
else:
|
||||
application_name = 'neutron-gateway'
|
||||
|
||||
# XXX Trying to track down a failure with juju run neutron-gateway/0 in
|
||||
# the post juju_set check. Try a sleep here to see if some network
|
||||
# reconfigureing on the gateway is still in progress and that's
|
||||
# causing the issue
|
||||
if ext_br_macs:
|
||||
logging.info('Setting {} on {} external port to {}'.format(
|
||||
config_key, application_name, ext_br_macs_str))
|
||||
@@ -377,7 +412,7 @@ def configure_gateway_ext_port(novaclient, neutronclient,
|
||||
model.set_application_config(
|
||||
lifecycle_utils.get_juju_model(), application_name,
|
||||
configuration={config_key: ext_br_macs_str})
|
||||
juju_wait.wait()
|
||||
juju_wait.wait(wait_for_workload=True)
|
||||
|
||||
|
||||
def create_project_network(neutron_client, project_id, net_name='private',
|
||||
@@ -444,10 +479,9 @@ def create_external_network(neutron_client, project_id, dvr_mode,
|
||||
'name': net_name,
|
||||
'router:external': True,
|
||||
'tenant_id': project_id,
|
||||
'provider:physical_network': 'physnet1',
|
||||
'provider:network_type': 'flat',
|
||||
}
|
||||
if not deprecated_external_networking(dvr_mode):
|
||||
network_msg['provider:physical_network'] = 'physnet1'
|
||||
network_msg['provider:network_type'] = 'flat'
|
||||
|
||||
logging.info('Creating new external network definition: %s',
|
||||
net_name)
|
||||
|
||||
Reference in New Issue
Block a user