diff --git a/neutron/common/exceptions.py b/neutron/common/exceptions.py index 65921626de6..3b98b705392 100644 --- a/neutron/common/exceptions.py +++ b/neutron/common/exceptions.py @@ -31,6 +31,15 @@ class QosRuleNotFound(e.NotFound): "could not be found.") +class QoSPolicyDefaultAlreadyExists(e.Conflict): + message = _("A default QoS policy exists for project %(project_id)s.") + + +class QoSPolicyDefaultNotFound(e.Conflict): + message = _("Default QoS policy for project %(project_id)s could not be " + "found.") + + class PortQosBindingNotFound(e.NotFound): message = _("QoS binding for port %(port_id)s and policy %(policy_id)s " "could not be found.") diff --git a/neutron/core_extensions/base.py b/neutron/core_extensions/base.py index 67cbf87e357..1545685f663 100644 --- a/neutron/core_extensions/base.py +++ b/neutron/core_extensions/base.py @@ -20,6 +20,8 @@ import six NETWORK = 'network' PORT = 'port' +EVENT_CREATE = 'create' +EVENT_UPDATE = 'update' CORE_RESOURCES = [NETWORK, PORT] @@ -29,12 +31,14 @@ CORE_RESOURCES = [NETWORK, PORT] class CoreResourceExtension(object): @abc.abstractmethod - def process_fields(self, context, resource_type, + def process_fields(self, context, resource_type, event_type, requested_resource, actual_resource): """Process extension fields. :param context: neutron api request context :param resource_type: core resource type (one of CORE_RESOURCES) + :param event_type: kind of event triggering this action (update, + create) :param requested_resource: resource dict that contains extension fields :param actual_resource: actual resource dict known to plugin """ diff --git a/neutron/core_extensions/qos.py b/neutron/core_extensions/qos.py index 121e1e3ce3b..0b7fb97ecaa 100644 --- a/neutron/core_extensions/qos.py +++ b/neutron/core_extensions/qos.py @@ -63,6 +63,17 @@ class QosCoreResourceExtension(base.CoreResourceExtension): policy.attach_port(port['id']) port[qos_consts.QOS_POLICY_ID] = qos_policy_id + def _create_network_policy(self, context, network, network_changes): + qos_policy_id = network_changes.get(qos_consts.QOS_POLICY_ID) + if not qos_policy_id: + qos_policy_id = policy_object.QosPolicyDefault.get_object( + context, project_id=network['project_id']) + + if qos_policy_id is not None: + policy = self._get_policy_obj(context, qos_policy_id) + policy.attach_network(network['id']) + network[qos_consts.QOS_POLICY_ID] = qos_policy_id + def _update_network_policy(self, context, network, network_changes): old_policy = policy_object.QosPolicy.get_network_policy( context.elevated(), network['id']) @@ -80,11 +91,13 @@ class QosCoreResourceExtension(base.CoreResourceExtension): with db_api.autonested_transaction(context.session): return getattr(self, method_name)(context=context, **kwargs) - def process_fields(self, context, resource_type, + def process_fields(self, context, resource_type, event_type, requested_resource, actual_resource): if (qos_consts.QOS_POLICY_ID in requested_resource and - self.plugin_loaded): - self._exec('_update_%s_policy' % resource_type, context, + self.plugin_loaded): + method_name = ('_%(event)s_%(resource)s_policy' % + {'event': event_type, 'resource': resource_type}) + self._exec(method_name, context, {resource_type: actual_resource, "%s_changes" % resource_type: requested_resource}) diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index fdd9050af31..2a29d33e865 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -2b42d90729da +62c781cb6192 diff --git a/neutron/db/migration/alembic_migrations/versions/pike/expand/62c781cb6192_add_qos_policies_default_table.py b/neutron/db/migration/alembic_migrations/versions/pike/expand/62c781cb6192_add_qos_policies_default_table.py new file mode 100644 index 00000000000..557ac77da73 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/pike/expand/62c781cb6192_add_qos_policies_default_table.py @@ -0,0 +1,44 @@ +# Copyright 2017 Intel Corporation +# +# 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. +# + +"""add is default to qos policies + +Revision ID: 62c781cb6192 +Revises: 2b42d90729da +Create Date: 2017-02-07 13:28:35.894357 + +""" + +# revision identifiers, used by Alembic. +revision = '62c781cb6192' +down_revision = '2b42d90729da' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.create_table( + 'qos_policies_default', + sa.Column('qos_policy_id', + sa.String(length=36), + sa.ForeignKey('qos_policies.id', ondelete='CASCADE'), + nullable=False), + sa.Column('project_id', + sa.String(length=255), + nullable=False, + index=True, + primary_key=True), + ) diff --git a/neutron/db/qos/models.py b/neutron/db/qos/models.py index da5f9b9b31a..3ad1cd06cd9 100644 --- a/neutron/db/qos/models.py +++ b/neutron/db/qos/models.py @@ -73,6 +73,16 @@ class QosPortPolicyBinding(model_base.BASEV2): cascade='delete', lazy='joined')) +class QosPolicyDefault(model_base.BASEV2, + model_base.HasProjectPrimaryKeyIndex): + __tablename__ = 'qos_policies_default' + qos_policy_id = sa.Column(sa.String(36), + sa.ForeignKey('qos_policies.id', + ondelete='CASCADE'), + nullable=False) + revises_on_change = ('qos_policy',) + + class QosBandwidthLimitRule(model_base.HasId, model_base.BASEV2): __tablename__ = 'qos_bandwidth_limit_rules' qos_policy_id = sa.Column(sa.String(36), diff --git a/neutron/extensions/qos.py b/neutron/extensions/qos.py index 020bca40e86..b1baa98c142 100644 --- a/neutron/extensions/qos.py +++ b/neutron/extensions/qos.py @@ -32,8 +32,10 @@ from neutron.objects.qos import rule as rule_object from neutron.plugins.common import constants from neutron.services.qos import qos_consts + ALIAS = "qos" QOS_PREFIX = "/qos" +COLLECTION_NAME = 'policies' # Attribute Map QOS_RULE_COMMON_FIELDS = { @@ -47,7 +49,7 @@ QOS_RULE_COMMON_FIELDS = { } RESOURCE_ATTRIBUTE_MAP = { - 'policies': { + COLLECTION_NAME: { 'id': {'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True, 'primary_key': True}, diff --git a/neutron/extensions/qos_default.py b/neutron/extensions/qos_default.py new file mode 100644 index 00000000000..d8534a0c31f --- /dev/null +++ b/neutron/extensions/qos_default.py @@ -0,0 +1,80 @@ +# Copyright (c) 2017 Intel Corporation. +# All rights reserved. +# +# 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 import converters +from neutron_lib.api import extensions + +from neutron.extensions import qos + + +# The alias of the extension. +ALIAS = 'qos-default' + +# The name of the extension. +NAME = 'QoS default policy' + +# The description of the extension. +DESCRIPTION = 'Expose the QoS default policy per project' + +# A timestamp of when the extension was introduced. +TIMESTAMP = '2017-041-06T10:00:00-00:00' + +# The list of required extensions. +REQUIRED_EXTENSIONS = [qos.ALIAS] + +# The list of optional extensions. +OPTIONAL_EXTENSIONS = None + +# The resource attribute map for the extension. +RESOURCE_ATTRIBUTE_MAP = { + qos.COLLECTION_NAME: { + 'is_default': {'allow_post': True, + 'allow_put': True, + 'default': False, + 'convert_to': converters.convert_to_boolean, + 'is_visible': True} + } +} + + +class Qos_default(extensions.ExtensionDescriptor): + + @classmethod + def get_name(cls): + return NAME + + @classmethod + def get_alias(cls): + return ALIAS + + @classmethod + def get_description(cls): + return DESCRIPTION + + @classmethod + def get_updated(cls): + return TIMESTAMP + + def get_required_extensions(self): + return REQUIRED_EXTENSIONS or [] + + def get_optional_extensions(self): + return OPTIONAL_EXTENSIONS or [] + + def get_extended_resources(self, version): + if version == "2.0": + return RESOURCE_ATTRIBUTE_MAP + else: + return {} diff --git a/neutron/objects/qos/policy.py b/neutron/objects/qos/policy.py index 8057f4131de..50e096ef5ba 100644 --- a/neutron/objects/qos/policy.py +++ b/neutron/objects/qos/policy.py @@ -20,7 +20,6 @@ from oslo_versionedobjects import base as obj_base from oslo_versionedobjects import exception from oslo_versionedobjects import fields as obj_fields -from neutron._i18n import _ from neutron.common import constants as n_const from neutron.common import exceptions from neutron.db import api as db_api @@ -28,6 +27,7 @@ from neutron.db import models_v2 from neutron.db.qos import api as qos_db_api from neutron.db.qos import models as qos_db_model from neutron.db.rbac_db_models import QosPolicyRBAC +from neutron.objects import base as base_db from neutron.objects import common_types from neutron.objects.db import api as obj_db_api from neutron.objects.qos import rule as rule_obj_impl @@ -42,7 +42,8 @@ class QosPolicy(rbac_db.NeutronRbacObject): # Version 1.3: Added standard attributes (created_at, revision, etc) # Version 1.4: Changed tenant_id to project_id # Version 1.5: Direction for bandwidth limit rule added - VERSION = '1.5' + # Version 1.6: Added "is_default" field + VERSION = '1.6' # required by RbacNeutronMetaclass rbac_db_model = QosPolicyRBAC @@ -57,32 +58,37 @@ class QosPolicy(rbac_db.NeutronRbacObject): 'name': obj_fields.StringField(), 'shared': obj_fields.BooleanField(default=False), 'rules': obj_fields.ListOfObjectsField('QosRule', subclasses=True), + 'is_default': obj_fields.BooleanField(default=False), } fields_no_update = ['id', 'project_id'] - synthetic_fields = ['rules'] + synthetic_fields = ['rules', 'is_default'] + + extra_filter_names = {'is_default'} binding_models = {'network': network_binding_model, 'port': port_binding_model} def obj_load_attr(self, attrname): - if attrname == 'project_id': - return super(QosPolicy, self).obj_load_attr(attrname) + if attrname == 'rules': + return self._reload_rules() + elif attrname == 'is_default': + return self._reload_is_default() + return super(QosPolicy, self).obj_load_attr(attrname) - if attrname != 'rules': - raise exceptions.ObjectActionError( - action='obj_load_attr', - reason=_('unable to load %s') % attrname) - - if not hasattr(self, attrname): - self.reload_rules() - - def reload_rules(self): + def _reload_rules(self): rules = rule_obj_impl.get_rules(self.obj_context, self.id) setattr(self, 'rules', rules) self.obj_reset_changes(['rules']) + def _reload_is_default(self): + if self.get_default() == self.id: + setattr(self, 'is_default', True) + else: + setattr(self, 'is_default', False) + self.obj_reset_changes(['is_default']) + def get_rule_by_id(self, rule_id): """Return rule specified by rule_id. @@ -107,7 +113,8 @@ class QosPolicy(rbac_db.NeutronRbacObject): not cls.is_accessible(context, policy_obj)): return - policy_obj.reload_rules() + policy_obj.obj_load_attr('rules') + policy_obj.obj_load_attr('is_default') return policy_obj @classmethod @@ -124,7 +131,8 @@ class QosPolicy(rbac_db.NeutronRbacObject): for obj in objs: if not cls.is_accessible(context, obj): continue - obj.reload_rules() + obj.obj_load_attr('rules') + obj.obj_load_attr('is_default') result.append(obj) return result @@ -149,7 +157,18 @@ class QosPolicy(rbac_db.NeutronRbacObject): def create(self): with db_api.autonested_transaction(self.obj_context.session): super(QosPolicy, self).create() - self.reload_rules() + if self.is_default: + self.set_default() + self.obj_load_attr('rules') + + def update(self): + with db_api.autonested_transaction(self.obj_context.session): + if 'is_default' in self.obj_what_changed(): + if self.is_default: + self.set_default() + else: + self.unset_default() + super(QosPolicy, self).update() def delete(self): with db_api.autonested_transaction(self.obj_context.session): @@ -184,6 +203,28 @@ class QosPolicy(rbac_db.NeutronRbacObject): policy_id=self.id, port_id=port_id) + def set_default(self): + if not self.get_default(): + qos_default_policy = QosPolicyDefault(self.obj_context, + qos_policy_id=self.id, + project_id=self.project_id) + qos_default_policy.create() + elif self.get_default() != self.id: + raise exceptions.QoSPolicyDefaultAlreadyExists( + project_id=self.project_id) + + def unset_default(self): + if self.get_default() == self.id: + qos_default_policy = QosPolicyDefault.get_object( + self.obj_context, project_id=self.project_id) + qos_default_policy.delete() + + def get_default(self): + qos_default_policy = QosPolicyDefault.get_object( + self.obj_context, project_id=self.project_id) + if qos_default_policy: + return qos_default_policy.qos_policy_id + def get_bound_networks(self): return qos_db_api.get_network_ids_by_network_policy_binding( self.obj_context, self.id) @@ -264,3 +305,21 @@ class QosPolicy(rbac_db.NeutronRbacObject): if 'rules' in primitive: primitive['rules'] = filter_ingress_bandwidth_limit_rules( primitive['rules']) + + if _target_version < (1, 6): + primitive.pop('is_default', None) + + +@obj_base.VersionedObjectRegistry.register +class QosPolicyDefault(base_db.NeutronDbObject): + # Version 1.0: Initial version + VERSION = '1.0' + + db_model = qos_db_model.QosPolicyDefault + + fields = { + 'qos_policy_id': common_types.UUIDField(), + 'project_id': obj_fields.StringField(), + } + + primary_keys = ['project_id'] diff --git a/neutron/plugins/ml2/extensions/qos.py b/neutron/plugins/ml2/extensions/qos.py index 62c69a001b3..46f11b0e9e3 100644 --- a/neutron/plugins/ml2/extensions/qos.py +++ b/neutron/plugins/ml2/extensions/qos.py @@ -32,13 +32,15 @@ class QosExtensionDriver(api.ExtensionDriver): def process_create_network(self, context, data, result): self.core_ext_handler.process_fields( - context, base_core.NETWORK, data, result) + context, base_core.NETWORK, base_core.EVENT_CREATE, data, result) - process_update_network = process_create_network + def process_update_network(self, context, data, result): + self.core_ext_handler.process_fields( + context, base_core.NETWORK, base_core.EVENT_UPDATE, data, result) def process_create_port(self, context, data, result): self.core_ext_handler.process_fields( - context, base_core.PORT, data, result) + context, base_core.PORT, base_core.EVENT_UPDATE, data, result) process_update_port = process_create_port diff --git a/neutron/services/qos/qos_plugin.py b/neutron/services/qos/qos_plugin.py index 58a0e5db23b..0da98691ceb 100644 --- a/neutron/services/qos/qos_plugin.py +++ b/neutron/services/qos/qos_plugin.py @@ -38,7 +38,9 @@ class QoSPlugin(qos.QoSPluginBase): service parameters over ports and networks. """ - supported_extension_aliases = ['qos', 'qos-bw-limit-direction'] + supported_extension_aliases = ['qos', + 'qos-bw-limit-direction', + 'qos-default'] __native_pagination_support = True __native_sorting_support = True @@ -299,7 +301,7 @@ class QoSPlugin(qos.QoSPluginBase): checker.check_bandwidth_rule_conflict(policy, rule_data) rule = rule_cls(context, qos_policy_id=policy_id, **rule_data) rule.create() - policy.reload_rules() + policy.obj_load_attr('rules') self.validate_policy(context, policy) self.driver_manager.call(qos_consts.UPDATE_POLICY_PRECOMMIT, context, policy) @@ -338,7 +340,7 @@ class QoSPlugin(qos.QoSPluginBase): rule = rule_cls(context, id=rule_id) rule.update_fields(rule_data, reset_changes=True) rule.update() - policy.reload_rules() + policy.obj_load_attr('rules') self.validate_policy(context, policy) self.driver_manager.call(qos_consts.UPDATE_POLICY_PRECOMMIT, context, policy) @@ -366,7 +368,7 @@ class QoSPlugin(qos.QoSPluginBase): policy = self._get_policy_obj(context, policy_id) rule = policy.get_rule_by_id(rule_id) rule.delete() - policy.reload_rules() + policy.obj_load_attr('rules') self.driver_manager.call(qos_consts.UPDATE_POLICY_PRECOMMIT, context, policy) diff --git a/neutron/tests/fullstack/resources/client.py b/neutron/tests/fullstack/resources/client.py index a77cf3a4f9f..b2ffb4dedeb 100644 --- a/neutron/tests/fullstack/resources/client.py +++ b/neutron/tests/fullstack/resources/client.py @@ -132,12 +132,14 @@ class ClientFixture(fixtures.Fixture): router=router_id, body=body) return router_interface_info - def create_qos_policy(self, tenant_id, name, description, shared): + def create_qos_policy(self, tenant_id, name, description, shared, + is_default): policy = self.client.create_qos_policy( body={'policy': {'name': name, 'description': description, 'shared': shared, - 'tenant_id': tenant_id}}) + 'tenant_id': tenant_id, + 'is_default': is_default}}) def detach_and_delete_policy(): qos_policy_id = policy['policy']['id'] diff --git a/neutron/tests/fullstack/test_qos.py b/neutron/tests/fullstack/test_qos.py index e5efe5e0c4f..1f5d2376b73 100644 --- a/neutron/tests/fullstack/test_qos.py +++ b/neutron/tests/fullstack/test_qos.py @@ -15,6 +15,7 @@ import functools from neutron_lib import constants +from neutronclient.common import exceptions from oslo_utils import uuidutils from neutron.agent.linux import tc_lib @@ -72,7 +73,7 @@ class BaseQoSRuleTestCase(object): def _create_qos_policy(self): return self.safe_client.create_qos_policy( self.tenant_id, 'fs_policy', 'Fullstack testing policy', - shared='False') + shared='False', is_default='False') def _prepare_vm_with_qos_policy(self, rule_add_functions): qos_policy = self._create_qos_policy() @@ -282,3 +283,60 @@ class TestQoSWithL2Population(base.BaseFullStackTestCase): rule_types = {t['type'] for t in res['rule_types']} expected_rules = set(ovs_drv.SUPPORTED_RULES) self.assertEqual(expected_rules, rule_types) + + +class TestQoSPolicyIsDefault(base.BaseFullStackTestCase): + + NAME = 'fs_policy' + DESCRIPTION = 'Fullstack testing policy' + SHARED = True + + def setUp(self): + host_desc = [] # No need to register agents for this test case + env_desc = environment.EnvironmentDescription(qos=True) + env = environment.Environment(env_desc, host_desc) + super(TestQoSPolicyIsDefault, self).setUp(env) + + def _create_qos_policy(self, project_id, is_default): + return self.safe_client.create_qos_policy( + project_id, self.NAME, self.DESCRIPTION, shared=self.SHARED, + is_default=is_default) + + def _update_qos_policy(self, qos_policy_id, is_default): + return self.client.update_qos_policy( + qos_policy_id, body={'policy': {'is_default': is_default}}) + + def test_create_one_default_qos_policy_per_project(self): + project_ids = [uuidutils.generate_uuid(), uuidutils.generate_uuid()] + for project_id in project_ids: + qos_policy = self._create_qos_policy(project_id, True) + self.assertTrue(qos_policy['is_default']) + self.assertEqual(project_id, qos_policy['project_id']) + qos_policy = self._create_qos_policy(project_id, False) + self.assertFalse(qos_policy['is_default']) + self.assertEqual(project_id, qos_policy['project_id']) + + def test_create_two_default_qos_policies_per_project(self): + project_id = uuidutils.generate_uuid() + qos_policy = self._create_qos_policy(project_id, True) + self.assertTrue(qos_policy['is_default']) + self.assertEqual(project_id, qos_policy['project_id']) + self.assertRaises(exceptions.Conflict, + self._create_qos_policy, project_id, True) + + def test_update_default_status(self): + project_ids = [uuidutils.generate_uuid(), uuidutils.generate_uuid()] + for project_id in project_ids: + qos_policy = self._create_qos_policy(project_id, True) + self.assertTrue(qos_policy['is_default']) + qos_policy = self._update_qos_policy(qos_policy['id'], False) + self.assertTrue(qos_policy['policy']['is_default']) + + def test_update_default_status_conflict(self): + project_id = uuidutils.generate_uuid() + qos_policy_1 = self._create_qos_policy(project_id, True) + self.assertTrue(qos_policy_1['is_default']) + qos_policy_2 = self._create_qos_policy(project_id, False) + self.assertFalse(qos_policy_2['is_default']) + self.assertRaises(exceptions.Conflict, + self._update_qos_policy, qos_policy_2['id'], True) diff --git a/neutron/tests/tempest/api/test_qos.py b/neutron/tests/tempest/api/test_qos.py index 728029786f7..287207e7460 100644 --- a/neutron/tests/tempest/api/test_qos.py +++ b/neutron/tests/tempest/api/test_qos.py @@ -82,7 +82,8 @@ class QosTestJSON(base.BaseAdminNetworkTest): def test_policy_update(self): policy = self.create_qos_policy(name='test-policy', description='', - shared=False) + shared=False, + tenant_id=self.admin_client.tenant_id) self.admin_client.update_qos_policy(policy['id'], description='test policy desc2', shared=True) @@ -119,7 +120,8 @@ class QosTestJSON(base.BaseAdminNetworkTest): def test_shared_policy_update(self): policy = self.create_qos_policy(name='test-policy', description='', - shared=True) + shared=True, + tenant_id=self.admin_client.tenant_id) self.admin_client.update_qos_policy(policy['id'], description='test policy desc2') @@ -606,7 +608,8 @@ class RbacSharedQosPoliciesTest(base.BaseAdminNetworkTest): def test_policy_sharing_with_wildcard(self): qos_pol = self.create_qos_policy( name=data_utils.rand_name('test-policy'), - description='test-shared-policy', shared=False) + description='test-shared-policy', shared=False, + tenant_id=self.admin_client.tenant_id) self.assertNotIn(qos_pol, self.client2.list_qos_policies()['policies']) # test update shared False -> True diff --git a/neutron/tests/unit/core_extensions/test_qos.py b/neutron/tests/unit/core_extensions/test_qos.py index a550e8fdf56..152f88314fd 100644 --- a/neutron/tests/unit/core_extensions/test_qos.py +++ b/neutron/tests/unit/core_extensions/test_qos.py @@ -19,6 +19,7 @@ from neutron_lib import context from neutron.common import exceptions as n_exc from neutron.core_extensions import base as base_core from neutron.core_extensions import qos as qos_core +from neutron.objects.qos import policy from neutron.plugins.common import constants as plugin_constants from neutron.services.qos import qos_consts from neutron.tests import base @@ -41,7 +42,7 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): def test_process_fields_no_qos_policy_id(self): self.core_extension.process_fields( - self.context, base_core.PORT, {}, None) + self.context, base_core.PORT, mock.ANY, {}, None) self.assertFalse(self.policy_m.called) def _mock_plugin_loaded(self, plugin_loaded): @@ -54,7 +55,7 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): def test_process_fields_no_qos_plugin_loaded(self): with self._mock_plugin_loaded(False): self.core_extension.process_fields( - self.context, base_core.PORT, + self.context, base_core.PORT, mock.ANY, {qos_consts.QOS_POLICY_ID: None}, None) self.assertFalse(self.policy_m.called) @@ -66,7 +67,7 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): qos_policy = mock.MagicMock() self.policy_m.get_object = mock.Mock(return_value=qos_policy) self.core_extension.process_fields( - self.context, base_core.PORT, + self.context, base_core.PORT, base_core.EVENT_UPDATE, {qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_port) @@ -85,7 +86,7 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): new_qos_policy = mock.MagicMock() self.policy_m.get_object = mock.Mock(return_value=new_qos_policy) self.core_extension.process_fields( - self.context, base_core.PORT, + self.context, base_core.PORT, base_core.EVENT_UPDATE, {qos_consts.QOS_POLICY_ID: qos_policy2_id}, actual_port) @@ -105,7 +106,7 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): new_qos_policy = mock.MagicMock() self.policy_m.get_object = mock.Mock(return_value=new_qos_policy) self.core_extension.process_fields( - self.context, base_core.PORT, + self.context, base_core.PORT, base_core.EVENT_UPDATE, {qos_consts.QOS_POLICY_ID: None}, actual_port) @@ -125,7 +126,7 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): self.policy_m.get_port_policy = mock.Mock( return_value=old_qos_policy) self.core_extension.process_fields( - context, base_core.PORT, + context, base_core.PORT, base_core.EVENT_UPDATE, {qos_consts.QOS_POLICY_ID: None}, actual_port) @@ -157,7 +158,7 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): shared=False, policy_tenant_id=self.context.tenant_id) - def test_process_resource_network_updated_no_policy(self): + def test_process_resource_update_network_updated_no_policy(self): with self._mock_plugin_loaded(True): network_id = mock.Mock() qos_policy_id = mock.Mock() @@ -169,14 +170,14 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): new_qos_policy = mock.MagicMock() self.policy_m.get_object = mock.Mock(return_value=new_qos_policy) self.core_extension.process_fields( - self.context, base_core.NETWORK, + self.context, base_core.NETWORK, base_core.EVENT_UPDATE, {qos_consts.QOS_POLICY_ID: None}, actual_network) old_qos_policy.detach_network.assert_called_once_with(network_id) self.assertIsNone(actual_network['qos_policy_id']) - def test_process_fields_network_new_policy(self): + def test_process_fields_update_network_new_policy(self): with self._mock_plugin_loaded(True): qos_policy_id = mock.Mock() actual_network = {'id': mock.Mock(), @@ -184,13 +185,13 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): qos_policy = mock.MagicMock() self.policy_m.get_object = mock.Mock(return_value=qos_policy) self.core_extension.process_fields( - self.context, base_core.NETWORK, + self.context, base_core.NETWORK, base_core.EVENT_UPDATE, {qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_network) qos_policy.attach_network.assert_called_once_with( actual_network['id']) - def test_process_fields_network_updated_policy(self): + def test_process_fields_update_network_updated_policy(self): with self._mock_plugin_loaded(True): qos_policy_id = mock.Mock() network_id = mock.Mock() @@ -202,7 +203,7 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): new_qos_policy = mock.MagicMock() self.policy_m.get_object = mock.Mock(return_value=new_qos_policy) self.core_extension.process_fields( - self.context, base_core.NETWORK, + self.context, base_core.NETWORK, base_core.EVENT_UPDATE, {qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_network) old_qos_policy.detach_network.assert_called_once_with(network_id) @@ -220,12 +221,12 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): old_qos_policy.tenant_id = policy_tenant_id self.policy_m.get_network_policy.return_value = old_qos_policy self.core_extension.process_fields( - context, base_core.NETWORK, + context, base_core.NETWORK, base_core.EVENT_UPDATE, {qos_consts.QOS_POLICY_ID: None}, actual_network) old_qos_policy.detach_network.assert_called_once_with(network_id) - def test_process_fields_network_updated_remove_shared_policy(self): + def test_process_fields_update_network_updated_remove_shared_policy(self): self._process_network_updated_policy( context=self.non_admin_context, shared=True, @@ -237,13 +238,13 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): shared=True, policy_tenant_id=self.non_admin_context.tenant_id) - def test_process_fields_network_updated_admin_remove_provided_policy(self): + def test_process_fields_update_network_admin_remove_provided_policy(self): self._process_network_updated_policy( context=self.context, shared=True, policy_tenant_id=self.non_admin_context.tenant_id) - def test_process_fields_network_updated_remove_provided_policy(self): + def test_process_fields_update_network_remove_provided_policy(self): self.policy_m.is_accessible.return_value = False self.assertRaises(n_exc.PolicyRemoveAuthorizationError, self._process_network_updated_policy, @@ -251,6 +252,58 @@ class QosCoreResourceExtensionTestCase(base.BaseTestCase): shared=False, policy_tenant_id=self.context.tenant_id) + def test_process_fields_create_network(self): + with self._mock_plugin_loaded(True): + qos_policy_id = mock.Mock() + network_id = mock.Mock() + actual_network = {'id': network_id, + qos_consts.QOS_POLICY_ID: qos_policy_id} + self.policy_m.get_network_policy = mock.Mock( + return_value=qos_policy_id) + qos_policy = mock.MagicMock() + self.policy_m.get_object = mock.Mock(return_value=qos_policy) + self.core_extension.process_fields( + self.context, base_core.NETWORK, base_core.EVENT_CREATE, + actual_network, actual_network) + qos_policy.attach_network.assert_called_once_with(network_id) + + def test_process_fields_create_network_no_policy(self): + with self._mock_plugin_loaded(True): + project_id = mock.Mock() + network_id = mock.Mock() + actual_network = {'project_id': project_id, + 'id': network_id, + qos_consts.QOS_POLICY_ID: None} + qos_policy_id = mock.Mock() + qos_policy = mock.MagicMock() + with mock.patch.object(policy.QosPolicyDefault, "get_object", + return_value=qos_policy_id) as mock_get_default_policy_id: + self.policy_m.get_object = mock.Mock(return_value=qos_policy) + self.core_extension.process_fields( + self.context, base_core.NETWORK, base_core.EVENT_CREATE, + actual_network, actual_network) + qos_policy.attach_network.assert_called_once_with(network_id) + mock_get_default_policy_id.assert_called_once_with( + self.context, project_id=project_id) + + def test_process_fields_create_network_no_default_policy(self): + with self._mock_plugin_loaded(True): + project_id = mock.Mock() + network_id = mock.Mock() + actual_network = {'project_id': project_id, + 'id': network_id, + qos_consts.QOS_POLICY_ID: None} + qos_policy = mock.MagicMock() + with mock.patch.object(policy.QosPolicyDefault, "get_object", + return_value=None) as mock_get_default_policy_id: + self.policy_m.get_object = mock.Mock(return_value=qos_policy) + self.core_extension.process_fields( + self.context, base_core.NETWORK, base_core.EVENT_CREATE, + actual_network, actual_network) + qos_policy.attach_network.assert_not_called() + mock_get_default_policy_id.assert_called_once_with( + self.context, project_id=project_id) + def test_extract_fields_plugin_not_loaded(self): with self._mock_plugin_loaded(False): fields = self.core_extension.extract_fields(None, None) diff --git a/neutron/tests/unit/objects/qos/test_policy.py b/neutron/tests/unit/objects/qos/test_policy.py index c5c7f475644..b30d537d560 100644 --- a/neutron/tests/unit/objects/qos/test_policy.py +++ b/neutron/tests/unit/objects/qos/test_policy.py @@ -11,6 +11,7 @@ # under the License. import mock +from oslo_utils import uuidutils from oslo_versionedobjects import exception import testtools @@ -39,6 +40,8 @@ class QosPolicyObjectTestCase(test_base.BaseObjectIfaceTestCase): def setUp(self): super(QosPolicyObjectTestCase, self).setUp() + mock.patch.object(policy.QosPolicy, 'get_default').start() + # qos_policy_ids will be incorrect, but we don't care in this test self.db_qos_bandwidth_rules = [ self.get_random_db_fields(rule.QosBandwidthLimitRule) @@ -98,18 +101,16 @@ class QosPolicyObjectTestCase(test_base.BaseObjectIfaceTestCase): def test_get_object(self): admin_context = self.context.elevated() - with mock.patch.object( - db_api, 'get_object', - return_value=self.db_objs[0]) as get_object_mock: - with mock.patch.object(self.context, - 'elevated', - return_value=admin_context) as context_mock: - obj = self._test_class.get_object(self.context, id='fake_id') - self.assertTrue(self._is_test_class(obj)) - self._check_equal(self.objs[0], obj) - context_mock.assert_called_once_with() - get_object_mock.assert_called_once_with( - admin_context, self._test_class.db_model, id='fake_id') + with mock.patch.object(db_api, 'get_object', + return_value=self.db_objs[0]) as get_object_mock, \ + mock.patch.object(self.context, 'elevated', + return_value=admin_context) as context_mock: + obj = self._test_class.get_object(self.context, id='fake_id') + self.assertTrue(self._is_test_class(obj)) + self._check_equal(self.objs[0], obj) + context_mock.assert_called_once_with() + get_object_mock.assert_called_once_with( + admin_context, self._test_class.db_model, id='fake_id') def test_to_dict_makes_primitive_field_value(self): # is_shared_with_tenant requires DB @@ -149,7 +150,7 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase, rules.append(rule_obj) if reload_rules: - policy_obj.reload_rules() + policy_obj.obj_load_attr('rules') return policy_obj, rules def test_attach_network_get_network_policy(self): @@ -303,6 +304,42 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase, self.assertRaises(n_exc.NetworkQosBindingNotFound, policy_obj.detach_network, self._network_id) + @mock.patch.object(policy.QosPolicyDefault, 'create') + def test_set_default_no_default_policy_exists(self, mock_default_create): + obj = self._create_test_policy() + with mock.patch.object(obj, 'get_default', return_value=None): + obj.set_default() + mock_default_create.assert_called_once_with() + + def test_set_default_default_policy_exists(self): + obj = self._create_test_policy() + with mock.patch.object(obj, 'get_default', return_value=mock.Mock()): + self.assertRaises(n_exc.QoSPolicyDefaultAlreadyExists, + obj.set_default) + + def test_set_default_is_default_policy(self): + obj = self._create_test_policy() + with mock.patch.object(obj, 'get_default', return_value=obj.id), \ + mock.patch.object(obj, 'set_default'): + obj.set_default() + + @mock.patch.object(policy.QosPolicyDefault, 'get_object') + @mock.patch.object(policy.QosPolicyDefault, 'delete') + def test_unset_default_default_policy_exists(self, mock_default_delete, + mock_default_get): + obj = self._create_test_policy() + with mock.patch.object(obj, 'get_default', return_value=obj.id): + mock_default_get.return_value = policy.QosPolicyDefault() + obj.unset_default() + mock_default_get.assert_called_once_with(obj.obj_context, + project_id=obj.project_id) + mock_default_delete.assert_called_once_with() + + def test_unset_default_no_default_policy_exists(self): + obj = self._create_test_policy() + with mock.patch.object(obj, 'get_default', return_value=None): + obj.unset_default() + def test_synthetic_rule_fields(self): policy_obj, rule_obj = self._create_test_policy_with_rules( [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT]) @@ -363,9 +400,16 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase, [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT]) self.assertEqual([], policy_obj.rules) - policy_obj.reload_rules() + policy_obj._reload_rules() self.assertEqual(rule_obj, policy_obj.rules) + def test_reload_is_default(self): + policy_obj = self._create_test_policy() + self.assertFalse(policy_obj.is_default) + policy_obj.set_default() + policy_obj._reload_is_default() + self.assertTrue(policy_obj.is_default) + def test_get_bound_tenant_ids_returns_set_of_tenant_ids(self): obj = self._create_test_policy() obj.attach_port(self._port['id']) @@ -475,13 +519,23 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase, self.assertIn(rule_objs[1], policy_obj_v1_4.rules) self.assertIn(rule_objs[2], policy_obj_v1_4.rules) - def test_filter_by_shared(self): + def test_v1_6_to_v1_5_drops_is_default(self): + policy_new = self._create_test_policy() + + policy_v1_5 = policy_new.obj_to_primitive(target_version='1.5') + self.assertNotIn('is_default', policy_v1_5['versioned_object.data']) + + @mock.patch.object(policy.QosPolicy, 'unset_default') + def test_filter_by_shared(self, *mocks): + project_id = uuidutils.generate_uuid() policy_obj = policy.QosPolicy( - self.context, name='shared-policy', shared=True) + self.context, name='shared-policy', shared=True, + project_id=project_id, is_default=False) policy_obj.create() policy_obj = policy.QosPolicy( - self.context, name='private-policy', shared=False) + self.context, name='private-policy', shared=False, + project_id=project_id) policy_obj.create() shared_policies = policy.QosPolicy.get_objects( @@ -499,3 +553,8 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase, # QoSPolicy currently cannot be loaded using constant queries number. # It can be reworked in follow-up patch. pass + + +class QosPolicyDefaultObjectTestCase(test_base.BaseObjectIfaceTestCase): + + _test_class = policy.QosPolicyDefault diff --git a/neutron/tests/unit/objects/qos/test_rule.py b/neutron/tests/unit/objects/qos/test_rule.py index e675ce4a83d..931bcb41e6b 100644 --- a/neutron/tests/unit/objects/qos/test_rule.py +++ b/neutron/tests/unit/objects/qos/test_rule.py @@ -12,6 +12,7 @@ from neutron_lib import constants +from oslo_utils import uuidutils from oslo_versionedobjects import exception from neutron.common import constants as n_const @@ -150,7 +151,8 @@ class QosBandwidthLimitRuleDbObjectTestCase(test_base.BaseDbObjectTestCase, for obj in self.db_objs: generated_qos_policy_id = obj['qos_policy_id'] policy_obj = policy.QosPolicy(self.context, - id=generated_qos_policy_id) + id=generated_qos_policy_id, + project_id=uuidutils.generate_uuid()) policy_obj.create() @@ -176,7 +178,8 @@ class QosDscpMarkingRuleDbObjectTestCase(test_base.BaseDbObjectTestCase, for obj in self.db_objs: generated_qos_policy_id = obj['qos_policy_id'] policy_obj = policy.QosPolicy(self.context, - id=generated_qos_policy_id) + id=generated_qos_policy_id, + project_id=uuidutils.generate_uuid()) policy_obj.create() @@ -203,5 +206,6 @@ class QosMinimumBandwidthRuleDbObjectTestCase(test_base.BaseDbObjectTestCase, for obj in self.db_objs: generated_qos_policy_id = obj['qos_policy_id'] policy_obj = policy.QosPolicy(self.context, - id=generated_qos_policy_id) + id=generated_qos_policy_id, + project_id=uuidutils.generate_uuid()) policy_obj.create() diff --git a/neutron/tests/unit/objects/test_network.py b/neutron/tests/unit/objects/test_network.py index 1485e624144..794d8d023fc 100644 --- a/neutron/tests/unit/objects/test_network.py +++ b/neutron/tests/unit/objects/test_network.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import mock + from neutron.objects import base as obj_base from neutron.objects import network from neutron.objects.qos import policy @@ -90,7 +92,8 @@ class NetworkDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, testlib_api.SqlTestCase): _test_class = network.Network - def test_qos_policy_id(self): + @mock.patch.object(policy.QosPolicy, 'unset_default') + def test_qos_policy_id(self, *mocks): policy_obj = policy.QosPolicy(self.context) policy_obj.create() @@ -116,7 +119,8 @@ class NetworkDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, obj = network.Network.get_object(self.context, id=obj.id) self.assertIsNone(obj.qos_policy_id) - def test__attach_qos_policy(self): + @mock.patch.object(policy.QosPolicy, 'unset_default') + def test__attach_qos_policy(self, *mocks): obj = self._make_object(self.obj_fields[0]) obj.create() diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index 99d01a575b4..17a19294e4f 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -66,7 +66,8 @@ object_data = { 'QosDscpMarkingRule': '1.3-0313c6554b34fd10c753cb63d638256c', 'QosMinimumBandwidthRule': '1.3-314c3419f4799067cc31cc319080adff', 'QosRuleType': '1.2-e6fd08fcca152c339cbd5e9b94b1b8e7', - 'QosPolicy': '1.5-50460f619c34428ec5651916e938e5a0', + 'QosPolicy': '1.6-4adb0cde3102c10d8970ec9487fd7fe7', + 'QosPolicyDefault': '1.0-59e5060eedb1f06dd0935a244d27d11c', 'Quota': '1.0-6bb6a0f1bd5d66a2134ffa1a61873097', 'QuotaUsage': '1.0-6fbf820368681aac7c5d664662605cf9', 'Reservation': '1.0-49929fef8e82051660342eed51b48f2a', diff --git a/neutron/tests/unit/objects/test_ports.py b/neutron/tests/unit/objects/test_ports.py index 2bebed1520e..b2aaf0a29ec 100644 --- a/neutron/tests/unit/objects/test_ports.py +++ b/neutron/tests/unit/objects/test_ports.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import mock from oslo_utils import uuidutils import testscenarios @@ -262,7 +263,8 @@ class PortDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, obj = ports.Port.get_object(self.context, id=obj.id) self.assertIn(sg2_id, obj.security_group_ids) - def test_qos_policy_id(self): + @mock.patch.object(policy.QosPolicy, 'unset_default') + def test_qos_policy_id(self, *mocks): policy_obj = policy.QosPolicy(self.context) policy_obj.create() @@ -288,7 +290,8 @@ class PortDbObjectTestCase(obj_test_base.BaseDbObjectTestCase, obj = ports.Port.get_object(self.context, id=obj.id) self.assertIsNone(obj.qos_policy_id) - def test__attach_qos_policy(self): + @mock.patch.object(policy.QosPolicy, 'unset_default') + def test__attach_qos_policy(self, *mocks): obj = self._make_object(self.obj_fields[0]) obj.create() diff --git a/neutron/tests/unit/services/qos/test_qos_plugin.py b/neutron/tests/unit/services/qos/test_qos_plugin.py index 8f1339cb0ea..1dfa37c283e 100644 --- a/neutron/tests/unit/services/qos/test_qos_plugin.py +++ b/neutron/tests/unit/services/qos/test_qos_plugin.py @@ -9,6 +9,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + import mock from neutron_lib import context from neutron_lib.plugins import directory @@ -39,13 +40,16 @@ class TestQosPlugin(base.BaseQosTestCase): mock.patch('neutron.objects.db.api.update_object').start() mock.patch('neutron.objects.db.api.delete_object').start() mock.patch('neutron.objects.db.api.get_object').start() - mock.patch( - 'neutron.objects.qos.policy.QosPolicy.obj_load_attr').start() + _mock_qos_load_attr = mock.patch( + 'neutron.objects.qos.policy.QosPolicy.obj_load_attr') + self.mock_qos_load_attr = _mock_qos_load_attr.start() # We don't use real models as per mocks above. We also need to mock-out # methods that work with real data types mock.patch( 'neutron.objects.base.NeutronDbObject.modify_fields_from_db' ).start() + mock.patch.object(policy_object.QosPolicy, 'unset_default').start() + mock.patch.object(policy_object.QosPolicy, 'set_default').start() cfg.CONF.set_override("core_plugin", DB_PLUGIN_KLASS) cfg.CONF.set_override("service_plugins", ["qos"]) @@ -67,7 +71,8 @@ class TestQosPlugin(base.BaseQosTestCase): 'project_id': uuidutils.generate_uuid(), 'name': 'test-policy', 'description': 'Test policy description', - 'shared': True}} + 'shared': True, + 'is_default': False}} self.rule_data = { 'bandwidth_limit_rule': {'id': uuidutils.generate_uuid(), @@ -339,13 +344,15 @@ class TestQosPlugin(base.BaseQosTestCase): 'tenant_id': project_id, 'name': 'test-policy', 'description': 'Test policy description', - 'shared': True}} + 'shared': True, + 'is_default': False}} policy_details = {'id': policy_id, 'project_id': project_id, 'name': 'test-policy', 'description': 'Test policy description', - 'shared': True} + 'shared': True, + 'is_default': False} with mock.patch('neutron.objects.qos.policy.QosPolicy') as QosMocked: self.qos_plugin.create_policy(self.ctxt, tenant_policy) @@ -412,27 +419,23 @@ class TestQosPlugin(base.BaseQosTestCase): def test_create_policy_rule_check_rule_min_less_than_max(self): _policy = self._get_policy() setattr(_policy, "rules", [self.rule]) - with mock.patch('neutron.objects.qos.rule.get_rules', - return_value=[self.rule]) as mock_get_rules, \ - mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', - return_value=_policy) as mock_qos_get_obj: + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy) as mock_qos_get_obj: self.qos_plugin.create_policy_minimum_bandwidth_rule( self.ctxt, _policy.id, self.rule_data) self._validate_driver_params('update_policy') - mock_get_rules.assert_called_once_with(self.ctxt, _policy.id) + self.mock_qos_load_attr.assert_called_once_with('rules') mock_qos_get_obj.assert_called_once_with(self.ctxt, id=_policy.id) def test_create_policy_rule_check_rule_max_more_than_min(self): _policy = self._get_policy() setattr(_policy, "rules", [self.min_rule]) - with mock.patch('neutron.objects.qos.rule.get_rules', - return_value=[self.rule]) as mock_get_rules, \ - mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', - return_value=_policy) as mock_qos_get_obj: + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy) as mock_qos_get_obj: self.qos_plugin.create_policy_bandwidth_limit_rule( self.ctxt, _policy.id, self.rule_data) self._validate_driver_params('update_policy') - mock_get_rules.assert_called_once_with(self.ctxt, _policy.id) + self.mock_qos_load_attr.assert_called_once_with('rules') mock_qos_get_obj.assert_called_once_with(self.ctxt, id=_policy.id) def test_create_policy_rule_check_rule_bwlimit_less_than_minbw(self): @@ -486,39 +489,32 @@ class TestQosPlugin(base.BaseQosTestCase): def test_update_policy_rule_check_rule_min_less_than_max(self): _policy = self._get_policy() setattr(_policy, "rules", [self.rule]) - with mock.patch('neutron.objects.qos.rule.get_rules', - return_value=[self.rule]) as mock_get_rules, \ - mock.patch( - 'neutron.objects.qos.policy.QosPolicy.get_object', - return_value=_policy): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): self.qos_plugin.update_policy_bandwidth_limit_rule( self.ctxt, self.rule.id, self.policy.id, self.rule_data) - mock_get_rules.assert_called_once_with(self.ctxt, _policy.id) + self.mock_qos_load_attr.assert_called_once_with('rules') self._validate_driver_params('update_policy') rules = [self.rule, self.min_rule] setattr(_policy, "rules", rules) - with mock.patch('neutron.objects.qos.rule.get_rules', - return_value=rules) as mock_get_rules, \ - mock.patch( - 'neutron.objects.qos.policy.QosPolicy.get_object', - return_value=_policy): + self.mock_qos_load_attr.reset_mock() + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): self.qos_plugin.update_policy_minimum_bandwidth_rule( self.ctxt, self.min_rule.id, self.policy.id, self.rule_data) - mock_get_rules.assert_called_once_with(self.ctxt, _policy.id) + self.mock_qos_load_attr.assert_called_once_with('rules') self._validate_driver_params('update_policy') def test_update_policy_rule_check_rule_bwlimit_less_than_minbw(self): _policy = self._get_policy() setattr(_policy, "rules", [self.rule]) - with mock.patch('neutron.objects.qos.rule.get_rules', - return_value=[self.rule]), mock.patch( - 'neutron.objects.qos.policy.QosPolicy.get_object', - return_value=_policy): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): self.qos_plugin.update_policy_bandwidth_limit_rule( self.ctxt, self.rule.id, self.policy.id, self.rule_data) - self.assertTrue(getattr(rule_object, "get_rules").called) + self.mock_qos_load_attr.assert_called_once_with('rules') self._validate_driver_params('update_policy') self.rule_data['minimum_bandwidth_rule']['min_kbps'] = 1000 with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', @@ -532,13 +528,11 @@ class TestQosPlugin(base.BaseQosTestCase): def test_update_policy_rule_check_rule_minbw_gr_than_bwlimit(self): _policy = self._get_policy() setattr(_policy, "rules", [self.min_rule]) - with mock.patch('neutron.objects.qos.rule.get_rules', - return_value=[self.min_rule]), mock.patch( - 'neutron.objects.qos.policy.QosPolicy.get_object', - return_value=_policy): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): self.qos_plugin.update_policy_minimum_bandwidth_rule( self.ctxt, self.min_rule.id, self.policy.id, self.rule_data) - self.assertTrue(getattr(rule_object, "get_rules").called) + self.mock_qos_load_attr.assert_called_once_with('rules') self._validate_driver_params('update_policy') self.rule_data['bandwidth_limit_rule']['max_kbps'] = 1 with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', diff --git a/neutron/tests/unit/services/revisions/test_revision_plugin.py b/neutron/tests/unit/services/revisions/test_revision_plugin.py index 21a0e94a1f9..ff9699834d7 100644 --- a/neutron/tests/unit/services/revisions/test_revision_plugin.py +++ b/neutron/tests/unit/services/revisions/test_revision_plugin.py @@ -170,7 +170,8 @@ class TestRevisionPlugin(test_plugin.Ml2PluginV2TestCase): with self.port() as port: rev = port['port']['revision_number'] qos_plugin = directory.get_plugin('QOS') - qos_policy = {'policy': {'name': "policy1", + qos_policy = {'policy': {'id': uuidutils.generate_uuid(), + 'name': "policy1", 'project_id': uuidutils.generate_uuid()}} qos_obj = qos_plugin.create_policy(self.ctx, qos_policy) data = {'port': {'qos_policy_id': qos_obj['id']}} @@ -182,7 +183,8 @@ class TestRevisionPlugin(test_plugin.Ml2PluginV2TestCase): with self.network() as network: rev = network['network']['revision_number'] qos_plugin = directory.get_plugin('QOS') - qos_policy = {'policy': {'name': "policy1", + qos_policy = {'policy': {'id': uuidutils.generate_uuid(), + 'name': "policy1", 'project_id': uuidutils.generate_uuid()}} qos_obj = qos_plugin.create_policy(self.ctx, qos_policy) data = {'network': {'qos_policy_id': qos_obj['id']}} diff --git a/releasenotes/notes/add_is_default_to_qos_policies-f7c6bbac08d474d5.yaml b/releasenotes/notes/add_is_default_to_qos_policies-f7c6bbac08d474d5.yaml new file mode 100644 index 00000000000..afda1e9d62a --- /dev/null +++ b/releasenotes/notes/add_is_default_to_qos_policies-f7c6bbac08d474d5.yaml @@ -0,0 +1,6 @@ +--- +prelude: > + Add 'default' behaviour to QoS policies +features: + - Neutron now supports having a default QoS policy in a project, assigned + automatically to all new networks created.