summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--neutron/agent/linux/ip_lib.py16
-rw-r--r--neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py13
-rw-r--r--neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py9
-rw-r--r--neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py5
-rw-r--r--neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py2
-rw-r--r--neutron/plugins/ml2/plugin.py6
-rw-r--r--neutron/privileged/agent/linux/ip_lib.py10
-rw-r--r--neutron/services/trunk/drivers/ovn/trunk_driver.py41
-rw-r--r--neutron/services/trunk/plugin.py10
-rw-r--r--neutron/tests/functional/agent/l3/framework.py13
-rw-r--r--neutron/tests/functional/agent/linux/test_ip_lib.py92
-rw-r--r--neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py16
-rw-r--r--neutron/tests/functional/privileged/agent/linux/test_ip_lib.py2
-rw-r--r--neutron/tests/functional/services/trunk/drivers/ovn/test_trunk_driver.py21
-rw-r--r--neutron/tests/unit/agent/l3/test_dvr_local_router.py1
-rw-r--r--neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_commands.py13
-rw-r--r--neutron/tests/unit/plugins/ml2/test_plugin.py49
-rw-r--r--neutron/tests/unit/services/trunk/drivers/ovn/test_trunk_driver.py12
-rw-r--r--neutron/tests/unit/services/trunk/test_plugin.py27
19 files changed, 258 insertions, 100 deletions
diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py
index e95dce3d44..b4887f1134 100644
--- a/neutron/agent/linux/ip_lib.py
+++ b/neutron/agent/linux/ip_lib.py
@@ -1574,12 +1574,24 @@ def list_ip_routes(namespace, ip_version, scope=None, via=None, table=None,
'source_prefix': get_attr(route, 'RTA_PREFSRC'),
'cidr': cidr,
'scope': IP_ADDRESS_SCOPE[int(route['scope'])],
- 'device': get_device(int(get_attr(route, 'RTA_OIF')), devices),
- 'via': get_attr(route, 'RTA_GATEWAY'),
'metric': metric,
'proto': proto,
}
+ multipath = get_attr(route, 'RTA_MULTIPATH')
+ if multipath:
+ value['device'] = None
+ mp_via = []
+ for mp in multipath:
+ mp_via.append({'device': get_device(int(mp['oif']), devices),
+ 'via': get_attr(mp, 'RTA_GATEWAY'),
+ 'weight': int(mp['hops']) + 1})
+ value['via'] = mp_via
+ else:
+ value['device'] = get_device(int(get_attr(route, 'RTA_OIF')),
+ devices)
+ value['via'] = get_attr(route, 'RTA_GATEWAY')
+
ret.append(value)
if scope:
diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py
index aac70ea76c..75c290e2e0 100644
--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py
+++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py
@@ -40,16 +40,23 @@ class API(api.API, metaclass=abc.ABCMeta):
"""
@abc.abstractmethod
- def set_lswitch_port(self, lport_name, if_exists=True, **columns):
+ def set_lswitch_port(self, lport_name, external_ids_update=None,
+ if_exists=True, **columns):
"""Create a command to set OVN logical switch port fields
:param lport_name: The name of the lport
:type lport_name: string
+ :param external_ids_update: Dictionary of keys to be updated
+ individually in the external IDs dictionary.
+ If "external_ids" is defined in "columns",
+ "external_ids" will override any
+ "external_ids_update" value.
+ :type external_ids_update: dictionary
+ :param if_exists: Do not fail if lport does not exist
+ :type if_exists: bool
:param columns: Dictionary of port columns
Supported columns: macs, external_ids,
parent_name, tag, enabled
- :param if_exists: Do not fail if lport does not exist
- :type if_exists: bool
:type columns: dictionary
:returns: :class:`Command` with no result
"""
diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py
index 936b0cfdd0..a1e29e6e98 100644
--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py
+++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py
@@ -148,9 +148,10 @@ class AddLSwitchPortCommand(command.BaseCommand):
class SetLSwitchPortCommand(command.BaseCommand):
- def __init__(self, api, lport, if_exists, **columns):
+ def __init__(self, api, lport, external_ids_update, if_exists, **columns):
super(SetLSwitchPortCommand, self).__init__(api)
self.lport = lport
+ self.external_ids_update = external_ids_update
self.columns = columns
self.if_exists = if_exists
@@ -195,6 +196,12 @@ class SetLSwitchPortCommand(command.BaseCommand):
for uuid in cur_port_dhcp_opts - new_port_dhcp_opts:
self.api._tables['DHCP_Options'].rows[uuid].delete()
+ external_ids_update = self.external_ids_update or {}
+ external_ids = getattr(port, 'external_ids', {})
+ for k, v in external_ids_update.items():
+ external_ids[k] = v
+ port.external_ids = external_ids
+
for col, val in self.columns.items():
setattr(port, col, val)
diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py
index 7a87492b1e..18464066bd 100644
--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py
+++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py
@@ -269,8 +269,9 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
return cmd.AddLSwitchPortCommand(self, lport_name, lswitch_name,
may_exist, **columns)
- def set_lswitch_port(self, lport_name, if_exists=True, **columns):
- return cmd.SetLSwitchPortCommand(self, lport_name,
+ def set_lswitch_port(self, lport_name, external_ids_update=None,
+ if_exists=True, **columns):
+ return cmd.SetLSwitchPortCommand(self, lport_name, external_ids_update,
if_exists, **columns)
def delete_lswitch_port(self, lport_name=None, lswitch_name=None,
diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py
index 45dadda6b5..190e530efc 100644
--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py
+++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py
@@ -724,7 +724,7 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
LOG.debug('OVN-NB Sync DHCP options for Neutron subnets started')
db_subnets = {}
- filters = {'enable_dhcp': [1]}
+ filters = {'enable_dhcp': [True]}
for subnet in self.core_plugin.get_subnets(ctx, filters=filters):
if (subnet['ip_version'] == constants.IP_VERSION_6 and
subnet.get('ipv6_address_mode') == constants.IPV6_SLAAC):
diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py
index 64e4d3952d..dcee882a59 100644
--- a/neutron/plugins/ml2/plugin.py
+++ b/neutron/plugins/ml2/plugin.py
@@ -1662,13 +1662,13 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
self._process_port_binding(mech_context, port_dict)
# process allowed address pairs
- db_port_obj[addr_apidef.ADDRESS_PAIRS] = (
+ port_dict[addr_apidef.ADDRESS_PAIRS] = (
self._process_create_allowed_address_pairs(
context, port_dict,
- port_dict.get(addr_apidef.ADDRESS_PAIRS)))
+ pdata.get(addr_apidef.ADDRESS_PAIRS)))
# handle DHCP setup
- dhcp_opts = port_dict.get(edo_ext.EXTRADHCPOPTS, [])
+ dhcp_opts = pdata.get(edo_ext.EXTRADHCPOPTS, [])
self._process_port_create_extra_dhcp_opts(context, port_dict,
dhcp_opts)
# send PRECOMMIT_CREATE notification
diff --git a/neutron/privileged/agent/linux/ip_lib.py b/neutron/privileged/agent/linux/ip_lib.py
index 1d006ac26b..593e66e394 100644
--- a/neutron/privileged/agent/linux/ip_lib.py
+++ b/neutron/privileged/agent/linux/ip_lib.py
@@ -40,7 +40,7 @@ NETNS_RUN_DIR = '/var/run/netns'
NUD_STATES = {state[1]: state[0] for state in ndmsg.states.items()}
-def _get_scope_name(scope):
+def get_scope_name(scope):
"""Return the name of the scope (given as a number), or the scope number
if the name is unknown.
@@ -142,7 +142,6 @@ def _make_route_dict(destination, nexthop, device, scope):
@privileged.default.entrypoint
def get_routing_table(ip_version, namespace=None):
"""Return a list of dictionaries, each representing a route.
-
:param ip_version: IP version of routes to return, for example 4
:param namespace: The name of the namespace from which to get the routes
:return: a list of dictionaries, each representing a route.
@@ -168,8 +167,7 @@ def get_routing_table(ip_version, namespace=None):
dst = route['dst']
nexthop = route.get('gateway')
oif = route.get('oif')
- scope = _get_scope_name(route['scope'])
-
+ scope = get_scope_name(route['scope'])
# If there is not a valid outgoing interface id, check if
# this is a multipath route (i.e. same destination with
# multiple outgoing interfaces)
@@ -313,7 +311,7 @@ def add_ip_address(ip_version, ip, prefixlen, device, namespace, scope,
mask=prefixlen,
family=family,
broadcast=broadcast,
- scope=_get_scope_name(scope))
+ scope=get_scope_name(scope))
except netlink_exceptions.NetlinkError as e:
if e.code == errno.EEXIST:
raise IpAddressAlreadyExists(ip=ip, device=device)
@@ -755,7 +753,7 @@ def _make_pyroute2_route_args(namespace, ip_version, cidr, device, via, table,
args = {'family': _IP_VERSION_FAMILY_MAP[ip_version]}
if not scope:
scope = 'global' if via else 'link'
- scope = _get_scope_name(scope)
+ scope = get_scope_name(scope)
if scope:
args['scope'] = scope
if cidr:
diff --git a/neutron/services/trunk/drivers/ovn/trunk_driver.py b/neutron/services/trunk/drivers/ovn/trunk_driver.py
index 8e5c5d5739..2c4a977fb2 100644
--- a/neutron/services/trunk/drivers/ovn/trunk_driver.py
+++ b/neutron/services/trunk/drivers/ovn/trunk_driver.py
@@ -21,7 +21,9 @@ from neutron_lib.services.trunk import constants as trunk_consts
from oslo_config import cfg
from oslo_log import log
-from neutron.common.ovn.constants import OVN_ML2_MECH_DRIVER_NAME
+from neutron.common.ovn import constants as ovn_const
+from neutron.db import db_base_plugin_common
+from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.objects import ports as port_obj
from neutron.services.trunk.drivers import base as trunk_base
@@ -47,20 +49,23 @@ class OVNTrunkHandler(object):
context = n_context.get_admin_context()
db_parent_port = port_obj.Port.get_object(context, id=parent_port)
parent_port_status = db_parent_port.status
- for port in subports:
+ for subport in subports:
with db_api.CONTEXT_WRITER.using(context), (
txn(check_error=True)) as ovn_txn:
- self._set_binding_profile(context, port, parent_port,
- parent_port_status, ovn_txn)
+ port = self._set_binding_profile(context, subport, parent_port,
+ parent_port_status, ovn_txn)
+ db_rev.bump_revision(context, port, ovn_const.TYPE_PORTS)
def _unset_sub_ports(self, subports):
txn = self.plugin_driver.nb_ovn.transaction
context = n_context.get_admin_context()
- for port in subports:
+ for subport in subports:
with db_api.CONTEXT_WRITER.using(context), (
txn(check_error=True)) as ovn_txn:
- self._unset_binding_profile(context, port, ovn_txn)
+ port = self._unset_binding_profile(context, subport, ovn_txn)
+ db_rev.bump_revision(context, port, ovn_const.TYPE_PORTS)
+ @db_base_plugin_common.convert_result_to_dict
def _set_binding_profile(self, context, subport, parent_port,
parent_port_status, ovn_txn):
LOG.debug("Setting parent %s for subport %s",
@@ -71,6 +76,9 @@ class OVNTrunkHandler(object):
"binding_profile: %s",
subport.port_id)
return
+ check_rev_cmd = self.plugin_driver.nb_ovn.check_revision_number(
+ db_port.id, db_port, ovn_const.TYPE_PORTS)
+ ovn_txn.add(check_rev_cmd)
try:
# NOTE(flaviof): We expect binding's host to be set. Otherwise,
# sub-port will not transition from DOWN to ACTIVE.
@@ -94,13 +102,18 @@ class OVNTrunkHandler(object):
LOG.debug("Port not found while trying to set "
"binding_profile: %s", subport.port_id)
return
+ ext_ids = {ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY: db_port.device_owner}
ovn_txn.add(self.plugin_driver.nb_ovn.set_lswitch_port(
lport_name=subport.port_id,
parent_name=parent_port,
- tag=subport.segmentation_id))
+ tag=subport.segmentation_id,
+ external_ids_update=ext_ids,
+ ))
LOG.debug("Done setting parent %s for subport %s",
parent_port, subport.port_id)
+ return db_port
+ @db_base_plugin_common.convert_result_to_dict
def _unset_binding_profile(self, context, subport, ovn_txn):
LOG.debug("Unsetting parent for subport %s", subport.port_id)
db_port = port_obj.Port.get_object(context, id=subport.port_id)
@@ -109,6 +122,9 @@ class OVNTrunkHandler(object):
"binding_profile: %s",
subport.port_id)
return
+ check_rev_cmd = self.plugin_driver.nb_ovn.check_revision_number(
+ db_port.id, db_port, ovn_const.TYPE_PORTS)
+ ovn_txn.add(check_rev_cmd)
try:
db_port.device_owner = ''
for binding in db_port.bindings:
@@ -128,12 +144,16 @@ class OVNTrunkHandler(object):
LOG.debug("Port not found while trying to unset "
"binding_profile: %s", subport.port_id)
return
+ ext_ids = {ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY: db_port.device_owner}
ovn_txn.add(self.plugin_driver.nb_ovn.set_lswitch_port(
lport_name=subport.port_id,
parent_name=[],
up=False,
- tag=[]))
+ tag=[],
+ external_ids_update=ext_ids,
+ ))
LOG.debug("Done unsetting parent for subport %s", subport.port_id)
+ return db_port
def trunk_created(self, trunk):
# Check if parent port is handled by OVN.
@@ -185,7 +205,8 @@ class OVNTrunkDriver(trunk_base.DriverBase):
@property
def is_loaded(self):
try:
- return OVN_ML2_MECH_DRIVER_NAME in cfg.CONF.ml2.mechanism_drivers
+ return (ovn_const.OVN_ML2_MECH_DRIVER_NAME in
+ cfg.CONF.ml2.mechanism_drivers)
except cfg.NoSuchOptError:
return False
@@ -205,7 +226,7 @@ class OVNTrunkDriver(trunk_base.DriverBase):
@classmethod
def create(cls, plugin_driver):
cls.plugin_driver = plugin_driver
- return cls(OVN_ML2_MECH_DRIVER_NAME,
+ return cls(ovn_const.OVN_ML2_MECH_DRIVER_NAME,
SUPPORTED_INTERFACES,
SUPPORTED_SEGMENTATION_TYPES,
None,
diff --git a/neutron/services/trunk/plugin.py b/neutron/services/trunk/plugin.py
index 0b3b0e8d07..4f4ddbd016 100644
--- a/neutron/services/trunk/plugin.py
+++ b/neutron/services/trunk/plugin.py
@@ -21,6 +21,7 @@ from neutron_lib.api.definitions import trunk_details
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
+from neutron_lib import constants as const
from neutron_lib import context
from neutron_lib.db import api as db_api
from neutron_lib.db import resource_extend
@@ -449,12 +450,19 @@ class TrunkPlugin(service_base.ServicePluginBase):
original_port = kwargs['original_port']
orig_vif_type = original_port.get(portbindings.VIF_TYPE)
new_vif_type = updated_port.get(portbindings.VIF_TYPE)
+ orig_status = original_port.get('status')
+ new_status = updated_port.get('status')
vif_type_changed = orig_vif_type != new_vif_type
+ trunk_id = trunk_details['trunk_id']
if vif_type_changed and new_vif_type == portbindings.VIF_TYPE_UNBOUND:
- trunk_id = trunk_details['trunk_id']
# NOTE(status_police) Trunk status goes to DOWN when the parent
# port is unbound. This means there are no more physical resources
# associated with the logical resource.
self.update_trunk(
context, trunk_id,
{'trunk': {'status': constants.TRUNK_DOWN_STATUS}})
+ elif new_status == const.PORT_STATUS_ACTIVE and \
+ new_status != orig_status:
+ self.update_trunk(
+ context, trunk_id,
+ {'trunk': {'status': constants.TRUNK_ACTIVE_STATUS}})
diff --git a/neutron/tests/functional/agent/l3/framework.py b/neutron/tests/functional/agent/l3/framework.py
index 0d60ea1fac..fd20010bc6 100644
--- a/neutron/tests/functional/agent/l3/framework.py
+++ b/neutron/tests/functional/agent/l3/framework.py
@@ -575,9 +575,9 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
def _assert_extra_routes(self, router, namespace=None, enable_gw=True):
if namespace is None:
namespace = router.ns_name
- routes = ip_lib.get_routing_table(4, namespace=namespace)
- routes = [{'nexthop': route['nexthop'],
- 'destination': route['destination']} for route in routes]
+ routes = ip_lib.list_ip_routes(namespace, constants.IP_VERSION_4)
+ routes = [{'nexthop': route['via'],
+ 'destination': route['cidr']} for route in routes]
for extra_route in router.router['routes']:
check = self.assertIn if enable_gw else self.assertNotIn
@@ -588,17 +588,16 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
ns_name = namespace or router.ns_name
routes = []
for ip_version in ip_versions:
- _routes = ip_lib.get_routing_table(ip_version,
- namespace=ns_name)
+ _routes = ip_lib.list_ip_routes(ns_name, ip_version)
routes.extend(_routes)
- routes = set(route['destination'] for route in routes)
+ routes = set(route['cidr'] for route in routes)
ex_gw_port = router.get_ex_gw_port()
if not ex_gw_port:
if not enable_gw:
return
self.fail('GW port is enabled but not present in the router')
- extra_subnets = ex_gw_port['extra_subnets']
+ extra_subnets = router.get_ex_gw_port()['extra_subnets']
for extra_subnet in (route['cidr'] for route in extra_subnets):
self.assertIn(extra_subnet, routes)
diff --git a/neutron/tests/functional/agent/linux/test_ip_lib.py b/neutron/tests/functional/agent/linux/test_ip_lib.py
index 19062417df..c66a78ab5f 100644
--- a/neutron/tests/functional/agent/linux/test_ip_lib.py
+++ b/neutron/tests/functional/agent/linux/test_ip_lib.py
@@ -387,49 +387,6 @@ class IpLibTestCase(IpLibTestFramework):
self.assertIsNone(
device.route.get_gateway(ip_version=ip_version))
- def test_get_routing_table(self):
- attr = self.generate_device_details(
- ip_cidrs=["%s/24" % TEST_IP, "fd00::1/64"]
- )
- device = self.manage_device(attr)
- device_ip = attr.ip_cidrs[0].split('/')[0]
- destination = '8.8.8.0/24'
- device.route.add_route(destination, device_ip)
-
- destination6 = 'fd01::/64'
- device.route.add_route(destination6, "fd00::2")
-
- expected_routes = [{'nexthop': device_ip,
- 'device': attr.name,
- 'destination': destination,
- 'scope': 'universe'},
- {'nexthop': None,
- 'device': attr.name,
- 'destination': str(
- netaddr.IPNetwork(attr.ip_cidrs[0]).cidr),
- 'scope': 'link'}]
-
- routes = ip_lib.get_routing_table(4, namespace=attr.namespace)
- self.assertCountEqual(expected_routes, routes)
- self.assertIsInstance(routes, list)
-
- expected_routes6 = [{'nexthop': "fd00::2",
- 'device': attr.name,
- 'destination': destination6,
- 'scope': 'universe'},
- {'nexthop': None,
- 'device': attr.name,
- 'destination': str(
- netaddr.IPNetwork(attr.ip_cidrs[1]).cidr),
- 'scope': 'universe'}]
- routes6 = ip_lib.get_routing_table(6, namespace=attr.namespace)
- self.assertCountEqual(expected_routes6, routes6)
- self.assertIsInstance(routes6, list)
-
- def test_get_routing_table_no_namespace(self):
- with testtools.ExpectedException(ip_lib.NetworkNamespaceNotFound):
- ip_lib.get_routing_table(4, namespace="nonexistent-netns")
-
def test_get_neigh_entries(self):
attr = self.generate_device_details(
ip_cidrs=["%s/24" % TEST_IP, "fd00::1/64"]
@@ -1158,3 +1115,52 @@ class IpLinkCommandTestCase(IpLibTestFramework):
device.link.create()
namespace = self.useFixture(net_helpers.NamespaceFixture())
device.link.set_netns(namespace.name)
+
+
+class ListIpRoutesTestCase(functional_base.BaseSudoTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.namespace = self.useFixture(net_helpers.NamespaceFixture()).name
+ self.device_names = ['test_device1', 'test_device2']
+ self.device_ips = ['10.0.0.1/24', '10.0.1.1/24']
+ self.device_cidrs = [netaddr.IPNetwork(ip_address).cidr for ip_address
+ in self.device_ips]
+ for idx, dev in enumerate(self.device_names):
+ ip_lib.IPWrapper(self.namespace).add_dummy(dev)
+ device = ip_lib.IPDevice(dev, namespace=self.namespace)
+ device.link.set_up()
+ device.addr.add(self.device_ips[idx])
+
+ def test_list_ip_routes_multipath(self):
+ multipath = [
+ {'device': self.device_names[0],
+ 'via': str(self.device_cidrs[0].ip + 100), 'weight': 10},
+ {'device': self.device_names[1],
+ 'via': str(self.device_cidrs[1].ip + 100), 'weight': 20},
+ {'via': str(self.device_cidrs[1].ip + 101), 'weight': 30},
+ {'via': str(self.device_cidrs[1].ip + 102)}]
+ ip_lib.add_ip_route(self.namespace, '1.2.3.0/24',
+ constants.IP_VERSION_4, via=multipath)
+
+ routes = ip_lib.list_ip_routes(self.namespace, constants.IP_VERSION_4)
+ multipath[2]['device'] = self.device_names[1]
+ multipath[3]['device'] = self.device_names[1]
+ multipath[3]['weight'] = 1
+ for route in (route for route in routes if
+ route['cidr'] == '1.2.3.0/24'):
+ if not isinstance(route['via'], list):
+ continue
+
+ self.assertEqual(len(multipath), len(route['via']))
+ for nexthop in multipath:
+ for mp in route['via']:
+ if nexthop != mp:
+ continue
+ break
+ else:
+ self.fail('Not matching route, routes: %s' % routes)
+
+ return
+
+ self.fail('Not matching route, routes: %s' % routes)
diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py
index 468eccef33..9298964b4d 100644
--- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py
+++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py
@@ -841,12 +841,12 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
txn.add(self.nb_api.pg_del(pg))
for lport_name in self.reset_lport_dhcpv4_options:
- txn.add(self.nb_api.set_lswitch_port(lport_name, True,
- dhcpv4_options=[]))
+ txn.add(self.nb_api.set_lswitch_port(
+ lport_name, if_exists=True, dhcpv4_options=[]))
for lport_name in self.reset_lport_dhcpv6_options:
- txn.add(self.nb_api.set_lswitch_port(lport_name, True,
- dhcpv6_options=[]))
+ txn.add(self.nb_api.set_lswitch_port(
+ lport_name, if_exists=True, dhcpv6_options=[]))
for dhcp_opts in self.stale_lport_dhcpv4_options:
dhcpv4_opts = txn.add(self.nb_api.add_dhcp_options(
@@ -859,7 +859,7 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
if dhcp_opts['port_id'] in self.orphaned_lport_dhcp_options:
continue
txn.add(self.nb_api.set_lswitch_port(
- lport_name, True, dhcpv4_options=dhcpv4_opts))
+ lport_name, if_exists=True, dhcpv4_options=dhcpv4_opts))
for dhcp_opts in self.stale_lport_dhcpv6_options:
dhcpv6_opts = txn.add(self.nb_api.add_dhcp_options(
@@ -872,7 +872,7 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
if dhcp_opts['port_id'] in self.orphaned_lport_dhcp_options:
continue
txn.add(self.nb_api.set_lswitch_port(
- lport_name, True, dhcpv6_options=dhcpv6_opts))
+ lport_name, if_exists=True, dhcpv6_options=dhcpv6_opts))
for row_uuid in self.missed_dhcp_options:
txn.add(self.nb_api.delete_dhcp_options(row_uuid))
@@ -889,12 +889,12 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
for port_id in self.lport_dhcpv4_disabled:
txn.add(self.nb_api.set_lswitch_port(
- port_id, True,
+ port_id, if_exists=True,
dhcpv4_options=[self.lport_dhcpv4_disabled[port_id]]))
for port_id in self.lport_dhcpv6_disabled:
txn.add(self.nb_api.set_lswitch_port(
- port_id, True,
+ port_id, if_exists=True,
dhcpv6_options=[self.lport_dhcpv6_disabled[port_id]]))
# Delete the first DNS record and clear the second row records
diff --git a/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py b/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py
index 25238217dd..2976421550 100644
--- a/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py
+++ b/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py
@@ -509,7 +509,7 @@ class RouteTestCase(functional_base.BaseSudoTestCase):
table = table or iproute_linux.DEFAULT_TABLE
if not scope:
scope = 'universe' if gateway else 'link'
- scope = priv_ip_lib._get_scope_name(scope)
+ scope = priv_ip_lib.get_scope_name(scope)
for cidr in cidrs:
ip_version = common_utils.get_ip_version(cidr)
if ip_version == n_cons.IP_VERSION_6 and not metric:
diff --git a/neutron/tests/functional/services/trunk/drivers/ovn/test_trunk_driver.py b/neutron/tests/functional/services/trunk/drivers/ovn/test_trunk_driver.py
index 4927bbd5c6..fed86fabbb 100644
--- a/neutron/tests/functional/services/trunk/drivers/ovn/test_trunk_driver.py
+++ b/neutron/tests/functional/services/trunk/drivers/ovn/test_trunk_driver.py
@@ -22,6 +22,8 @@ from neutron_lib.plugins import utils
from neutron_lib.services.trunk import constants as trunk_consts
from oslo_utils import uuidutils
+from neutron.common.ovn import constants as ovn_const
+
class TestOVNTrunkDriver(base.TestOVNFunctionalBase):
@@ -60,18 +62,29 @@ class TestOVNTrunkDriver(base.TestOVNFunctionalBase):
for row in self.nb_api.tables[
'Logical_Switch_Port'].rows.values():
if row.parent_name and row.tag:
+ device_owner = row.external_ids[
+ ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY]
+ revision_number = row.external_ids[
+ ovn_const.OVN_REV_NUM_EXT_ID_KEY]
ovn_trunk_info.append({'port_id': row.name,
'parent_port_id': row.parent_name,
- 'tag': row.tag})
+ 'tag': row.tag,
+ 'device_owner': device_owner,
+ 'revision_number': revision_number,
+ })
return ovn_trunk_info
def _verify_trunk_info(self, trunk, has_items):
ovn_subports_info = self._get_ovn_trunk_info()
neutron_subports_info = []
for subport in trunk.get('sub_ports', []):
- neutron_subports_info.append({'port_id': subport['port_id'],
- 'parent_port_id': [trunk['port_id']],
- 'tag': [subport['segmentation_id']]})
+ neutron_subports_info.append(
+ {'port_id': subport['port_id'],
+ 'parent_port_id': [trunk['port_id']],
+ 'tag': [subport['segmentation_id']],
+ 'device_owner': trunk_consts.TRUNK_SUBPORT_OWNER,
+ 'revision_number': '2',
+ })
# Check that the subport has the binding is active.
binding = obj_reg.load_class('PortBinding').get_object(
self.context, port_id=subport['port_id'], host='')
diff --git a/neutron/tests/unit/agent/l3/test_dvr_local_router.py b/neutron/tests/unit/agent/l3/test_dvr_local_router.py
index ded27933d8..78057c4ea3 100644
--- a/neutron/tests/unit/agent/l3/test_dvr_local_router.py
+++ b/neutron/tests/unit/agent/l3/test_dvr_local_router.py
@@ -303,6 +303,7 @@ class TestDvrRouterOperations(base.BaseTestCase):
self.assertFalse(ri.floating_forward_rules(fip))
def test_floating_forward_rules(self):
+ self.utils_exec.return_value = "iptables v1.6.2 (legacy)"
router = mock.MagicMock()
router.get.return_value = [{'host': HOSTNAME},
{'host': mock.sentinel.otherhost}]
diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_commands.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_commands.py
index 387ded939d..39bf2a2334 100644
--- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_commands.py
+++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_commands.py
@@ -200,7 +200,8 @@ class TestSetLSwitchPortCommand(TestBaseCommand):
with mock.patch.object(idlutils, 'row_by_value',
side_effect=idlutils.RowNotFound):
cmd = commands.SetLSwitchPortCommand(
- self.ovn_api, 'fake-lsp', if_exists=if_exists)
+ self.ovn_api, 'fake-lsp', external_ids_update=None,
+ if_exists=if_exists)
if if_exists:
cmd.run_idl(self.transaction)
else:
@@ -220,8 +221,8 @@ class TestSetLSwitchPortCommand(TestBaseCommand):
with mock.patch.object(idlutils, 'row_by_value',
return_value=fake_lsp):
cmd = commands.SetLSwitchPortCommand(
- self.ovn_api, fake_lsp.name, if_exists=True,
- external_ids=new_ext_ids)
+ self.ovn_api, fake_lsp.name, external_ids_update=new_ext_ids,
+ if_exists=True, external_ids=new_ext_ids)
cmd.run_idl(self.transaction)
self.assertEqual(new_ext_ids, fake_lsp.external_ids)
@@ -255,7 +256,8 @@ class TestSetLSwitchPortCommand(TestBaseCommand):
with mock.patch.object(idlutils, 'row_by_value',
return_value=fake_lsp):
cmd = commands.SetLSwitchPortCommand(
- self.ovn_api, fake_lsp.name, if_exists=True, **columns)
+ self.ovn_api, fake_lsp.name, external_ids_update=None,
+ if_exists=True, **columns)
cmd.run_idl(self.transaction)
if clear_v4_opts and clear_v6_opts:
@@ -307,7 +309,8 @@ class TestSetLSwitchPortCommand(TestBaseCommand):
with mock.patch.object(idlutils, 'row_by_value',
return_value=fake_lsp):
cmd = commands.SetLSwitchPortCommand(
- self.ovn_api, fake_lsp.name, if_exists=True,
+ self.ovn_api, fake_lsp.name, external_ids_update=ext_ids,
+ if_exists=True,
external_ids=ext_ids, dhcpv4_options=dhcpv4_opts,
dhcpv6_options=dhcpv6_opts)
if not isinstance(dhcpv4_opts, list):
diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py
index d2c17b67fc..6a46334442 100644
--- a/neutron/tests/unit/plugins/ml2/test_plugin.py
+++ b/neutron/tests/unit/plugins/ml2/test_plugin.py
@@ -1650,6 +1650,55 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
ctx, ports
)
+ def test_create_ports_bulk_with_allowed_address_pairs(self):
+ ctx = context.get_admin_context()
+ with self.network() as net:
+
+ aap = [{
+ 'ip_address': '1.2.3.4',
+ 'mac_address': '01:23:45:67:89:ab',
+ }]
+ ports_in = {
+ 'ports': [{'port': {
+ 'allowed_address_pairs': aap,
+ 'network_id': net['network']['id'],
+ 'tenant_id': self._tenant_id,
+
+ 'admin_state_up': True,
+ 'device_id': '',
+ 'device_owner': '',
+ 'fixed_ips': constants.ATTR_NOT_SPECIFIED,
+ 'name': '',
+ 'security_groups': constants.ATTR_NOT_SPECIFIED,
+ }}]}
+ ports_out = self.plugin.create_port_bulk(ctx, ports_in)
+ self.assertEqual(aap, ports_out[0]['allowed_address_pairs'])
+
+ def test_create_ports_bulk_with_extra_dhcp_opts(self):
+ ctx = context.get_admin_context()
+ with self.network() as net:
+
+ edo = [{
+ 'opt_name': 'domain-name-servers',
+ 'opt_value': '10.0.0.1',
+ 'ip_version': 4,
+ }]
+ ports_in = {
+ 'ports': [{'port': {
+ 'extra_dhcp_opts': edo,
+ 'network_id': net['network']['id'],
+ 'tenant_id': self._tenant_id,
+
+ 'admin_state_up': True,
+ 'device_id': '',
+ 'device_owner': '',
+ 'fixed_ips': constants.ATTR_NOT_SPECIFIED,
+ 'name': '',
+ 'security_groups': constants.ATTR_NOT_SPECIFIED,
+ }}]}
+ ports_out = self.plugin.create_port_bulk(ctx, ports_in)
+ self.assertEqual(edo, ports_out[0]['extra_dhcp_opts'])
+
def test_delete_port_no_notify_in_disassociate_floatingips(self):
ctx = context.get_admin_context()
plugin = directory.get_plugin()
diff --git a/neutron/tests/unit/services/trunk/drivers/ovn/test_trunk_driver.py b/neutron/tests/unit/services/trunk/drivers/ovn/test_trunk_driver.py
index 8f18b960c1..2d05c9b0f2 100644
--- a/neutron/tests/unit/services/trunk/drivers/ovn/test_trunk_driver.py
+++ b/neutron/tests/unit/services/trunk/drivers/ovn/test_trunk_driver.py
@@ -87,6 +87,8 @@ class TestTrunkHandler(base.BaseTestCase):
"neutron.objects.ports.PortBinding.update_object").start()
self.mock_clear_levels = mock.patch(
"neutron.objects.ports.PortBindingLevel.delete_objects").start()
+ self.mock_bump_revision = mock.patch(
+ "neutron.db.ovn_revision_numbers_db.bump_revision").start()
def _get_fake_port_obj(self, port_id):
with mock.patch('uuid.UUID') as mock_uuid:
@@ -129,7 +131,9 @@ class TestTrunkHandler(base.BaseTestCase):
calls = [mock.call(lport_name=s_port.port_id,
parent_name=trunk.port_id,
- tag=s_port.segmentation_id)
+ tag=s_port.segmentation_id,
+ external_ids_update={
+ 'neutron:device_owner': 'trunk:subport'})
for trunk, s_port in [(self.trunk_1, self.sub_port_1),
(self.trunk_1, self.sub_port_2)]]
self._assert_calls(self.plugin_driver.nb_ovn.set_lswitch_port, calls)
@@ -196,7 +200,8 @@ class TestTrunkHandler(base.BaseTestCase):
calls = [mock.call(lport_name=s_port.port_id,
parent_name=[],
tag=[],
- up=False)
+ up=False,
+ external_ids_update={'neutron:device_owner': ''})
for trunk, s_port in [(self.trunk_1, self.sub_port_1),
(self.trunk_1, self.sub_port_2)]]
self._assert_calls(self.plugin_driver.nb_ovn.set_lswitch_port, calls)
@@ -225,7 +230,8 @@ class TestTrunkHandler(base.BaseTestCase):
calls = [mock.call(lport_name=s_port.port_id,
parent_name=[],
tag=[],
- up=False)
+ up=False,
+ external_ids_update={'neutron:device_owner': ''})
for trunk, s_port in [(self.trunk_1, self.sub_port_1)]]
self._assert_calls(self.plugin_driver.nb_ovn.set_lswitch_port, calls)
diff --git a/neutron/tests/unit/services/trunk/test_plugin.py b/neutron/tests/unit/services/trunk/test_plugin.py
index 62aa20e163..c3400a041f 100644
--- a/neutron/tests/unit/services/trunk/test_plugin.py
+++ b/neutron/tests/unit/services/trunk/test_plugin.py
@@ -19,6 +19,7 @@ from neutron_lib.api.definitions import portbindings
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
+from neutron_lib import constants as neutron_const
from neutron_lib.plugins import directory
from neutron_lib.services.trunk import constants
import testtools
@@ -294,6 +295,32 @@ class TrunkPluginTestCase(test_plugin.Ml2PluginV2TestCase):
{'sub_ports': [{'port_id': subport['port']['id']}]})
self.assertEqual(constants.TRUNK_DOWN_STATUS, trunk['status'])
+ def test__trigger_trunk_status_change_parent_port_status_down(self):
+ callback = register_mock_callback(resources.TRUNK, events.AFTER_UPDATE)
+ with self.port() as parent:
+ parent['status'] = neutron_const.PORT_STATUS_DOWN
+ original_port = {'status': neutron_const.PORT_STATUS_DOWN}
+ _, _ = (
+ self._test__trigger_trunk_status_change(
+ parent, original_port,
+ constants.TRUNK_DOWN_STATUS,
+ constants.TRUNK_DOWN_STATUS))
+ callback.assert_not_called()
+
+ def test__trigger_trunk_status_change_parent_port_status_up(self):
+ callback = register_mock_callback(resources.TRUNK, events.AFTER_UPDATE)
+ with self.port() as parent:
+ parent['status'] = neutron_const.PORT_STATUS_ACTIVE
+ original_port = {'status': neutron_const.PORT_STATUS_DOWN}
+ _, _ = (
+ self._test__trigger_trunk_status_change(
+ parent, original_port,
+ constants.TRUNK_DOWN_STATUS,
+ constants.TRUNK_ACTIVE_STATUS))
+ callback.assert_called_once_with(
+ resources.TRUNK, events.AFTER_UPDATE,
+ self.trunk_plugin, payload=mock.ANY)
+
def test__trigger_trunk_status_change_vif_type_changed_unbound(self):
callback = register_mock_callback(resources.TRUNK, events.AFTER_UPDATE)
with self.port() as parent: