ovs-agent: Report resource info in heartbeat

Example config for ovs-agent:

ml2_conf.ini:
[ovs]
bridge_mappings = physnet0:br-test
resource_provider_bandwidths = br-test:100000:100000

Agent configurations now includes 'resource_provider_bandwidths' and
'resource_provider_inventory_defaults'.

Change-Id: Ib197573e5cdb60ef0db4e7a771c3179bf9d5bb95
Co-Authored-By: Lajos Katona <lajos.katona@ericsson.com>
Depends-On: https://review.openstack.org/577220
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:
Bence Romsics 2018-07-10 15:37:52 +02:00 committed by Lajos Katona
parent c9444141b6
commit f352f9faaa
7 changed files with 129 additions and 3 deletions

View File

@ -250,3 +250,6 @@ EXT_PARENT_RESOURCE_MAPPING = {
l3.FLOATINGIP: plugin_consts.L3 l3.FLOATINGIP: plugin_consts.L3
} }
EXT_PARENT_PREFIX = 'ext_parent' EXT_PARENT_PREFIX = 'ext_parent'
RP_BANDWIDTHS = 'resource_provider_bandwidths'
RP_INVENTORY_DEFAULTS = 'resource_provider_inventory_defaults'

View File

@ -817,3 +817,24 @@ def port_ip_changed(new_port, original_port):
return True return True
return False 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})

View File

@ -62,6 +62,34 @@ ovs_opts = [
"mapping, make sure to disconnect it from the " "mapping, make sure to disconnect it from the "
"integration bridge as it won't be managed by the " "integration bridge as it won't be managed by the "
"agent anymore.")), "agent anymore.")),
cfg.ListOpt('resource_provider_bandwidths',
default=[],
help=_("Comma-separated list of "
"<bridge>:<egress_bw>:<ingress_bw> 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, cfg.BoolOpt('use_veth_interconnection', default=False,
help=_("Use veths instead of patch ports to interconnect the " help=_("Use veths instead of patch ports to interconnect the "
"integration bridge to physical networks. " "integration bridge to physical networks. "

View File

@ -30,6 +30,7 @@ from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources as callback_resources from neutron_lib.callbacks import resources as callback_resources
from neutron_lib import constants as n_const from neutron_lib import constants as n_const
from neutron_lib import context 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.plugins import utils as plugin_utils
from neutron_lib.utils import helpers from neutron_lib.utils import helpers
from oslo_config import cfg from oslo_config import cfg
@ -39,7 +40,7 @@ from oslo_service import loopingcall
from oslo_service import systemd from oslo_service import systemd
from oslo_utils import netutils from oslo_utils import netutils
from osprofiler import profiler from osprofiler import profiler
from six import moves import six
from neutron._i18n import _ from neutron._i18n import _
from neutron.agent.common import ip_lib 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.use_veth_interconnection = ovs_conf.use_veth_interconnection
self.veth_mtu = agent_conf.veth_mtu self.veth_mtu = agent_conf.veth_mtu
self.available_local_vlans = set(moves.range(n_const.MIN_VLAN_TAG, self.available_local_vlans = set(six.moves.range(
n_const.MAX_VLAN_TAG + 1)) n_const.MIN_VLAN_TAG, n_const.MAX_VLAN_TAG + 1))
self.tunnel_types = agent_conf.tunnel_types or [] self.tunnel_types = agent_conf.tunnel_types or []
self.l2_pop = agent_conf.l2_population self.l2_pop = agent_conf.l2_population
# TODO(ethuleau): Change ARP responder so it's not dependent on the # 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.setup_rpc()
self.bridge_mappings = self._parse_bridge_mappings( self.bridge_mappings = self._parse_bridge_mappings(
ovs_conf.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.setup_physical_bridges(self.bridge_mappings)
self.vlan_manager = vlanmanager.LocalVlanManager() self.vlan_manager = vlanmanager.LocalVlanManager()
@ -270,6 +280,9 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
'host': host, 'host': host,
'topic': n_const.L2_AGENT_TOPIC, 'topic': n_const.L2_AGENT_TOPIC,
'configurations': {'bridge_mappings': self.bridge_mappings, 'configurations': {'bridge_mappings': self.bridge_mappings,
c_const.RP_BANDWIDTHS: self.rp_bandwidths,
c_const.RP_INVENTORY_DEFAULTS:
self.rp_inventory_defaults,
'integration_bridge': 'integration_bridge':
ovs_conf.integration_bridge, ovs_conf.integration_bridge,
'tunnel_types': self.tunnel_types, 'tunnel_types': self.tunnel_types,

View File

@ -524,3 +524,27 @@ class TestIECUnitConversions(BaseUnitConversionTest, base.BaseTestCase):
expected_kilobits, expected_kilobits,
utils.bits_to_kilobits(input_bits, self.base_unit) 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)

View File

@ -2311,6 +2311,31 @@ class TestOvsNeutronAgent(object):
br, 'add', mock.Mock(), mock.Mock(), ip) br, 'add', mock.Mock(), mock.Mock(), ip)
self.assertFalse(br.install_arp_responder.called) 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, class TestOvsNeutronAgentOFCtl(TestOvsNeutronAgent,
ovs_test_base.OVSOFCtlTestBase): ovs_test_base.OVSOFCtlTestBase):

View File

@ -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.