From b9d009abc9eb6e14fe3f9226dc1e2b8abc40fc33 Mon Sep 17 00:00:00 2001 From: Bence Romsics Date: Thu, 20 Feb 2020 15:49:51 +0100 Subject: [PATCH] New resource OS::Neutron::QoSMinimumBandwidthRule The 'minimum_bandwidth_rule' QoS rule type of Neutron (supported in both egress and ingress directions since the Stein release) did not have a Heat resource yet. This change adds it. Change-Id: I693fe2f7801f78f827ef1c74e5874018cd9cf51b --- .../engine/resources/openstack/neutron/qos.py | 89 ++++++++++++- heat/policies/resource_types.py | 3 + heat/tests/openstack/neutron/test_qos.py | 123 ++++++++++++++++++ ...nimum-bandwidth-rule-cb38db4ebc27688e.yaml | 7 + 4 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/neutron-qos-minimum-bandwidth-rule-cb38db4ebc27688e.yaml diff --git a/heat/engine/resources/openstack/neutron/qos.py b/heat/engine/resources/openstack/neutron/qos.py index 2a75428b1b..683aecbf2d 100644 --- a/heat/engine/resources/openstack/neutron/qos.py +++ b/heat/engine/resources/openstack/neutron/qos.py @@ -292,9 +292,96 @@ class QoSDscpMarkingRule(QoSRule): return [self.resource_id, self.policy_id] +class QoSMinimumBandwidthRule(QoSRule): + """A resource for guaranteeing bandwidth. + + This rule can be associated with a QoS policy, and then the policy + can be used by a neutron port to provide guaranteed bandwidth QoS + capabilities. + + Depending on drivers the guarantee may be enforced on two levels. + First when a server is placed (scheduled) on physical infrastructure + and/or second in the data plane of the physical hypervisor. For details + please see Neutron documentation: + + https://docs.openstack.org/neutron/latest/admin/config-qos-min-bw.html + + The default policy usage of this resource is limited to + administrators only. + """ + + entity = 'minimum_bandwidth_rule' + + required_service_extension = 'qos-bw-minimum-ingress' + + support_status = support.SupportStatus( + status=support.SUPPORTED, + version='14.0.0', + ) + + PROPERTIES = ( + MIN_BANDWIDTH, DIRECTION + ) = ( + 'min_kbps', 'direction' + ) + + properties_schema = { + MIN_BANDWIDTH: properties.Schema( + properties.Schema.INTEGER, + _('Min bandwidth in kbps.'), + required=True, + update_allowed=True, + constraints=[ + constraints.Range(min=0), + ], + ), + DIRECTION: properties.Schema( + properties.Schema.STRING, + _('Traffic direction from the point of view of the port.'), + update_allowed=True, + constraints=[ + constraints.AllowedValues(['egress', 'ingress']), + ], + default='egress', + ), + } + + properties_schema.update(QoSRule.properties_schema) + + def handle_create(self): + props = self.prepare_properties(self.properties, + self.physical_resource_name()) + props.pop(self.POLICY) + + rule = self.client().create_minimum_bandwidth_rule( + self.policy_id, + {'minimum_bandwidth_rule': props})['minimum_bandwidth_rule'] + + self.resource_id_set(rule['id']) + + def handle_delete(self): + if self.resource_id is None: + return + + with self.client_plugin().ignore_not_found: + self.client().delete_minimum_bandwidth_rule( + self.resource_id, self.policy_id) + + def handle_update(self, json_snippet, tmpl_diff, prop_diff): + if prop_diff: + self.client().update_minimum_bandwidth_rule( + self.resource_id, + self.policy_id, + {'minimum_bandwidth_rule': prop_diff}) + + def _res_get_args(self): + return [self.resource_id, self.policy_id] + + def resource_mapping(): return { 'OS::Neutron::QoSPolicy': QoSPolicy, 'OS::Neutron::QoSBandwidthLimitRule': QoSBandwidthLimitRule, - 'OS::Neutron::QoSDscpMarkingRule': QoSDscpMarkingRule + 'OS::Neutron::QoSDscpMarkingRule': QoSDscpMarkingRule, + 'OS::Neutron::QoSMinimumBandwidthRule': QoSMinimumBandwidthRule, } diff --git a/heat/policies/resource_types.py b/heat/policies/resource_types.py index 194986de40..39e6d25965 100644 --- a/heat/policies/resource_types.py +++ b/heat/policies/resource_types.py @@ -53,6 +53,9 @@ resource_types_policies = [ policy.RuleDefault( name=POLICY_ROOT % 'OS::Neutron::QoSDscpMarkingRule', check_str=base.RULE_PROJECT_ADMIN), + policy.RuleDefault( + name=POLICY_ROOT % 'OS::Neutron::QoSMinimumBandwidthRule', + check_str=base.RULE_PROJECT_ADMIN), policy.RuleDefault( name=POLICY_ROOT % 'OS::Neutron::Segment', check_str=base.RULE_PROJECT_ADMIN), diff --git a/heat/tests/openstack/neutron/test_qos.py b/heat/tests/openstack/neutron/test_qos.py index f740d586bf..b490638f53 100644 --- a/heat/tests/openstack/neutron/test_qos.py +++ b/heat/tests/openstack/neutron/test_qos.py @@ -58,6 +58,18 @@ resources: tenant_id: d66c74c01d6c41b9846088c1ad9634d0 ''' +minimum_bandwidth_rule_template = ''' +heat_template_version: 2016-04-08 +description: This template to define a neutron minimum bandwidth rule. +resources: + my_minimum_bandwidth_rule: + type: OS::Neutron::QoSMinimumBandwidthRule + properties: + policy: 477e8273-60a7-4c41-b683-fdb0bc7cd151 + min_kbps: 1000 + tenant_id: d66c74c01d6c41b9846088c1ad9634d0 +''' + class NeutronQoSPolicyTest(common.HeatTestCase): def setUp(self): @@ -392,3 +404,114 @@ class NeutronQoSDscpMarkingRuleTest(common.HeatTestCase): self.neutronclient.show_dscp_marking_rule.assert_called_once_with( self.dscp_marking_rule.resource_id, self.policy_id) + + +class NeutronQoSMinimumBandwidthRuleTest(common.HeatTestCase): + def setUp(self): + super(NeutronQoSMinimumBandwidthRuleTest, self).setUp() + + self.ctx = utils.dummy_context() + tpl = template_format.parse(minimum_bandwidth_rule_template) + self.stack = stack.Stack( + self.ctx, + 'neutron_minimum_bandwidth_rule_test', + template.Template(tpl) + ) + + self.neutronclient = mock.MagicMock() + self.patchobject(neutron.NeutronClientPlugin, 'has_extension', + return_value=True) + self.minimum_bandwidth_rule = self.stack['my_minimum_bandwidth_rule'] + self.minimum_bandwidth_rule.client = mock.MagicMock( + return_value=self.neutronclient) + self.find_mock = self.patchobject( + neutron.neutronV20, + 'find_resourceid_by_name_or_id') + self.policy_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151' + self.find_mock.return_value = self.policy_id + + def test_rule_handle_create(self): + rule = { + 'minimum_bandwidth_rule': { + 'id': 'cf0eab12-ef8b-4a62-98d0-70576583c17a', + 'min_kbps': 1000, + 'direction': 'egress', + 'tenant_id': 'd66c74c01d6c41b9846088c1ad9634d0' + } + } + + create_props = {'min_kbps': 1000, + 'direction': 'egress', + 'tenant_id': 'd66c74c01d6c41b9846088c1ad9634d0'} + self.neutronclient.create_minimum_bandwidth_rule.return_value = rule + + self.minimum_bandwidth_rule.handle_create() + self.assertEqual('cf0eab12-ef8b-4a62-98d0-70576583c17a', + self.minimum_bandwidth_rule.resource_id) + self.neutronclient.create_minimum_bandwidth_rule.\ + assert_called_once_with( + self.policy_id, + {'minimum_bandwidth_rule': create_props}) + + def test_rule_handle_delete(self): + rule_id = 'cf0eab12-ef8b-4a62-98d0-70576583c17a' + self.minimum_bandwidth_rule.resource_id = rule_id + self.neutronclient.delete_minimum_bandwidth_rule.return_value = None + + self.assertIsNone(self.minimum_bandwidth_rule.handle_delete()) + self.neutronclient.delete_minimum_bandwidth_rule.\ + assert_called_once_with(rule_id, self.policy_id) + + def test_rule_handle_delete_not_found(self): + rule_id = 'cf0eab12-ef8b-4a62-98d0-70576583c17a' + self.minimum_bandwidth_rule.resource_id = rule_id + not_found = self.neutronclient.NotFound + self.neutronclient.delete_minimum_bandwidth_rule.side_effect =\ + not_found + + self.assertIsNone(self.minimum_bandwidth_rule.handle_delete()) + self.neutronclient.delete_minimum_bandwidth_rule.\ + assert_called_once_with(rule_id, self.policy_id) + + def test_rule_handle_delete_resource_id_is_none(self): + self.minimum_bandwidth_rule.resource_id = None + self.assertIsNone(self.minimum_bandwidth_rule.handle_delete()) + self.assertEqual(0, + self.neutronclient.minimum_bandwidth_rule.call_count) + + def test_rule_handle_update(self): + rule_id = 'cf0eab12-ef8b-4a62-98d0-70576583c17a' + self.minimum_bandwidth_rule.resource_id = rule_id + + prop_diff = { + 'min_kbps': 500 + } + + self.minimum_bandwidth_rule.handle_update( + json_snippet={}, + tmpl_diff={}, + prop_diff=prop_diff.copy()) + + self.neutronclient.update_minimum_bandwidth_rule.\ + assert_called_once_with( + rule_id, + self.policy_id, + {'minimum_bandwidth_rule': prop_diff}) + + def test_rule_get_attr(self): + self.minimum_bandwidth_rule.resource_id = 'test rule' + rule = { + 'minimum_bandwidth_rule': { + 'id': 'cf0eab12-ef8b-4a62-98d0-70576583c17a', + 'min_kbps': 1000, + 'direction': 'egress', + 'tenant_id': 'd66c74c01d6c41b9846088c1ad9634d0' + } + } + self.neutronclient.show_minimum_bandwidth_rule.return_value = rule + + self.assertEqual(rule['minimum_bandwidth_rule'], + self.minimum_bandwidth_rule.FnGetAtt('show')) + + self.neutronclient.show_minimum_bandwidth_rule.assert_called_once_with( + self.minimum_bandwidth_rule.resource_id, self.policy_id) diff --git a/releasenotes/notes/neutron-qos-minimum-bandwidth-rule-cb38db4ebc27688e.yaml b/releasenotes/notes/neutron-qos-minimum-bandwidth-rule-cb38db4ebc27688e.yaml new file mode 100644 index 0000000000..e82fd803e9 --- /dev/null +++ b/releasenotes/notes/neutron-qos-minimum-bandwidth-rule-cb38db4ebc27688e.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + New resource ``OS::Neutron::QoSMinimumBandwidthRule`` to support + ``minimum_bandwidth_rules`` in Neutron QoS. This resource depends + on Neutron API extension ``qos-bw-minimum-ingress`` and according + to the default policy it is admin-only.