Merge "Support for minimum bandwidth rules in tunnelled networks"
This commit is contained in:
commit
c078c6569f
|
@ -40,9 +40,6 @@ Limitations
|
||||||
technical reasons (in this case the port is created too late for
|
technical reasons (in this case the port is created too late for
|
||||||
Neutron to affect scheduling).
|
Neutron to affect scheduling).
|
||||||
|
|
||||||
* Bandwidth guarantees for ports can only be requested on networks
|
|
||||||
backed by a physical network (physnet).
|
|
||||||
|
|
||||||
* In Stein there is no support for networks with multiple physnets.
|
* In Stein there is no support for networks with multiple physnets.
|
||||||
However some simpler multi-segment networks are still supported:
|
However some simpler multi-segment networks are still supported:
|
||||||
|
|
||||||
|
@ -185,6 +182,13 @@ supported:
|
||||||
by a ``direct-physical`` port.
|
by a ``direct-physical`` port.
|
||||||
|
|
||||||
|
|
||||||
|
Since 2023.1 (Antelope), Open vSwitch and OVN mechanism drivers can specify
|
||||||
|
the available bandwidth for tunnelled networks (SR-IOV does not support these
|
||||||
|
network types yet). The key "rp_tunnelled" is used to model those networks
|
||||||
|
that are not backed by a physical network. This bandwidth models the limits
|
||||||
|
of the VTEP/TEP interface used to send the tunnelled traffic (VXLAN, Geneve).
|
||||||
|
|
||||||
|
|
||||||
neutron-server config
|
neutron-server config
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -260,9 +264,20 @@ Valid values are all the
|
||||||
|
|
||||||
[ovs]
|
[ovs]
|
||||||
bridge_mappings = physnet0:br-physnet0,...
|
bridge_mappings = physnet0:br-physnet0,...
|
||||||
resource_provider_bandwidths = br-physnet0:10000000:10000000,...
|
resource_provider_bandwidths = br-physnet0:10000000:10000000,rp_tunnelled:20000000:20000000,...
|
||||||
#resource_provider_inventory_defaults = step_size:1000,...
|
#resource_provider_inventory_defaults = step_size:1000,...
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
"rp_tunnelled" is not a bridge nor an interface present in the host.
|
||||||
|
The ML2/OVS agent will read the host local "resource_provider_bandwidths"
|
||||||
|
and will assign, by default, the "rp_tunnelled" resource provider to
|
||||||
|
the local host where is running. In other words, it is not needed to
|
||||||
|
populate "resource_provider_hypervisors" with the host assigned to this
|
||||||
|
specific resource provider.
|
||||||
|
|
||||||
|
|
||||||
neutron-sriov-agent config
|
neutron-sriov-agent config
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -296,9 +311,9 @@ SR-IOV and OVS agents. This is how the values are registered:
|
||||||
$ root@dev20:~# ovs-vsctl list Open_vSwitch
|
$ root@dev20:~# ovs-vsctl list Open_vSwitch
|
||||||
...
|
...
|
||||||
external_ids : {hostname=dev20.fistro.com, \
|
external_ids : {hostname=dev20.fistro.com, \
|
||||||
ovn-cms-options="resource_provider_bandwidths=br-ex:1001:2000;br-ex2:3000:4000, \
|
ovn-cms-options="resource_provider_bandwidths=br-ex:1001:2000;br-ex2:3000:4000;rp_tunnelled:5000:6000, \
|
||||||
resource_provider_inventory_defaults=allocation_ratio:1.0;min_unit:10, \
|
resource_provider_inventory_defaults=allocation_ratio:1.0;min_unit:10, \
|
||||||
resource_provider_hypervisors=br-ex:dev20.fistro.com;br-ex2:dev20.fistro.com", \
|
resource_provider_hypervisors=br-ex:dev20.fistro.com;br-ex2:dev20.fistro.com;rp_tunnelled:dev20.fistro.com", \
|
||||||
rundir="/var/run/openvswitch", \
|
rundir="/var/run/openvswitch", \
|
||||||
system-id="029e7d3d-d2ab-4f2c-bc92-ec58c94a8fc1"}
|
system-id="029e7d3d-d2ab-4f2c-bc92-ec58c94a8fc1"}
|
||||||
...
|
...
|
||||||
|
@ -354,7 +369,9 @@ queue periodically.
|
||||||
$ openstack network agent show -f value -c configuration 5e57b85f-b017-419a-8745-9c406e149f9e
|
$ openstack network agent show -f value -c configuration 5e57b85f-b017-419a-8745-9c406e149f9e
|
||||||
{'bridge_mappings': {'physnet0': 'br-physnet0'},
|
{'bridge_mappings': {'physnet0': 'br-physnet0'},
|
||||||
'resource_provider_bandwidths': {'br-physnet0': {'egress': 10000000,
|
'resource_provider_bandwidths': {'br-physnet0': {'egress': 10000000,
|
||||||
'ingress': 10000000}},
|
'ingress': 10000000}
|
||||||
|
'rp_tunnelled': {'egress': 20000000,
|
||||||
|
'ingress': 20000000}},
|
||||||
'resource_provider_inventory_defaults': {'allocation_ratio': 1.0,
|
'resource_provider_inventory_defaults': {'allocation_ratio': 1.0,
|
||||||
'min_unit': 1,
|
'min_unit': 1,
|
||||||
'reserved': 0,
|
'reserved': 0,
|
||||||
|
@ -578,6 +595,7 @@ Please find an example in section `Propagation of resource information`_.
|
||||||
| 1c7e83f0-108d-5c35-ada7-7ebebbe43aad | devstack0:NIC Switch agent:ens5 | 2 | 3b36d91e-bf60-460f-b1f8-3322dee5cdfd | 4a8a819d-61f9-5822-8c5c-3e9c7cb942d6 |
|
| 1c7e83f0-108d-5c35-ada7-7ebebbe43aad | devstack0:NIC Switch agent:ens5 | 2 | 3b36d91e-bf60-460f-b1f8-3322dee5cdfd | 4a8a819d-61f9-5822-8c5c-3e9c7cb942d6 |
|
||||||
| 89ca1421-5117-5348-acab-6d0e2054239c | devstack0:Open vSwitch agent | 0 | 3b36d91e-bf60-460f-b1f8-3322dee5cdfd | 3b36d91e-bf60-460f-b1f8-3322dee5cdfd |
|
| 89ca1421-5117-5348-acab-6d0e2054239c | devstack0:Open vSwitch agent | 0 | 3b36d91e-bf60-460f-b1f8-3322dee5cdfd | 3b36d91e-bf60-460f-b1f8-3322dee5cdfd |
|
||||||
| f9c9ce07-679d-5d72-ac5f-31720811629a | devstack0:Open vSwitch agent:br-physnet0 | 2 | 3b36d91e-bf60-460f-b1f8-3322dee5cdfd | 89ca1421-5117-5348-acab-6d0e2054239c |
|
| f9c9ce07-679d-5d72-ac5f-31720811629a | devstack0:Open vSwitch agent:br-physnet0 | 2 | 3b36d91e-bf60-460f-b1f8-3322dee5cdfd | 89ca1421-5117-5348-acab-6d0e2054239c |
|
||||||
|
| 521f53a6-c8c0-583c-98da-7a47f39ff887 | devstack0:Open vSwitch agent:rp_tunnelled| 2 | 3b36d91e-bf60-460f-b1f8-3322dee5cdfd | 89ca1421-5117-5348-acab-6d0e2054239c |
|
||||||
+--------------------------------------+------------------------------------------+------------+--------------------------------------+--------------------------------------+
|
+--------------------------------------+------------------------------------------+------------+--------------------------------------+--------------------------------------+
|
||||||
|
|
||||||
* Does Placement have the expected traits?
|
* Does Placement have the expected traits?
|
||||||
|
@ -587,6 +605,7 @@ Please find an example in section `Propagation of resource information`_.
|
||||||
# as admin
|
# as admin
|
||||||
$ openstack --os-placement-api-version 1.17 trait list | awk '/CUSTOM_/ { print $2 }' | sort
|
$ openstack --os-placement-api-version 1.17 trait list | awk '/CUSTOM_/ { print $2 }' | sort
|
||||||
CUSTOM_PHYSNET_PHYSNET0
|
CUSTOM_PHYSNET_PHYSNET0
|
||||||
|
CUSTOM_TUNNELLED_NETWORKS
|
||||||
CUSTOM_VNIC_TYPE_DIRECT
|
CUSTOM_VNIC_TYPE_DIRECT
|
||||||
CUSTOM_VNIC_TYPE_DIRECT_PHYSICAL
|
CUSTOM_VNIC_TYPE_DIRECT_PHYSICAL
|
||||||
CUSTOM_VNIC_TYPE_MACVTAP
|
CUSTOM_VNIC_TYPE_MACVTAP
|
||||||
|
|
|
@ -15,8 +15,12 @@
|
||||||
from neutron_lib import constants as nlib_const
|
from neutron_lib import constants as nlib_const
|
||||||
from neutron_lib.placement import utils as place_utils
|
from neutron_lib.placement import utils as place_utils
|
||||||
import os_resource_classes as orc
|
import os_resource_classes as orc
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from neutron.common import _constants as n_const
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,6 +103,7 @@ class PlacementState(object):
|
||||||
self._device_mappings = device_mappings
|
self._device_mappings = device_mappings
|
||||||
self._supported_vnic_types = supported_vnic_types
|
self._supported_vnic_types = supported_vnic_types
|
||||||
self._client = client
|
self._client = client
|
||||||
|
self._rp_tun_name = cfg.CONF.ml2.tunnelled_network_rp_name
|
||||||
|
|
||||||
def _deferred_update_physnet_traits(self):
|
def _deferred_update_physnet_traits(self):
|
||||||
traits = []
|
traits = []
|
||||||
|
@ -111,6 +116,10 @@ class PlacementState(object):
|
||||||
name=place_utils.physnet_trait(physnet)))
|
name=place_utils.physnet_trait(physnet)))
|
||||||
return traits
|
return traits
|
||||||
|
|
||||||
|
def _deferred_update_tunnelled_traits(self):
|
||||||
|
return [DeferredCall(self._client.update_trait,
|
||||||
|
name=n_const.TRAIT_NETWORK_TUNNEL)]
|
||||||
|
|
||||||
def _deferred_update_vnic_type_traits(self):
|
def _deferred_update_vnic_type_traits(self):
|
||||||
traits = []
|
traits = []
|
||||||
for vnic_type in self._supported_vnic_types:
|
for vnic_type in self._supported_vnic_types:
|
||||||
|
@ -123,6 +132,7 @@ class PlacementState(object):
|
||||||
def deferred_update_traits(self):
|
def deferred_update_traits(self):
|
||||||
traits = []
|
traits = []
|
||||||
traits += self._deferred_update_physnet_traits()
|
traits += self._deferred_update_physnet_traits()
|
||||||
|
traits += self._deferred_update_tunnelled_traits()
|
||||||
traits += self._deferred_update_vnic_type_traits()
|
traits += self._deferred_update_vnic_type_traits()
|
||||||
return traits
|
return traits
|
||||||
|
|
||||||
|
@ -196,7 +206,8 @@ class PlacementState(object):
|
||||||
|
|
||||||
def deferred_update_resource_provider_traits(self):
|
def deferred_update_resource_provider_traits(self):
|
||||||
rp_traits = []
|
rp_traits = []
|
||||||
|
tunnelled_trait_mappings = {
|
||||||
|
self._rp_tun_name: n_const.TRAIT_NETWORK_TUNNEL}
|
||||||
physnet_trait_mappings = {}
|
physnet_trait_mappings = {}
|
||||||
for physnet, devices in self._device_mappings.items():
|
for physnet, devices in self._device_mappings.items():
|
||||||
for device in devices:
|
for device in devices:
|
||||||
|
@ -210,8 +221,8 @@ class PlacementState(object):
|
||||||
self._driver_uuid_namespace,
|
self._driver_uuid_namespace,
|
||||||
self._hypervisor_rps[device]['name'],
|
self._hypervisor_rps[device]['name'],
|
||||||
device)
|
device)
|
||||||
traits = []
|
traits = [physnet_trait_mappings.get(device) or
|
||||||
traits.append(physnet_trait_mappings[device])
|
tunnelled_trait_mappings[device]]
|
||||||
traits.extend(vnic_type_traits)
|
traits.extend(vnic_type_traits)
|
||||||
rp_traits.append(
|
rp_traits.append(
|
||||||
DeferredCall(
|
DeferredCall(
|
||||||
|
|
|
@ -135,7 +135,8 @@ def get_hypervisor_hostname():
|
||||||
|
|
||||||
# TODO(bence romsics): rehome this to neutron_lib.placement.utils
|
# TODO(bence romsics): rehome this to neutron_lib.placement.utils
|
||||||
def default_rp_hypervisors(hypervisors, device_mappings,
|
def default_rp_hypervisors(hypervisors, device_mappings,
|
||||||
default_hypervisor=None):
|
default_hypervisor=None,
|
||||||
|
tunnelled_network_rp_name=None):
|
||||||
"""Fill config option 'resource_provider_hypervisors' with defaults.
|
"""Fill config option 'resource_provider_hypervisors' with defaults.
|
||||||
|
|
||||||
:param hypervisors: Config option 'resource_provider_hypervisors'
|
:param hypervisors: Config option 'resource_provider_hypervisors'
|
||||||
|
@ -145,14 +146,13 @@ def default_rp_hypervisors(hypervisors, device_mappings,
|
||||||
format.
|
format.
|
||||||
:param default_hypervisor: Default hypervisor hostname. If not set,
|
:param default_hypervisor: Default hypervisor hostname. If not set,
|
||||||
it tries to default to fully qualified domain name (fqdn)
|
it tries to default to fully qualified domain name (fqdn)
|
||||||
|
:param tunnelled_network_rp_name: the resource provider name for tunnelled
|
||||||
|
networks; if present, it will be added to the devices list.
|
||||||
"""
|
"""
|
||||||
_default_hypervisor = default_hypervisor or get_hypervisor_hostname()
|
_default_hypervisor = default_hypervisor or get_hypervisor_hostname()
|
||||||
|
# device_mappings = {'physnet1': ['br-phy1'], 'physnet2': ['br-phy2'], ...}
|
||||||
rv = {}
|
devices = {dev for devs in device_mappings.values() for dev in devs}
|
||||||
for _physnet, devices in device_mappings.items():
|
if tunnelled_network_rp_name:
|
||||||
for device in devices:
|
devices.add(tunnelled_network_rp_name)
|
||||||
if device in hypervisors:
|
return {device: hypervisors.get(device) or _default_hypervisor
|
||||||
rv[device] = hypervisors[device]
|
for device in devices}
|
||||||
else:
|
|
||||||
rv[device] = _default_hypervisor
|
|
||||||
return rv
|
|
||||||
|
|
|
@ -78,3 +78,8 @@ IDPOOL_SELECT_SIZE = 100
|
||||||
AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP,
|
AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP,
|
||||||
constants.DEVICE_OWNER_DISTRIBUTED,
|
constants.DEVICE_OWNER_DISTRIBUTED,
|
||||||
constants.DEVICE_OWNER_AGENT_GW]
|
constants.DEVICE_OWNER_AGENT_GW]
|
||||||
|
|
||||||
|
# TODO(ralonsoh): move this constant to neutron_lib.placement.constants
|
||||||
|
# Tunnelled networks resource provider default name.
|
||||||
|
RP_TUNNELLED = 'rp_tunnelled'
|
||||||
|
TRAIT_NETWORK_TUNNEL = 'CUSTOM_NETWORK_TUNNEL_PROVIDER'
|
||||||
|
|
|
@ -883,7 +883,8 @@ def port_ip_changed(new_port, original_port):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def validate_rp_bandwidth(rp_bandwidths, device_names):
|
def validate_rp_bandwidth(rp_bandwidths, device_names,
|
||||||
|
tunnelled_network_rp_name=None):
|
||||||
"""Validate resource provider bandwidths against device names.
|
"""Validate resource provider bandwidths against device names.
|
||||||
|
|
||||||
:param rp_bandwidths: Dict containing resource provider bandwidths,
|
:param rp_bandwidths: Dict containing resource provider bandwidths,
|
||||||
|
@ -892,10 +893,14 @@ def validate_rp_bandwidth(rp_bandwidths, device_names):
|
||||||
:param device_names: A set of the device names given in bridge_mappings
|
:param device_names: A set of the device names given in bridge_mappings
|
||||||
in case of ovs-agent or in physical_device_mappings
|
in case of ovs-agent or in physical_device_mappings
|
||||||
in case of sriov-agent
|
in case of sriov-agent
|
||||||
|
:param tunnelled_network_rp_name: the resource provider name for tunnelled
|
||||||
|
networks; if present, it will be added
|
||||||
|
to the devices list.
|
||||||
:raises ValueError: In case of the devices (keys) in the rp_bandwidths dict
|
:raises ValueError: In case of the devices (keys) in the rp_bandwidths dict
|
||||||
are not in the device_names set.
|
are not in the device_names set.
|
||||||
"""
|
"""
|
||||||
|
if tunnelled_network_rp_name:
|
||||||
|
device_names.add(tunnelled_network_rp_name)
|
||||||
for dev_name in rp_bandwidths:
|
for dev_name in rp_bandwidths:
|
||||||
if dev_name not in device_names:
|
if dev_name not in device_names:
|
||||||
raise ValueError(_(
|
raise ValueError(_(
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from neutron._i18n import _
|
from neutron._i18n import _
|
||||||
|
from neutron.common import _constants as common_const
|
||||||
|
|
||||||
|
|
||||||
ml2_opts = [
|
ml2_opts = [
|
||||||
cfg.ListOpt('type_drivers',
|
cfg.ListOpt('type_drivers',
|
||||||
|
@ -65,7 +67,16 @@ ml2_opts = [
|
||||||
cfg.IntOpt('overlay_ip_version',
|
cfg.IntOpt('overlay_ip_version',
|
||||||
default=4,
|
default=4,
|
||||||
help=_("IP version of all overlay (tunnel) network endpoints. "
|
help=_("IP version of all overlay (tunnel) network endpoints. "
|
||||||
"Use a value of 4 for IPv4 or 6 for IPv6."))
|
"Use a value of 4 for IPv4 or 6 for IPv6.")),
|
||||||
|
cfg.StrOpt('tunnelled_network_rp_name',
|
||||||
|
default=common_const.RP_TUNNELLED,
|
||||||
|
help=_("Resource provider name for the host with tunnelled "
|
||||||
|
"networks. This resource provider represents the "
|
||||||
|
"available bandwidth for all tunnelled networks in a "
|
||||||
|
"compute node. NOTE: this parameter is used both by the "
|
||||||
|
"Neutron server and the mechanism driver agents; it is "
|
||||||
|
"recommended not to change it once any resource "
|
||||||
|
"provider register has been created.")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -432,6 +432,11 @@ class QosOVSAgentDriver(qos.QosLinuxAgentDriver,
|
||||||
'vif_port was not found. It seems that port is already '
|
'vif_port was not found. It seems that port is already '
|
||||||
'deleted', port.get('port_id'))
|
'deleted', port.get('port_id'))
|
||||||
return
|
return
|
||||||
|
elif not port.get('physical_network'):
|
||||||
|
LOG.debug('update_minimum_bandwidth was received for port %s but '
|
||||||
|
'has no physical network associated',
|
||||||
|
port.get('port_id'))
|
||||||
|
return
|
||||||
|
|
||||||
self.ports[port['port_id']][(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH,
|
self.ports[port['port_id']][(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH,
|
||||||
rule.direction)] = port
|
rule.direction)] = port
|
||||||
|
|
|
@ -62,6 +62,7 @@ from neutron.api.rpc.handlers import securitygroups_rpc as sg_rpc
|
||||||
from neutron.common import config
|
from neutron.common import config
|
||||||
from neutron.common import utils as n_utils
|
from neutron.common import utils as n_utils
|
||||||
from neutron.conf.agent import common as agent_config
|
from neutron.conf.agent import common as agent_config
|
||||||
|
from neutron.conf.plugins.ml2 import config as ml2_config
|
||||||
from neutron.conf import service as service_conf
|
from neutron.conf import service as service_conf
|
||||||
from neutron.plugins.ml2.drivers.agent import capabilities
|
from neutron.plugins.ml2.drivers.agent import capabilities
|
||||||
from neutron.plugins.ml2.drivers.l2pop.rpc_manager import l2population_rpc
|
from neutron.plugins.ml2.drivers.l2pop.rpc_manager import l2population_rpc
|
||||||
|
@ -233,8 +234,9 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
|
||||||
self._validate_rp_pkt_processing_cfg()
|
self._validate_rp_pkt_processing_cfg()
|
||||||
|
|
||||||
br_set = set(self.bridge_mappings.values())
|
br_set = set(self.bridge_mappings.values())
|
||||||
n_utils.validate_rp_bandwidth(self.rp_bandwidths,
|
n_utils.validate_rp_bandwidth(
|
||||||
br_set)
|
self.rp_bandwidths, br_set,
|
||||||
|
tunnelled_network_rp_name=self.conf.ml2.tunnelled_network_rp_name)
|
||||||
self.rp_inventory_defaults = place_utils.parse_rp_inventory_defaults(
|
self.rp_inventory_defaults = place_utils.parse_rp_inventory_defaults(
|
||||||
ovs_conf.resource_provider_inventory_defaults)
|
ovs_conf.resource_provider_inventory_defaults)
|
||||||
# At the moment the format of
|
# At the moment the format of
|
||||||
|
@ -250,7 +252,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
|
||||||
self.rp_hypervisors = utils.default_rp_hypervisors(
|
self.rp_hypervisors = utils.default_rp_hypervisors(
|
||||||
ovs_conf.resource_provider_hypervisors,
|
ovs_conf.resource_provider_hypervisors,
|
||||||
{k: [v] for k, v in self.bridge_mappings.items()},
|
{k: [v] for k, v in self.bridge_mappings.items()},
|
||||||
ovs_conf.resource_provider_default_hypervisor
|
default_hypervisor=ovs_conf.resource_provider_default_hypervisor,
|
||||||
|
tunnelled_network_rp_name=self.conf.ml2.tunnelled_network_rp_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.phys_brs = {}
|
self.phys_brs = {}
|
||||||
|
@ -2930,6 +2933,7 @@ def main(bridge_classes):
|
||||||
l2_agent_extensions_manager.register_opts(cfg.CONF)
|
l2_agent_extensions_manager.register_opts(cfg.CONF)
|
||||||
agent_config.setup_privsep()
|
agent_config.setup_privsep()
|
||||||
service_conf.register_service_opts(service_conf.RPC_EXTRA_OPTS, cfg.CONF)
|
service_conf.register_service_opts(service_conf.RPC_EXTRA_OPTS, cfg.CONF)
|
||||||
|
ml2_config.register_ml2_plugin_opts(cfg=cfg.CONF)
|
||||||
|
|
||||||
ext_mgr = l2_agent_extensions_manager.L2AgentExtensionsManager(cfg.CONF)
|
ext_mgr = l2_agent_extensions_manager.L2AgentExtensionsManager(cfg.CONF)
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ from neutron_lib.placement import utils as placement_utils
|
||||||
from neutron_lib.plugins import constants as plugins_constants
|
from neutron_lib.plugins import constants as plugins_constants
|
||||||
from neutron_lib.plugins import directory
|
from neutron_lib.plugins import directory
|
||||||
from neutron_lib.utils import helpers
|
from neutron_lib.utils import helpers
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from ovsdbapp.backend.ovs_idl import event as row_event
|
from ovsdbapp.backend.ovs_idl import event as row_event
|
||||||
|
|
||||||
|
@ -174,6 +175,7 @@ class OVNClientPlacementExtension(object):
|
||||||
self._plugin = None
|
self._plugin = None
|
||||||
self.uuid_ns = ovn_const.OVN_RP_UUID
|
self.uuid_ns = ovn_const.OVN_RP_UUID
|
||||||
self.supported_vnic_types = ovn_const.OVN_SUPPORTED_VNIC_TYPES
|
self.supported_vnic_types = ovn_const.OVN_SUPPORTED_VNIC_TYPES
|
||||||
|
self._rp_tun_name = cfg.CONF.ml2.tunnelled_network_rp_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def placement_plugin(self):
|
def placement_plugin(self):
|
||||||
|
@ -247,6 +249,13 @@ class OVNClientPlacementExtension(object):
|
||||||
LOG.debug('Building placement options for chassis %s: %s',
|
LOG.debug('Building placement options for chassis %s: %s',
|
||||||
chassis.name, cms_options)
|
chassis.name, cms_options)
|
||||||
hypervisor_rps = {}
|
hypervisor_rps = {}
|
||||||
|
|
||||||
|
# ML2/OVN can also track tunnelled networks bandwidth. The key
|
||||||
|
# RP_TUNNELLED must be defined in "resource_provider_bandwidths" and
|
||||||
|
# "resource_provider_hypervisors". E.g.:
|
||||||
|
# ovn-cms-options =
|
||||||
|
# resource_provider_bandwidths=br-ex:100:200;rp_tunnelled:300:400
|
||||||
|
# resource_provider_hypervisors=br-ex:host1,rp_tunnelled:host1
|
||||||
for device, hyperv in cms_options[ovn_const.RP_HYPERVISORS].items():
|
for device, hyperv in cms_options[ovn_const.RP_HYPERVISORS].items():
|
||||||
try:
|
try:
|
||||||
hypervisor_rps[device] = {'name': hyperv,
|
hypervisor_rps[device] = {'name': hyperv,
|
||||||
|
@ -254,7 +263,12 @@ class OVNClientPlacementExtension(object):
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
bridges = set(itertools.chain(*bridge_mappings.values()))
|
rp_devices = set(itertools.chain(*bridge_mappings.values()))
|
||||||
|
# If "ml2.tunnelled_network_rp_name" is present in configured resource
|
||||||
|
# providers, that means this ML2/OVN host will track the tunnelled
|
||||||
|
# networks available bandwidth.
|
||||||
|
if self._rp_tun_name in hypervisor_rps:
|
||||||
|
rp_devices.add(self._rp_tun_name)
|
||||||
# Remove "cms_options[RP_BANDWIDTHS]" not present in "hypervisor_rps"
|
# Remove "cms_options[RP_BANDWIDTHS]" not present in "hypervisor_rps"
|
||||||
# and "bridge_mappings". If we don't have a way to match the RP bridge
|
# and "bridge_mappings". If we don't have a way to match the RP bridge
|
||||||
# with a host ("hypervisor_rps") or a way to match the RP bridge with
|
# with a host ("hypervisor_rps") or a way to match the RP bridge with
|
||||||
|
@ -262,8 +276,8 @@ class OVNClientPlacementExtension(object):
|
||||||
rp_bw = cms_options[n_const.RP_BANDWIDTHS]
|
rp_bw = cms_options[n_const.RP_BANDWIDTHS]
|
||||||
if rp_bw:
|
if rp_bw:
|
||||||
cms_options[n_const.RP_BANDWIDTHS] = {
|
cms_options[n_const.RP_BANDWIDTHS] = {
|
||||||
device: bw for device, bw in rp_bw.items() if
|
rp_device: bw for rp_device, bw in rp_bw.items() if
|
||||||
device in hypervisor_rps and device in bridges}
|
rp_device in hypervisor_rps and rp_device in rp_devices}
|
||||||
|
|
||||||
# NOTE(ralonsoh): OVN only reports min BW RPs; packet processing RPs
|
# NOTE(ralonsoh): OVN only reports min BW RPs; packet processing RPs
|
||||||
# will be added in a future implementation. If no RP_BANDWIDTHS values
|
# will be added in a future implementation. If no RP_BANDWIDTHS values
|
||||||
|
|
|
@ -20,8 +20,6 @@ from neutron_lib.services.qos import base
|
||||||
from neutron_lib.services.qos import constants as qos_consts
|
from neutron_lib.services.qos import constants as qos_consts
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from neutron.objects import network as network_object
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -76,17 +74,6 @@ class OVSDriver(base.DriverBase):
|
||||||
def validate_rule_for_port(self, context, rule, port):
|
def validate_rule_for_port(self, context, rule, port):
|
||||||
return self.validate_rule_for_network(context, rule, port.network_id)
|
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=network_id)
|
|
||||||
physnet = net.segments[0].physical_network
|
|
||||||
if physnet is None:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
"""Register the driver."""
|
"""Register the driver."""
|
||||||
|
|
|
@ -49,6 +49,7 @@ from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from neutron._i18n import _
|
from neutron._i18n import _
|
||||||
|
from neutron.common import _constants as n_const
|
||||||
from neutron.db import db_base_plugin_common
|
from neutron.db import db_base_plugin_common
|
||||||
from neutron.exceptions import qos as neutron_qos_exc
|
from neutron.exceptions import qos as neutron_qos_exc
|
||||||
from neutron.extensions import qos
|
from neutron.extensions import qos
|
||||||
|
@ -254,17 +255,22 @@ class QoSPlugin(qos.QoSPluginBase):
|
||||||
# support will be available. See Placement spec:
|
# support will be available. See Placement spec:
|
||||||
# https://review.opendev.org/565730
|
# https://review.opendev.org/565730
|
||||||
first_segment = segments[0]
|
first_segment = segments[0]
|
||||||
if not first_segment or not first_segment.physical_network:
|
if not first_segment:
|
||||||
return []
|
return []
|
||||||
physnet_trait = pl_utils.physnet_trait(
|
elif not first_segment.physical_network:
|
||||||
first_segment.physical_network)
|
# If there is no physical network this is because this is an
|
||||||
|
# overlay network (tunnelled network).
|
||||||
|
net_trait = n_const.TRAIT_NETWORK_TUNNEL
|
||||||
|
else:
|
||||||
|
net_trait = pl_utils.physnet_trait(first_segment.physical_network)
|
||||||
|
|
||||||
# NOTE(ralonsoh): we should not rely on the current execution order of
|
# NOTE(ralonsoh): we should not rely on the current execution order of
|
||||||
# the port extending functions. Although here we have
|
# the port extending functions. Although here we have
|
||||||
# port_res[VNIC_TYPE], we should retrieve this value from the port DB
|
# port_res[VNIC_TYPE], we should retrieve this value from the port DB
|
||||||
# object instead.
|
# object instead.
|
||||||
vnic_trait = pl_utils.vnic_type_trait(vnic_type)
|
vnic_trait = pl_utils.vnic_type_trait(vnic_type)
|
||||||
|
|
||||||
return [physnet_trait, vnic_trait]
|
return [net_trait, vnic_trait]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@resource_extend.extends([port_def.COLLECTION_NAME_BULK])
|
@resource_extend.extends([port_def.COLLECTION_NAME_BULK])
|
||||||
|
|
|
@ -825,30 +825,42 @@ class TestMinBwQoSOvs(_TestMinBwQoS, base.BaseFullStackTestCase):
|
||||||
qoses, queues = self._qos_info(vm.bridge)
|
qoses, queues = self._qos_info(vm.bridge)
|
||||||
self.fail(queuenum + qoses + queues)
|
self.fail(queuenum + qoses + queues)
|
||||||
|
|
||||||
def test_min_bw_qos_create_network_vxlan_not_supported(self):
|
def test_min_bw_qos_create_network_vxlan_supported(self):
|
||||||
qos_policy = self._create_qos_policy()
|
qos_policy = self._create_qos_policy()
|
||||||
qos_policy_id = qos_policy['id']
|
qos_policy_id = qos_policy['id']
|
||||||
self.safe_client.create_minimum_bandwidth_rule(
|
self.safe_client.create_minimum_bandwidth_rule(
|
||||||
self.tenant_id, qos_policy_id, MIN_BANDWIDTH, self.direction)
|
self.tenant_id, qos_policy_id, MIN_BANDWIDTH, self.direction)
|
||||||
network_args = {'network_type': 'vxlan',
|
network_args = {'network_type': 'vxlan',
|
||||||
'qos_policy_id': qos_policy_id}
|
'qos_policy_id': qos_policy_id}
|
||||||
self.assertRaises(
|
net = self.safe_client.create_network(
|
||||||
exceptions.Conflict,
|
|
||||||
self.safe_client.create_network,
|
|
||||||
self.tenant_id, name='network-test', **network_args)
|
self.tenant_id, name='network-test', **network_args)
|
||||||
|
self.assertEqual(qos_policy_id, net['qos_policy_id'])
|
||||||
|
|
||||||
def test_min_bw_qos_update_network_vxlan_not_supported(self):
|
def test_min_bw_qos_create_and_update_network_vxlan_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 = self._create_qos_policy()
|
||||||
qos_policy_id = qos_policy['id']
|
qos_policy_id = qos_policy['id']
|
||||||
self.safe_client.create_minimum_bandwidth_rule(
|
self.safe_client.create_minimum_bandwidth_rule(
|
||||||
self.tenant_id, qos_policy_id, MIN_BANDWIDTH, self.direction)
|
self.tenant_id, qos_policy_id, MIN_BANDWIDTH, self.direction)
|
||||||
self.assertRaises(
|
network_args = {'network_type': 'vxlan',
|
||||||
exceptions.Conflict,
|
'qos_policy_id': qos_policy_id}
|
||||||
self.client.update_network, network['id'],
|
network = self.safe_client.create_network(
|
||||||
body={'network': {'qos_policy_id': qos_policy_id}})
|
self.tenant_id, name='network-test', **network_args)
|
||||||
|
self.assertEqual(qos_policy_id, network['qos_policy_id'])
|
||||||
|
|
||||||
|
qos_policy2 = self._create_qos_policy()
|
||||||
|
qos_policy2_id = qos_policy2['id']
|
||||||
|
self.client.update_network(
|
||||||
|
network['id'], body={'network': {'qos_policy_id': qos_policy2_id}})
|
||||||
|
_net = self.client.show_network(network['id'])
|
||||||
|
self.assertEqual(qos_policy2_id, _net['network']['qos_policy_id'])
|
||||||
|
|
||||||
|
# This action will remove the QoS policy from the network. This is also
|
||||||
|
# necessary before the cleanUp call, that will delete the QoS policy
|
||||||
|
# before the network.
|
||||||
|
self.client.update_network(
|
||||||
|
network['id'], body={'network': {'qos_policy_id': None}})
|
||||||
|
_net = self.client.show_network(network['id'])
|
||||||
|
self.assertIsNone(_net['network']['qos_policy_id'])
|
||||||
|
|
||||||
def test_min_bw_qos_port_removed(self):
|
def test_min_bw_qos_port_removed(self):
|
||||||
"""Test if min BW limit config is properly removed when port removed.
|
"""Test if min BW limit config is properly removed when port removed.
|
||||||
|
|
|
@ -33,6 +33,7 @@ from neutron.common import utils
|
||||||
from neutron.conf.agent import common as agent_config
|
from neutron.conf.agent import common as agent_config
|
||||||
from neutron.conf.agent import ovs_conf as ovs_agent_config
|
from neutron.conf.agent import ovs_conf as ovs_agent_config
|
||||||
from neutron.conf import common as common_config
|
from neutron.conf import common as common_config
|
||||||
|
from neutron.conf.plugins.ml2 import config as ml2_config
|
||||||
from neutron.conf.plugins.ml2.drivers import agent
|
from neutron.conf.plugins.ml2.drivers import agent
|
||||||
from neutron.conf.plugins.ml2.drivers import ovs_conf
|
from neutron.conf.plugins.ml2.drivers import ovs_conf
|
||||||
from neutron.plugins.ml2.drivers.openvswitch.agent.extension_drivers \
|
from neutron.plugins.ml2.drivers.openvswitch.agent.extension_drivers \
|
||||||
|
@ -140,6 +141,7 @@ class OVSAgentTestFramework(base.BaseOVSLinuxTestCase, OVSOFControllerHelper):
|
||||||
agent_config.register_agent_state_opts_helper(config)
|
agent_config.register_agent_state_opts_helper(config)
|
||||||
ovs_agent_config.register_ovs_agent_opts(config)
|
ovs_agent_config.register_ovs_agent_opts(config)
|
||||||
ext_manager.register_opts(config)
|
ext_manager.register_opts(config)
|
||||||
|
ml2_config.register_ml2_plugin_opts(cfg=config)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def _configure_agent(self):
|
def _configure_agent(self):
|
||||||
|
|
|
@ -16,6 +16,8 @@ from unittest import mock
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from neutron.agent.common import placement_report
|
from neutron.agent.common import placement_report
|
||||||
|
from neutron.common import _constants as n_const
|
||||||
|
from neutron.conf.plugins.ml2 import config as ml2_config
|
||||||
from neutron.tests import base
|
from neutron.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,6 +45,7 @@ class DeferredCallTestCase(base.BaseTestCase):
|
||||||
class PlacementStateTestCase(base.BaseTestCase):
|
class PlacementStateTestCase(base.BaseTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
ml2_config.register_ml2_plugin_opts()
|
||||||
super(PlacementStateTestCase, self).setUp()
|
super(PlacementStateTestCase, self).setUp()
|
||||||
self.client_mock = mock.Mock()
|
self.client_mock = mock.Mock()
|
||||||
self.driver_uuid_namespace = uuid.UUID(
|
self.driver_uuid_namespace = uuid.UUID(
|
||||||
|
@ -61,6 +64,10 @@ class PlacementStateTestCase(base.BaseTestCase):
|
||||||
'hypervisor_rps': {
|
'hypervisor_rps': {
|
||||||
'eth0': {'name': 'fakehost', 'uuid': self.hypervisor1_rp_uuid},
|
'eth0': {'name': 'fakehost', 'uuid': self.hypervisor1_rp_uuid},
|
||||||
'eth1': {'name': 'fakehost', 'uuid': self.hypervisor1_rp_uuid},
|
'eth1': {'name': 'fakehost', 'uuid': self.hypervisor1_rp_uuid},
|
||||||
|
# NOTE(ralonsoh): use the 'rp_tunnelled' n-lib constant once
|
||||||
|
# merged.
|
||||||
|
'rp_tunnelled': {'name': 'fakehost',
|
||||||
|
'uuid': self.hypervisor1_rp_uuid},
|
||||||
},
|
},
|
||||||
'device_mappings': {},
|
'device_mappings': {},
|
||||||
'supported_vnic_types': [],
|
'supported_vnic_types': [],
|
||||||
|
@ -195,6 +202,7 @@ class PlacementStateTestCase(base.BaseTestCase):
|
||||||
},
|
},
|
||||||
'rp_bandwidths': {
|
'rp_bandwidths': {
|
||||||
'eth0': {'egress': 1, 'ingress': 1},
|
'eth0': {'egress': 1, 'ingress': 1},
|
||||||
|
'rp_tunnelled': {'egress': 2, 'ingress': 3},
|
||||||
},
|
},
|
||||||
'supported_vnic_types': ['normal'],
|
'supported_vnic_types': ['normal'],
|
||||||
})
|
})
|
||||||
|
@ -211,6 +219,13 @@ class PlacementStateTestCase(base.BaseTestCase):
|
||||||
'1ea6f823-bcf2-5dc5-9bee-4ee6177a6451'),
|
'1ea6f823-bcf2-5dc5-9bee-4ee6177a6451'),
|
||||||
traits=mock.ANY),
|
traits=mock.ANY),
|
||||||
|
|
||||||
|
# uuid -v5 '00000000-0000-0000-0000-000000000001' \
|
||||||
|
# 'fakehost:rp_tunnelled'
|
||||||
|
mock.call(
|
||||||
|
resource_provider_uuid=uuid.UUID(
|
||||||
|
'357001cb-88b4-5e1d-ae6e-85b238a7a83e'),
|
||||||
|
traits=mock.ANY),
|
||||||
|
|
||||||
# uuid -v5 '00000000-0000-0000-0000-000000000001' 'fakehost'
|
# uuid -v5 '00000000-0000-0000-0000-000000000001' 'fakehost'
|
||||||
mock.call(
|
mock.call(
|
||||||
resource_provider_uuid=uuid.UUID(
|
resource_provider_uuid=uuid.UUID(
|
||||||
|
@ -223,8 +238,9 @@ class PlacementStateTestCase(base.BaseTestCase):
|
||||||
actual_traits = [set(args[1]['traits']) for args in
|
actual_traits = [set(args[1]['traits']) for args in
|
||||||
self.client_mock.update_resource_provider_traits.call_args_list]
|
self.client_mock.update_resource_provider_traits.call_args_list]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[set(['CUSTOM_PHYSNET_PHYSNET0', 'CUSTOM_VNIC_TYPE_NORMAL']),
|
[{'CUSTOM_PHYSNET_PHYSNET0', 'CUSTOM_VNIC_TYPE_NORMAL'},
|
||||||
set(['CUSTOM_VNIC_TYPE_NORMAL'])],
|
{n_const.TRAIT_NETWORK_TUNNEL, 'CUSTOM_VNIC_TYPE_NORMAL'},
|
||||||
|
{'CUSTOM_VNIC_TYPE_NORMAL'}],
|
||||||
actual_traits)
|
actual_traits)
|
||||||
|
|
||||||
def test_deferred_update_resource_provider_inventories_bw(self):
|
def test_deferred_update_resource_provider_inventories_bw(self):
|
||||||
|
|
|
@ -16,9 +16,12 @@
|
||||||
import socket
|
import socket
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
from neutron.agent.common import utils
|
from neutron.agent.common import utils
|
||||||
from neutron.agent.linux import interface
|
from neutron.agent.linux import interface
|
||||||
from neutron.conf.agent import common as config
|
from neutron.conf.agent import common as config
|
||||||
|
from neutron.conf.plugins.ml2 import config as ml2_config
|
||||||
from neutron.tests import base
|
from neutron.tests import base
|
||||||
from neutron.tests.unit import testlib_api
|
from neutron.tests.unit import testlib_api
|
||||||
|
|
||||||
|
@ -158,6 +161,10 @@ class TestGetHypervisorHostname(base.BaseTestCase):
|
||||||
# TODO(bence romsics): rehome this to neutron_lib
|
# TODO(bence romsics): rehome this to neutron_lib
|
||||||
class TestDefaultRpHypervisors(base.BaseTestCase):
|
class TestDefaultRpHypervisors(base.BaseTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
ml2_config.register_ml2_plugin_opts()
|
||||||
|
|
||||||
@mock.patch.object(utils, 'get_hypervisor_hostname',
|
@mock.patch.object(utils, 'get_hypervisor_hostname',
|
||||||
return_value='thishost')
|
return_value='thishost')
|
||||||
def test_defaults(self, hostname_mock):
|
def test_defaults(self, hostname_mock):
|
||||||
|
@ -197,3 +204,26 @@ class TestDefaultRpHypervisors(base.BaseTestCase):
|
||||||
default_hypervisor='defaulthost',
|
default_hypervisor='defaulthost',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rp_tunnelled = cfg.CONF.ml2.tunnelled_network_rp_name
|
||||||
|
self.assertEqual(
|
||||||
|
{'eth0': 'thathost', 'eth1': 'defaulthost',
|
||||||
|
rp_tunnelled: 'defaulthost'},
|
||||||
|
utils.default_rp_hypervisors(
|
||||||
|
hypervisors={'eth0': 'thathost'},
|
||||||
|
device_mappings={'physnet0': ['eth0', 'eth1']},
|
||||||
|
default_hypervisor='defaulthost',
|
||||||
|
tunnelled_network_rp_name=rp_tunnelled
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
{'eth0': 'thathost', 'eth1': 'defaulthost',
|
||||||
|
rp_tunnelled: 'thathost'},
|
||||||
|
utils.default_rp_hypervisors(
|
||||||
|
hypervisors={'eth0': 'thathost', rp_tunnelled: 'thathost'},
|
||||||
|
device_mappings={'physnet0': ['eth0', 'eth1']},
|
||||||
|
default_hypervisor='defaulthost',
|
||||||
|
tunnelled_network_rp_name=rp_tunnelled
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -386,19 +386,39 @@ class QosOVSAgentDriverTestCase(ovs_test_base.OVSAgentConfigTestBase):
|
||||||
self.qos_driver.delete_minimum_bandwidth({'port_id': 'p_id'})
|
self.qos_driver.delete_minimum_bandwidth({'port_id': 'p_id'})
|
||||||
mock_delete_minimum_bandwidth_queue.assert_called_once_with('p_id')
|
mock_delete_minimum_bandwidth_queue.assert_called_once_with('p_id')
|
||||||
|
|
||||||
def test_update_minimum_bandwidth_no_vif_port(self):
|
@mock.patch.object(qos_driver, 'LOG')
|
||||||
|
def test_update_minimum_bandwidth_no_vif_port(self, mock_log):
|
||||||
with mock.patch.object(self.qos_driver.br_int,
|
with mock.patch.object(self.qos_driver.br_int,
|
||||||
'update_minimum_bandwidth_queue') \
|
'update_minimum_bandwidth_queue') \
|
||||||
as mock_delete_minimum_bandwidth_queue:
|
as mock_delete_minimum_bandwidth_queue:
|
||||||
self.qos_driver.update_minimum_bandwidth({}, mock.ANY)
|
self.qos_driver.update_minimum_bandwidth(
|
||||||
|
{'port_id': 'portid'}, mock.ANY)
|
||||||
mock_delete_minimum_bandwidth_queue.assert_not_called()
|
mock_delete_minimum_bandwidth_queue.assert_not_called()
|
||||||
|
mock_log.debug.assert_called_once_with(
|
||||||
|
'update_minimum_bandwidth was received for port %s but '
|
||||||
|
'vif_port was not found. It seems that port is already '
|
||||||
|
'deleted', 'portid')
|
||||||
|
|
||||||
|
@mock.patch.object(qos_driver, 'LOG')
|
||||||
|
def test_update_minimum_bandwidth_no_physical_network(self, mock_log):
|
||||||
|
with mock.patch.object(self.qos_driver.br_int,
|
||||||
|
'update_minimum_bandwidth_queue') \
|
||||||
|
as mock_delete_minimum_bandwidth_queue:
|
||||||
|
port = {'vif_port': mock.ANY, 'port_id': 'portid',
|
||||||
|
'physical_network': None}
|
||||||
|
self.qos_driver.update_minimum_bandwidth(port, mock.ANY)
|
||||||
|
mock_delete_minimum_bandwidth_queue.assert_not_called()
|
||||||
|
mock_log.debug.assert_called_once_with(
|
||||||
|
'update_minimum_bandwidth was received for port %s but '
|
||||||
|
'has no physical network associated', 'portid')
|
||||||
|
|
||||||
def test_update_minimum_bandwidth_no_phy_brs(self):
|
def test_update_minimum_bandwidth_no_phy_brs(self):
|
||||||
vif_port = mock.Mock()
|
vif_port = mock.Mock()
|
||||||
vif_port.ofport = 'ofport'
|
vif_port.ofport = 'ofport'
|
||||||
rule = mock.Mock()
|
rule = mock.Mock()
|
||||||
rule.min_kbps = 1500
|
rule.min_kbps = 1500
|
||||||
port = {'port_id': 'port_id', 'vif_port': vif_port}
|
port = {'port_id': 'port_id', 'vif_port': vif_port,
|
||||||
|
'physical_network': mock.ANY}
|
||||||
with mock.patch.object(self.qos_driver.br_int,
|
with mock.patch.object(self.qos_driver.br_int,
|
||||||
'update_minimum_bandwidth_queue') \
|
'update_minimum_bandwidth_queue') \
|
||||||
as mock_delete_minimum_bandwidth_queue, \
|
as mock_delete_minimum_bandwidth_queue, \
|
||||||
|
@ -413,7 +433,8 @@ class QosOVSAgentDriverTestCase(ovs_test_base.OVSAgentConfigTestBase):
|
||||||
vif_port.ofport = 'ofport'
|
vif_port.ofport = 'ofport'
|
||||||
rule = mock.Mock()
|
rule = mock.Mock()
|
||||||
rule.min_kbps = 1500
|
rule.min_kbps = 1500
|
||||||
port = {'port_id': 'port_id', 'vif_port': vif_port}
|
port = {'port_id': 'port_id', 'vif_port': vif_port,
|
||||||
|
'physical_network': mock.ANY}
|
||||||
with mock.patch.object(self.qos_driver.br_int,
|
with mock.patch.object(self.qos_driver.br_int,
|
||||||
'update_minimum_bandwidth_queue') \
|
'update_minimum_bandwidth_queue') \
|
||||||
as mock_delete_minimum_bandwidth_queue, \
|
as mock_delete_minimum_bandwidth_queue, \
|
||||||
|
|
|
@ -27,12 +27,14 @@ class TestOVSDriver(base.BaseQosTestCase):
|
||||||
super(TestOVSDriver, self).setUp()
|
super(TestOVSDriver, self).setUp()
|
||||||
self.driver = driver.OVSDriver.create()
|
self.driver = driver.OVSDriver.create()
|
||||||
|
|
||||||
def test_validate_min_bw_rule_vs_physnet_non_physnet(self):
|
def test_validate_min_bw_rule(self):
|
||||||
scenarios = [
|
# Minimum bandwidth rules are now allowed for tunnelled networks since
|
||||||
({'physical_network': 'fake physnet'}, self.assertTrue),
|
# LP#1991965. The ML2/OVS backend cannot enforce them but Placement can
|
||||||
({}, self.assertFalse),
|
# schedule a VM using this information.
|
||||||
]
|
scenarios = [{'physical_network': 'fake physnet'},
|
||||||
for segment_kwargs, test_method in scenarios:
|
{},
|
||||||
|
]
|
||||||
|
for segment_kwargs in scenarios:
|
||||||
segment = network_object.NetworkSegment(**segment_kwargs)
|
segment = network_object.NetworkSegment(**segment_kwargs)
|
||||||
net = network_object.Network(mock.Mock(), segments=[segment])
|
net = network_object.Network(mock.Mock(), segments=[segment])
|
||||||
rule = mock.Mock()
|
rule = mock.Mock()
|
||||||
|
@ -41,7 +43,7 @@ class TestOVSDriver(base.BaseQosTestCase):
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
'neutron.objects.network.Network.get_object',
|
'neutron.objects.network.Network.get_object',
|
||||||
return_value=net):
|
return_value=net):
|
||||||
test_method(self.driver.validate_rule_for_port(
|
self.assertTrue(self.driver.validate_rule_for_port(
|
||||||
mock.Mock(), rule, port))
|
mock.Mock(), rule, port))
|
||||||
test_method(self.driver.validate_rule_for_network(
|
self.assertTrue(self.driver.validate_rule_for_network(
|
||||||
mock.Mock(), rule, network_id=mock.Mock()))
|
mock.Mock(), rule, network_id=mock.Mock()))
|
||||||
|
|
|
@ -15,6 +15,7 @@ from unittest import mock
|
||||||
|
|
||||||
from keystoneauth1 import exceptions as ks_exc
|
from keystoneauth1 import exceptions as ks_exc
|
||||||
import netaddr
|
import netaddr
|
||||||
|
from neutron_lib.api.definitions import portbindings
|
||||||
from neutron_lib.api.definitions import qos
|
from neutron_lib.api.definitions import qos
|
||||||
from neutron_lib.callbacks import events
|
from neutron_lib.callbacks import events
|
||||||
from neutron_lib import constants as lib_constants
|
from neutron_lib import constants as lib_constants
|
||||||
|
@ -23,6 +24,7 @@ from neutron_lib import exceptions as lib_exc
|
||||||
from neutron_lib.exceptions import placement as pl_exc
|
from neutron_lib.exceptions import placement as pl_exc
|
||||||
from neutron_lib.exceptions import qos as qos_exc
|
from neutron_lib.exceptions import qos as qos_exc
|
||||||
from neutron_lib.objects import utils as obj_utils
|
from neutron_lib.objects import utils as obj_utils
|
||||||
|
from neutron_lib.placement import utils as pl_utils
|
||||||
from neutron_lib.plugins import constants as plugins_constants
|
from neutron_lib.plugins import constants as plugins_constants
|
||||||
from neutron_lib.plugins import directory
|
from neutron_lib.plugins import directory
|
||||||
from neutron_lib.services.qos import constants as qos_consts
|
from neutron_lib.services.qos import constants as qos_consts
|
||||||
|
@ -32,6 +34,7 @@ from oslo_config import cfg
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
|
from neutron.common import _constants as n_const
|
||||||
from neutron.exceptions import qos as neutron_qos_exc
|
from neutron.exceptions import qos as neutron_qos_exc
|
||||||
from neutron.extensions import qos_pps_minimum_rule_alias
|
from neutron.extensions import qos_pps_minimum_rule_alias
|
||||||
from neutron.extensions import qos_rules_alias
|
from neutron.extensions import qos_rules_alias
|
||||||
|
@ -83,6 +86,7 @@ class TestQosPlugin(base.BaseQosTestCase):
|
||||||
|
|
||||||
self.ctxt = context.Context('fake_user', 'fake_tenant')
|
self.ctxt = context.Context('fake_user', 'fake_tenant')
|
||||||
self.admin_ctxt = context.get_admin_context()
|
self.admin_ctxt = context.get_admin_context()
|
||||||
|
self.default_uuid = 'fake_uuid'
|
||||||
|
|
||||||
self.policy_data = {
|
self.policy_data = {
|
||||||
'policy': {'id': uuidutils.generate_uuid(),
|
'policy': {'id': uuidutils.generate_uuid(),
|
||||||
|
@ -129,6 +133,9 @@ class TestQosPlugin(base.BaseQosTestCase):
|
||||||
self.min_pps_rule = rule_object.QosMinimumPacketRateRule(
|
self.min_pps_rule = rule_object.QosMinimumPacketRateRule(
|
||||||
self.ctxt, **self.rule_data['minimum_packet_rate_rule'])
|
self.ctxt, **self.rule_data['minimum_packet_rate_rule'])
|
||||||
|
|
||||||
|
self._rp_tun_name = cfg.CONF.ml2.tunnelled_network_rp_name
|
||||||
|
self._rp_tun_trait = n_const.TRAIT_NETWORK_TUNNEL
|
||||||
|
|
||||||
def _validate_driver_params(self, method_name, ctxt):
|
def _validate_driver_params(self, method_name, ctxt):
|
||||||
call_args = self.qos_plugin.driver_manager.call.call_args[0]
|
call_args = self.qos_plugin.driver_manager.call.call_args[0]
|
||||||
self.assertTrue(self.qos_plugin.driver_manager.call.called)
|
self.assertTrue(self.qos_plugin.driver_manager.call.called)
|
||||||
|
@ -172,7 +179,7 @@ class TestQosPlugin(base.BaseQosTestCase):
|
||||||
return_value=min_pps_rules), \
|
return_value=min_pps_rules), \
|
||||||
mock.patch(
|
mock.patch(
|
||||||
'uuid.uuid5',
|
'uuid.uuid5',
|
||||||
return_value='fake_uuid',
|
return_value=self.default_uuid,
|
||||||
side_effect=request_groups_uuids):
|
side_effect=request_groups_uuids):
|
||||||
return qos_plugin.QoSPlugin._extend_port_resource_request(
|
return qos_plugin.QoSPlugin._extend_port_resource_request(
|
||||||
port_res, self.port)
|
port_res, self.port)
|
||||||
|
@ -333,34 +340,33 @@ class TestQosPlugin(base.BaseQosTestCase):
|
||||||
|
|
||||||
port = self._create_and_extend_port([self.min_bw_rule],
|
port = self._create_and_extend_port([self.min_bw_rule],
|
||||||
physical_network=None)
|
physical_network=None)
|
||||||
self.assertIsNone(port.get('resource_request'))
|
expected = {
|
||||||
|
'request_groups': [{'id': self.default_uuid,
|
||||||
|
'required': [self._rp_tun_trait,
|
||||||
|
'CUSTOM_VNIC_TYPE_NORMAL'],
|
||||||
|
'resources': {
|
||||||
|
orc.NET_BW_EGR_KILOBIT_PER_SEC: 10}}],
|
||||||
|
'same_subtree': [self.default_uuid]}
|
||||||
|
self.assertEqual(expected, port['resource_request'])
|
||||||
|
|
||||||
def test__extend_port_resource_request_mix_rules_non_provider_net(self):
|
def test__extend_port_resource_request_mix_rules_non_provider_net(self):
|
||||||
self.min_bw_rule.direction = lib_constants.EGRESS_DIRECTION
|
self.min_bw_rule.direction = lib_constants.EGRESS_DIRECTION
|
||||||
|
|
||||||
port = self._create_and_extend_port([self.min_bw_rule],
|
port = self._create_and_extend_port(
|
||||||
[self.min_pps_rule],
|
[self.min_bw_rule], [self.min_pps_rule], physical_network=None,
|
||||||
physical_network=None)
|
request_groups_uuids=['fake_uuid0', 'fake_uuid1'])
|
||||||
self.assertEqual(
|
request_groups = [
|
||||||
1,
|
{'id': 'fake_uuid0',
|
||||||
len(port['resource_request']['request_groups'])
|
'required': [self._rp_tun_trait,
|
||||||
)
|
'CUSTOM_VNIC_TYPE_NORMAL'],
|
||||||
self.assertEqual(
|
'resources': {orc.NET_BW_EGR_KILOBIT_PER_SEC: 10}},
|
||||||
'fake_uuid',
|
{'id': 'fake_uuid1',
|
||||||
port['resource_request']['request_groups'][0]['id']
|
'required': ['CUSTOM_VNIC_TYPE_NORMAL'],
|
||||||
)
|
'resources': {orc.NET_PACKET_RATE_KILOPACKET_PER_SEC: 10}}]
|
||||||
self.assertEqual(
|
expected = {
|
||||||
['CUSTOM_VNIC_TYPE_NORMAL'],
|
'request_groups': request_groups,
|
||||||
port['resource_request']['request_groups'][0]['required']
|
'same_subtree': ['fake_uuid0', 'fake_uuid1']}
|
||||||
)
|
self.assertEqual(expected, port['resource_request'])
|
||||||
self.assertEqual(
|
|
||||||
{orc.NET_PACKET_RATE_KILOPACKET_PER_SEC: 10},
|
|
||||||
port['resource_request']['request_groups'][0]['resources'],
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
['fake_uuid'],
|
|
||||||
port['resource_request']['same_subtree'],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test__extend_port_resource_request_bulk_min_bw_rule(self):
|
def test__extend_port_resource_request_bulk_min_bw_rule(self):
|
||||||
self.min_bw_rule.direction = lib_constants.EGRESS_DIRECTION
|
self.min_bw_rule.direction = lib_constants.EGRESS_DIRECTION
|
||||||
|
@ -1844,6 +1850,24 @@ class TestQosPlugin(base.BaseQosTestCase):
|
||||||
self.qos_plugin.get_rule_type,
|
self.qos_plugin.get_rule_type,
|
||||||
self.ctxt, qos_consts.RULE_TYPE_MINIMUM_PACKET_RATE)
|
self.ctxt, qos_consts.RULE_TYPE_MINIMUM_PACKET_RATE)
|
||||||
|
|
||||||
|
def test__get_min_bw_traits(self):
|
||||||
|
vnic_type = portbindings.VNIC_NORMAL
|
||||||
|
segments = [None]
|
||||||
|
ret = self.qos_plugin._get_min_bw_traits(vnic_type, segments)
|
||||||
|
self.assertEqual([], ret)
|
||||||
|
|
||||||
|
segments = [mock.Mock(physical_network=None)]
|
||||||
|
ret = self.qos_plugin._get_min_bw_traits(vnic_type, segments)
|
||||||
|
# NOTE(ralonsoh): once implemented, use the neutron-lib method to
|
||||||
|
# generate the tunnelled networks trait.
|
||||||
|
self.assertEqual([self._rp_tun_trait,
|
||||||
|
pl_utils.vnic_type_trait(vnic_type)], ret)
|
||||||
|
|
||||||
|
segments = [mock.Mock(physical_network='physnet_1')]
|
||||||
|
ret = self.qos_plugin._get_min_bw_traits(vnic_type, segments)
|
||||||
|
self.assertEqual([pl_utils.physnet_trait('physnet_1'),
|
||||||
|
pl_utils.vnic_type_trait(vnic_type)], ret)
|
||||||
|
|
||||||
|
|
||||||
class QoSRuleAliasTestExtensionManager(object):
|
class QoSRuleAliasTestExtensionManager(object):
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
ML2/OVS and ML2/OVN now support modelling tunnelled networks in the
|
||||||
|
Placement API. The "tunnelled_network_rp_name" configuration option
|
||||||
|
defines the resource provider name used to represent all tunnelled
|
||||||
|
networks in a compute node (by default "rp_tunnelled"). If this string
|
||||||
|
is present in the "resource_provider_bandwidths" dictionary, the
|
||||||
|
corresponding mechanism driver will create a resource provider for
|
||||||
|
the overlay traffic.
|
Loading…
Reference in New Issue