diff --git a/unit_tests/utilitites/test_zaza_utilitites_local_utils.py b/unit_tests/utilitites/test_zaza_utilitites_local_utils.py index 742bdd4..24152a3 100644 --- a/unit_tests/utilitites/test_zaza_utilitites_local_utils.py +++ b/unit_tests/utilitites/test_zaza_utilitites_local_utils.py @@ -23,31 +23,43 @@ class TestLocalUtils(ut_utils.BaseTestCase): _yaml_dict) self._open.assert_called_once_with(_filename, "r") - def test_get_network_env_vars(self): + def test_get_undercloud_env_vars(self): self.patch_object(_local_utils.os.environ, "get") - _env = {"NET_ID": "netid", - "NAMESERVER": "10.0.0.10", - "GATEWAY": "10.0.0.1", - "CIDR_EXT": "10.0.0.0/24", - "CIDR_PRIV": "192.168.0.0/24"} - _result = {} - _result["net_id"] = _env["NET_ID"] - _result["external_dns"] = _env["NAMESERVER"] - _result["default_gateway"] = _env["GATEWAY"] - _result["external_net_cidr"] = _env["CIDR_EXT"] - _result["private_net_cidr"] = _env["CIDR_PRIV"] def _get_env(key): return _env.get(key) self.get.side_effect = _get_env - self.assertEqual(_local_utils.get_network_env_vars(), - _result) + # 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_network_env_vars") + self.patch_object(_local_utils, "get_undercloud_env_vars") net_topology = "topo" _data = {net_topology: {"network": "DATA"}} self.get_yaml_config.return_value = _data @@ -63,14 +75,14 @@ class TestLocalUtils(ut_utils.BaseTestCase): _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_network_env_vars.assert_not_called() + self.get_undercloud_env_vars.assert_not_called() # Update with environmental variables _more_data = {"network": "NEW", "other": "DATA"} - self.get_network_env_vars.return_value = _more_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_network_env_vars.assert_called_once_with() + self.get_undercloud_env_vars.assert_called_once_with() diff --git a/unit_tests/utilitites/test_zaza_utilitites_openstack_utils.py b/unit_tests/utilitites/test_zaza_utilitites_openstack_utils.py index 7434b0e..b3b9989 100644 --- a/unit_tests/utilitites/test_zaza_utilitites_openstack_utils.py +++ b/unit_tests/utilitites/test_zaza_utilitites_openstack_utils.py @@ -11,6 +11,8 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): 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, @@ -30,6 +32,18 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): "tenant_id": self.project_id}} self.address_scopes = { "address_scopes": [self.address_scope["address_scope"]]} + + 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 self.neutronclient.create_port.return_value = self.port @@ -41,8 +55,9 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): self.address_scopes) self.neutronclient.create_address_scope.return_value = ( self.address_scope) - self.ext_net = "ext_net" - self.private_net = "private_net" + + 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") @@ -102,6 +117,27 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): 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") diff --git a/zaza/charm_tests/dragent/configure.py b/zaza/charm_tests/dragent/configure.py new file mode 100644 index 0000000..ac53cdb --- /dev/null +++ b/zaza/charm_tests/dragent/configure.py @@ -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) diff --git a/zaza/charm_tests/dragent/configure_dragent.py b/zaza/charm_tests/dragent/configure_dragent.py deleted file mode 100644 index f22df00..0000000 --- a/zaza/charm_tests/dragent/configure_dragent.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 - -import os - -from zaza.configure import ( - network, - bgp_speaker, -) -from zaza.utilities import ( - _local_utils, - openstack_utils, -) - -DEFAULT_PEER_APPLICATION_NAME = "quagga" -# Find a default network.yaml in tests/bundles/network.yaml of the charm -DEFAULT_TOPOLOGY_FILE = os.path.join("tests", "bundles", "network.yaml") - - -def setup(net_topology="pool", net_topology_file="network.yaml"): - """Setup BGP networking - - :param net_topology: String name of network topology - :type net_topology: string - :param net_info: Network configuration dictionary - :type net_info: dict - :param keystone_session: Keystone session object for overcloud - :type keystone_session: keystoneauth1.session.Session object - :returns: None - :rtype: None - """ - - _local_utils.setup_logging() - - # Check for the network.yaml configuration - if (not os.path.exists(net_topology_file) and - os.path.exists(DEFAULT_TOPOLOGY_FILE)): - net_topology_file = DEFAULT_TOPOLOGY_FILE - - # Get network configuration from YAML and/or environment variables - net_info = _local_utils.get_net_info(net_topology, - net_topology_file=net_topology_file) - - # Get keystone session - keystone_session = openstack_utils.get_overcloud_keystone_session() - - # Confugre the network - network.setup_sdn( - net_topology, net_info, keystone_session=keystone_session) - # Configure BGP - bgp_speaker.setup_bgp_speaker( - peer_application_name=DEFAULT_PEER_APPLICATION_NAME, - keystone_session=keystone_session) diff --git a/zaza/charm_tests/dragent/test_dragent.py b/zaza/charm_tests/dragent/test.py similarity index 100% rename from zaza/charm_tests/dragent/test_dragent.py rename to zaza/charm_tests/dragent/test.py diff --git a/zaza/charm_tests/dragent/tests.py b/zaza/charm_tests/dragent/tests.py index baffcf8..46b3aa3 100644 --- a/zaza/charm_tests/dragent/tests.py +++ b/zaza/charm_tests/dragent/tests.py @@ -3,17 +3,19 @@ import unittest from zaza.utilities import _local_utils -from zaza.charm_tests.dragent import test_dragent +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_dragent.test_bgp_routes(peer_application_name="quagga") + test.test_bgp_routes(peer_application_name=self.BGP_PEER_APPLICATION) if __name__ == "__main__": diff --git a/zaza/configure/network.py b/zaza/configure/network.py index 8009955..5105938 100755 --- a/zaza/configure/network.py +++ b/zaza/configure/network.py @@ -7,14 +7,76 @@ import sys from zaza.utilities import _local_utils from zaza.utilities import openstack_utils +"""Configure network -def setup_sdn(net_topology, net_info, keystone_session=None): +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 net_topology: String name of network topology - :type net_topology: string - :param net_info: Network configuration dictionary - :type net_info: dict + :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 @@ -31,14 +93,14 @@ def setup_sdn(net_topology, net_info, keystone_session=None): neutron_client = openstack_utils.get_neutron_session_client( keystone_session) - # Resolve the project name from the overcloud opentackrc into a project id + # 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 net_info.get("subnetpool_prefix"): + if network_config.get("subnetpool_prefix"): subnetpools = True logging.info("Configuring overcloud network") @@ -46,68 +108,67 @@ def setup_sdn(net_topology, net_info, keystone_session=None): ext_network = openstack_utils.create_external_network( neutron_client, project_id, - net_info.get("dvr_enabled", False), - net_info["external_net_name"]) + network_config.get("dvr_enabled", False), + network_config["external_net_name"]) openstack_utils.create_external_subnet( neutron_client, project_id, ext_network, - net_info["default_gateway"], - net_info["external_net_cidr"], - net_info["start_floating_ip"], - net_info["end_floating_ip"], - net_info["external_subnet_name"]) - # Should this be --enable_snat = False + 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 = net_info.get("ip_version") or 4 + ip_version = network_config.get("ip_version") or 4 subnetpool = None if subnetpools: address_scope = openstack_utils.create_address_scope( neutron_client, project_id, - net_info.get("address_scope"), + network_config.get("address_scope"), ip_version=ip_version) subnetpool = openstack_utils.create_subnetpool( neutron_client, project_id, - net_info.get("subnetpool_name"), - net_info.get("subnetpool_prefix"), + 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=net_info["network_type"]) + network_type=network_config["network_type"]) project_subnet = openstack_utils.create_project_subnet( neutron_client, project_id, project_network, - net_info.get("private_net_cidr"), + network_config.get("private_net_cidr"), subnetpool=subnetpool, ip_version=ip_version) openstack_utils.update_subnet_dns( neutron_client, project_subnet, - net_info["external_dns"]) + network_config["external_dns"]) openstack_utils.plug_subnet_into_router( neutron_client, - net_info["router_name"], + network_config["router_name"], project_network, project_subnet) openstack_utils.add_neutron_secgroup_rules(neutron_client, project_id) -def setup_gateway_ext_port(net_info, keystone_session=None): +def setup_gateway_ext_port(network_config, keystone_session=None): """Setup external port on Neutron Gateway. For OpenStack on OpenStack scenarios - :param net_info: Network configuration dictionary - :type net_info: dict + :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 @@ -125,8 +186,8 @@ def setup_gateway_ext_port(net_info, keystone_session=None): # Add an interface to the neutron-gateway units and tell juju to use it # as the external port. - if "net_id" in net_info.keys(): - net_id = net_info["net_id"] + if "net_id" in network_config.keys(): + net_id = network_config["net_id"] else: net_id = None @@ -134,13 +195,28 @@ def setup_gateway_ext_port(net_info, keystone_session=None): openstack_utils.configure_gateway_ext_port( nova_client, neutron_client, - dvr_mode=net_info.get("dvr_enabled", False), + 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 """ @@ -164,14 +240,14 @@ def run_from_cli(): ignore_env_vars = _local_utils.parse_arg(options, "ignore_env_vars") logging.info("Setting up %s network" % (net_topology)) - net_info = _local_utils.get_net_info( + 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(net_info) + setup_gateway_ext_port(network_config) - setup_sdn(net_topology, net_info) + setup_sdn(network_config) if __name__ == "__main__": diff --git a/zaza/utilities/_local_utils.py b/zaza/utilities/_local_utils.py index 77ac5f3..82b9c82 100644 --- a/zaza/utilities/_local_utils.py +++ b/zaza/utilities/_local_utils.py @@ -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] @@ -115,7 +114,10 @@ def get_yaml_config(config_file): 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 @@ -132,8 +134,9 @@ def get_net_info(net_topology, ignore_env_vars=False, .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))) return net_info diff --git a/zaza/utilities/openstack_utils.py b/zaza/utilities/openstack_utils.py index 1691f92..5d6748f 100644 --- a/zaza/utilities/openstack_utils.py +++ b/zaza/utilities/openstack_utils.py @@ -401,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))