Fill port-resource-request

port-resource-request is an admin-only, read-only attribute for neutron
ports. The content of this field is filled from the QoS minimum
bandwidth rule attached to the port.

Change-Id: Ic9862a1b5e24eb798d67823ac3bacab9e54420d8
Partial-Bug: #1578989
See-Also: https://review.openstack.org/502306 (nova spec)
See-Also: https://review.openstack.org/508149 (neutron spec)
This commit is contained in:
Lajos Katona 2018-07-20 17:04:46 +02:00
parent 7bb0b84151
commit e78f82e64f
3 changed files with 185 additions and 0 deletions

View File

@ -106,6 +106,10 @@ rules = [
'get_port:binding:profile',
'rule:admin_only',
description='Access rule for getting binding profile of port'),
policy.RuleDefault(
'get_port:resource_request',
'rule:admin_only',
description='Access rule for getting resource request of port'),
# TODO(amotoki): Add get_port:binding:vnic_type
# TODO(amotoki): Add get_port:binding:data_plane_status

View File

@ -13,16 +13,24 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import port_resource_request
from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import qos as qos_apidef
from neutron_lib.api.definitions import qos_bw_minimum_ingress
from neutron_lib.callbacks import events as callbacks_events
from neutron_lib.callbacks import registry as callbacks_registry
from neutron_lib.callbacks import resources as callbacks_resources
from neutron_lib import constants as nl_constants
from neutron_lib import context
from neutron_lib.db import api as db_api
from neutron_lib import exceptions as lib_exc
from neutron_lib.placement import constants as pl_constants
from neutron_lib.placement import utils as pl_utils
from neutron_lib.services.qos import constants as qos_consts
from neutron.common import exceptions as n_exc
from neutron.db import _resource_extend as resource_extend
from neutron.db import db_base_plugin_common
from neutron.extensions import qos
from neutron.objects import base as base_obj
@ -34,6 +42,7 @@ from neutron.objects.qos import rule_type as rule_type_object
from neutron.services.qos.drivers import manager
@resource_extend.has_resource_extenders
class QoSPlugin(qos.QoSPluginBase):
"""Implementation of the Neutron QoS Service Plugin.
@ -45,6 +54,7 @@ class QoSPlugin(qos.QoSPluginBase):
'qos-bw-limit-direction',
'qos-default',
'qos-rule-type-details',
port_resource_request.ALIAS,
qos_bw_minimum_ingress.ALIAS]
__native_pagination_support = True
@ -68,6 +78,69 @@ class QoSPlugin(qos.QoSPluginBase):
callbacks_resources.NETWORK,
callbacks_events.PRECOMMIT_UPDATE)
@staticmethod
@resource_extend.extends([port_def.COLLECTION_NAME])
def _extend_port_resource_request(port_res, port_db):
"""Add resource request to a port."""
port_res['resource_request'] = None
qos_policy = policy_object.QosPolicy.get_port_policy(
context.get_admin_context(), port_res['id'])
# Note(lajoskatona): QosPolicyPortBinding is not ready for some
# reasons, so let's try and fetch the QoS policy directly if there is a
# qos_policy_id in port_res.
if (not qos_policy and 'qos_policy_id' in port_res and
port_res['qos_policy_id']):
qos_policy = policy_object.QosPolicy.get_policy_obj(
context.get_admin_context(), port_res['qos_policy_id']
)
# Note(lajoskatona): handle the case when the port inherits qos-policy
# from the network.
if not qos_policy:
net = network_object.Network.get_object(
context.get_admin_context(), id=port_res['network_id'])
if net.qos_policy_id:
qos_policy = policy_object.QosPolicy.get_network_policy(
context.get_admin_context(), net.id)
if not qos_policy:
return port_res
resources = {}
rule_direction_class = {
nl_constants.INGRESS_DIRECTION:
pl_constants.CLASS_NET_BW_INGRESS_KBPS,
nl_constants.EGRESS_DIRECTION:
pl_constants.CLASS_NET_BW_EGRESS_KBPS
}
for rule in qos_policy.rules:
if rule.rule_type == qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH:
resources[rule_direction_class[rule.direction]] = rule.min_kbps
if not resources:
return port_res
vnic_trait = pl_utils.vnic_type_trait(
port_res[portbindings.VNIC_TYPE])
# TODO(lajoskatona): Change to handle all segments when any traits
# support will be available. See Placement spec:
# https://review.openstack.org/565730
first_segment = network_object.NetworkSegment.get_objects(
context.get_admin_context(),
network_id=port_res['network_id'])[0]
if not first_segment or not first_segment.physical_network:
return port_res
physnet_trait = pl_utils.physnet_trait(
first_segment.physical_network)
resource_request = {
'required': [physnet_trait, vnic_trait],
'resources': resources
}
port_res['resource_request'] = resource_request
return port_res
def _get_ports_with_policy(self, context, policy):
networks_ids = policy.get_bound_networks()
ports_with_net_policy = ports_object.Port.get_objects(

View File

@ -14,8 +14,10 @@ import copy
import mock
from neutron_lib.callbacks import events
from neutron_lib import constants as lib_constants
from neutron_lib import context
from neutron_lib import exceptions as lib_exc
from neutron_lib.placement import constants as pl_constants
from neutron_lib.plugins import constants as plugins_constants
from neutron_lib.plugins import directory
from neutron_lib.services.qos import constants as qos_consts
@ -107,6 +109,112 @@ class TestQosPlugin(base.BaseQosTestCase):
self.assertEqual(call_args[1], ctxt)
self.assertIsInstance(call_args[2], policy_object.QosPolicy)
def _create_and_extend_port(self, bw_rules, physical_network='public',
has_qos_policy=True, network_qos=None):
network_id = uuidutils.generate_uuid()
if has_qos_policy or network_qos:
policy = self.policy
policy_id = self.policy.id
self.policy.rules = bw_rules
for rule in bw_rules:
rule.qos_policy_id = self.policy.id
else:
policy = None
policy_id = None
port_res = {
"id": uuidutils.generate_uuid(),
"qos_policy_id": policy_id,
"network_id": network_id,
"binding:vnic_type": "normal",
}
network_mock = mock.MagicMock(id=network_id, qos_policy_id=policy_id)
segment_mock = mock.MagicMock(network_id=network_id,
physical_network=physical_network)
with mock.patch(
'neutron.objects.network.Network.get_object',
return_value=network_mock
), mock.patch(
'neutron.objects.network.NetworkSegment.get_objects',
return_value=[segment_mock]
), mock.patch(
'neutron.objects.qos.policy.QosPolicy.get_port_policy',
return_value=policy
):
return qos_plugin.QoSPlugin._extend_port_resource_request(
port_res, {})
def test__extend_port_resource_request_min_bw_rule(self):
self.min_rule.direction = lib_constants.EGRESS_DIRECTION
port = self._create_and_extend_port([self.min_rule])
self.assertEqual(
['CUSTOM_PHYSNET_PUBLIC', 'CUSTOM_VNIC_TYPE_NORMAL'],
port['resource_request']['required']
)
self.assertEqual(
{pl_constants.CLASS_NET_BW_EGRESS_KBPS: 10},
port['resource_request']['resources'],
)
def test__extend_port_resource_request_mixed_rules(self):
self.min_rule.direction = lib_constants.EGRESS_DIRECTION
min_rule_ingress_data = {
'id': uuidutils.generate_uuid(),
'min_kbps': 20,
'direction': lib_constants.INGRESS_DIRECTION}
min_rule_ingress = rule_object.QosMinimumBandwidthRule(
self.ctxt, **min_rule_ingress_data)
port = self._create_and_extend_port([self.min_rule, min_rule_ingress])
self.assertEqual(
['CUSTOM_PHYSNET_PUBLIC', 'CUSTOM_VNIC_TYPE_NORMAL'],
port['resource_request']['required']
)
self.assertEqual(
{
pl_constants.CLASS_NET_BW_EGRESS_KBPS: 10,
pl_constants.CLASS_NET_BW_INGRESS_KBPS: 20
},
port['resource_request']['resources'],
)
def test__extend_port_resource_request_non_min_bw_rule(self):
port = self._create_and_extend_port([self.rule])
self.assertIsNone(port.get('resource_request'))
def test__extend_port_resource_request_non_provider_net(self):
self.min_rule.direction = lib_constants.EGRESS_DIRECTION
port = self._create_and_extend_port([self.min_rule],
physical_network=None)
self.assertIsNone(port.get('resource_request'))
def test__extend_port_resource_request_no_qos_policy(self):
port = self._create_and_extend_port([], physical_network='public',
has_qos_policy=False)
self.assertIsNone(port.get('resource_request'))
def test__extend_port_resource_request_inherited_policy(self):
self.min_rule.direction = lib_constants.EGRESS_DIRECTION
self.policy.rules = [self.min_rule]
self.min_rule.qos_policy_id = self.policy.id
port = self._create_and_extend_port([self.min_rule],
network_qos=self.policy)
self.assertEqual(
['CUSTOM_PHYSNET_PUBLIC', 'CUSTOM_VNIC_TYPE_NORMAL'],
port['resource_request']['required']
)
self.assertEqual(
{pl_constants.CLASS_NET_BW_EGRESS_KBPS: 10},
port['resource_request']['resources'],
)
def test_get_ports_with_policy(self):
network_ports = [
mock.MagicMock(qos_policy_id=None),