diff --git a/tobiko/openstack/neutron/config.py b/tobiko/openstack/neutron/config.py index 59277216c..6f7624db2 100644 --- a/tobiko/openstack/neutron/config.py +++ b/tobiko/openstack/neutron/config.py @@ -55,7 +55,16 @@ OPTIONS = [ help="Host where nameservers files are located"), cfg.ListOpt('nameservers_filenames', default=['/etc/resolv.conf'], - help="File to parse for getting default nameservers list") + help="File to parse for getting default nameservers list"), + cfg.IntOpt('bwlimit_kbps', + default=100, + help="The BW limit value configured for the QoS Policy Rule"), + cfg.StrOpt('direction', + default='egress', + help="The direction for the QoS Policy Rule"), + cfg.IntOpt('dscp_mark', + default=40, + help="The DSCP marking value for the QoS Policy Rule") ] diff --git a/tobiko/openstack/stacks/__init__.py b/tobiko/openstack/stacks/__init__.py index 881542a06..a17b4436c 100644 --- a/tobiko/openstack/stacks/__init__.py +++ b/tobiko/openstack/stacks/__init__.py @@ -28,6 +28,7 @@ CentosFlavorStackFixture = _centos.CentosFlavorStackFixture CentosImageFixture = _centos.CentosImageFixture CentosServerStackFixture = _centos.CentosServerStackFixture CentosExternalServerStackFixture = _centos.CentosExternalServerStackFixture +CentosQosServerStackFixture = _centos.CentosQosServerStackFixture CirrosFlavorStackFixture = _cirros.CirrosFlavorStackFixture CirrosImageFixture = _cirros.CirrosImageFixture diff --git a/tobiko/openstack/stacks/_centos.py b/tobiko/openstack/stacks/_centos.py index 14d744599..3c009a5df 100644 --- a/tobiko/openstack/stacks/_centos.py +++ b/tobiko/openstack/stacks/_centos.py @@ -57,3 +57,8 @@ class CentosServerStackFixture(_nova.ServerStackFixture): class CentosExternalServerStackFixture(CentosServerStackFixture, _nova.ExternalServerStackFixture): pass + + +class CentosQosServerStackFixture(CentosServerStackFixture, + _nova.QosServerStackFixture): + pass diff --git a/tobiko/openstack/stacks/_neutron.py b/tobiko/openstack/stacks/_neutron.py index 7dbd96cff..bf603d286 100644 --- a/tobiko/openstack/stacks/_neutron.py +++ b/tobiko/openstack/stacks/_neutron.py @@ -299,6 +299,37 @@ class NetworkWithNetMtuWriteStackFixture(NetworkStackFixture): return dict(value_specs, mtu=self.custom_mtu_size) +@neutron.skip_if_missing_networking_extensions('qos') +class QosPolicyStackFixture(heat.HeatStackFixture): + """Heat stack with a QoS Policy and some QoS Policy Rules + """ + has_qos_policy = True + has_bwlimit = True + has_dscp_marking = True + bwlimit_kbps = CONF.tobiko.neutron.bwlimit_kbps + bwlimit_burst_kbps = int(0.8 * bwlimit_kbps) + direction = CONF.tobiko.neutron.direction + dscp_mark = CONF.tobiko.neutron.dscp_mark + + #: Heat template file + template = _hot.heat_template_file('neutron/qos.yaml') + + +@neutron.skip_if_missing_networking_extensions('qos') +class NetworkQosPolicyStackFixture(NetworkStackFixture): + + #: stack with the qos policy for the network + qos_stack = tobiko.required_setup_fixture(QosPolicyStackFixture) + + has_qos_policy = True + + @property + def network_value_specs(self): + value_specs = super(NetworkQosPolicyStackFixture, + self).network_value_specs + return dict(value_specs, qos_policy_id=self.qos_stack.qos_policy_id) + + @neutron.skip_if_missing_networking_extensions('security-group') class SecurityGroupsFixture(heat.HeatStackFixture): """Heat stack with some security groups diff --git a/tobiko/openstack/stacks/_nova.py b/tobiko/openstack/stacks/_nova.py index 418ab0052..958556873 100644 --- a/tobiko/openstack/stacks/_nova.py +++ b/tobiko/openstack/stacks/_nova.py @@ -405,6 +405,12 @@ class ExternalServerStackFixture(ServerStackFixture, abc.ABC): return self.network_stack.network_id +class QosServerStackFixture(ServerStackFixture, abc.ABC): + #: stack with the network with a qos policy + network_stack = tobiko.required_setup_fixture( + _neutron.NetworkQosPolicyStackFixture) + + class PeerServerStackFixture(ServerStackFixture, abc.ABC): """Server witch networking access requires passing by another Nova server """ diff --git a/tobiko/openstack/stacks/neutron/network.yaml b/tobiko/openstack/stacks/neutron/network.yaml index 65bf23cdf..5dc0c0493 100644 --- a/tobiko/openstack/stacks/neutron/network.yaml +++ b/tobiko/openstack/stacks/neutron/network.yaml @@ -88,6 +88,11 @@ parameters: type: boolean default: false + has_qos_policy: + description: whether qos policy is attached to the network + type: boolean + default: false + conditions: has_ipv4: @@ -117,6 +122,9 @@ conditions: - get_param: has_l3_ha - get_param: has_gateway + has_qos_policy: + get_param: has_qos_policy + resources: @@ -202,3 +210,8 @@ outputs: description: Network MTU value (integer) value: {get_attr: [network, mtu]} condition: has_net_mtu + + qos_policy_id: + description: QoS Policy ID attached to the network + value: {get_attr: [network, qos_policy_id]} + condition: has_qos_policy diff --git a/tobiko/openstack/stacks/neutron/qos.yaml b/tobiko/openstack/stacks/neutron/qos.yaml new file mode 100644 index 000000000..b1c7deeab --- /dev/null +++ b/tobiko/openstack/stacks/neutron/qos.yaml @@ -0,0 +1,86 @@ +heat_template_version: newton + +description: | + Creates a qos policy with two qos rules (dscp marking and bw limit) + + +parameters: + has_qos_policy: + type: boolean + default: false + + has_bwlimit: + type: boolean + default: false + + has_dscp_marking: + type: boolean + default: false + + bwlimit_kbps: + type: number + + bwlimit_burst_kbps: + type: number + default: 0 + + bwlimit_direction: + type: string + default: egress + constraints: + - allowed_values: [ egress, ingress ] + + dscp_mark: + type: number + constraints: + - allowed_values: [ 0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 46, 48, 56 ] + + +conditions: + has_qos_policy: + get_param: has_qos_policy + + has_bwlimit: + get_param: has_bwlimit + + has_dscp_marking: + get_param: has_dscp_marking + + +resources: + qos_policy: + type: OS::Neutron::QoSPolicy + condition: has_qos_policy + + qos_bwlimit_rule: + type: OS::Neutron::QoSBandwidthLimitRule + condition: has_bwlimit + properties: + max_burst_kbps: {get_param: bwlimit_burst_kbps} + max_kbps: {get_param: bwlimit_kbps} + direction: {get_param: bwlimit_direction} + policy: {get_resource: qos_policy} + + qos_dscp_rule: + type: OS::Neutron::QoSDscpMarkingRule + condition: has_dscp_marking + properties: + dscp_mark: {get_param: dscp_mark} + policy: {get_resource: qos_policy} + + +outputs: + qos_policy_id: + description: QoS Policy ID + value: {get_resource: qos_policy} + condition: has_qos_policy + + qos_bwlimit_rule_id: + description: QoS BW limit rule ID + value: {get_resource: qos_bwlimit_rule} + condition: has_bwlimit + + qos_dscp_rule_id: + description: QoS DSCP marking rule ID + value: {get_resource: qos_dscp_rule} + condition: has_dscp_marking diff --git a/tobiko/tests/scenario/neutron/test_qos.py b/tobiko/tests/scenario/neutron/test_qos.py new file mode 100644 index 000000000..9d181c90f --- /dev/null +++ b/tobiko/tests/scenario/neutron/test_qos.py @@ -0,0 +1,51 @@ +# Copyright (c) 2021 Red Hat +# 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 absolute_import + +from oslo_log import log +import testtools + +import tobiko +from tobiko.openstack import stacks +from tobiko.openstack import topology +from tobiko.tripleo import containers +from tobiko.tripleo import overcloud + + +LOG = log.getLogger(__name__) + + +class QoSBasicTest(testtools.TestCase): + """Tests QoS basic functionality""" + + #: Resources stack with QoS Policy and QoS Rules and Advanced server + stack = tobiko.required_setup_fixture(stacks.CentosQosServerStackFixture) + + def setUp(self): + """Skip these tests if OVN is configured and OSP version is lower than + 16.1 + """ + super(QoSBasicTest, self).setUp() + if (overcloud.has_overcloud() and + topology.verify_osp_version('16.0', lower=True) and + containers.ovn_used_on_overcloud()): + self.skip("QoS not supported in this setup") + + def test_qos_basic(self): + # Verify QoS Policy attached to the network corresponds with the QoS + # Policy previously created + self.assertEqual(self.stack.network_stack.qos_stack.qos_policy_id, + self.stack.network_stack.qos_policy_id) + self.assertIsNone(self.stack.port_details['qos_policy_id'])