diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py index 21f32537da0..7f3aad6af59 100644 --- a/neutron/agent/ovn/metadata/agent.py +++ b/neutron/agent/ovn/metadata/agent.py @@ -19,6 +19,7 @@ import re import threading import uuid +import netaddr from neutron_lib import constants as n_const from oslo_concurrency import lockutils from oslo_config import cfg @@ -50,7 +51,8 @@ MAC_PATTERN = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I) OVN_VIF_PORT_TYPES = ("", "external", ovn_const.LSP_TYPE_LOCALPORT, ) MetadataPortInfo = collections.namedtuple('MetadataPortInfo', ['mac', - 'ip_addresses']) + 'ip_addresses', + 'logical_port']) OVN_METADATA_UUID_NAMESPACE = uuid.UUID('d34bf9f6-da32-4871-9af8-15a4626b41ab') @@ -105,7 +107,7 @@ class PortBindingChassisEvent(row_event.RowEvent): net_name = ovn_utils.get_network_name_from_datapath( row.datapath) LOG.info(self.LOG_MSG, row.logical_port, net_name) - self.agent.update_datapath(str(row.datapath.uuid), net_name) + self.agent.provision_datapath(row.datapath) except ConfigException: # We're now in the reader lock mode, we need to exit the # context and then use writer lock @@ -365,14 +367,12 @@ class MetadataAgent(object): "br-int instead.") return 'br-int' - def get_networks(self): - """Return a map of relevant datapath UUIDs to Neutron network names.""" + def get_networks_datapaths(self): + """Return a set of datapath objects of the VIF ports on the current + chassis. + """ ports = self.sb_idl.get_ports_on_chassis(self.chassis) - return { - str(p.datapath.uuid): ovn_utils.get_network_name_from_datapath( - p.datapath) - for p in self._vif_ports(ports) - } + return set(p.datapath for p in self._vif_ports(ports)) @_sync_lock def sync(self): @@ -387,10 +387,10 @@ class MetadataAgent(object): system_namespaces = tuple( ns.decode('utf-8') if isinstance(ns, bytes) else ns for ns in ip_lib.list_network_namespaces()) - nets = self.get_networks() + net_datapaths = self.get_networks_datapaths() metadata_namespaces = [ - self._get_namespace_name(net) - for net in nets.values() + self._get_namespace_name(str(datapath.uuid)) + for datapath in net_datapaths ] unused_namespaces = [ns for ns in system_namespaces if ns.startswith(NS_PREFIX) and @@ -400,8 +400,8 @@ class MetadataAgent(object): # now that all obsolete namespaces are cleaned up, deploy required # networks - for datapath, net_name in nets.items(): - self.provision_datapath(datapath, net_name) + for datapath in net_datapaths: + self.provision_datapath(datapath) @staticmethod def _get_veth_name(datapath): @@ -444,25 +444,6 @@ class MetadataAgent(object): ip.garbage_collect_namespace() - def update_datapath(self, datapath, net_name): - """Update the metadata service for this datapath. - - This function will: - * Provision the namespace if it wasn't already in place. - * Update the namespace if it was already serving metadata (for example, - after binding/unbinding the first/last port of a subnet in our - chassis). - * Tear down the namespace if there are no more ports in our chassis - for this datapath. - """ - ports = self.sb_idl.get_ports_on_chassis(self.chassis) - datapath_ports = [p for p in self._vif_ports(ports) if - str(p.datapath.uuid) == datapath] - if datapath_ports: - self.provision_datapath(datapath, net_name) - else: - self.teardown_datapath(net_name) - def _ensure_datapath_checksum(self, namespace): """Ensure the correct checksum in the metadata packets in DPDK bridges @@ -482,22 +463,75 @@ class MetadataAgent(object): iptables_mgr.ipv4['mangle'].add_rule('POSTROUTING', rule, wrap=False) iptables_mgr.apply() - def provision_datapath(self, datapath, net_name): - """Provision the datapath so that it can serve metadata. + def _get_port_ips(self, port): + # Retrieve IPs from the port mac column which is in form + # [" ... "] + mac_field_attrs = port.mac[0].split() + ips = mac_field_attrs[1:] + if not ips: + LOG.debug("Port %s IP addresses were not retrieved from the " + "Port_Binding MAC column %s", port.uuid, mac_field_attrs) + return ips - This function will create the namespace and VETH pair if needed - and assign the IP addresses to the interface corresponding to the - metadata port of the network. It will also remove existing IP - addresses that are no longer needed. + def _active_subnets_cidrs(self, datapath_ports_ips, metadata_port_cidrs): + active_subnets_cidrs = set() + # Prepopulate a dictionary where each metadata_port_cidr(string) maps + # to its netaddr.IPNetwork object. This is so we dont have to + # reconstruct IPNetwork objects repeatedly in the for loop + metadata_cidrs_to_network_objects = { + metadata_port_cidr: netaddr.IPNetwork(metadata_port_cidr) + for metadata_port_cidr in metadata_port_cidrs + } + + for datapath_port_ip in datapath_ports_ips: + ip_obj = netaddr.IPAddress(datapath_port_ip) + for metadata_cidr, metadata_cidr_obj in \ + metadata_cidrs_to_network_objects.items(): + if ip_obj in metadata_cidr_obj: + active_subnets_cidrs.add(metadata_cidr) + break + return active_subnets_cidrs + + def _process_cidrs(self, current_namespace_cidrs, + datapath_ports_ips, metadata_port_subnet_cidrs): + active_subnets_cidrs = self._active_subnets_cidrs( + datapath_ports_ips, metadata_port_subnet_cidrs) + + cidrs_to_add = active_subnets_cidrs - current_namespace_cidrs + + if n_const.METADATA_CIDR not in current_namespace_cidrs: + cidrs_to_add.add(n_const.METADATA_CIDR) + else: + active_subnets_cidrs.add(n_const.METADATA_CIDR) + + cidrs_to_delete = current_namespace_cidrs - active_subnets_cidrs + + return cidrs_to_add, cidrs_to_delete + + def _get_provision_params(self, datapath): + """Performs datapath preprovision checks and returns paremeters + needed to provision namespace. + + Function will confirm that: + 1. Datapath metadata port has valid MAC and subnet CIDRs + 2. There are datapath port IPs + + If any of those rules are not valid the nemaspace for the + provided datapath will be tore down. + If successful, returns datapath's network name, ports IPs + and meta port info """ - LOG.info("Provisioning metadata for network %s", net_name) - port = self.sb_idl.get_metadata_port_network(datapath) + net_name = ovn_utils.get_network_name_from_datapath(datapath) + datapath_uuid = str(datapath.uuid) + + metadata_port = self.sb_idl.get_metadata_port_network(datapath_uuid) # If there's no metadata port or it doesn't have a MAC or IP # addresses, then tear the namespace down if needed. This might happen # when there are no subnets yet created so metadata port doesn't have # an IP address. - if not (port and port.mac and - port.external_ids.get(ovn_const.OVN_CIDRS_EXT_ID_KEY, None)): + if not (metadata_port and metadata_port.mac and + metadata_port.external_ids.get( + ovn_const.OVN_CIDRS_EXT_ID_KEY, None)): LOG.debug("There is no metadata port for network %s or it has no " "MAC or IP addresses configured, tearing the namespace " "down if needed", net_name) @@ -505,9 +539,7 @@ class MetadataAgent(object): return # First entry of the mac field must be the MAC address. - match = MAC_PATTERN.match(port.mac[0].split(' ')[0]) - # If it is not, we can't provision the namespace. Tear it down if - # needed and log the error. + match = MAC_PATTERN.match(metadata_port.mac[0].split(' ')[0]) if not match: LOG.error("Metadata port for network %s doesn't have a MAC " "address, tearing the namespace down if needed", @@ -517,10 +549,44 @@ class MetadataAgent(object): mac = match.group() ip_addresses = set( - port.external_ids[ovn_const.OVN_CIDRS_EXT_ID_KEY].split(' ')) - ip_addresses.add(n_const.METADATA_CIDR) - metadata_port = MetadataPortInfo(mac, ip_addresses) + metadata_port.external_ids[ + ovn_const.OVN_CIDRS_EXT_ID_KEY].split(' ')) + metadata_port_info = MetadataPortInfo(mac, ip_addresses, + metadata_port.logical_port) + chassis_ports = self.sb_idl.get_ports_on_chassis(self.chassis) + datapath_ports_ips = [] + for chassis_port in self._vif_ports(chassis_ports): + if str(chassis_port.datapath.uuid) == datapath_uuid: + datapath_ports_ips.extend(self._get_port_ips(chassis_port)) + + if not datapath_ports_ips: + LOG.debug("No valid VIF ports were found for network %s, " + "tearing the namespace down if needed", net_name) + self.teardown_datapath(net_name) + return + + return net_name, datapath_ports_ips, metadata_port_info + + def provision_datapath(self, datapath): + """Provision the datapath so that it can serve metadata. + + This function will create the namespace and VETH pair if needed + and assign the IP addresses to the interface corresponding to the + metadata port of the network. It will also remove existing IP from + the namespace if they are no longer needed. + + :param datapath: datapath object. + :return: The metadata namespace name for the datapath or None + if namespace was not provisioned + """ + + provision_params = self._get_provision_params(datapath) + if not provision_params: + return + net_name, datapath_ports_ips, metadata_port_info = provision_params + + LOG.info("Provisioning metadata for network %s", net_name) # Create the VETH pair if it's not created. Also the add_veth function # will create the namespace for us. namespace = self._get_namespace_name(net_name) @@ -545,26 +611,29 @@ class MetadataAgent(object): ip2.link.set_up() # Configure the MAC address. - ip2.link.set_address(metadata_port.mac) - dev_info = ip2.addr.list() + ip2.link.set_address(metadata_port_info.mac) - # Configure the IP addresses on the VETH pair and remove those - # that we no longer need. - current_cidrs = {dev['cidr'] for dev in dev_info} - cidrs_to_delete = list(current_cidrs - metadata_port.ip_addresses) + cidrs_to_add, cidrs_to_delete = self._process_cidrs( + {dev['cidr'] for dev in ip2.addr.list()}, + datapath_ports_ips, + metadata_port_info.ip_addresses + ) + # Delete any non active addresses from the network namespace if cidrs_to_delete: - ip2.addr.delete_multiple(cidrs_to_delete) + ip2.addr.delete_multiple(list(cidrs_to_delete)) # NOTE(dalvarez): metadata only works on IPv4. We're doing this # extra check here because it could be that the metadata port has # an IPv6 address if there's an IPv6 subnet with SLAAC in its # network. Neutron IPAM will autoallocate an IPv6 address for every # port in the network. - ipv4_cidrs = [] - for cidr in metadata_port.ip_addresses - current_cidrs: - if utils.get_ip_version(cidr) == n_const.IP_VERSION_4: - ipv4_cidrs.append(cidr) - ip2.addr.add_multiple(ipv4_cidrs) + ipv4_cidrs_to_add = [ + cidr + for cidr in cidrs_to_add + if utils.get_ip_version(cidr) == n_const.IP_VERSION_4] + + if ipv4_cidrs_to_add: + ip2.addr.add_multiple(ipv4_cidrs_to_add) # Check that this port is not attached to any other OVS bridge. This # can happen when the OVN bridge changes (for example, during a @@ -589,7 +658,8 @@ class MetadataAgent(object): veth_name[0]).execute() self.ovs_idl.db_set( 'Interface', veth_name[0], - ('external_ids', {'iface-id': port.logical_port})).execute() + ('external_ids', {'iface-id': + metadata_port_info.logical_port})).execute() # Ensure the correct checksum in the metadata traffic. self._ensure_datapath_checksum(namespace) diff --git a/neutron/tests/unit/agent/ovn/metadata/test_agent.py b/neutron/tests/unit/agent/ovn/metadata/test_agent.py index 04e857532c5..79e684c553c 100644 --- a/neutron/tests/unit/agent/ovn/metadata/test_agent.py +++ b/neutron/tests/unit/agent/ovn/metadata/test_agent.py @@ -36,7 +36,15 @@ from neutron.tests import base OvnPortInfo = collections.namedtuple( 'OvnPortInfo', ['datapath', 'type', 'mac', 'external_ids', 'logical_port']) -DatapathInfo = collections.namedtuple('DatapathInfo', ['uuid', 'external_ids']) + + +class DatapathInfo: + def __init__(self, uuid, external_ids): + self.uuid = uuid + self.external_ids = external_ids + + def __hash__(self): + return hash(self.uuid) def makePort(datapath=None, type='', mac=None, external_ids=None, @@ -93,7 +101,7 @@ class TestMetadataAgent(base.BaseTestCase): pdp.assert_has_calls( [ - mock.call(p.datapath.uuid, p.datapath.uuid) + mock.call(p.datapath) for p in self.ports ], any_order=True @@ -117,7 +125,7 @@ class TestMetadataAgent(base.BaseTestCase): pdp.assert_has_calls( [ - mock.call(p.datapath.uuid, p.datapath.uuid) + mock.call(p.datapath) for p in self.ports ], any_order=True @@ -125,50 +133,44 @@ class TestMetadataAgent(base.BaseTestCase): lnn.assert_called_once_with() tdp.assert_called_once_with('3') - def test_get_networks(self): - """Test which networks are provisioned. - + def test_get_networks_datapaths(self): + """Test get_networks_datapaths returns only datapath objects for the + networks containing vif ports of type ''(blank) and 'external'. This test simulates that this chassis has the following ports: - * datapath '0': 1 port - * datapath '1': 2 ports - * datapath '2': 1 port + * datapath '1': 1 port type '' , 1 port 'external' and + 1 port 'unknown' + * datapath '2': 1 port type '' * datapath '3': 1 port with type 'external' - * datapath '5': 1 port with type 'unknown' + * datapath '4': 1 port with type 'unknown' - It is expected that only datapaths '0', '1' and '2' are scheduled for - provisioning. + It is expected that only datapaths '1', '2' and '3' are returned """ - self.ports.append(makePort(datapath=DatapathInfo(uuid='1', - external_ids={'name': 'neutron-1'}))) - self.ports.append(makePort(datapath=DatapathInfo(uuid='3', - external_ids={'name': 'neutron-3'}), type='external')) - self.ports.append(makePort(datapath=DatapathInfo(uuid='5', - external_ids={'name': 'neutron-5'}), type='unknown')) + datapath_1 = DatapathInfo(uuid='uuid1', + external_ids={'name': 'neutron-1'}) + datapath_2 = DatapathInfo(uuid='uuid2', + external_ids={'name': 'neutron-2'}) + datapath_3 = DatapathInfo(uuid='uuid3', + external_ids={'name': 'neutron-3'}) + datapath_4 = DatapathInfo(uuid='uuid4', + external_ids={'name': 'neutron-4'}) - expected_networks = {str(i): str(i) for i in range(0, 4)} - self.assertEqual(expected_networks, self.agent.get_networks()) + ports = [ + makePort(datapath_1, type=''), + makePort(datapath_1, type='external'), + makePort(datapath_1, type='unknown'), + makePort(datapath_2, type=''), + makePort(datapath_3, type='external'), + makePort(datapath_4, type='unknown') + ] - def test_update_datapath_provision(self): - self.ports.append(makePort(datapath=DatapathInfo(uuid='3', - external_ids={'name': 'neutron-3'}), type='external')) - - with mock.patch.object(self.agent, 'provision_datapath', - return_value=None) as pdp,\ - mock.patch.object(self.agent, 'teardown_datapath') as tdp: - self.agent.update_datapath('1', 'a') - self.agent.update_datapath('3', 'b') - expected_calls = [mock.call('1', 'a'), mock.call('3', 'b')] - pdp.assert_has_calls(expected_calls) - tdp.assert_not_called() - - def test_update_datapath_teardown(self): - with mock.patch.object(self.agent, 'provision_datapath', - return_value=None) as pdp,\ - mock.patch.object(self.agent, 'teardown_datapath') as tdp: - self.agent.update_datapath('5', 'a') - tdp.assert_called_once_with('a') - pdp.assert_not_called() + with mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis', + return_value=ports): + expected_datapaths = set([datapath_1, datapath_2, datapath_3]) + self.assertSetEqual( + expected_datapaths, + self.agent.get_networks_datapaths() + ) def test_teardown_datapath(self): """Test teardown datapath. @@ -197,6 +199,174 @@ class TestMetadataAgent(base.BaseTestCase): del_veth.assert_called_once_with('veth_0') garbage_collect.assert_called_once_with() + def test__process_cidrs_when_current_namespace_empty(self): + current_namespace_cidrs = set() + datapath_port_ips = ['10.0.0.2', '10.0.0.3', '10.0.1.5'] + metadaport_subnet_cidrs = ['10.0.0.0/30', '10.0.1.0/28', '11.0.1.2/24'] + + expected_cidrs_to_add = set(['10.0.0.0/30', '10.0.1.0/28', + n_const.METADATA_CIDR]) + expected_cidrs_to_delete = set() + + actual_result = self.agent._process_cidrs(current_namespace_cidrs, + datapath_port_ips, + metadaport_subnet_cidrs) + actual_cidrs_to_add, actual_cidrs_to_delete = actual_result + + self.assertSetEqual(actual_cidrs_to_add, expected_cidrs_to_add) + self.assertSetEqual(actual_cidrs_to_delete, expected_cidrs_to_delete) + + def test__process_cidrs_when_current_namespace_only_contains_metadata_cidr( + self): + current_namespace_cidrs = set([n_const.METADATA_CIDR]) + datapath_port_ips = ['10.0.0.2', '10.0.0.3', '10.0.1.5'] + metadaport_subnet_cidrs = ['10.0.0.0/30', '10.0.1.0/28', '11.0.1.2/24'] + + expected_cidrs_to_add = set(['10.0.0.0/30', '10.0.1.0/28']) + expected_cidrs_to_delete = set() + + actual_result = self.agent._process_cidrs(current_namespace_cidrs, + datapath_port_ips, + metadaport_subnet_cidrs) + actual_cidrs_to_add, actual_cidrs_to_delete = actual_result + + self.assertSetEqual(actual_cidrs_to_add, expected_cidrs_to_add) + self.assertSetEqual(actual_cidrs_to_delete, expected_cidrs_to_delete) + + def test__process_cidrs_when_current_namespace_contains_stale_cidr(self): + current_namespace_cidrs = set([n_const.METADATA_CIDR, '10.0.1.0/31']) + datapath_port_ips = ['10.0.0.2', '10.0.0.3', '10.0.1.5'] + metadaport_subnet_cidrs = ['10.0.0.0/30', '10.0.1.0/28', '11.0.1.2/24'] + + expected_cidrs_to_add = set(['10.0.0.0/30', '10.0.1.0/28']) + expected_cidrs_to_delete = set(['10.0.1.0/31']) + + actual_result = self.agent._process_cidrs(current_namespace_cidrs, + datapath_port_ips, + metadaport_subnet_cidrs) + actual_cidrs_to_add, actual_cidrs_to_delete = actual_result + + self.assertSetEqual(actual_cidrs_to_add, expected_cidrs_to_add) + self.assertSetEqual(actual_cidrs_to_delete, expected_cidrs_to_delete) + + def test__process_cidrs_when_current_namespace_contains_mix_cidrs(self): + """Current namespace cidrs contains stale cidrs and it is missing + new required cidrs. + """ + current_namespace_cidrs = set([n_const.METADATA_CIDR, + '10.0.1.0/31', + '10.0.1.0/28']) + datapath_port_ips = ['10.0.0.2', '10.0.1.5'] + metadaport_subnet_cidrs = ['10.0.0.0/30', '10.0.1.0/28', '11.0.1.2/24'] + + expected_cidrs_to_add = set(['10.0.0.0/30']) + expected_cidrs_to_delete = set(['10.0.1.0/31']) + + actual_result = self.agent._process_cidrs(current_namespace_cidrs, + datapath_port_ips, + metadaport_subnet_cidrs) + actual_cidrs_to_add, actual_cidrs_to_delete = actual_result + + self.assertSetEqual(actual_cidrs_to_add, expected_cidrs_to_add) + self.assertSetEqual(actual_cidrs_to_delete, expected_cidrs_to_delete) + + def test__get_provision_params_returns_none_when_metadata_port_is_missing( + self): + """Should return None when there is no metadata port in datapath and + call teardown datapath. + """ + network_id = '1' + datapath = DatapathInfo(uuid='test123', + external_ids={'name': 'neutron-{}'.format(network_id)}) + + with mock.patch.object( + self.agent.sb_idl, 'get_metadata_port_network', + return_value=None),\ + mock.patch.object( + self.agent, 'teardown_datapath') as tdp: + self.assertIsNone(self.agent._get_provision_params(datapath)) + tdp.assert_called_once_with(network_id) + + def test__get_provision_params_returns_none_when_metadata_port_missing_mac( + self): + """Should return None when metadata port is missing MAC and + call teardown datapath. + """ + network_id = '1' + datapath = DatapathInfo(uuid='test123', + external_ids={'name': 'neutron-{}'.format(network_id)}) + metadadata_port = makePort(datapath, + mac=['NO_MAC_HERE 1.2.3.4'], + external_ids={'neutron:cidrs': + '10.204.0.10/29'}) + + with mock.patch.object( + self.agent.sb_idl, 'get_metadata_port_network', + return_value=metadadata_port),\ + mock.patch.object( + self.agent, 'teardown_datapath') as tdp: + self.assertIsNone(self.agent._get_provision_params(datapath)) + tdp.assert_called_once_with(network_id) + + def test__get_provision_params_returns_none_when_no_vif_ports(self): + """Should return None when there are no datapath ports with type + "external" or ""(blank) and call teardown datapath. + """ + network_id = '1' + datapath = DatapathInfo(uuid='test123', + external_ids={'name': 'neutron-{}'.format(network_id)}) + datapath_ports = [makePort(datapath, type='not_vif_type')] + metadadata_port = makePort(datapath, + mac=['fa:16:3e:22:65:18 1.2.3.4'], + external_ids={'neutron:cidrs': + '10.204.0.10/29'}) + + with mock.patch.object(self.agent.sb_idl, 'get_metadata_port_network', + return_value=metadadata_port),\ + mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis', + return_value=datapath_ports),\ + mock.patch.object(self.agent, 'teardown_datapath') as tdp: + self.assertIsNone(self.agent._get_provision_params(datapath)) + tdp.assert_called_once_with(network_id) + + def test__get_provision_params_returns_provision_parameters(self): + """The happy path when datapath has ports with "external" or ""(blank) + types and metadata port contains MAC and subnet CIDRs. + """ + network_id = '1' + port_ip = '1.2.3.4' + metada_port_mac = "fa:16:3e:22:65:18" + metada_port_subnet_cidr = "10.204.0.10/29" + metada_port_logical_port = "3b66c176-199b-48ec-8331-c1fd3f6e2b44" + + datapath = DatapathInfo(uuid='test123', + external_ids={'name': 'neutron-{}'.format(network_id)}) + datapath_ports = [makePort(datapath, + mac=['fa:16:3e:e7:ac {}'.format(port_ip)])] + metadadata_port = makePort(datapath, + mac=[ + '{} 10.204.0.1'.format(metada_port_mac) + ], + external_ids={'neutron:cidrs': + metada_port_subnet_cidr}, + logical_port=metada_port_logical_port) + + with mock.patch.object(self.agent.sb_idl, 'get_metadata_port_network', + return_value=metadadata_port),\ + mock.patch.object(self.agent.sb_idl, 'get_ports_on_chassis', + return_value=datapath_ports): + actual_params = self.agent._get_provision_params(datapath) + + net_name, datapath_port_ips, metadata_port_info = actual_params + + self.assertEqual(network_id, net_name) + self.assertListEqual([port_ip], datapath_port_ips) + self.assertEqual(metada_port_mac, metadata_port_info.mac) + self.assertSetEqual(set([metada_port_subnet_cidr]), + metadata_port_info.ip_addresses) + self.assertEqual(metada_port_logical_port, + metadata_port_info.logical_port) + def test_provision_datapath(self): """Test datapath provisioning. @@ -204,16 +374,21 @@ class TestMetadataAgent(base.BaseTestCase): namespace are created, that the interface is properly configured with the right IP addresses and that the metadata proxy is spawned. """ + net_name = '123' + metadaport_logical_port = '123-abc-456' + datapath_ports_ips = ['10.0.0.1', '10.0.0.2'] + metada_port_info = agent.MetadataPortInfo( + mac='aa:bb:cc:dd:ee:ff', + ip_addresses=['10.0.0.1/23', + '2001:470:9:1224:5595:dd51:6ba2:e788/64'], + logical_port=metadaport_logical_port + ) + provision_params = (net_name, datapath_ports_ips, metada_port_info,) + nemaspace_name = 'namespace' - metadata_port = makePort(mac=['aa:bb:cc:dd:ee:ff'], - external_ids={ - 'neutron:cidrs': '10.0.0.1/23 ' - '2001:470:9:1224:5595:dd51:6ba2:e788/64'}, - logical_port='port') - - with mock.patch.object(self.agent.sb_idl, - 'get_metadata_port_network', - return_value=metadata_port),\ + with mock.patch.object(self.agent, + '_get_provision_params', + return_value=provision_params),\ mock.patch.object( ip_lib, 'device_exists', return_value=False),\ mock.patch.object( @@ -221,7 +396,7 @@ class TestMetadataAgent(base.BaseTestCase): mock.patch.object(agent.MetadataAgent, '_get_veth_name', return_value=['veth_0', 'veth_1']),\ mock.patch.object(agent.MetadataAgent, '_get_namespace_name', - return_value='namespace'),\ + return_value=nemaspace_name),\ mock.patch.object(ip_link, 'set_up') as link_set_up,\ mock.patch.object(ip_link, 'set_address') as link_set_addr,\ mock.patch.object(ip_addr, 'list', return_value=[]),\ @@ -241,13 +416,14 @@ class TestMetadataAgent(base.BaseTestCase): # We need to assert that it was deleted first. self.agent.ovs_idl.list_br.return_value.execute.return_value = ( ['br-int', 'br-fake']) - self.agent.provision_datapath('1', '1') + self.agent.provision_datapath('fake_datapath') # Check that the port was deleted from br-fake self.agent.ovs_idl.del_port.assert_called_once_with( 'veth_0', bridge='br-fake', if_exists=True) # Check that the VETH pair is created - add_veth.assert_called_once_with('veth_0', 'veth_1', 'namespace') + add_veth.assert_called_once_with('veth_0', 'veth_1', + nemaspace_name) # Make sure that the two ends of the VETH pair have been set as up. self.assertEqual(2, link_set_up.call_count) link_set_addr.assert_called_once_with('aa:bb:cc:dd:ee:ff') @@ -255,15 +431,18 @@ class TestMetadataAgent(base.BaseTestCase): self.agent.ovs_idl.add_port.assert_called_once_with( 'br-int', 'veth_0') self.agent.ovs_idl.db_set.assert_called_once_with( - 'Interface', 'veth_0', ('external_ids', {'iface-id': 'port'})) + 'Interface', 'veth_0', + ('external_ids', {'iface-id': metadaport_logical_port})) + # Check that the metadata port has the IP addresses properly + # configured and that IPv6 address has been skipped. expected_call = [n_const.METADATA_CIDR, '10.0.0.1/23'] self.assertCountEqual(expected_call, ip_addr_add_multiple.call_args.args[0]) # Check that metadata proxy has been spawned spawn_mdp.assert_called_once_with( - mock.ANY, 'namespace', 80, mock.ANY, - bind_address=n_const.METADATA_V4_IP, network_id='1') - mock_checksum.assert_called_once_with('namespace') + mock.ANY, nemaspace_name, 80, mock.ANY, + bind_address=n_const.METADATA_V4_IP, network_id=net_name) + mock_checksum.assert_called_once_with(nemaspace_name) def test__load_config(self): # Chassis name UUID formatted string. OVN bridge "br-ovn".