diff options
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: |