Improve agent provision performance for large networks
Before this patch, the metadata agent would provision network namespace for all subnets under a network(datapath) as soon as the first VM(vif port) was mounted on the chassis. This operation can take very long time for networks with lots of subnets. See the linked bug for more details. This patch changes this mechanism to "lazy load" where metadata agent provisions metadata namespace with only the subnets belonging to the active ports on the chassis. This results in virtually constant throughput not effected by the number of subnets. Closes-Bug: #1981113 Change-Id: Ia2a66cfd3fd1380c5204109742d44f09160548d2
This commit is contained in:
@@ -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
|
||||
# ["<port_mac> <ip1> <ip2> ... <ipN>"]
|
||||
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)
|
||||
|
||||
@@ -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".
|
||||
|
||||
Reference in New Issue
Block a user