diff --git a/devstack/devstackgaterc b/devstack/devstackgaterc index 904d5113b..60165e0bb 100644 --- a/devstack/devstackgaterc +++ b/devstack/devstackgaterc @@ -17,7 +17,7 @@ # http://git.openstack.org/cgit/openstack-infra/project-config/tree/jenkins/jobs/dragonflow.yaml # -export OVERRIDE_ENABLED_SERVICES=key,n-api,n-cpu,n-cond,n-sch,n-crt,n-cauth,n-obj,g-api,g-reg,c-sch,c-api,c-vol,horizon,rabbit,mysql,dstat,df-controller,df-redis,df-redis-server,q-svc,df-l3-agent,df-metadata +export OVERRIDE_ENABLED_SERVICES=key,n-api,n-cpu,n-cond,n-sch,n-crt,n-cauth,n-obj,g-api,g-reg,c-sch,c-api,c-vol,horizon,rabbit,mysql,dstat,df-controller,df-redis,df-redis-server,q-svc,df-l3-agent,df-metadata,q-qos export DEVSTACK_LOCAL_CONFIG+=$'\n'"DF_REDIS_PUBSUB=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"DF_RUNNING_IN_GATE=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"NEUTRON_CREATE_INITIAL_NETWORKS=False" diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 507514c69..ef48f7fca 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -145,6 +145,32 @@ function configure_df_metadata_service { fi } +function configure_qos { + Q_SERVICE_PLUGIN_CLASSES+=",qos" + Q_ML2_PLUGIN_EXT_DRIVERS+=",qos" + iniset /$Q_PLUGIN_CONF_FILE ml2 extension_drivers "$Q_ML2_PLUGIN_EXT_DRIVERS" + + # NOTE: QoS plugin use "message_queue" as default driver if no + # notification driver in neutron.conf, or it will use driver + # assigned in neutron.conf. + # If "df" is the only mech driver for ml2, we use + # "df_notification_driver" as notification driver. + # If ml2 has other mech driver except "df", we should add + # "message_queue,df_notification_driver" to existing + # notification driver + if [[ "$Q_ML2_PLUGIN_MECHANISM_DRIVERS" == "df" ]]; then + NOTIFICATION_DRIVER="df_notification_driver" + else + NOTIFICATION_DRIVER=$(iniget ${NEUTRON_CONF} qos notification_drivers) + if [[ "$NOTIFICATION_DRIVER" == "" ]]; then + NOTIFICATION_DRIVER="message_queue,df_notification_driver" + else + NOTIFICATION_DRIVER+=",df_notification_driver" + fi + fi + iniset $NEUTRON_CONF qos notification_drivers "$NOTIFICATION_DRIVER" +} + function configure_df_plugin { echo "Configuring Neutron for Dragonflow" @@ -160,6 +186,9 @@ function configure_df_plugin { Q_SERVICE_PLUGIN_CLASSES="" else DF_APPS_LIST=$ML2_APPS_LIST + if is_service_enabled q-qos ; then + configure_qos + fi fi # Generate configuration file diff --git a/dragonflow/cli/df_db.py b/dragonflow/cli/df_db.py index ff29b8b9c..a63cb843b 100644 --- a/dragonflow/cli/df_db.py +++ b/dragonflow/cli/df_db.py @@ -24,7 +24,7 @@ from dragonflow.common import utils as df_utils cfg.CONF.register_opts(common_params.DF_OPTS, 'df') db_tables = ['lport', 'lswitch', 'lrouter', 'chassis', 'secgroup', - 'tunnel_key', 'floatingip', 'publisher'] + 'tunnel_key', 'floatingip', 'publisher', 'qospolicy'] usage_str = "The following commands are supported:\n" \ "1) df-db ls - print all the db tables \n" \ diff --git a/dragonflow/db/api_nb.py b/dragonflow/db/api_nb.py index a1fa21b55..396a47532 100644 --- a/dragonflow/db/api_nb.py +++ b/dragonflow/db/api_nb.py @@ -743,6 +743,40 @@ class NbApi(object): topic, ) + def create_qos_policy(self, policy_id, topic, **columns): + policy = {'id': policy_id, + 'topic': topic} + policy.update(columns) + policy_json = jsonutils.dumps(policy) + + self.driver.create_key('qospolicy', policy_id, policy_json, topic) + self._send_db_change_event('qospolicy', policy_id, 'create', + policy_json, topic) + + def update_qos_policy(self, policy_id, topic, **columns): + qospolicy_json = self.driver.get_key('qospolicy', policy_id, topic) + policy = jsonutils.loads(qospolicy_json) + policy.update(columns) + policy_json = jsonutils.dumps(policy) + + self.driver.set_key('qospolicy', policy_id, policy_json, topic) + self._send_db_change_event('qospolicy', policy_id, 'set', + policy_json, topic) + + def delete_qos_policy(self, policy_id, topic): + self.driver.delete_key('qospolicy', policy_id, topic) + self._send_db_change_event('qospolicy', policy_id, 'delete', policy_id, + topic) + + def get_qos_policy(self, policy_id, topic=None): + try: + qospolicy_value = self.driver.get_key('qospolicy', + policy_id, topic) + return QosPolicy(qospolicy_value) + except Exception: + LOG.exception(_LE('Could not get qos policy %s'), policy_id) + return None + @six.add_metaclass(abc.ABCMeta) class DbStoreObject(object): @@ -927,6 +961,9 @@ class LogicalPort(DbStoreObject): def get_binding_vnic_type(self): return self.lport.get('binding_vnic_type') + def get_qos_policy_id(self): + return self.lport.get('qos_policy_id') + def get_version(self): return self.lport['version'] @@ -1212,3 +1249,57 @@ class Publisher(DbStoreObject): def __str__(self): return str(self.publisher) + + +class QosPolicy(DbStoreObject): + + def __init__(self, value): + self.qospolicy = jsonutils.loads(value) + + def get_id(self): + return self.qospolicy.get('id') + + def get_name(self): + return self.qospolicy.get('name') + + def get_topic(self): + return self.qospolicy.get('topic') + + def get_type(self): + return self.qospolicy.get('type') + + def get_version(self): + return self.qospolicy.get('version') + + def get_max_burst_kbps(self): + rules = self.qospolicy.get('rules') + max_burst_kbps = None + for rule in rules: + if rule['type'] == 'bandwidth_limit': + max_burst_kbps = rule.get('max_burst_kbps') + break + + return max_burst_kbps + + def get_max_kbps(self): + rules = self.qospolicy.get('rules') + max_kbps = None + for rule in rules: + if rule['type'] == 'bandwidth_limit': + max_kbps = rule.get('max_kbps') + break + + return max_kbps + + def get_dscp_marking(self): + rules = self.qospolicy.get('rules') + dscp_marking = None + for rule in rules: + if rule['type'] == 'dscp_marking': + dscp_marking = rule.get('dscp_mark') + break + + return dscp_marking + + def __str__(self): + return str(self.qospolicy) diff --git a/dragonflow/db/neutron/lockedobjects_db.py b/dragonflow/db/neutron/lockedobjects_db.py index 9bb1e7f1e..b70f5b632 100644 --- a/dragonflow/db/neutron/lockedobjects_db.py +++ b/dragonflow/db/neutron/lockedobjects_db.py @@ -47,6 +47,8 @@ RESOURCE_ML2_CORE = 2 # network, subnet, port RESOURCE_ML2_SECURITY_GROUP = 3 RESOURCE_ML2_SECURITY_GROUP_RULE_CREATE = 4 RESOURCE_ML2_SECURITY_GROUP_RULE_DELETE = 5 +RESOURCE_QOS_POLICY_CREATE_OR_UPDATE = 6 +RESOURCE_QOS_POLICY_DELETE = 7 LOG = log.getLogger(__name__) @@ -107,6 +109,11 @@ def _get_lock_id_by_resource_type(type, *args, **kwargs): lock_id = args[1]['security_group_rule']['tenant_id'] elif RESOURCE_ML2_SECURITY_GROUP_RULE_DELETE == type: lock_id = args[1]['context'].tenant_id + elif RESOURCE_QOS_POLICY_CREATE_OR_UPDATE == type: + lock_id = args[0][2]['tenant_id'] + elif RESOURCE_QOS_POLICY_DELETE == type: + # when delete qos policy, there's no tenant_id in args. + lock_id = GLOBAL_LOCK_ID if not lock_id: lock_id = GLOBAL_LOCK_ID diff --git a/dragonflow/neutron/ml2/mech_driver.py b/dragonflow/neutron/ml2/mech_driver.py index 2f38b9977..75f2a783c 100644 --- a/dragonflow/neutron/ml2/mech_driver.py +++ b/dragonflow/neutron/ml2/mech_driver.py @@ -246,7 +246,8 @@ class DFMechDriver(driver_api.MechanismDriver): router_external=network['router:external'], mtu=network.get('mtu'), version=network['revision_number'], - subnets=[]) + subnets=[], + qos_policy_id=network.get('qos_policy_id')) LOG.info(_LI("DFMechDriver: create network %s"), network['id']) return network @@ -263,9 +264,28 @@ class DFMechDriver(driver_api.MechanismDriver): except df_exceptions.DBKeyNotFound: LOG.debug("lswitch %s is not found in DF DB, might have " "been deleted concurrently" % network_id) + return LOG.info(_LI("DFMechDriver: delete network %s"), network_id) + @lock_db.wrap_db_lock(lock_db.RESOURCE_ML2_CORE) + def update_network_postcommit(self, context): + network = context.current + + self.nb_api.update_lswitch( + id=network['id'], + topic=network['tenant_id'], + name=network.get('name', df_const.DF_NETWORK_DEFAULT_NAME), + network_type=network.get('provider:network_type'), + segmentation_id=network.get('provider:segmentation_id'), + router_external=network.get('router:external'), + mtu=network.get('mtu'), + version=network['revision_number'], + qos_policy_id=network.get('qos_policy_id')) + + LOG.info(_LI("DFMechDriver: update network %s"), network['id']) + return network + def _get_dhcp_port_for_subnet(self, context, subnet_id): filters = {'fixed_ips': {'subnet_id': [subnet_id]}, 'device_owner': [n_const.DEVICE_OWNER_DHCP]} @@ -370,8 +390,9 @@ class DFMechDriver(driver_api.MechanismDriver): dhcp_ip, dhcp_port = self._handle_create_subnet_dhcp( plugin_context, subnet) - except Exception as e: - LOG.exception(e) + except Exception: + LOG.exception( + _LE("Failed to create dhcp port for subnet %s"), subnet['id']) return None self.nb_api.add_subnet( @@ -447,8 +468,10 @@ class DFMechDriver(driver_api.MechanismDriver): plugin_context, old_subnet, new_subnet) - except Exception as e: - LOG.exception(e) + except Exception: + LOG.exception( + _LE("Failed to create dhcp port for subnet %s"), + new_subnet['id']) return None self.nb_api.update_subnet( @@ -489,6 +512,7 @@ class DFMechDriver(driver_api.MechanismDriver): except df_exceptions.DBKeyNotFound: LOG.debug("network %s is not found in DB, might have " "been deleted concurrently" % net_id) + return LOG.info(_LI("DFMechDriver: delete subnet %s"), subnet_id) @@ -532,7 +556,8 @@ class DFMechDriver(driver_api.MechanismDriver): remote_vtep=remote_vtep, allowed_address_pairs=port.get(addr_pair.ADDRESS_PAIRS, []), binding_profile=port.get(portbindings.PROFILE, None), - binding_vnic_type=port.get(portbindings.VNIC_TYPE, None)) + binding_vnic_type=port.get(portbindings.VNIC_TYPE, None), + qos_policy_id=port.get('qos_policy_id', None)) LOG.info(_LI("DFMechDriver: create port %s"), port['id']) return port @@ -629,8 +654,8 @@ class DFMechDriver(driver_api.MechanismDriver): []), binding_profile=updated_port.get(portbindings.PROFILE, None), binding_vnic_type=updated_port.get(portbindings.VNIC_TYPE, None), - version=updated_port['revision_number'], - remote_vtep=remote_vtep) + version=updated_port['revision_number'], remote_vtep=remote_vtep, + qos_policy_id=updated_port.get('qos_policy_id')) LOG.info(_LI("DFMechDriver: update port %s"), updated_port['id']) return updated_port @@ -646,6 +671,7 @@ class DFMechDriver(driver_api.MechanismDriver): except df_exceptions.DBKeyNotFound: LOG.debug("port %s is not found in DF DB, might have " "been deleted concurrently" % port_id) + return LOG.info(_LI("DFMechDriver: delete port %s"), port_id) diff --git a/dragonflow/neutron/services/qos/__init__.py b/dragonflow/neutron/services/qos/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dragonflow/neutron/services/qos/notification_drivers/__init__.py b/dragonflow/neutron/services/qos/notification_drivers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dragonflow/neutron/services/qos/notification_drivers/df_qos_notification_driver.py b/dragonflow/neutron/services/qos/notification_drivers/df_qos_notification_driver.py new file mode 100644 index 000000000..f581e0ed1 --- /dev/null +++ b/dragonflow/neutron/services/qos/notification_drivers/df_qos_notification_driver.py @@ -0,0 +1,75 @@ +# 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 import manager +from neutron.plugins.common import constants as service_constants +from neutron.plugins.ml2 import plugin as ml2_plugin +from neutron.services.qos.notification_drivers import qos_base + +from dragonflow.db.neutron import lockedobjects_db as lock_db + + +class DFQosServiceNotificationDriver( + qos_base.QosServiceNotificationDriverBase): + """Dragonflow notification driver for QoS.""" + + def __init__(self): + self._nb_api = None + + def get_description(self): + return "Notification driver for Dragonflow" + + @property + def _plugin(self): + return manager.NeutronManager.get_service_plugins().get( + service_constants.QOS) + + @property + def nb_api(self): + if self._nb_api is None: + plugin = manager.NeutronManager.get_plugin() + if isinstance(plugin, ml2_plugin.Ml2Plugin): + mech_driver = plugin.mechanism_manager.mech_drivers['df'].obj + self._nb_api = mech_driver.nb_api + else: + # DF neutron plugin + self._nb_api = plugin.nb_api + return self._nb_api + + @lock_db.wrap_db_lock(lock_db.RESOURCE_QOS_POLICY_CREATE_OR_UPDATE) + def create_policy(self, context, policy): + self.nb_api.create_qos_policy(policy['id'], + policy['tenant_id'], + name=policy['name'], + rules=policy.get('rules', []), + version=policy['revision_number']) + + @lock_db.wrap_db_lock(lock_db.RESOURCE_QOS_POLICY_CREATE_OR_UPDATE) + def update_policy(self, context, policy): + policy_id = policy['id'] + # NOTE: Neutron will not pass policy with latest revision_number + # in argument. Get the latest policy from neutron. + policy_neutron = self._plugin.get_policy(context, policy_id) + + self.nb_api.update_qos_policy( + policy_id, policy_neutron['tenant_id'], + name=policy['name'], rules=policy_neutron['rules'], + version=policy_neutron['revision_number']) + + @lock_db.wrap_db_lock(lock_db.RESOURCE_QOS_POLICY_DELETE) + def delete_policy(self, context, policy): + policy_id = policy['id'] + # Only id will be in policy in the argument. Get full policy from + # neutron. + policy_neutron = self._plugin.get_policy(context, policy_id) + + self.nb_api.delete_qos_policy(policy_id, policy_neutron['tenant_id']) diff --git a/dragonflow/tests/fullstack/test_neutron_api.py b/dragonflow/tests/fullstack/test_neutron_api.py index dccff9611..19daa2b35 100644 --- a/dragonflow/tests/fullstack/test_neutron_api.py +++ b/dragonflow/tests/fullstack/test_neutron_api.py @@ -14,11 +14,15 @@ import contextlib from neutronclient.common import exceptions as n_exc from oslo_concurrency import lockutils +from oslo_config import cfg from dragonflow.tests.common import utils from dragonflow.tests.fullstack import test_base from dragonflow.tests.fullstack import test_objects as objects +# TODO(xiaohhui): This should be removed, once the DFPlugin has been removed. +DF_PLUGIN = 'dragonflow.neutron.plugin.DFPlugin' + class TestNeutronAPIandDB(test_base.DFTestBase): @@ -174,6 +178,75 @@ class TestNeutronAPIandDB(test_base.DFTestBase): network.close() self.assertFalse(network.exists()) + def test_create_port_with_qospolicy(self): + if cfg.CONF.core_plugin == DF_PLUGIN: + return + + network = self.store(objects.NetworkTestObj(self.neutron, self.nb_api)) + network_id = network.create() + self.assertTrue(network.exists()) + + qospolicy = self.store(objects.QosPolicyTestObj(self.neutron, + self.nb_api)) + qos_policy_id = qospolicy.create() + self.assertTrue(qospolicy.exists()) + + port = self.store(objects.PortTestObj(self.neutron, + self.nb_api, + network_id)) + port_param = { + 'admin_state_up': True, + 'name': 'port1', + 'network_id': network_id, + 'qos_policy_id': qos_policy_id + } + port.create(port_param) + self.assertTrue(port.exists()) + self.assertEqual(qos_policy_id, + port.get_logical_port().get_qos_policy_id()) + + port.close() + self.assertFalse(port.exists()) + network.close() + self.assertFalse(network.exists()) + qospolicy.close() + self.assertFalse(qospolicy.exists()) + + def test_update_port_with_qospolicy(self): + if cfg.CONF.core_plugin == DF_PLUGIN: + return + + network = self.store(objects.NetworkTestObj(self.neutron, self.nb_api)) + network_id = network.create() + self.assertTrue(network.exists()) + + qospolicy = self.store(objects.QosPolicyTestObj(self.neutron, + self.nb_api)) + qos_policy_id = qospolicy.create() + self.assertTrue(qospolicy.exists()) + + port = self.store(objects.PortTestObj(self.neutron, + self.nb_api, + network_id)) + port.create() + self.assertTrue(port.exists()) + + port_param = { + 'admin_state_up': True, + 'name': 'port1', + 'qos_policy_id': qos_policy_id + } + port.update(port_param) + self.assertEqual(qos_policy_id, + port.get_logical_port().get_qos_policy_id()) + + port.close() + self.assertFalse(port.exists()) + network.close() + self.assertFalse(network.exists()) + qospolicy.close() + self.assertFalse(qospolicy.exists()) + def test_delete_router_interface_port(self): router = self.store(objects.RouterTestObj(self.neutron, self.nb_api)) network = self.store(objects.NetworkTestObj(self.neutron, self.nb_api)) @@ -241,6 +314,20 @@ class TestNeutronAPIandDB(test_base.DFTestBase): secgroup.close() self.assertFalse(secgroup.exists()) + def test_create_delete_qos_policy(self): + if cfg.CONF.core_plugin == DF_PLUGIN: + return + + qospolicy = self.store( + objects.QosPolicyTestObj(self.neutron, self.nb_api)) + policy_id = qospolicy.create() + self.assertTrue(qospolicy.exists()) + rule = {'max_kbps': '1000', 'max_burst_kbps': '100'} + qospolicy.create_rule(policy_id, rule) + self.assertTrue(qospolicy.exists()) + qospolicy.close() + self.assertFalse(qospolicy.exists()) + @contextlib.contextmanager def _prepare_ext_net(self): external_net = objects.find_first_network(self.neutron, diff --git a/dragonflow/tests/fullstack/test_object_version.py b/dragonflow/tests/fullstack/test_object_version.py index b3d06aad2..a72612a5f 100644 --- a/dragonflow/tests/fullstack/test_object_version.py +++ b/dragonflow/tests/fullstack/test_object_version.py @@ -13,10 +13,13 @@ import contextlib from oslo_concurrency import lockutils +from oslo_config import cfg from dragonflow.tests.fullstack import test_base from dragonflow.tests.fullstack import test_objects as objects +DF_PLUGIN = 'dragonflow.neutron.plugin.DFPlugin' + class TestObjectVersion(test_base.DFTestBase): @@ -127,6 +130,25 @@ class TestObjectVersion(test_base.DFTestBase): secgroup.close() self.assertFalse(secgroup.exists()) + def test_qospolicy_version(self): + if cfg.CONF.core_plugin == DF_PLUGIN: + return + + qospolicy = self.store(objects.QosPolicyTestObj(self.neutron, + self.nb_api)) + policy_id = qospolicy.create() + self.assertTrue(qospolicy.exists()) + version = self.nb_api.get_qos_policy(policy_id).get_version() + + rule = {'max_kbps': '1000', 'max_burst_kbps': '100'} + qospolicy.create_rule(policy_id, rule) + self.assertTrue(qospolicy.exists()) + new_version = self.nb_api.get_qos_policy(policy_id).get_version() + self.assertGreater(new_version, version) + + qospolicy.close() + self.assertFalse(qospolicy.exists()) + @contextlib.contextmanager def _prepare_ext_net(self): external_net = objects.find_first_network(self.neutron, diff --git a/dragonflow/tests/fullstack/test_objects.py b/dragonflow/tests/fullstack/test_objects.py index b06c3f2f9..ab4daac4e 100644 --- a/dragonflow/tests/fullstack/test_objects.py +++ b/dragonflow/tests/fullstack/test_objects.py @@ -444,3 +444,34 @@ class FloatingipTestObj(object): return True return False utils.wait_until_true(internal_predicate, timeout, sleep, exception) + + +class QosPolicyTestObj(object): + def __init__(self, neutron, nb_api): + self.policy_id = None + self.neutron = neutron + self.nb_api = nb_api + self.closed = False + + def create(self, qospolicy={'name': 'myqospolicy1'}): + new_qospolicy = self.neutron.create_qos_policy({'policy': + qospolicy}) + self.policy_id = new_qospolicy['policy']['id'] + return self.policy_id + + def create_rule(self, policy_id, rule): + self.neutron.create_bandwidth_limit_rule( + policy_id, {'bandwidth_limit_rule': rule}) + return + + def close(self): + if self.closed or self.policy_id is None: + return + self.neutron.delete_qos_policy(self.policy_id) + self.closed = True + + def exists(self): + qospolicy = self.nb_api.get_qos_policy(self.policy_id) + if qospolicy: + return True + return False diff --git a/dragonflow/tests/unit/test_df_qos_notification_driver.py b/dragonflow/tests/unit/test_df_qos_notification_driver.py new file mode 100644 index 000000000..a03df5052 --- /dev/null +++ b/dragonflow/tests/unit/test_df_qos_notification_driver.py @@ -0,0 +1,98 @@ +# 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 mock +from neutron.conf.services import qos_driver_manager as driver_mgr_config +from neutron import manager +from neutron.objects.qos import rule +from neutron.plugins.ml2 import config as ml2_config + +from dragonflow.tests.unit import test_mech_driver + + +class TestDFQosNotificationDriver(test_mech_driver.DFMechanismDriverTestCase): + + """Test case of df qos notification drvier""" + + _extension_drivers = ['qos'] + + def get_additional_service_plugins(self): + p = super(TestDFQosNotificationDriver, + self).get_additional_service_plugins() + p.update({'qos_plugin_name': 'qos'}) + return p + + def setUp(self): + ml2_config.cfg.CONF.set_override('extension_drivers', + self._extension_drivers, + group='ml2') + driver_mgr_config.register_qos_plugin_opts(ml2_config.cfg.CONF) + ml2_config.cfg.CONF.set_override('notification_drivers', + ['df_notification_driver'], 'qos') + super(TestDFQosNotificationDriver, self).setUp() + self.plugin = (manager.NeutronManager. + get_service_plugins()['QOS']) + self.driver = ( + self.plugin.notification_driver_manager.notification_drivers[0]) + + def _test_create_policy(self): + qos_policy = {'policy': {'name': "policy1", 'tenant_id': "tenant1"}} + qos_obj = self.plugin.create_policy(self.context, qos_policy) + self.assertGreater(qos_obj['revision_number'], 0) + self.driver.nb_api.create_qos_policy.assert_called_with( + mock.ANY, 'tenant1', name='policy1', + rules=[], version=qos_obj['revision_number']) + return qos_obj + + def test_create_policy(self): + self._test_create_policy() + + def test_update_policy(self): + qos_obj = self._test_create_policy() + new_qos_obj = self.plugin.update_policy( + self.context, qos_obj['id'], {'policy': {'name': 'policy2'}}) + self.assertGreater(new_qos_obj['revision_number'], + qos_obj['revision_number']) + self.driver.nb_api.update_qos_policy.assert_called_with( + qos_obj['id'], 'tenant1', name='policy2', + rules=[], version=new_qos_obj['revision_number']) + + def test_create_delete_policy_rule(self): + qos_obj = self._test_create_policy() + qos_rule = {'max_burst_kbps': 1000, + 'max_kbps': 100} + qos_rule_obj = self.plugin.create_policy_rule( + self.context, rule.QosBandwidthLimitRule, + qos_obj['id'], {'bandwidth_limit_rule': qos_rule}) + new_qos_obj = self.plugin.get_policy(self.context, qos_obj['id']) + self.assertGreater(new_qos_obj['revision_number'], + qos_obj['revision_number']) + self.driver.nb_api.update_qos_policy.assert_called_with( + qos_obj['id'], 'tenant1', name='policy1', + rules=[qos_rule_obj], version=new_qos_obj['revision_number']) + + self.plugin.delete_policy_rule(self.context, + rule.QosBandwidthLimitRule, + qos_rule_obj['id'], + qos_obj['id']) + newer_qos_obj = self.plugin.get_policy(self.context, qos_obj['id']) + self.assertGreater(newer_qos_obj['revision_number'], + new_qos_obj['revision_number']) + self.driver.nb_api.update_qos_policy.assert_called_with( + qos_obj['id'], 'tenant1', name='policy1', + rules=[], version=newer_qos_obj['revision_number']) + + def test_delete_policy(self): + qos_obj = self._test_create_policy() + self.plugin.delete_policy(self.context, qos_obj['id']) + self.driver.nb_api.delete_qos_policy.assert_called_with( + qos_obj['id'], 'tenant1') diff --git a/dragonflow/tests/unit/test_mech_driver.py b/dragonflow/tests/unit/test_mech_driver.py index a36e582ef..97a61a8b0 100644 --- a/dragonflow/tests/unit/test_mech_driver.py +++ b/dragonflow/tests/unit/test_mech_driver.py @@ -123,7 +123,8 @@ class TestDFMechDriver(DFMechanismDriverTestCase): physical_network=network['provider:physical_network'], router_external=network['router:external'], mtu=network['mtu'], version=network['revision_number'], - subnets=[]) + subnets=[], + qos_policy_id=None) return network def test_create_network_revision(self): @@ -221,7 +222,8 @@ class TestDFMechDriver(DFMechanismDriverTestCase): remote_vtep=False, allowed_address_pairs=mock.ANY, binding_profile=mock.ANY, - binding_vnic_type=mock.ANY) + binding_vnic_type=mock.ANY, + qos_policy_id=None) data = {'port': {'name': 'updated'}} req = self.new_update_request('ports', data, port['id']) @@ -244,7 +246,8 @@ class TestDFMechDriver(DFMechanismDriverTestCase): port_security_enabled=mock.ANY, allowed_address_pairs=mock.ANY, binding_profile=mock.ANY, - binding_vnic_type=mock.ANY) + binding_vnic_type=mock.ANY, + qos_policy_id=None) def test_delete_network(self): network = self._test_create_network_revision() @@ -272,6 +275,7 @@ class TestDFMechDriver(DFMechanismDriverTestCase): device_id=port['device_id'], security_groups=mock.ANY, port_security_enabled=mock.ANY, + qos_policy_id=mock.ANY, remote_vtep=True, allowed_address_pairs=mock.ANY, binding_profile=profile, @@ -291,6 +295,7 @@ class TestDFMechDriver(DFMechanismDriverTestCase): version=mock.ANY, device_owner=port['device_owner'], device_id=port['device_id'], + qos_policy_id=mock.ANY, remote_vtep=True, security_groups=mock.ANY, port_security_enabled=mock.ANY, diff --git a/setup.cfg b/setup.cfg index 3353efda4..5b376cd18 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,6 +48,8 @@ output_file = dragonflow/locale/dragonflow.pot [entry_points] +neutron.qos.notification_drivers = + df_notification_driver = dragonflow.neutron.services.qos.notification_drivers.df_qos_notification_driver:DFQosServiceNotificationDriver neutron.ml2.mechanism_drivers = df = dragonflow.neutron.ml2.mech_driver:DFMechDriver neutron.db.alembic_migrations =