Merge "Add minimum bw qos rule validation for network" into stable/victoria

This commit is contained in:
Zuul 2021-03-10 13:49:23 +00:00 committed by Gerrit Code Review
commit 19b42a3186
8 changed files with 148 additions and 2 deletions

View File

@ -154,6 +154,18 @@ class QosServiceDriverManager(object):
return False
def validate_rule_for_network(self, context, rule, network_id):
for driver in self._drivers:
if driver.is_rule_supported(rule):
# https://review.opendev.org/c/openstack/neutron-lib/+/774083
# is not present, in this release, in neutron-lib.
if hasattr(driver, 'validate_rule_for_network'):
return driver.validate_rule_for_network(context, rule,
network_id)
return True
return False
@property
def supported_rule_types(self):
if not self._drivers:

View File

@ -60,11 +60,14 @@ class OVSDriver(base.DriverBase):
requires_rpc_notifications=True)
def validate_rule_for_port(self, context, rule, port):
return self.validate_rule_for_network(context, rule, port.network_id)
def validate_rule_for_network(self, context, rule, network_id):
# Minimum-bandwidth rule is only supported on networks whose
# first segment is backed by a physnet.
if rule.rule_type == qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH:
net = network_object.Network.get_object(
context, id=port.network_id)
context, id=network_id)
physnet = net.segments[0].physical_network
if physnet is None:
return False

View File

@ -49,6 +49,11 @@ from neutron.objects.qos import rule_type as rule_type_object
from neutron.services.qos.drivers import manager
class QosRuleNotSupportedByNetwork(lib_exc.Conflict):
message = _("Rule %(rule_type)s is not supported "
"by network %(network_id)s")
@resource_extend.has_resource_extenders
class QoSPlugin(qos.QoSPluginBase):
"""Implementation of the Neutron QoS Service Plugin.
@ -86,6 +91,10 @@ class QoSPlugin(qos.QoSPluginBase):
self._validate_update_network_callback,
callbacks_resources.NETWORK,
callbacks_events.PRECOMMIT_UPDATE)
callbacks_registry.subscribe(
self._validate_create_network_callback,
callbacks_resources.NETWORK,
callbacks_events.PRECOMMIT_CREATE)
@staticmethod
@resource_extend.extends([port_def.COLLECTION_NAME])
@ -252,6 +261,20 @@ class QoSPlugin(qos.QoSPluginBase):
self.validate_policy_for_port(context, policy, updated_port)
def _validate_create_network_callback(self, resource, event, trigger,
**kwargs):
context = kwargs['context']
network_id = kwargs['network']['id']
network = network_object.Network.get_object(context, id=network_id)
policy_id = network.qos_policy_id
if policy_id is None:
return
policy = policy_object.QosPolicy.get_object(
context.elevated(), id=policy_id)
self.validate_policy_for_network(context, policy, network_id)
def _validate_update_network_callback(self, resource, event, trigger,
payload=None):
context = payload.context
@ -266,6 +289,9 @@ class QoSPlugin(qos.QoSPluginBase):
policy = policy_object.QosPolicy.get_object(
context.elevated(), id=policy_id)
self.validate_policy_for_network(
context, policy, network_id=updated_network['id'])
ports = ports_object.Port.get_objects(
context, network_id=updated_network['id'])
# Filter only this ports which don't have overwritten policy
@ -289,6 +315,13 @@ class QoSPlugin(qos.QoSPluginBase):
raise qos_exc.QosRuleNotSupported(rule_type=rule.rule_type,
port_id=port['id'])
def validate_policy_for_network(self, context, policy, network_id):
for rule in policy.rules:
if not self.driver_manager.validate_rule_for_network(
context, rule, network_id):
raise QosRuleNotSupportedByNetwork(
rule_type=rule.rule_type, network_id=network_id)
def reject_min_bw_rule_updates(self, context, policy):
ports = self._get_ports_with_policy(context, policy)
for port in ports:

View File

@ -81,7 +81,7 @@ class ClientFixture(fixtures.Fixture):
def create_network(self, tenant_id, name=None, external=False,
network_type=None, segmentation_id=None,
physical_network=None, mtu=None):
physical_network=None, mtu=None, qos_policy_id=None):
resource_type = 'network'
name = name or utils.get_rand_name(prefix=resource_type)
@ -96,6 +96,8 @@ class ClientFixture(fixtures.Fixture):
spec['provider:physical_network'] = physical_network
if mtu is not None:
spec['mtu'] = mtu
if qos_policy_id is not None:
spec['qos_policy_id'] = qos_policy_id
return self._create_resource(resource_type, spec)

View File

@ -707,6 +707,31 @@ class TestMinBwQoSOvs(_TestMinBwQoS, base.BaseFullStackTestCase):
queues = '\nList of OVS Queue registers:\n%s' % '\n'.join(queues)
self.fail(queuenum + qoses + queues)
def test_min_bw_qos_create_network_vxlan_not_supported(self):
qos_policy = self._create_qos_policy()
qos_policy_id = qos_policy['id']
self.safe_client.create_minimum_bandwidth_rule(
self.tenant_id, qos_policy_id, MIN_BANDWIDTH, self.direction)
network_args = {'network_type': 'vxlan',
'qos_policy_id': qos_policy_id}
self.assertRaises(
exceptions.Conflict,
self.safe_client.create_network,
self.tenant_id, name='network-test', **network_args)
def test_min_bw_qos_update_network_vxlan_not_supported(self):
network_args = {'network_type': 'vxlan'}
network = self.safe_client.create_network(
self.tenant_id, name='network-test', **network_args)
qos_policy = self._create_qos_policy()
qos_policy_id = qos_policy['id']
self.safe_client.create_minimum_bandwidth_rule(
self.tenant_id, qos_policy_id, MIN_BANDWIDTH, self.direction)
self.assertRaises(
exceptions.Conflict,
self.client.update_network, network['id'],
body={'network': {'qos_policy_id': qos_policy_id}})
def test_min_bw_qos_port_removed(self):
"""Test if min BW limit config is properly removed when port removed.

View File

@ -43,3 +43,5 @@ class TestOVSDriver(base.BaseQosTestCase):
return_value=net):
test_method(self.driver.validate_rule_for_port(
mock.Mock(), rule, port))
test_method(self.driver.validate_rule_for_network(
mock.Mock(), rule, network_id=mock.Mock()))

View File

@ -119,6 +119,29 @@ class TestQoSDriversRulesValidations(TestQosDriversManagerBase):
else:
is_rule_supported_mock.assert_not_called()
def test_validate_rule_for_network(self):
driver_manager = self._create_manager_with_drivers({
'driver-A': {
'is_loaded': True,
'rules': {
qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: {
"min_kbps": {'type:values': None},
'direction': {
'type:values': lib_consts.VALID_DIRECTIONS}
}
}
}
})
rule = rule_object.QosMinimumBandwidthRule(
self.ctxt, id=uuidutils.generate_uuid())
is_rule_supported_mock = mock.Mock()
is_rule_supported_mock.return_value = True
driver_manager._drivers[0].is_rule_supported = is_rule_supported_mock
self.assertTrue(driver_manager.validate_rule_for_network(
mock.Mock(), rule, mock.Mock()))
is_rule_supported_mock.assert_called_once_with(rule)
def test_validate_rule_for_port_rule_vif_type_supported(self):
port = self._get_port(
portbindings.VIF_TYPE_OVS, portbindings.VNIC_NORMAL)

View File

@ -326,6 +326,8 @@ class TestQosPlugin(base.BaseQosTestCase):
'neutron.objects.qos.policy.QosPolicy.get_object',
return_value=policy_mock
) as get_policy, mock.patch.object(
self.qos_plugin, "validate_policy_for_network"
) as validate_policy_for_network, mock.patch.object(
self.qos_plugin, "validate_policy_for_ports"
) as validate_policy_for_ports, mock.patch.object(
self.ctxt, "elevated", return_value=admin_ctxt
@ -337,6 +339,7 @@ class TestQosPlugin(base.BaseQosTestCase):
states=(kwargs['original_network'],)))
if policy_id is None or policy_id == original_policy_id:
get_policy.assert_not_called()
validate_policy_for_network.assert_not_called()
get_ports.assert_not_called()
validate_policy_for_ports.assert_not_called()
else:
@ -384,6 +387,20 @@ class TestQosPlugin(base.BaseQosTestCase):
except qos_exc.QosRuleNotSupported:
self.fail("QosRuleNotSupported exception unexpectedly raised")
def test_validate_policy_for_network(self):
network = uuidutils.generate_uuid()
with mock.patch.object(
self.qos_plugin.driver_manager, "validate_rule_for_network",
return_value=True
):
self.policy.rules = [self.rule]
try:
self.qos_plugin.validate_policy_for_network(
self.ctxt, self.policy, network_id=network)
except qos_exc.QosRuleNotSupportedByNetwork:
self.fail("QosRuleNotSupportedByNetwork "
"exception unexpectedly raised")
def test_create_min_bw_rule_on_bound_port(self):
policy = self._get_policy()
policy.rules = [self.min_rule]
@ -1236,6 +1253,35 @@ class TestQosPluginDB(base.BaseQosTestCase):
network.create()
return network
def _test_validate_create_network_callback(self, network_qos=False):
net_qos_obj = self._make_qos_policy()
net_qos_id = net_qos_obj.id if network_qos else None
network = self._make_network(qos_policy_id=net_qos_id)
kwargs = {"context": self.context,
"network": network}
with mock.patch.object(self.qos_plugin,
'validate_policy_for_network') \
as mock_validate_policy:
self.qos_plugin._validate_create_network_callback(
'NETWORK', 'precommit_create', 'test_plugin', **kwargs)
qos_policy = None
if network_qos:
qos_policy = net_qos_obj
if qos_policy:
mock_validate_policy.assert_called_once_with(
self.context, qos_policy, network.id)
else:
mock_validate_policy.assert_not_called()
def test_validate_create_network_callback(self):
self._test_validate_create_network_callback(network_qos=True)
def test_validate_create_network_callback_no_qos(self):
self._test_validate_create_network_callback(network_qos=False)
def _test_validate_create_port_callback(self, port_qos=False,
network_qos=False):
net_qos_obj = self._make_qos_policy()