diff --git a/neutron/common/constants.py b/neutron/common/constants.py index bb16f1dfaa0..3139ac5ee2e 100644 --- a/neutron/common/constants.py +++ b/neutron/common/constants.py @@ -250,3 +250,6 @@ EXT_PARENT_RESOURCE_MAPPING = { l3.FLOATINGIP: plugin_consts.L3 } EXT_PARENT_PREFIX = 'ext_parent' + +RP_BANDWIDTHS = 'resource_provider_bandwidths' +RP_INVENTORY_DEFAULTS = 'resource_provider_inventory_defaults' diff --git a/neutron/common/utils.py b/neutron/common/utils.py index c10410df3ce..ce6cd5f0da1 100644 --- a/neutron/common/utils.py +++ b/neutron/common/utils.py @@ -817,3 +817,24 @@ def port_ip_changed(new_port, original_port): return True return False + + +def validate_rp_bandwidth(rp_bandwidths, device_names): + """Validate resource provider bandwidths against device names. + + :param rp_bandwidths: Dict containing resource provider bandwidths, + in the form: + {'phy1': {'ingress': 100, 'egress': 100}} + :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 sriov-agent + :raises ValueError: In case of the devices (keys) in the rp_bandwidths dict + are not in the device_names set. + """ + + for dev_name in rp_bandwidths: + if dev_name not in device_names: + raise ValueError(_( + "Invalid resource_provider_bandwidths: " + "Device name %(dev_name)s is missing from " + "device mappings") % {'dev_name': dev_name}) diff --git a/neutron/conf/plugins/ml2/drivers/ovs_conf.py b/neutron/conf/plugins/ml2/drivers/ovs_conf.py index 47c3f85f9e1..6f4e1718b14 100644 --- a/neutron/conf/plugins/ml2/drivers/ovs_conf.py +++ b/neutron/conf/plugins/ml2/drivers/ovs_conf.py @@ -62,6 +62,34 @@ ovs_opts = [ "mapping, make sure to disconnect it from the " "integration bridge as it won't be managed by the " "agent anymore.")), + cfg.ListOpt('resource_provider_bandwidths', + default=[], + help=_("Comma-separated list of " + ":: tuples, showing " + "the available bandwidth for the given bridge in the " + "given direction. The direction is meant from VM " + "perspective. Bandwidth is measured in kilobits per " + "second (kbps). The bridge must appear in " + "bridge_mappings as the value. But not all bridges in " + "bridge_mappings must be listed here. For a bridge not " + "listed here we neither create a resource provider in " + "placement nor report inventories against. An omitted " + "direction means we do not report an inventory for the " + "corresponding class.")), + cfg.DictOpt('resource_provider_inventory_defaults', + default={'allocation_ratio': 1.0, + 'min_unit': 1, + 'step_size': 1, + 'reserved': 0}, + help=_("Key:value pairs to specify defaults used " + "while reporting resource provider inventories. " + "Possible keys with their types: " + "allocation_ratio:float, " + "max_unit:int, min_unit:int, " + "reserved:int, step_size:int, " + "See also: " + "https://developer.openstack.org/api-ref/placement/" + "#update-resource-provider-inventories")), cfg.BoolOpt('use_veth_interconnection', default=False, help=_("Use veths instead of patch ports to interconnect the " "integration bridge to physical networks. " diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py index 22b5706a085..7a209ef9ed5 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -30,6 +30,7 @@ from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources as callback_resources from neutron_lib import constants as n_const from neutron_lib import context +from neutron_lib.placement import utils as place_utils from neutron_lib.plugins import utils as plugin_utils from neutron_lib.utils import helpers from oslo_config import cfg @@ -39,7 +40,7 @@ from oslo_service import loopingcall from oslo_service import systemd from oslo_utils import netutils from osprofiler import profiler -from six import moves +import six from neutron._i18n import _ from neutron.agent.common import ip_lib @@ -150,8 +151,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, self.use_veth_interconnection = ovs_conf.use_veth_interconnection self.veth_mtu = agent_conf.veth_mtu - self.available_local_vlans = set(moves.range(n_const.MIN_VLAN_TAG, - n_const.MAX_VLAN_TAG + 1)) + self.available_local_vlans = set(six.moves.range( + n_const.MIN_VLAN_TAG, n_const.MAX_VLAN_TAG + 1)) self.tunnel_types = agent_conf.tunnel_types or [] self.l2_pop = agent_conf.l2_population # TODO(ethuleau): Change ARP responder so it's not dependent on the @@ -187,6 +188,15 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, self.setup_rpc() self.bridge_mappings = self._parse_bridge_mappings( ovs_conf.bridge_mappings) + self.rp_bandwidths = place_utils.parse_rp_bandwidths( + ovs_conf.resource_provider_bandwidths) + + br_set = set(six.itervalues(self.bridge_mappings)) + n_utils.validate_rp_bandwidth(self.rp_bandwidths, + br_set) + self.rp_inventory_defaults = place_utils.parse_rp_inventory_defaults( + ovs_conf.resource_provider_inventory_defaults) + self.setup_physical_bridges(self.bridge_mappings) self.vlan_manager = vlanmanager.LocalVlanManager() @@ -270,6 +280,9 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, 'host': host, 'topic': n_const.L2_AGENT_TOPIC, 'configurations': {'bridge_mappings': self.bridge_mappings, + c_const.RP_BANDWIDTHS: self.rp_bandwidths, + c_const.RP_INVENTORY_DEFAULTS: + self.rp_inventory_defaults, 'integration_bridge': ovs_conf.integration_bridge, 'tunnel_types': self.tunnel_types, diff --git a/neutron/tests/unit/common/test_utils.py b/neutron/tests/unit/common/test_utils.py index 5ed2259e7b7..3ff9c64affc 100644 --- a/neutron/tests/unit/common/test_utils.py +++ b/neutron/tests/unit/common/test_utils.py @@ -524,3 +524,27 @@ class TestIECUnitConversions(BaseUnitConversionTest, base.BaseTestCase): expected_kilobits, utils.bits_to_kilobits(input_bits, self.base_unit) ) + + +class TestRpBandwidthValidator(base.BaseTestCase): + + def setUp(self): + super(TestRpBandwidthValidator, self).setUp() + self.device_name_set = {'ens4', 'ens7'} + self.valid_rp_bandwidths = { + 'ens7': {'egress': 10000, 'ingress': 10000} + } + self.not_valid_rp_bandwidth = { + 'ens8': {'egress': 10000, 'ingress': 10000} + } + + def test_validate_rp_bandwidth_with_device_names(self): + try: + utils.validate_rp_bandwidth(self.valid_rp_bandwidths, + self.device_name_set) + except ValueError: + self.fail("validate_rp_bandwidth failed to validate %s" % + self.valid_rp_bandwidths) + + self.assertRaises(ValueError, utils.validate_rp_bandwidth, + self.not_valid_rp_bandwidth, self.device_name_set) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index e8d96764e14..88ed95e21db 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -2311,6 +2311,31 @@ class TestOvsNeutronAgent(object): br, 'add', mock.Mock(), mock.Mock(), ip) self.assertFalse(br.install_arp_responder.called) + def test_configurations_has_rp_bandwidth(self): + self.assertIn(c_const.RP_BANDWIDTHS, + self.agent.agent_state['configurations']) + + def test_configurations_has_rp_default_inventory(self): + self.assertIn(c_const.RP_INVENTORY_DEFAULTS, + self.agent.agent_state['configurations']) + rp_inv_defaults = \ + self.agent.agent_state['configurations'][ + c_const.RP_INVENTORY_DEFAULTS] + self.assertListEqual( + sorted(['reserved', 'min_unit', 'allocation_ratio', 'step_size']), + sorted(list(rp_inv_defaults))) + self.assertEqual(1.0, rp_inv_defaults['allocation_ratio']) + self.assertEqual(1, rp_inv_defaults['min_unit']) + self.assertEqual(1, rp_inv_defaults['step_size']) + self.assertEqual(0, rp_inv_defaults['reserved']) + + def test__validate_rp_bandwidth_bridges(self): + cfg.CONF.set_override('bridge_mappings', [], 'OVS') + cfg.CONF.set_override(c_const.RP_BANDWIDTHS, + ['no_such_br_in_bridge_mappings:1:1'], + 'OVS') + self.assertRaises(ValueError, self._make_agent) + class TestOvsNeutronAgentOFCtl(TestOvsNeutronAgent, ovs_test_base.OVSOFCtlTestBase): diff --git a/releasenotes/notes/bandwidth-config-ovs-5bede7fb43b0a574.yaml b/releasenotes/notes/bandwidth-config-ovs-5bede7fb43b0a574.yaml new file mode 100644 index 00000000000..40dea096578 --- /dev/null +++ b/releasenotes/notes/bandwidth-config-ovs-5bede7fb43b0a574.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + New configuration options for neutron-ovs-agent under section ``[ovs]``: + ``resource_provider_bandwidths`` and + ``resource_provider_inventory_defaults``. + The former controls the ``total`` (available bandwidth) field of the + physical network interface resource provider inventories. It defaults + to not creating resource providers in Placement. The latter can be used + to tune the other fields (``allocation_ratio``, ``min_unit``, + ``max_unit``, ``reserved``, ``step_size``) of resource provider + inventories.