Merge "Improve agent provision performance for large networks"
This commit is contained in:
commit
133960c097
@ -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".
|
||||
|
Loading…
Reference in New Issue
Block a user