import six
from oslo_config import cfg
+from oslo_db import api as oslo_db_api
from oslo_db import exception as os_db_exception
from oslo_db.sqlalchemy import session
+ from oslo_utils import uuidutils
from sqlalchemy import exc
from sqlalchemy import orm
--- /dev/null
- 8675309a5c4f
++48153cb5f051
+4ffceebfada
+kilo
--- /dev/null
-Revises: 599c6a226151
+ # Copyright 2015 Huawei Technologies India Pvt Ltd, 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.
+ #
+
+ """qos db changes
+
+ Revision ID: 48153cb5f051
-down_revision = '52c5312f6baf'
++Revises: 8675309a5c4f
+ Create Date: 2015-06-24 17:03:34.965101
+
+ """
+
+ # revision identifiers, used by Alembic.
+ revision = '48153cb5f051'
++down_revision = '8675309a5c4f'
+
+ from alembic import op
+ import sqlalchemy as sa
+
+ from neutron.api.v2 import attributes as attrs
+
+
+ def upgrade():
+ op.create_table(
+ 'qos_policies',
+ sa.Column('id', sa.String(length=36), primary_key=True),
+ sa.Column('name', sa.String(length=attrs.NAME_MAX_LEN)),
+ sa.Column('description', sa.String(length=attrs.DESCRIPTION_MAX_LEN)),
+ sa.Column('shared', sa.Boolean()),
+ sa.Column('tenant_id', sa.String(length=attrs.TENANT_ID_MAX_LEN),
+ index=True))
+
+ op.create_table(
+ 'qos_network_policy_bindings',
+ sa.Column('policy_id', sa.String(length=36),
+ sa.ForeignKey('qos_policies.id', ondelete='CASCADE'),
+ nullable=False),
+ sa.Column('network_id', sa.String(length=36),
+ sa.ForeignKey('networks.id', ondelete='CASCADE'),
+ nullable=False, unique=True))
+
+ op.create_table(
+ 'qos_port_policy_bindings',
+ sa.Column('policy_id', sa.String(length=36),
+ sa.ForeignKey('qos_policies.id', ondelete='CASCADE'),
+ nullable=False),
+ sa.Column('port_id', sa.String(length=36),
+ sa.ForeignKey('ports.id', ondelete='CASCADE'),
+ nullable=False, unique=True))
+
+ op.create_table(
+ 'qos_rules',
+ sa.Column('id', sa.String(length=36), primary_key=True),
+ sa.Column('qos_policy_id', sa.String(length=36),
+ sa.ForeignKey('qos_policies.id', ondelete='CASCADE'),
+ nullable=False),
+ sa.Column('type', sa.String(length=255)))
+
+ op.create_table(
+ 'qos_bandwidth_limit_rules',
+ sa.Column('id', sa.String(length=36),
+ sa.ForeignKey('qos_rules.id', ondelete='CASCADE'),
+ nullable=False,
+ primary_key=True),
+ sa.Column('max_kbps', sa.Integer()),
+ sa.Column('max_burst_kbps', sa.Integer()))
from neutron.db import models_v2 # noqa
from neutron.db import portbindings_db # noqa
from neutron.db import portsecurity_db # noqa
+ from neutron.db.qos import models as qos_models # noqa
from neutron.db import quota_db # noqa
+from neutron.db import rbac_db_models # noqa
from neutron.db import securitygroups_db # noqa
from neutron.db import servicetype_db # noqa
from neutron.ipam.drivers.neutrondb_ipam import db_models # noqa
--- /dev/null
- def __new__(cls, *args, **kwargs):
- cls_ = super(QosRulesExtenderMeta, cls).__new__(cls, *args, **kwargs)
+ # Copyright 2015 Red Hat, Inc.
+ # 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.
+
+ import abc
+
+ from oslo_versionedobjects import base as obj_base
+ from oslo_versionedobjects import fields as obj_fields
+ import six
+
+ from neutron.common import exceptions
+ from neutron.common import utils
+ from neutron.db import api as db_api
+ from neutron.db.qos import api as qos_db_api
+ from neutron.db.qos import models as qos_db_model
+ from neutron.extensions import qos as qos_extension
+ from neutron.objects import base
+ from neutron.objects.qos import rule as rule_obj_impl
+
+
+ class QosRulesExtenderMeta(abc.ABCMeta):
+
- cls_.rule_fields = {}
++ def __new__(mcs, name, bases, dct):
++ cls = super(QosRulesExtenderMeta, mcs).__new__(mcs, name, bases, dct)
+
- cls_.fields[field] = obj_fields.ListOfObjectsField(rule_cls_name)
- cls_.rule_fields[field] = rule_cls_name
++ cls.rule_fields = {}
+ for rule in qos_extension.VALID_RULE_TYPES:
+ rule_cls_name = 'Qos%sRule' % utils.camelize(rule)
+ field = '%s_rules' % rule
- cls_.synthetic_fields = list(cls_.rule_fields.keys())
++ cls.fields[field] = obj_fields.ListOfObjectsField(rule_cls_name)
++ cls.rule_fields[field] = rule_cls_name
+
- return cls_
++ cls.synthetic_fields = list(cls.rule_fields.keys())
+
++ return cls
+
+
+ @obj_base.VersionedObjectRegistry.register
+ @six.add_metaclass(QosRulesExtenderMeta)
+ class QosPolicy(base.NeutronObject):
+
+ db_model = qos_db_model.QosPolicy
+
+ port_binding_model = qos_db_model.QosPortPolicyBinding
+ network_binding_model = qos_db_model.QosNetworkPolicyBinding
+
+ fields = {
+ 'id': obj_fields.UUIDField(),
+ 'tenant_id': obj_fields.UUIDField(),
+ 'name': obj_fields.StringField(),
+ 'description': obj_fields.StringField(),
+ 'shared': obj_fields.BooleanField()
+ }
+
+ fields_no_update = ['id', 'tenant_id']
+
+ def obj_load_attr(self, attrname):
+ if attrname not in self.rule_fields:
+ raise exceptions.ObjectActionError(
+ action='obj_load_attr', reason='unable to load %s' % attrname)
+
+ rule_cls = getattr(rule_obj_impl, self.rule_fields[attrname])
+ rules = rule_cls.get_rules_by_policy(self._context, self.id)
+ setattr(self, attrname, rules)
+ self.obj_reset_changes([attrname])
+
+ @classmethod
+ def _get_object_policy(cls, context, model, **kwargs):
+ # TODO(QoS): we should make sure we use public functions
+ binding_db_obj = db_api._find_object(context, model, **kwargs)
+ # TODO(QoS): rethink handling missing binding case
+ if binding_db_obj:
+ return cls.get_by_id(context, binding_db_obj['policy_id'])
+
+ @classmethod
+ def get_network_policy(cls, context, network_id):
+ return cls._get_object_policy(context, cls.network_binding_model,
+ network_id=network_id)
+
+ @classmethod
+ def get_port_policy(cls, context, port_id):
+ return cls._get_object_policy(context, cls.port_binding_model,
+ port_id=port_id)
+
+ def attach_network(self, network_id):
+ qos_db_api.create_policy_network_binding(self._context,
+ policy_id=self.id,
+ network_id=network_id)
+
+ def attach_port(self, port_id):
+ qos_db_api.create_policy_port_binding(self._context,
+ policy_id=self.id,
+ port_id=port_id)
+
+ def detach_network(self, network_id):
+ qos_db_api.delete_policy_network_binding(self._context,
+ policy_id=self.id,
+ network_id=network_id)
+
+ def detach_port(self, port_id):
+ qos_db_api.delete_policy_port_binding(self._context,
+ policy_id=self.id,
+ port_id=port_id)
--- /dev/null
- return self._filter_fields(
- fields, lambda key: self._is_addn_field(key))
+ # Copyright 2015 Huawei Technologies India Pvt Ltd, Inc.
+ # 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.
+
+ import abc
+
+ from oslo_versionedobjects import base as obj_base
+ from oslo_versionedobjects import fields as obj_fields
+ import six
+
+ from neutron.db import api as db_api
+ from neutron.db.qos import models as qos_db_model
+ from neutron.extensions import qos as qos_extension
+ from neutron.objects import base
+
+
+ @six.add_metaclass(abc.ABCMeta)
+ class QosRule(base.NeutronObject):
+
+ base_db_model = qos_db_model.QosRule
+
+ fields = {
+ 'id': obj_fields.UUIDField(),
+ 'type': obj_fields.StringField(),
+ 'qos_policy_id': obj_fields.UUIDField()
+ }
+
+ fields_no_update = ['id', 'tenant_id', 'qos_policy_id']
+
+ # each rule subclass should redefine it
+ rule_type = None
+
+ _core_fields = list(fields.keys())
+
+ _common_fields = ['id']
+
+ @classmethod
+ def _is_common_field(cls, field):
+ return field in cls._common_fields
+
+ @classmethod
+ def _is_core_field(cls, field):
+ return field in cls._core_fields
+
+ @classmethod
+ def _is_addn_field(cls, field):
+ return not cls._is_core_field(field) or cls._is_common_field(field)
+
+ @staticmethod
+ def _filter_fields(fields, func):
+ return {
+ key: val for key, val in fields.items()
+ if func(key)
+ }
+
+ def _get_changed_core_fields(self):
+ fields = self.obj_get_changes()
+ return self._filter_fields(fields, self._is_core_field)
+
+ def _get_changed_addn_fields(self):
+ fields = self.obj_get_changes()
++ return self._filter_fields(fields, self._is_addn_field)
+
+ def _copy_common_fields(self, from_, to_):
+ for field in self._common_fields:
+ to_[field] = from_[field]
+
+ @classmethod
+ def get_objects(cls, context, **kwargs):
+ # TODO(QoS): support searching for subtype fields
+ db_objs = db_api.get_objects(context, cls.base_db_model, **kwargs)
+ return [cls.get_by_id(context, db_obj['id']) for db_obj in db_objs]
+
+ @classmethod
+ def get_by_id(cls, context, id):
+ obj = super(QosRule, cls).get_by_id(context, id)
+
+ if obj:
+ # the object above does not contain fields from base QosRule yet,
+ # so fetch it and mix its fields into the object
+ base_db_obj = db_api.get_object(context, cls.base_db_model, id)
+ for field in cls._core_fields:
+ setattr(obj, field, base_db_obj[field])
+
+ obj.obj_reset_changes()
+ return obj
+
+ # TODO(QoS): create and update are not transactional safe
+ def create(self):
+
+ # TODO(QoS): enforce that type field value is bound to specific class
+ self.type = self.rule_type
+
+ # create base qos_rule
+ core_fields = self._get_changed_core_fields()
+ base_db_obj = db_api.create_object(
+ self._context, self.base_db_model, core_fields)
+
+ # create type specific qos_..._rule
+ addn_fields = self._get_changed_addn_fields()
+ self._copy_common_fields(core_fields, addn_fields)
+ addn_db_obj = db_api.create_object(
+ self._context, self.db_model, addn_fields)
+
+ # merge two db objects into single neutron one
+ self.from_db_object(base_db_obj, addn_db_obj)
+
+ def update(self):
+ updated_db_objs = []
+
+ # TODO(QoS): enforce that type field cannot be changed
+
+ # update base qos_rule, if needed
+ core_fields = self._get_changed_core_fields()
+ if core_fields:
+ base_db_obj = db_api.update_object(
+ self._context, self.base_db_model, self.id, core_fields)
+ updated_db_objs.append(base_db_obj)
+
+ addn_fields = self._get_changed_addn_fields()
+ if addn_fields:
+ addn_db_obj = db_api.update_object(
+ self._context, self.db_model, self.id, addn_fields)
+ updated_db_objs.append(addn_db_obj)
+
+ # update neutron object with values from both database objects
+ self.from_db_object(*updated_db_objs)
+
+ # delete is the same, additional rule object cleanup is done thru cascading
+
+ @classmethod
+ def get_rules_by_policy(cls, context, policy_id):
+ return cls.get_objects(context, qos_policy_id=policy_id)
+
+
+ @obj_base.VersionedObjectRegistry.register
+ class QosBandwidthLimitRule(QosRule):
+
+ db_model = qos_db_model.QosBandwidthLimitRule
+
+ rule_type = qos_extension.RULE_TYPE_BANDWIDTH_LIMIT
+
+ fields = {
+ 'max_kbps': obj_fields.IntegerField(nullable=True),
+ 'max_burst_kbps': obj_fields.IntegerField(nullable=True)
+ }
VPN = "VPN"
METERING = "METERING"
L3_ROUTER_NAT = "L3_ROUTER_NAT"
+FLAVORS = "FLAVORS"
+ QOS = "QOS"
# Maps extension alias to service type
EXT_TO_SERVICE_MAPPING = {
'vpnaas': VPN,
'metering': METERING,
'router': L3_ROUTER_NAT,
- 'flavors': FLAVORS
++ 'flavors': FLAVORS,
+ 'qos': QOS,
}
# Service operation status constants
self.assertTrue(my_func.called)
+class TestEnsureDir(base.BaseTestCase):
+ @mock.patch('os.makedirs')
+ def test_ensure_dir_no_fail_if_exists(self, makedirs):
+ error = OSError()
+ error.errno = errno.EEXIST
+ makedirs.side_effect = error
+ utils.ensure_dir("/etc/create/concurrently")
+
+ @mock.patch('os.makedirs')
+ def test_ensure_dir_calls_makedirs(self, makedirs):
+ utils.ensure_dir("/etc/create/directory")
+ makedirs.assert_called_once_with("/etc/create/directory", 0o755)
++
++
+ class TestCamelize(base.BaseTestCase):
+ def test_camelize(self):
+ data = {'bandwidth_limit': 'BandwidthLimit',
+ 'test': 'Test',
+ 'some__more__dashes': 'SomeMoreDashes',
+ 'a_penguin_walks_into_a_bar': 'APenguinWalksIntoABar'}
+
+ for s, expected in data.items():
+ self.assertEqual(expected, utils.camelize(s))
# run the transaction balancing function defined in this test
plugin.delete_port(self.context, 'fake_id')
self.assertTrue(self.notify.call_count)
- # Only check transaction is closed when not reading since we don't
- # care much about reads in these tests.
+
+
+ class TestMl2PluginCreateUpdateNetwork(base.BaseTestCase):
+ def setUp(self):
+ super(TestMl2PluginCreateUpdateNetwork, self).setUp()
+ self.context = mock.MagicMock()
+ self.notify_p = mock.patch('neutron.callbacks.registry.notify')
+ self.notify = self.notify_p.start()
+
+ def _ensure_transaction_is_closed(self):
+ transaction = self.context.session.begin(subtransactions=True)
+ enter = transaction.__enter__.call_count
+ exit = transaction.__exit__.call_count
+ self.assertEqual(enter, exit)
+
+ def _create_plugin_for_create_update_network(self):
+ plugin = ml2_plugin.Ml2Plugin()
+ plugin.extension_manager = mock.Mock()
+ plugin.type_manager = mock.Mock()
+ plugin.mechanism_manager = mock.Mock()
+ plugin.notifier = mock.Mock()
+ mock.patch('neutron.extensions.providernet.'
+ '_raise_if_updates_provider_attributes').start()
+
- lambda r, e, t, **kwargs: None if e == events.AFTER_READ
- else self._ensure_transaction_is_closed())
+ self.notify.side_effect = (
- # TODO(QoS): Figure out why it passes locally but fails in gate
- self.skipTest("Gate is voodoo failing")
++ lambda r, e, t, **kwargs: self._ensure_transaction_is_closed())
+
+ return plugin
+
+ def test_create_network_rpc_outside_transaction(self):
- # TODO(QoS): Figure out why it passes locally but fails in gate
- self.skipTest("Gate is voodoo failing")
+ with mock.patch.object(ml2_plugin.Ml2Plugin, '__init__') as init,\
+ mock.patch.object(base_plugin.NeutronDbPluginV2,
+ 'create_network'):
+ init.return_value = None
+
+ plugin = self._create_plugin_for_create_update_network()
+
+ plugin.create_network(self.context, mock.MagicMock())
+
+ kwargs = {'context': self.context, 'network': mock.ANY}
+ self.notify.assert_called_once_with('network', 'after_create',
+ plugin, **kwargs)
+
+ def test_create_network_bulk_rpc_outside_transaction(self):
- self.notify.assert_called_with('network', 'after_update',
+ with mock.patch.object(ml2_plugin.Ml2Plugin, '__init__') as init,\
+ mock.patch.object(base_plugin.NeutronDbPluginV2,
+ 'create_network'):
+ init.return_value = None
+
+ plugin = self._create_plugin_for_create_update_network()
+
+ plugin.create_network_bulk(self.context,
+ {'networks':
+ [mock.MagicMock(), mock.MagicMock()]})
+
+ self.assertEqual(2, self.notify.call_count)
+
+ def test_update_network_rpc_outside_transaction(self):
+ with mock.patch.object(ml2_plugin.Ml2Plugin, '__init__') as init,\
+ mock.patch.object(base_plugin.NeutronDbPluginV2,
+ 'update_network'):
+ init.return_value = None
+ plugin = self._create_plugin_for_create_update_network()
+
+ plugin.update_network(self.context, 'fake_id', mock.MagicMock())
+
+ kwargs = {
+ 'context': self.context,
+ 'network': mock.ANY,
+ }
++ self.notify.assert_called_once_with('network', 'after_update',
+ plugin, **kwargs)
oslo.concurrency>=2.1.0 # Apache-2.0
oslo.config>=1.11.0 # Apache-2.0
oslo.context>=0.2.0 # Apache-2.0
-oslo.db>=1.10.0 # Apache-2.0
+oslo.db>=1.12.0 # Apache-2.0
oslo.i18n>=1.5.0 # Apache-2.0
-oslo.log>=1.2.0 # Apache-2.0
-oslo.messaging!=1.12.0,>=1.8.0 # Apache-2.0
-oslo.middleware!=2.0.0,>=1.2.0 # Apache-2.0
+oslo.log>=1.6.0 # Apache-2.0
- oslo.messaging>=1.16.0 # Apache-2.0
++oslo.messaging!=1.17.0,!=1.17.1,>=1.16.0 # Apache-2.0
+oslo.middleware>=2.4.0 # Apache-2.0
oslo.policy>=0.5.0 # Apache-2.0
oslo.rootwrap>=2.0.0 # Apache-2.0
oslo.serialization>=1.4.0 # Apache-2.0
oslo.service>=0.1.0 # Apache-2.0
-oslo.utils>=1.6.0 # Apache-2.0
+oslo.utils>=1.9.0 # Apache-2.0
+ oslo.versionedobjects>=0.3.0,!=0.5.0
python-novaclient>=2.22.0
cliff>=1.13.0 # Apache-2.0
coverage>=3.6
fixtures>=1.3.1
- mock!=1.1.4,>=1.1;python_version!='2.6'
- mock==1.0.1;python_version=='2.6'
-mock>=1.0
++mock>=1.2
python-subunit>=0.0.18
requests-mock>=0.6.0 # Apache-2.0
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2