From 42cfa055c278b1daa2c486a277e71c674836fb51 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 11 Nov 2021 17:04:16 +0000 Subject: [PATCH] Add network QoS inheritance to floating IP Added information of the floating IP network QoS policy to the ``FloatingIP`` OVO. The view-only parameter added allows to check the network QoS policy in the floating IP object. This patch does not implement any change in the L3 code (OVS or OVN). This patch does not change any existing behaviour. NOTE: bump neutron-lib version Depends-On: https://review.opendev.org/c/openstack/neutron-lib/+/817936 Partial-Bug: #1950454 Change-Id: I9d7bb54b14fb983161fdf51c96b6fda107db4fe6 --- neutron/db/l3_fip_qos.py | 11 ++++---- neutron/db/qos/models.py | 8 ++++++ neutron/extensions/qos_fip_network_policy.py | 20 ++++++++++++++ neutron/objects/router.py | 13 +++++++-- neutron/tests/unit/extensions/test_l3.py | 15 ++++++----- neutron/tests/unit/fake_resources.py | 16 +++++++++++ neutron/tests/unit/objects/test_objects.py | 2 +- neutron/tests/unit/objects/test_router.py | 28 ++++++++++++++++++++ 8 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 neutron/extensions/qos_fip_network_policy.py diff --git a/neutron/db/l3_fip_qos.py b/neutron/db/l3_fip_qos.py index e100fdf3b66..b7958243c8d 100644 --- a/neutron/db/l3_fip_qos.py +++ b/neutron/db/l3_fip_qos.py @@ -26,11 +26,12 @@ class FloatingQoSDbMixin(object): @staticmethod @resource_extend.extends([l3_apidef.FLOATINGIPS]) def _extend_extra_fip_dict(fip_res, fip_db): - if fip_db.get('qos_policy_binding'): - fip_res[qos_consts.QOS_POLICY_ID] = ( - fip_db.qos_policy_binding.policy_id) - else: - fip_res[qos_consts.QOS_POLICY_ID] = None + qos_id = (fip_db.qos_policy_binding.policy_id if + fip_db.qos_policy_binding else None) + fip_res[qos_consts.QOS_POLICY_ID] = qos_id + qos_id = (fip_db.qos_network_policy_binding.policy_id if + fip_db.qos_network_policy_binding else None) + fip_res[qos_consts.QOS_NETWORK_POLICY_ID] = qos_id return fip_res def _create_fip_qos_db(self, context, fip_id, policy_id): diff --git a/neutron/db/qos/models.py b/neutron/db/qos/models.py index 9f63877b9e8..f0662d5a91f 100644 --- a/neutron/db/qos/models.py +++ b/neutron/db/qos/models.py @@ -61,6 +61,14 @@ class QosNetworkPolicyBinding(model_base.BASEV2): backref=sa.orm.backref('qos_network_policy_binding', uselist=False, lazy='joined'), sync_backref=False, viewonly=True) + floatingip = sa.orm.relationship( + l3.FloatingIP, + primaryjoin=('QosNetworkPolicyBinding.network_id == ' + 'FloatingIP.floating_network_id'), + foreign_keys=network_id, + backref=sa.orm.backref('qos_network_policy_binding', uselist=False, + lazy='joined'), + sync_backref=False, viewonly=True) class QosFIPPolicyBinding(model_base.BASEV2): diff --git a/neutron/extensions/qos_fip_network_policy.py b/neutron/extensions/qos_fip_network_policy.py new file mode 100644 index 00000000000..3e16171565c --- /dev/null +++ b/neutron/extensions/qos_fip_network_policy.py @@ -0,0 +1,20 @@ +# Copyright (c) 2021 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib.api.definitions import qos_fip_network_policy +from neutron_lib.api import extensions as api_extensions + + +class Qos_fip_network_policy(api_extensions.APIExtensionDescriptor): + api_definition = qos_fip_network_policy diff --git a/neutron/objects/router.py b/neutron/objects/router.py index c06c49f7e42..e9ae872a39c 100644 --- a/neutron/objects/router.py +++ b/neutron/objects/router.py @@ -235,7 +235,8 @@ class Router(base.NeutronDbObject): class FloatingIP(base.NeutronDbObject): # Version 1.0: Initial version # Version 1.1: Added qos_policy_id field - VERSION = '1.1' + # Version 1.2: Added qos_network_policy_id field + VERSION = '1.2' db_model = l3.FloatingIP @@ -248,6 +249,8 @@ class FloatingIP(base.NeutronDbObject): 'fixed_port_id': common_types.UUIDField(nullable=True), 'fixed_ip_address': obj_fields.IPAddressField(nullable=True), 'qos_policy_id': common_types.UUIDField(nullable=True, default=None), + 'qos_network_policy_id': common_types.UUIDField(nullable=True, + default=None), 'router_id': common_types.UUIDField(nullable=True), 'last_known_router_id': common_types.UUIDField(nullable=True), 'status': common_types.FloatingIPStatusEnumField(nullable=True), @@ -257,6 +260,7 @@ class FloatingIP(base.NeutronDbObject): 'floating_network_id', 'floating_port_id'] synthetic_fields = ['dns', 'qos_policy_id', + 'qos_network_policy_id', ] @classmethod @@ -314,13 +318,18 @@ class FloatingIP(base.NeutronDbObject): if db_obj.get('qos_policy_binding'): self.qos_policy_id = db_obj.qos_policy_binding.policy_id fields_to_change.append('qos_policy_id') - + if db_obj.get('qos_network_policy_binding'): + self.qos_network_policy_id = ( + db_obj.qos_network_policy_binding.policy_id) + fields_to_change.append('qos_network_policy_binding') self.obj_reset_changes(fields_to_change) def obj_make_compatible(self, primitive, target_version): _target_version = versionutils.convert_version_to_tuple(target_version) if _target_version < (1, 1): primitive.pop('qos_policy_id', None) + if _target_version < (1, 2): + primitive.pop('qos_network_policy_id', None) @classmethod def get_scoped_floating_ips(cls, context, router_ids): diff --git a/neutron/tests/unit/extensions/test_l3.py b/neutron/tests/unit/extensions/test_l3.py index bbdedeaff66..5791dcd2002 100644 --- a/neutron/tests/unit/extensions/test_l3.py +++ b/neutron/tests/unit/extensions/test_l3.py @@ -2746,8 +2746,9 @@ class L3NatTestCaseBase(L3NatTestCaseMixin): port_id=port_id, router_id=router_id) skip = ('description', 'dns_domain', 'dns_name', - 'port_details', 'qos_policy_id', 'revision_number', - 'status', 'standard_attr_id') + 'port_details', 'qos_network_policy_id', + 'qos_policy_id', 'revision_number', 'status', + 'standard_attr_id') for k in skip: payload.states[0].pop(k, None) payload.states[1].pop(k, None) @@ -2793,8 +2794,9 @@ class L3NatTestCaseBase(L3NatTestCaseMixin): port_id=None, router_id=None) skip = ('description', 'dns_domain', 'dns_name', - 'port_details', 'qos_policy_id', 'revision_number', - 'status', 'standard_attr_id') + 'port_details', 'qos_network_policy_id', + 'qos_policy_id', 'revision_number', 'status', + 'standard_attr_id') for k in skip: payload.states[0].pop(k, None) payload.states[1].pop(k, None) @@ -2839,8 +2841,9 @@ class L3NatTestCaseBase(L3NatTestCaseMixin): port_id=None, router_id=None) skip = ('description', 'dns_domain', 'dns_name', - 'port_details', 'qos_policy_id', 'revision_number', - 'status', 'standard_attr_id') + 'port_details', 'qos_network_policy_id', + 'qos_policy_id', 'revision_number', 'status', + 'standard_attr_id') for k in skip: payload.states[0].pop(k, None) payload.states[1].pop(k, None) diff --git a/neutron/tests/unit/fake_resources.py b/neutron/tests/unit/fake_resources.py index 31583cbd63b..0251e5eec92 100644 --- a/neutron/tests/unit/fake_resources.py +++ b/neutron/tests/unit/fake_resources.py @@ -205,6 +205,20 @@ class FakeStandardAttribute(object): self.revision_number = revision_number +class FakeQosNetworkPolicyBinding(object): + + def __init__(self, policy_id=mock.ANY, network_id=mock.ANY): + self.policy_id = policy_id + self.network_id = network_id + + +class FakeQosFIPPolicyBinding(object): + + def __init__(self, policy_id=mock.ANY, fip_id=mock.ANY): + self.policy_id = policy_id + self.fip_id = fip_id + + class FakeResource(dict): def __init__(self, manager=None, info=None, loaded=False, methods=None): @@ -688,6 +702,8 @@ class FakeFloatingIp(object): 'dns_name': '', 'project_id': '', 'standard_attr': FakeStandardAttribute(), + 'qos_policy_binding': FakeQosFIPPolicyBinding(), + 'qos_network_policy_binding': FakeQosNetworkPolicyBinding(), } # Overwrite default attributes. diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index 68250bd7d85..727dc15adb1 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -46,7 +46,7 @@ object_data = { 'FlatAllocation': '1.0-bf666f24f4642b047eeca62311fbcb41', 'Flavor': '1.0-82194de5c9aafce08e8527bb7977f5c6', 'FlavorServiceProfileBinding': '1.0-a2c8731e16cefdac4571f80abf1f8930', - 'FloatingIP': '1.1-595514f8e992849cfc67981d1fd6614e', + 'FloatingIP': '1.2-fb6675ea98d2a37e5883dc5962ec401c', 'FloatingIPDNS': '1.0-ee3db848500fa1825235f701828c06d5', 'GeneveAllocation': '1.0-d5f76e8eac60a778914d61dd8e23e90f', 'GeneveEndpoint': '1.0-040f026996b5952e2ae4ccd40ac61ca6', diff --git a/neutron/tests/unit/objects/test_router.py b/neutron/tests/unit/objects/test_router.py index be4b7697a5d..b73d157d815 100644 --- a/neutron/tests/unit/objects/test_router.py +++ b/neutron/tests/unit/objects/test_router.py @@ -12,9 +12,12 @@ # License for the specific language governing permissions and limitations # under the License. +from unittest import mock + from oslo_utils import uuidutils from neutron.objects.qos import binding as qos_binding +from neutron.objects.qos import policy from neutron.objects import router from neutron.tests.unit.objects import test_base as obj_test_base from neutron.tests.unit import testlib_api @@ -201,11 +204,36 @@ class FloatingIPDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, self.context, fip_id=fip_id) self.assertEqual([], qos_fip_binding) + @mock.patch.object(policy.QosPolicy, 'unset_default') + def test_qos_network_policy_id(self, *mocks): + policy_obj = policy.QosPolicy(self.context) + policy_obj.create() + + obj = self._make_object(self.obj_fields[0]) + obj.create() + obj = router.FloatingIP.get_object(self.context, id=obj.id) + self.assertIsNone(obj.qos_network_policy_id) + self.assertIsNone(obj.qos_policy_id) + + network = self._create_test_network(qos_policy_id=policy_obj.id) + self.update_obj_fields({'floating_network_id': network.id}) + obj = self._make_object(self.obj_fields[1]) + obj.create() + obj = router.FloatingIP.get_object(self.context, id=obj.id) + self.assertEqual(policy_obj.id, obj.qos_network_policy_id) + self.assertIsNone(obj.qos_policy_id) + def test_v1_1_to_v1_0_drops_qos_policy_id(self): obj = self._make_object(self.obj_fields[0]) obj_v1_0 = obj.obj_to_primitive(target_version='1.0') self.assertNotIn('qos_policy_id', obj_v1_0['versioned_object.data']) + def test_v1_2_to_v1_1_drops_qos_network_policy_id(self): + obj = self._make_object(self.obj_fields[0]) + obj_v1_1 = obj.obj_to_primitive(target_version='1.1') + self.assertNotIn('qos_network_policy_id', + obj_v1_1['versioned_object.data']) + class DvrFipGatewayPortAgentBindingTestCase( obj_test_base.BaseObjectIfaceTestCase):