diff --git a/heat/engine/resources/openstack/neutron/qos.py b/heat/engine/resources/openstack/neutron/qos.py index d767050aee..a2a65adb9e 100644 --- a/heat/engine/resources/openstack/neutron/qos.py +++ b/heat/engine/resources/openstack/neutron/qos.py @@ -378,10 +378,97 @@ class QoSMinimumBandwidthRule(QoSRule): return [self.resource_id, self.policy_id] +class QoSMinimumPacketRateRule(QoSRule): + """A resource for guaranteeing packet rate. + + This rule can be associated with a QoS policy, and then the policy + can be used by a neutron port to provide guaranteed packet rate 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-pps.html + + The default policy usage of this resource is limited to + administrators only. + """ + + entity = 'minimum_packet_rate_rule' + + required_service_extension = 'qos-pps-minimum' + + support_status = support.SupportStatus( + status=support.SUPPORTED, + version='19.0.0', + ) + + PROPERTIES = ( + MIN_PACKET_RATE, DIRECTION + ) = ( + 'min_kpps', 'direction' + ) + + properties_schema = { + MIN_PACKET_RATE: properties.Schema( + properties.Schema.INTEGER, + _('Min packet rate in kpps.'), + 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(['any', '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_packet_rate_rule( + self.policy_id, + {'minimum_packet_rate_rule': props})['minimum_packet_rate_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_packet_rate_rule( + self.resource_id, self.policy_id) + + def handle_update(self, json_snippet, tmpl_diff, prop_diff): + if prop_diff: + self.client().update_minimum_packet_rate_rule( + self.resource_id, + self.policy_id, + {'minimum_packet_rate_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::QoSMinimumBandwidthRule': QoSMinimumBandwidthRule, + 'OS::Neutron::QoSMinimumPacketRateRule': QoSMinimumPacketRateRule, } diff --git a/heat/policies/resource_types.py b/heat/policies/resource_types.py index 39e6d25965..3bb3fc6f1d 100644 --- a/heat/policies/resource_types.py +++ b/heat/policies/resource_types.py @@ -56,6 +56,9 @@ resource_types_policies = [ policy.RuleDefault( name=POLICY_ROOT % 'OS::Neutron::QoSMinimumBandwidthRule', check_str=base.RULE_PROJECT_ADMIN), + policy.RuleDefault( + name=POLICY_ROOT % 'OS::Neutron::QoSMinimumPacketRateRule', + 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 d46eb64f48..306b15b4f2 100644 --- a/heat/tests/openstack/neutron/test_qos.py +++ b/heat/tests/openstack/neutron/test_qos.py @@ -70,6 +70,19 @@ resources: tenant_id: d66c74c01d6c41b9846088c1ad9634d0 ''' +minimum_packet_rate_rule_template = ''' +heat_template_version: 2021-04-16 +description: This template to define a neutron minimum packet rate rule. +resources: + my_minimum_packet_rate_rule: + type: OS::Neutron::QoSMinimumPacketRateRule + properties: + policy: 477e8273-60a7-4c41-b683-fdb0bc7cd151 + min_kpps: 1000 + direction: any + tenant_id: d66c74c01d6c41b9846088c1ad9634d0 +''' + class NeutronQoSPolicyTest(common.HeatTestCase): def setUp(self): @@ -515,3 +528,118 @@ class NeutronQoSMinimumBandwidthRuleTest(common.HeatTestCase): self.neutronclient.show_minimum_bandwidth_rule.assert_called_once_with( self.minimum_bandwidth_rule.resource_id, self.policy_id) + + +class NeutronQoSMinimumPacketRateRuleTest(common.HeatTestCase): + def setUp(self): + super(NeutronQoSMinimumPacketRateRuleTest, self).setUp() + + self.ctx = utils.dummy_context() + tpl = template_format.parse(minimum_packet_rate_rule_template) + self.stack = stack.Stack( + self.ctx, + 'neutron_minimum_packet_rate_rule_test', + template.Template(tpl) + ) + + self.neutronclient = mock.MagicMock() + self.patchobject(neutron.NeutronClientPlugin, 'has_extension', + return_value=True) + self.minimum_packet_rate_rule = self.stack[ + 'my_minimum_packet_rate_rule'] + self.minimum_packet_rate_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_packet_rate_rule': { + 'id': 'cf0eab12-ef8b-4a62-98d0-70576583c17a', + 'min_kpps': 1000, + 'direction': 'any', + 'tenant_id': 'd66c74c01d6c41b9846088c1ad9634d0' + } + } + + create_props = {'min_kpps': 1000, + 'direction': 'any', + 'tenant_id': 'd66c74c01d6c41b9846088c1ad9634d0'} + self.neutronclient.create_minimum_packet_rate_rule.return_value = rule + + self.minimum_packet_rate_rule.handle_create() + self.assertEqual('cf0eab12-ef8b-4a62-98d0-70576583c17a', + self.minimum_packet_rate_rule.resource_id) + self.neutronclient.create_minimum_packet_rate_rule.\ + assert_called_once_with( + self.policy_id, + {'minimum_packet_rate_rule': create_props}) + + def test_rule_handle_delete(self): + rule_id = 'cf0eab12-ef8b-4a62-98d0-70576583c17a' + self.minimum_packet_rate_rule.resource_id = rule_id + self.neutronclient.delete_minimum_packet_rate_rule.return_value = None + + self.assertIsNone(self.minimum_packet_rate_rule.handle_delete()) + self.neutronclient.delete_minimum_packet_rate_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_packet_rate_rule.resource_id = rule_id + not_found = self.neutronclient.NotFound + self.neutronclient.delete_minimum_packet_rate_rule.side_effect =\ + not_found + + self.assertIsNone(self.minimum_packet_rate_rule.handle_delete()) + self.neutronclient.delete_minimum_packet_rate_rule.\ + assert_called_once_with(rule_id, self.policy_id) + + def test_rule_handle_delete_resource_id_is_none(self): + self.minimum_packet_rate_rule.resource_id = None + self.assertIsNone(self.minimum_packet_rate_rule.handle_delete()) + self.assertEqual( + 0, + self.neutronclient.minimum_packet_rate_rule.call_count) + + def test_rule_handle_update(self): + rule_id = 'cf0eab12-ef8b-4a62-98d0-70576583c17a' + self.minimum_packet_rate_rule.resource_id = rule_id + + prop_diff = { + 'min_kpps': 500 + } + + self.minimum_packet_rate_rule.handle_update( + json_snippet={}, + tmpl_diff={}, + prop_diff=prop_diff.copy()) + + self.neutronclient.update_minimum_packet_rate_rule.\ + assert_called_once_with( + rule_id, + self.policy_id, + {'minimum_packet_rate_rule': prop_diff}) + + def test_rule_get_attr(self): + self.minimum_packet_rate_rule.resource_id = 'test rule' + rule = { + 'minimum_packet_rate_rule': { + 'id': 'cf0eab12-ef8b-4a62-98d0-70576583c17a', + 'min_kpps': 1000, + 'direction': 'egress', + 'tenant_id': 'd66c74c01d6c41b9846088c1ad9634d0' + } + } + self.neutronclient.show_minimum_packet_rate_rule.return_value = rule + + self.assertEqual(rule['minimum_packet_rate_rule'], + self.minimum_packet_rate_rule.FnGetAtt('show')) + + self.neutronclient.show_minimum_packet_rate_rule.\ + assert_called_once_with( + self.minimum_packet_rate_rule.resource_id, + self.policy_id) diff --git a/releasenotes/notes/neutron-qos-minimum-packet-rate-rule-e58e9ced636320f1.yaml b/releasenotes/notes/neutron-qos-minimum-packet-rate-rule-e58e9ced636320f1.yaml new file mode 100644 index 0000000000..2e8af66ee1 --- /dev/null +++ b/releasenotes/notes/neutron-qos-minimum-packet-rate-rule-e58e9ced636320f1.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added ``OS::Neutron::QoSMinimumPacketRateRule`` resource to support + ``minimum_packet_rate_rule`` in Neutron QoS. This resource depends + on Neutron API extension ``qos-pps-minimum`` and according + to the default policy it is admin-only. diff --git a/requirements.txt b/requirements.txt index a6a85292a6..457724754b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,7 +50,7 @@ python-magnumclient>=2.3.0 # Apache-2.0 python-manilaclient>=1.16.0 # Apache-2.0 python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0 python-monascaclient>=1.12.0 # Apache-2.0 -python-neutronclient>=6.14.0 # Apache-2.0 +python-neutronclient>=7.7.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0 python-octaviaclient>=1.8.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0