From 242f127f5adab0b70ca05a95fc9bf1a9c3bd7589 Mon Sep 17 00:00:00 2001 From: Songming Yan Date: Sun, 30 Jul 2017 15:54:50 +0800 Subject: [PATCH] Add Qos translator in neutron datasource drive. Parts that require QoS extension placed in a separate driver. So that the standard neutronv2_driver continues to work for environments without the QoS extension. Implements: blueprint add-qos-in-neutron-datasource-driver. Change-Id: I3554f3c31f419b85ec27e734147fcb6eb2a81bde --- congress/datasources/neutronv2_qos_driver.py | 147 +++++++++++ .../datasources/test_neutronv2_qos_driver.py | 193 ++++++++++++++ congress_tempest_tests/config.py | 0 .../services/congress_network/__init__.py | 0 .../services/congress_network/qos_client.py | 68 +++++ .../congress_network/qos_rule_client.py | 73 +++++ .../test_neutronv2_qos.py | 249 ++++++++++++++++++ .../tests/scenario/manager_congress.py | 8 + devstack/plugin.sh | 2 + 9 files changed, 740 insertions(+) create mode 100755 congress/datasources/neutronv2_qos_driver.py create mode 100644 congress/tests/datasources/test_neutronv2_qos_driver.py mode change 100644 => 100755 congress_tempest_tests/config.py create mode 100644 congress_tempest_tests/services/congress_network/__init__.py create mode 100644 congress_tempest_tests/services/congress_network/qos_client.py create mode 100755 congress_tempest_tests/services/congress_network/qos_rule_client.py create mode 100755 congress_tempest_tests/tests/scenario/congress_datasources/test_neutronv2_qos.py mode change 100644 => 100755 congress_tempest_tests/tests/scenario/manager_congress.py diff --git a/congress/datasources/neutronv2_qos_driver.py b/congress/datasources/neutronv2_qos_driver.py new file mode 100755 index 000000000..c944b5e38 --- /dev/null +++ b/congress/datasources/neutronv2_qos_driver.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# Copyright (c) 2014 VMware, 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. +# + +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +import neutronclient.v2_0.client +from oslo_log import log as logging + +from congress.datasources import constants +from congress.datasources import datasource_driver +from congress.datasources import datasource_utils as ds_utils + +LOG = logging.getLogger(__name__) + + +class NeutronV2QosDriver(datasource_driver.PollingDataSourceDriver): + + PORTS = 'ports' + QOS_POLICY_PORT_BINDINGS = 'qos_policy_port_bindings' + QOS_POLICIES = 'policies' + QOS_RULES = 'rules' + + # This is the most common per-value translator, so define it once here. + value_trans = {'translation-type': 'VALUE'} + + ports_qos_policies_translator = { + 'translation-type': 'LIST', + 'table-name': QOS_POLICY_PORT_BINDINGS, + 'parent-key': 'id', + 'parent-col-name': 'port_id', + 'parent-key-desc': 'UUID of port', + 'val-col': 'qos_policy_id', + 'val-col-desc': 'UUID of qos policy', + 'translator': value_trans} + + ports_translator = { + 'translation-type': 'HDICT', + 'table-name': PORTS, + 'selector-type': 'DICT_SELECTOR', + 'field-translators': + ({'fieldname': 'id', 'desc': 'UUID of port', + 'translator': value_trans}, + {'fieldname': 'qos_policies', + 'translator': ports_qos_policies_translator})} + + qos_rules_translator = { + 'translation-type': 'HDICT', + 'table-name': QOS_RULES, + 'parent-key': 'id', + 'parent-col-name': 'qos_policy_id', + 'parent-key-desc': 'uuid of qos policy', + 'selector-type': 'DICT_SELECTOR', + 'in-list': True, + 'field-translators': + ({'fieldname': 'id', 'desc': 'The UUID for the the QoS minimum' + 'bandwidth rule translator', + 'translator': value_trans}, + {'fieldname': 'min_kbps', 'desc': 'min_kbps bandwidth ' + 'limit rule', + 'translator': value_trans}, + {'fieldname': 'direction', 'desc': 'minimum bandwidth ' + 'rule direction', + 'translator': value_trans}, + {'fieldname': 'type', 'desc': 'type of qos rule', + 'translator': value_trans}, + {'fieldname': 'dscp_mark', 'desc': 'mark of the dscp rule', + 'translator': value_trans}, + {'fieldname': 'max_burst_kbps', 'desc': 'max_burst_kps limit', + 'translator': value_trans}, + {'fieldname': 'max_kbps', 'desc': 'max_kps bandwidth limit', + 'translator': value_trans})} + + qos_policy_translator = { + 'translation-type': 'HDICT', + 'table-name': QOS_POLICIES, + 'selector-type': 'DICT_SELECTOR', + 'field-translators': + ({'fieldname': 'id', 'desc': 'The UUID for the qos policy', + 'translator': value_trans}, + {'fieldname': 'tenant_id', 'desc': 'Tenant ID', + 'translator': value_trans}, + {'fieldname': 'name', 'desc': 'The qos policy name', + 'translator': value_trans}, + {'fieldname': 'description', 'desc': 'qos policy description', + 'translator': value_trans}, + {'fieldname': 'shared', 'desc': 'The qos policy share', + 'translator': value_trans}, + {'fieldname': 'rules', + 'translator': qos_rules_translator})} + + TRANSLATORS = [ports_translator, qos_policy_translator] + + def __init__(self, name='', args=None): + super(NeutronV2QosDriver, self).__init__(name, args=args) + self.creds = args + session = ds_utils.get_keystone_session(self.creds) + self.neutron = neutronclient.v2_0.client.Client(session=session) + self.initialize_update_methods() + self._init_end_start_poll() + + @staticmethod + def get_datasource_info(): + result = {} + result['id'] = 'neutronv2_qos' + result['description'] = ('Datasource driver that interfaces with QoS ' + 'extension of ' + 'OpenStack Networking aka Neutron.') + result['config'] = ds_utils.get_openstack_required_config() + result['config']['lazy_tables'] = constants.OPTIONAL + result['secret'] = ['password'] + return result + + def initialize_update_methods(self): + ports_method = lambda: self._translate_ports(self.neutron.list_ports()) + self.add_update_method(ports_method, self.ports_translator) + qos_policy_method = lambda: self._translate_qos_policies( + self.neutron.list_qos_policies()) + self.add_update_method(qos_policy_method, + self.qos_policy_translator) + + @ds_utils.update_state_on_changed(PORTS) + def _translate_ports(self, obj): + LOG.debug("ports: %s", obj) + row_data = NeutronV2QosDriver.convert_objs(obj['ports'], + self.ports_translator) + return row_data + + @ds_utils.update_state_on_changed(QOS_POLICIES) + def _translate_qos_policies(self, obj): + LOG.debug("qos_policies: %s", obj) + row_data = NeutronV2QosDriver.convert_objs(obj['policies'], + self.qos_policy_translator) + return row_data diff --git a/congress/tests/datasources/test_neutronv2_qos_driver.py b/congress/tests/datasources/test_neutronv2_qos_driver.py new file mode 100644 index 000000000..1e271769b --- /dev/null +++ b/congress/tests/datasources/test_neutronv2_qos_driver.py @@ -0,0 +1,193 @@ +# Copyright (c) 2013 VMware, 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. +# + +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import + +import mock + +from congress.datasources import neutronv2_qos_driver +from congress.tests import base +from congress.tests import helper + + +class TestNeutronV2QosDriver(base.TestCase): + + def setUp(self): + super(TestNeutronV2QosDriver, self).setUp() + self.neutron_client_p = mock.patch( + "neutronclient.v2_0.client.Client") + self.neutron_client_p.start() + + args = helper.datasource_openstack_args() + args['poll_time'] = 0 + args['client'] = mock.MagicMock() + self.driver = neutronv2_qos_driver.NeutronV2QosDriver(args=args) + + self.mock_ports = {'ports': [ + {u'admin_state_up': True, + u'allowed_address_pairs': [], + u'binding:host_id': None, + u'binding:vif_details': {u'port_filter': True}, + u'binding:vif_type': u'ovs', + u'binding:vnic_type': u'normal', + u'device_id': u'f42dc4f1-f371-48cc-95be-cf1b97112ab8', + u'device_owner': u'network:router_gateway', + u'fixed_ips': [ + {u'ip_address': u'1.1.1.2', + u'subnet_id': u'10d20df9-e8ba-4756-ba30-d573ceb2e99a'}], + u'id': u'04627c85-3553-436c-a7c5-0a64f5b87bb9', + u'mac_address': u'fa:16:3e:f3:19:e5', + u'name': u'', + u'network_id': u'ecdea1af-7197-43c8-b3b0-34d90f72a2a8', + u'port_security_enabled': False, + u'security_groups': [], + u'status': u'DOWN', + u'tenant_id': u''}, + {u'admin_state_up': True, + u'allowed_address_pairs': [], + u'binding:host_id': None, + u'binding:vif_details': {u'port_filter': True}, + u'binding:vif_type': u'ovs', + u'binding:vnic_type': u'normal', + u'device_id': u'f42dc4f1-f371-48cc-95be-cf1b97112ab8', + u'device_owner': u'network:router_interface', + u'fixed_ips': [ + {u'ip_address': u'169.254.169.253', + u'subnet_id': u'aa9ad4f7-baf0-4a41-85c3-1cc8a3066db6'}], + u'id': u'87f8933a-9582-48d8-ad16-9abf6e545002', + u'mac_address': u'fa:16:3e:b7:78:e8', + u'name': u'', + u'network_id': u'6743ff85-2cfd-48a7-9d3f-472cd418783e', + u'port_security_enabled': False, + u'security_groups': [], + u'status': u'DOWN', + u'tenant_id': u''}, + {u'admin_state_up': True, + u'allowed_address_pairs': [], + u'binding:host_id': None, + u'binding:vif_details': {u'port_filter': True}, + u'binding:vif_type': u'ovs', + u'binding:vnic_type': u'normal', + u'device_id': u'f42dc4f1-f371-48cc-95be-cf1b97112ab8', + u'device_owner': u'network:router_interface', + u'fixed_ips': [ + {u'ip_address': u'10.0.0.1', + u'subnet_id': u'3c0eb3a3-4d16-4b1b-b327-44417182d0bb'}], + u'id': u'c58c3246-6c2e-490a-b4d9-3b8d5191b465', + u'mac_address': u'fa:16:3e:08:31:6e', + u'name': u'', + u'network_id': u'63ce8fbb-12e9-4ecd-9b56-1bbf8b51217d', + u'port_security_enabled': False, + u'security_groups': [], + u'status': u'DOWN', + u'tenant_id': u'feee0a965cc34274917fb753623dd57d'}, + {u'admin_state_up': True, + u'allowed_address_pairs': [], + u'binding:host_id': None, + u'binding:vif_details': {u'port_filter': True}, + u'binding:vif_type': u'ovs', + u'binding:vnic_type': u'normal', + u'device_id': u'', + u'device_owner': u'', + u'fixed_ips': [ + {u'ip_address': u'10.0.0.2', + u'subnet_id': u'3c0eb3a3-4d16-4b1b-b327-44417182d0bb'}], + u'id': u'eb50003b-a081-4533-92aa-1cbd97f526a8', + u'mac_address': u'fa:16:3e:af:56:fa', + u'name': u'', + u'network_id': u'63ce8fbb-12e9-4ecd-9b56-1bbf8b51217d', + u'port_security_enabled': True, + u'security_groups': [u'e0239062-4243-4798-865f-7055f03786d6'], + u'qos_policies': [u'be50b732-4508-4a94-9c3c-8dc4b96a2b43'], + u'status': u'DOWN', + u'tenant_id': u'feee0a965cc34274917fb753623dd57d'}]} + + self.mock_qos_policies = {'policies': [ + {u'name': u'ysm', + u'rules': [ + {u'max_kbps': 100, + u'direction': u'egress', + u'qos_policy_id': u'be50b732-4508-4a94-9c3c-8dc4b96a2b43', + u'type': u'bandwidth_limit', + u'id': u'9daaa87a-5441-49ef-8f25-2810d37c3a60', + u'max_burst_kbps': 500}, + {u'dscp_mark': 10, + u'type': u'dscp_marking', + u'id': u'6be91937-b9ec-4209-a430-0c2694df1095', + u'qos_policy_id': u'be50b732-4508-4a94-9c3c-8dc4b96a2b43'}, + {u'id': u'015f3dc8-7d3e-4598-8996-0597328c4db5', + u'direction': u'egress', + u'type': u'minimum_bandwidth', + u'qos_policy_id': u'be50b732-4508-4a94-9c3c-8dc4b96a2b43', + u'min_kbps': 100}], + u'tenant_id': u'feee0a965cc34274917fb753623dd57d', + u'is_default': False, + u'shared': False, + u'project_id': u'feee0a965cc34274917fb753623dd57d', + u'id': u'be50b732-4508-4a94-9c3c-8dc4b96a2b43', + u'description': u''}]} + + self.expected_state = { + 'ports': set([(u'04627c85-3553-436c-a7c5-0a64f5b87bb9',), + (u'87f8933a-9582-48d8-ad16-9abf6e545002',), + (u'c58c3246-6c2e-490a-b4d9-3b8d5191b465',), + (u'eb50003b-a081-4533-92aa-1cbd97f526a8',)]), + 'qos_policy_port_bindings': + set([('eb50003b-a081-4533-92aa-1cbd97f526a8', + 'be50b732-4508-4a94-9c3c-8dc4b96a2b43')]), + 'policies': + set([('be50b732-4508-4a94-9c3c-8dc4b96a2b43', + 'feee0a965cc34274917fb753623dd57d', + 'ysm', + '', + 'False')]), + 'rules': + set([('be50b732-4508-4a94-9c3c-8dc4b96a2b43', + '015f3dc8-7d3e-4598-8996-0597328c4db5', + 100, + 'egress', + 'minimum_bandwidth', + 'None', + 'None', + 'None'), + ('be50b732-4508-4a94-9c3c-8dc4b96a2b43', + '6be91937-b9ec-4209-a430-0c2694df1095', + 'None', + 'None', + 'dscp_marking', + 10, + 'None', + 'None'), + ('be50b732-4508-4a94-9c3c-8dc4b96a2b43', + '9daaa87a-5441-49ef-8f25-2810d37c3a60', + 'None', + 'egress', + 'bandwidth_limit', + 'None', + 500, + 100)])} + + def test_update_from_datasource(self): + with base.nested( + mock.patch.object(self.driver.neutron, + "list_ports", + return_value=self.mock_ports), + mock.patch.object(self.driver.neutron, + "list_qos_policies", + return_value=self.mock_qos_policies)): + self.driver.update_from_datasource() + self.assertEqual(self.expected_state, self.driver.state) diff --git a/congress_tempest_tests/config.py b/congress_tempest_tests/config.py old mode 100644 new mode 100755 diff --git a/congress_tempest_tests/services/congress_network/__init__.py b/congress_tempest_tests/services/congress_network/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/congress_tempest_tests/services/congress_network/qos_client.py b/congress_tempest_tests/services/congress_network/qos_client.py new file mode 100644 index 000000000..f8cd1ee75 --- /dev/null +++ b/congress_tempest_tests/services/congress_network/qos_client.py @@ -0,0 +1,68 @@ +# 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 tempest.lib.services.network import base + + +class QosPoliciesClient(base.BaseNetworkClient): + + def create_qos_policy(self, **kwargs): + """Creates an OpenStack Networking qos_policy. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/network/v2/index.html#quality-of-service + """ + uri = '/qos/policies' + post_data = {'policy': kwargs} + return self.create_resource(uri, post_data) + + def update_security_group(self, qos_policy_id, **kwargs): + """Updates a qos policy. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/network/v2/index.html#quality-of-service + """ + uri = '/qos/policies/%s' % qos_policy_id + post_data = {'policy': kwargs} + return self.update_resource(uri, post_data) + + def show_qos_policy(self, qos_policy_id, **fields): + """Shows details for a qos policy. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/network/v2/index.html#quality-of-service + """ + uri = '/qos/policies/%s' % qos_policy_id + return self.show_resource(uri, **fields) + + def delete_qos_policy(self, qos_policy_id): + """Deletes an OpenStack Networking qos policy. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/network/v2/index.html#quality-of-service + """ + uri = '/qos/policies/%s' % qos_policy_id + return self.delete_resource(uri) + + def list_qos_policy(self, **filters): + """Lists OpenStack Networking qos policies. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/network/v2/index.html#quality-of-service + """ + uri = '/qos/policies' + return self.list_resources(uri, **filters) diff --git a/congress_tempest_tests/services/congress_network/qos_rule_client.py b/congress_tempest_tests/services/congress_network/qos_rule_client.py new file mode 100755 index 000000000..3369fc66b --- /dev/null +++ b/congress_tempest_tests/services/congress_network/qos_rule_client.py @@ -0,0 +1,73 @@ +# 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 tempest.lib.services.network import base + + +class QosRuleClient(base.BaseNetworkClient): + + def create_qos_rule(self, qos_policy_id, qos_rule_type, **kwargs): + """Creates an OpenStack Networking qos rule. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/network/v2/index.html#quality-of-service + """ + uri = '/qos/policies/%s/%s' % (qos_policy_id, qos_rule_type) + post_data = {qos_rule_type[:-1]: kwargs} + return self.create_resource(uri, post_data) + + def update_qos_rule(self, qos_policy_id, qos_rule_type, + qos_rule_id, **kwargs): + """Updates a qos rule policy. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/network/v2/index.html#quality-of-service + """ + uri = '/qos/policies/%s/%s/%s' % (qos_policy_id, + qos_rule_type, qos_rule_id) + post_data = {'bandwidth_limit_rules': kwargs} + return self.update_resource(uri, post_data) + + def show_qos_rule(self, qos_policy_id, qos_rule_type, + qos_rule_id, **fields): + """Shows details for a qos rule policy. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/network/v2/index.html#quality-of-service + """ + uri = '/qos/policies/%s/%s/%s' % (qos_policy_id, + qos_rule_type, qos_rule_id) + return self.show_resource(uri, **fields) + + def delete_qos_rule(self, qos_policy_id, qos_rule_type, qos_rule_id): + """Deletes an OpenStack Networking qos rule policy. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/network/v2/index.html#quality-of-service + """ + uri = '/qos/policies/%s/%s/%s' % (qos_policy_id, + qos_rule_type, qos_rule_id) + return self.delete_resource(uri) + + def list_qos_rule(self, qos_policy_id, **filters): + """Lists OpenStack Networking qos rule policies. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/network/v2/index.html#quality-of-service + """ + uri = '/qos/policies/%s' % (qos_policy_id) + return self.list_resources(uri, **filters) diff --git a/congress_tempest_tests/tests/scenario/congress_datasources/test_neutronv2_qos.py b/congress_tempest_tests/tests/scenario/congress_datasources/test_neutronv2_qos.py new file mode 100755 index 000000000..eb64e7584 --- /dev/null +++ b/congress_tempest_tests/tests/scenario/congress_datasources/test_neutronv2_qos.py @@ -0,0 +1,249 @@ +# Copyright 2014 OpenStack Foundation +# 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 time + +from tempest import clients +from tempest.common import utils as tempest_utils +from tempest import config +from tempest.lib.common.utils import test_utils +from tempest.lib import decorators +from tempest.lib import exceptions +from tempest import test + +from congress_tempest_tests.tests.scenario import helper +from congress_tempest_tests.tests.scenario import manager_congress + +CONF = config.CONF + +RULE_TYPE = "bandwidth_limit_rules" + + +class TestNeutronV2QosDriver(manager_congress.ScenarioPolicyBase): + + DATASOURCE_NAME = 'neutronv2_qos' + + @classmethod + def skip_checks(cls): + super(TestNeutronV2QosDriver, cls).skip_checks() + # TODO(qos): check whether QoS extension is enabled + if not (CONF.network.project_networks_reachable + or CONF.network.public_network_id): + msg = ('Either project_networks_reachable must be "true", or ' + 'public_network_id must be defined.') + cls.enabled = False + raise cls.skipException(msg) + + if not CONF.service_available.neutron: + skip_msg = ("%s skipped as neutron is not available" + % cls.__name__) + raise cls.skipException(skip_msg) + + if not tempest_utils.is_extension_enabled('qos', 'network'): + skip_msg = ("%s skipped as neutron QoS extension is not available" + % cls.__name__) + raise cls.skipException(skip_msg) + + def setUp(self): + super(TestNeutronV2QosDriver, self).setUp() + self.qos_rules = [] + self.qos_policies = [] + + self.os_primary = clients.Manager( + self.os_admin.auth_provider.credentials) + + body = {"config": {"username": CONF.auth.admin_username, + "tenant_name": CONF.auth.admin_project_name, + "password": CONF.auth.admin_password, + "auth_url": CONF.identity.uri}, + "driver": self.DATASOURCE_NAME, + "name": self.DATASOURCE_NAME} + try: + self.os_admin.congress_client.create_datasource(body)['id'] + except exceptions.Conflict: + pass + + self.datasource_id = manager_congress.get_datasource_id( + self.os_admin.congress_client, self.DATASOURCE_NAME) + + # Get client + self.admin_qos_client = self.os_admin.qos_client + self.admin_qos_rule_client = self.os_admin.qos_rule_client + self.networks_client = self.os_primary.networks_client + self.ports_client = self.os_primary.ports_client + + # Create qos and qos rule + self.qos_policy = self._create_qos_policy('test_qos_policy', + description="test", + share=True) + self.qos_rule = self._create_qos_bandwidth_limit_rule( + self.qos_policy['id'], 1000, 1000) + + # Associate policy with port + body = self.networks_client.create_network( + name="test_qos_network") + self.network = body["network"] + body = self.ports_client.create_port( + network_id=self.network['id']) + self.port = body["port"] + self.ports_client.update_port( + self.port['id'], qos_policy_id=self.qos_policy['id']) + + def tearDown(self): + super(TestNeutronV2QosDriver, self).tearDown() + # Clear port and net + self.ports_client.delete_port(self.port['id']) + self.networks_client.delete_network(self.network["id"]) + + # Clear qos policy and qos rule + self.admin_qos_rule_client.delete_qos_rule( + self.qos_policy['id'], RULE_TYPE, self.qos_rule['id']) + self.admin_qos_client.delete_qos_policy(self.qos_policy['id']) + + def _create_qos_policy(self, name, description=None, share=False): + """Wrapper utility that returns a test QoS policy.""" + body = self.admin_qos_client.create_qos_policy( + name=name, description=description, shared=share) + qos_policy = body['policy'] + self.qos_policies.append(qos_policy) + return qos_policy + + def _create_qos_bandwidth_limit_rule(self, policy_id, max_kbps, + max_burst_kbps, + direction='egress'): + """Wrapper utility that returns a test QoS bandwidth limit rule.""" + rule_type = RULE_TYPE + body = self.admin_qos_rule_client.create_qos_rule( + policy_id, rule_type, + max_kbps=max_kbps, max_burst_kbps=max_burst_kbps, + direction=direction) + qos_rule = body['bandwidth_limit_rule'] + self.qos_rules.append(qos_rule) + return qos_rule + + @decorators.attr(type='smoke') + @test.services('network') + def test_neutronv2_ports_tables(self): + port_schema = ( + self.os_admin.congress_client.show_datasource_table_schema( + self.datasource_id, 'ports')['columns']) + + port_qos_binding_schema = ( + self.os_admin.congress_client.show_datasource_table_schema( + self.datasource_id, 'qos_policy_port_bindings')['columns']) + + qos_policy_schema = ( + self.os_admin.congress_client.show_datasource_table_schema( + self.datasource_id, 'policies')['columns']) + + qos_rule_schema = ( + self.os_admin.congress_client.show_datasource_table_schema( + self.datasource_id, 'rules')['columns']) + + @helper.retry_on_exception + def _check_data_for_port(): + ports_from_neutron = self.ports_client.list_ports() + port_map = {} + for port in ports_from_neutron['ports']: + port_map[port['id']] = port + + client = self.os_admin.congress_client + client.request_refresh(self.datasource_id) + time.sleep(1) + + ports = (client.list_datasource_rows(self.datasource_id, 'ports')) + qos_policy_port_bindings = ( + client.list_datasource_rows( + self.datasource_id, 'qos_policy_port_bindings')) + + # Validate ports table + for row in ports['results']: + port_row = port_map[row['data'][0]] + for index in range(len(port_schema)): + if (str(row['data'][index]) != + str(port_row[port_schema[index]['name']])): + return False + + # validate qos_policy_port_bindings table + for row in qos_policy_port_bindings['results']: + port_row = port_map[row['data'][0]] + for index in range(len(port_qos_binding_schema)): + row_index = port_qos_binding_schema[index]['name'] + # Translate port_id -> id + if row_index == 'port_id': + if (str(row['data'][index]) != + str(port_row['id'])): + return False + elif row_index == 'qos_policy_id': + if (str(row['data'][index]) not in + port_row['policies']): + return False + return True + + @helper.retry_on_exception + def _check_data_for_qos(): + qos_from_neutron = self.admin_qos_client.list_qos_policy() + rule_from_neutron = self.admin_qos_rule_client.list_qos_rule( + self.qos_policy['id']) + policy_map = {} + rule_map = {} + for policy in qos_from_neutron['policies']: + policy_map[policy['id']] = policy + + for rule in rule_from_neutron['policy']['rules']: + rule_map[self.qos_policy['id']] = rule + client = self.os_admin.congress_client + client.request_refresh(self.datasource_id) + time.sleep(1) + qos_policies = (client.list_datasource_rows( + self.datasource_id, 'policies')) + qos_rules = (client.list_datasource_rows( + self.datasource_id, 'rules')) + + # Validate policies table + for row in qos_policies['results']: + policy_row = policy_map[row['data'][0]] + for index in range(len(qos_policy_schema)): + if (str(row['data'][index]) != + str(policy_row[qos_policy_schema[index]['name']])): + return False + + # Validate rules table + for row in qos_rules['results']: + rule_row = rule_map[row['data'][0]] + for index in range(len(qos_rule_schema)): + if str(row['data'][index]) != "None": + if (str(row['data'][index]) != + str(rule_row[qos_rule_schema[index]['name']])): + return False + return True + + if not test_utils.call_until_true(func=_check_data_for_port, + duration=200, sleep_for=10): + raise exceptions.TimeoutException("Data did not converge in time " + "or failure in server") + + if not test_utils.call_until_true(func=_check_data_for_qos, + duration=200, sleep_for=10): + raise exceptions.TimeoutException("Data did not converge in time " + "or failure in server") + + @decorators.attr(type='smoke') + def test_update_no_error(self): + if not test_utils.call_until_true( + func=lambda: self.check_datasource_no_error( + self.DATASOURCE_NAME), + duration=30, sleep_for=5): + raise exceptions.TimeoutException('Datasource could not poll ' + 'without error.') diff --git a/congress_tempest_tests/tests/scenario/manager_congress.py b/congress_tempest_tests/tests/scenario/manager_congress.py old mode 100644 new mode 100755 index ad9bc6782..1ace0b500 --- a/congress_tempest_tests/tests/scenario/manager_congress.py +++ b/congress_tempest_tests/tests/scenario/manager_congress.py @@ -22,6 +22,8 @@ from tempest import config from tempest.lib.common.utils import data_utils from tempest import manager as tempestmanager +from congress_tempest_tests.services.congress_network import qos_client +from congress_tempest_tests.services.congress_network import qos_rule_client from congress_tempest_tests.services.policy import policy_client # use local copy of tempest scenario manager during upstream refactoring from congress_tempest_tests.tests.scenario import manager @@ -57,6 +59,12 @@ class ScenarioPolicyBase(manager.NetworkScenarioTest): cls.os_admin.congress_client = policy_client.PolicyClient( auth_prov, "policy", CONF.identity.region) + cls.os_admin.qos_client = qos_client.QosPoliciesClient( + auth_prov, "network", CONF.identity.region) + + cls.os_admin.qos_rule_client = qos_rule_client.QosRuleClient( + auth_prov, "network", CONF.identity.region) + # Get telemtery_client if getattr(CONF.service_available, 'ceilometer', False): import ceilometer.tests.tempest.service.client as telemetry_client diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 82c82a0c3..905a118d7 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -71,6 +71,7 @@ function configure_congress { # fi CONGRESS_DRIVERS="congress.datasources.neutronv2_driver.NeutronV2Driver," + CONGRESS_DRIVERS+="congress.datasources.neutronv2_qos_driver.NeutronV2QosDriver," CONGRESS_DRIVERS+="congress.datasources.glancev2_driver.GlanceV2Driver," CONGRESS_DRIVERS+="congress.datasources.nova_driver.NovaDriver," CONGRESS_DRIVERS+="congress.datasources.keystonev3_driver.KeystoneV3Driver," @@ -96,6 +97,7 @@ function configure_congress { function configure_congress_datasources { _configure_service neutron neutronv2 + _configure_service neutron neutronv2_qos _configure_service nova nova _configure_service key keystonev3 _configure_service ceilometer ceilometer