diff --git a/neutron/agent/common/placement_report.py b/neutron/agent/common/placement_report.py index ee1d26796a2..cd37d19d1cd 100644 --- a/neutron/agent/common/placement_report.py +++ b/neutron/agent/common/placement_report.py @@ -83,8 +83,7 @@ class PlacementState(object): rp_inventory_defaults, driver_uuid_namespace, agent_type, - agent_host, - agent_host_rp_uuid, + hypervisor_rps, device_mappings, supported_vnic_types, client): @@ -92,8 +91,7 @@ class PlacementState(object): self._rp_inventory_defaults = rp_inventory_defaults self._driver_uuid_namespace = driver_uuid_namespace self._agent_type = agent_type - self._agent_host = agent_host - self._agent_host_rp_uuid = agent_host_rp_uuid + self._hypervisor_rps = hypervisor_rps self._device_mappings = device_mappings self._supported_vnic_types = supported_vnic_types self._client = client @@ -124,44 +122,49 @@ class PlacementState(object): traits += self._deferred_update_vnic_type_traits() return traits - def _deferred_create_agent_rp(self): - agent_rp_name = '%s:%s' % (self._agent_host, self._agent_type) - agent_rp_uuid = place_utils.agent_resource_provider_uuid( - self._driver_uuid_namespace, self._agent_host) - agent_rp = DeferredCall( - self._client.ensure_resource_provider, - resource_provider={ - 'name': agent_rp_name, - 'uuid': agent_rp_uuid, - 'parent_provider_uuid': self._agent_host_rp_uuid}) - return agent_rp + def _deferred_create_agent_rps(self): + # While an instance of this class represents a single agent, + # that agent is allowed to handle devices of multiple hypervisors. + # Since each hypervisor has its own root resource provider + # we must create an agent RP under each hypervisor RP. + rps = [] + for hypervisor in self._hypervisor_rps.values(): + agent_rp_name = '%s:%s' % (hypervisor['name'], self._agent_type) + agent_rp_uuid = place_utils.agent_resource_provider_uuid( + self._driver_uuid_namespace, hypervisor['name']) + rps.append( + DeferredCall( + self._client.ensure_resource_provider, + resource_provider={ + 'name': agent_rp_name, + 'uuid': agent_rp_uuid, + 'parent_provider_uuid': hypervisor['uuid']})) + return rps - def _deferred_create_device_rps(self, agent_rp): + def _deferred_create_device_rps(self): rps = [] for device in self._rp_bandwidths: - rp_name = '%s:%s' % (agent_rp['resource_provider']['name'], device) + hypervisor = self._hypervisor_rps[device] + rp_name = '%s:%s:%s' % ( + hypervisor['name'], self._agent_type, device) rp_uuid = place_utils.device_resource_provider_uuid( self._driver_uuid_namespace, - self._agent_host, + hypervisor['name'], device) rps.append( DeferredCall( self._client.ensure_resource_provider, {'name': rp_name, 'uuid': rp_uuid, - 'parent_provider_uuid': agent_rp[ - 'resource_provider']['uuid']})) + 'parent_provider_uuid': hypervisor['uuid']})) return rps def deferred_create_resource_providers(self): - agent_rp = self._deferred_create_agent_rp() - # XXX(bence romsics): I don't like digging in the deferred agent - # object, but without proper Promises I don't see a significantly - # nicer solution. - device_rps = self._deferred_create_device_rps(agent_rp=agent_rp.kwargs) + agent_rps = self._deferred_create_agent_rps() + device_rps = self._deferred_create_device_rps() rps = [] - rps.append(agent_rp) + rps.extend(agent_rps) rps.extend(device_rps) return rps @@ -179,7 +182,7 @@ class PlacementState(object): for device in self._rp_bandwidths: rp_uuid = place_utils.device_resource_provider_uuid( self._driver_uuid_namespace, - self._agent_host, + self._hypervisor_rps[device]['name'], device) traits = [] traits.append(physnet_trait_mappings[device]) @@ -198,7 +201,7 @@ class PlacementState(object): for device, bw_values in self._rp_bandwidths.items(): rp_uuid = place_utils.device_resource_provider_uuid( self._driver_uuid_namespace, - self._agent_host, + self._hypervisor_rps[device]['name'], device) inventories = {} diff --git a/neutron/agent/common/utils.py b/neutron/agent/common/utils.py index 5d52adc3d15..840e41f272d 100644 --- a/neutron/agent/common/utils.py +++ b/neutron/agent/common/utils.py @@ -14,6 +14,7 @@ # under the License. import os +import socket from neutron_lib.utils import runtime from oslo_config import cfg @@ -67,3 +68,27 @@ def load_interface_driver(conf, get_networks_callback=None): def is_agent_down(heart_beat_time): return timeutils.is_older_than(heart_beat_time, cfg.CONF.agent_down_time) + + +# TODO(bence romsics): rehome this to neutron_lib.placement.utils +def default_rp_hypervisors(hypervisors, device_mappings): + """Fill config option 'resource_provider_hypervisors' with defaults. + + Default hypervisor names to socket.gethostname(). Since libvirt knows + itself by the same name, the default is good for libvirt. + + :param hypervisors: Config option 'resource_provider_hypervisors' + as parsed by oslo.config, that is a dict with keys of physical devices + and values of hypervisor names. + :param device_mappings: Device mappings standardized to the list-valued + format. + """ + default_hypervisor = socket.gethostname() + rv = {} + for _physnet, devices in device_mappings.items(): + for device in devices: + if device in hypervisors: + rv[device] = hypervisors[device] + else: + rv[device] = default_hypervisor + return rv diff --git a/neutron/conf/plugins/ml2/drivers/mech_sriov/agent_common.py b/neutron/conf/plugins/ml2/drivers/mech_sriov/agent_common.py index 9696928ee72..458a354d6b6 100644 --- a/neutron/conf/plugins/ml2/drivers/mech_sriov/agent_common.py +++ b/neutron/conf/plugins/ml2/drivers/mech_sriov/agent_common.py @@ -58,6 +58,15 @@ sriov_nic_opts = [ "inventories against. An omitted direction means we do " "not report an inventory for the corresponding " "class.")), + cfg.DictOpt('resource_provider_hypervisors', + default={}, + help=_("Mapping of network devices to hypervisors: " + ":,... " + "hypervisor name is used to locate the parent of the " + "resource provider tree. Only needs to be set in the " + "rare case when the hypervisor name is different from " + "the DEFAULT.host config option value as known by the " + "nova-compute managing that hypervisor.")), cfg.DictOpt('resource_provider_inventory_defaults', default={'allocation_ratio': 1.0, 'min_unit': 1, diff --git a/neutron/conf/plugins/ml2/drivers/ovs_conf.py b/neutron/conf/plugins/ml2/drivers/ovs_conf.py index f545648dca9..d21035a2277 100644 --- a/neutron/conf/plugins/ml2/drivers/ovs_conf.py +++ b/neutron/conf/plugins/ml2/drivers/ovs_conf.py @@ -76,6 +76,15 @@ ovs_opts = [ "placement nor report inventories against. An omitted " "direction means we do not report an inventory for the " "corresponding class.")), + cfg.DictOpt('resource_provider_hypervisors', + default={}, + help=_("Mapping of bridges to hypervisors: " + ":,... " + "hypervisor name is used to locate the parent of the " + "resource provider tree. Only needs to be set in the " + "rare case when the hypervisor name is different from " + "the DEFAULT.host config option value as known by the " + "nova-compute managing that hypervisor.")), cfg.DictOpt('resource_provider_inventory_defaults', default={'allocation_ratio': 1.0, 'min_unit': 1, diff --git a/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py b/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py index f3747473dcc..5537d69baf0 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/agent/sriov_nic_agent.py @@ -34,6 +34,7 @@ from osprofiler import profiler import six from neutron._i18n import _ +from neutron.agent.common import utils from neutron.agent.l2 import l2_agent_extensions_manager as ext_manager from neutron.agent import rpc as agent_rpc from neutron.agent import securitygroups_rpc as agent_sg_rpc @@ -133,7 +134,8 @@ class SriovNicSwitchRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin): @profiler.trace_cls("rpc") class SriovNicSwitchAgent(object): def __init__(self, physical_devices_mappings, exclude_devices, - polling_interval, rp_bandwidths, rp_inventory_defaults): + polling_interval, rp_bandwidths, rp_inventory_defaults, + rp_hypervisors): self.polling_interval = polling_interval self.network_ports = collections.defaultdict(list) @@ -162,6 +164,7 @@ class SriovNicSwitchAgent(object): n_constants.RP_BANDWIDTHS: rp_bandwidths, n_constants.RP_INVENTORY_DEFAULTS: rp_inventory_defaults, + 'resource_provider_hypervisors': rp_hypervisors, 'extensions': self.ext_manager.names()} # TODO(mangelajo): optimize resource_versions (see ovs agent) @@ -514,6 +517,10 @@ class SriovNicAgentConfigParser(object): cfg.CONF.SRIOV_NIC.resource_provider_bandwidths) self.rp_inventory_defaults = place_utils.parse_rp_inventory_defaults( cfg.CONF.SRIOV_NIC.resource_provider_inventory_defaults) + self.rp_hypervisors = utils.default_rp_hypervisors( + cfg.CONF.SRIOV_NIC.resource_provider_hypervisors, + self.device_mappings + ) self._validate() def _validate(self): @@ -546,6 +553,7 @@ def main(): exclude_devices = config_parser.exclude_devices rp_bandwidths = config_parser.rp_bandwidths rp_inventory_defaults = config_parser.rp_inventory_defaults + rp_hypervisors = config_parser.rp_hypervisors except ValueError: LOG.exception("Failed on Agent configuration parse. " @@ -560,7 +568,8 @@ def main(): exclude_devices, polling_interval, rp_bandwidths, - rp_inventory_defaults) + rp_inventory_defaults, + rp_hypervisors) except exc.SriovNicError: LOG.exception("Agent Initialization Failed") raise SystemExit(1) 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 f9cf3d4dfb8..c5881aadc1d 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -222,6 +222,10 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, br_set) self.rp_inventory_defaults = place_utils.parse_rp_inventory_defaults( ovs_conf.resource_provider_inventory_defaults) + self.rp_hypervisors = utils.default_rp_hypervisors( + ovs_conf.resource_provider_hypervisors, + {k: [v] for k, v in self.bridge_mappings.items()} + ) self.setup_physical_bridges(self.bridge_mappings) self.vlan_manager = vlanmanager.LocalVlanManager() @@ -314,6 +318,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, n_const.RP_BANDWIDTHS: self.rp_bandwidths, n_const.RP_INVENTORY_DEFAULTS: self.rp_inventory_defaults, + 'resource_provider_hypervisors': + self.rp_hypervisors, 'integration_bridge': ovs_conf.integration_bridge, 'tunnel_types': self.tunnel_types, diff --git a/neutron/services/placement_report/plugin.py b/neutron/services/placement_report/plugin.py index 4db077f5ccc..49e4987f7d2 100644 --- a/neutron/services/placement_report/plugin.py +++ b/neutron/services/placement_report/plugin.py @@ -76,6 +76,25 @@ class PlacementReportPlugin(service_base.ServicePluginBase): uuid_ns = mech_driver.resource_provider_uuid5_namespace supported_vnic_types = mech_driver.supported_vnic_types device_mappings = mech_driver.get_standard_device_mappings(agent) + if 'resource_provider_hypervisors' in configurations: + # When the agent has the fix for + # https://bugs.launchpad.net/neutron/+bug/1853840 + # it sends us hypervisor names (compute nodes in nova terminology). + hypervisors = configurations['resource_provider_hypervisors'] + else: + # For older agents without the fix we have to assume the old + # buggy behavior. There we assumed DEFAULT.host is the same as the + # hypervisor name, which is true in many deployments, but not + # always. (In nova terminology: The compute host's DEFAULT.host is + # not neccessarily the same as the compute node name. We may even + # have multiple compute nodes behind a compute host.) + # TODO(bence romsics): This else branch can be removed when we no + # longer want to support pre-Ussuri agents. + hypervisors = { + device: agent['host'] + for device + in configurations['resource_provider_bandwidths'].keys() + } log_msg = ( 'Synchronization of resources ' @@ -84,8 +103,16 @@ class PlacementReportPlugin(service_base.ServicePluginBase): 'to placement %(result)s.') try: - agent_host_rp_uuid = self._get_rp_by_name( - name=agent['host'])['uuid'] + name2uuid = {} + for name in hypervisors.values(): + name2uuid[name] = self._get_rp_by_name(name=name)['uuid'] + + hypervisor_rps = {} + for device, hypervisor in hypervisors.items(): + hypervisor_rps[device] = { + 'name': hypervisor, + 'uuid': name2uuid[hypervisor], + } except (IndexError, ks_exc.HttpError, ks_exc.ClientException): agent_db.resources_synced = False agent_db.update() @@ -105,8 +132,7 @@ class PlacementReportPlugin(service_base.ServicePluginBase): 'resource_provider_inventory_defaults'], driver_uuid_namespace=uuid_ns, agent_type=agent['agent_type'], - agent_host=agent['host'], - agent_host_rp_uuid=agent_host_rp_uuid, + hypervisor_rps=hypervisor_rps, device_mappings=device_mappings, supported_vnic_types=supported_vnic_types, client=self._placement_client) diff --git a/neutron/tests/unit/agent/common/test_placement_report.py b/neutron/tests/unit/agent/common/test_placement_report.py index 03ecdd3baca..4d6fff29120 100644 --- a/neutron/tests/unit/agent/common/test_placement_report.py +++ b/neutron/tests/unit/agent/common/test_placement_report.py @@ -48,15 +48,23 @@ class PlacementStateTestCase(base.BaseTestCase): self.client_mock = mock.Mock() self.driver_uuid_namespace = uuid.UUID( '00000000-0000-0000-0000-000000000001') - self.agent_host_rp_uuid = uuid.UUID( - '00000000-0000-0000-0000-000000000002') + # uuid below generated by the following command: + # uuid -v5 '00000000-0000-0000-0000-000000000001' 'fakehost' + self.hypervisor1_rp_uuid = uuid.UUID( + 'c0b4abe5-516f-54b8-b965-ff94060dcbcc') + # uuid below generated by the following command: + # uuid -v5 '00000000-0000-0000-0000-000000000001' 'fakehost2' + self.hypervisor2_rp_uuid = uuid.UUID( + '544155b7-1295-5f10-b5f0-eadc50abc6d4') self.kwargs = { 'rp_bandwidths': {}, 'rp_inventory_defaults': {}, 'driver_uuid_namespace': self.driver_uuid_namespace, 'agent_type': 'fake agent type', - 'agent_host': 'fakehost', - 'agent_host_rp_uuid': self.agent_host_rp_uuid, + 'hypervisor_rps': { + 'eth0': {'name': 'fakehost', 'uuid': self.hypervisor1_rp_uuid}, + 'eth1': {'name': 'fakehost', 'uuid': self.hypervisor1_rp_uuid}, + }, 'device_mappings': {}, 'supported_vnic_types': [], 'client': self.client_mock, @@ -92,11 +100,11 @@ class PlacementStateTestCase(base.BaseTestCase): self.client_mock.update_trait.assert_any_call( name='CUSTOM_VNIC_TYPE_DIRECT') - def test__deferred_create_agent_rp(self): + def test__deferred_create_agent_rps(self): state = placement_report.PlacementState(**self.kwargs) - deferred = state._deferred_create_agent_rp() - deferred.execute() + for deferred in state._deferred_create_agent_rps(): + deferred.execute() self.client_mock.ensure_resource_provider.assert_called_with( resource_provider={ @@ -104,7 +112,37 @@ class PlacementStateTestCase(base.BaseTestCase): # uuid below generated by the following command: # uuid -v5 '00000000-0000-0000-0000-000000000001' 'fakehost' 'uuid': uuid.UUID('c0b4abe5-516f-54b8-b965-ff94060dcbcc'), - 'parent_provider_uuid': self.agent_host_rp_uuid}) + 'parent_provider_uuid': self.hypervisor1_rp_uuid}) + + def test__deferred_create_agent_rps_multiple_hypervisors(self): + self.kwargs['hypervisor_rps']['eth1'] = { + 'name': 'fakehost2', + 'uuid': self.hypervisor2_rp_uuid, + } + state = placement_report.PlacementState(**self.kwargs) + + for deferred in state._deferred_create_agent_rps(): + deferred.execute() + + self.client_mock.ensure_resource_provider.assert_has_calls( + any_order=True, + calls=[ + mock.call(resource_provider={ + 'name': 'fakehost:fake agent type', + # uuid below generated by the following command: + # uuid -v5 '00000000-0000-0000-0000-000000000001' \ + # 'fakehost' + 'uuid': uuid.UUID('c0b4abe5-516f-54b8-b965-ff94060dcbcc'), + 'parent_provider_uuid': self.hypervisor1_rp_uuid}), + mock.call(resource_provider={ + 'name': 'fakehost2:fake agent type', + # uuid below generated by the following command: + # uuid -v5 '00000000-0000-0000-0000-000000000001' \ + # 'fakehost2' + 'uuid': uuid.UUID('544155b7-1295-5f10-b5f0-eadc50abc6d4'), + 'parent_provider_uuid': self.hypervisor2_rp_uuid}), + ] + ) def test_deferred_create_resource_providers(self): self.kwargs.update({ diff --git a/neutron/tests/unit/agent/common/test_utils.py b/neutron/tests/unit/agent/common/test_utils.py index 889bc4b7c79..622c64f0591 100644 --- a/neutron/tests/unit/agent/common/test_utils.py +++ b/neutron/tests/unit/agent/common/test_utils.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import socket + import mock from neutron.agent.common import utils @@ -75,3 +77,26 @@ class TestLoadInterfaceDriver(base.BaseTestCase): self.conf.set_override('interface_driver', 'openvswitchXX') with testlib_api.ExpectedException(SystemExit): utils.load_interface_driver(self.conf) + + +# TODO(bence romsics): rehome this to neutron_lib +class TestDefaultRpHypervisors(base.BaseTestCase): + + def test_defaults(self): + this_host = socket.gethostname() + + self.assertEqual( + {'eth0': this_host, 'eth1': this_host}, + utils.default_rp_hypervisors( + hypervisors={}, + device_mappings={'physnet0': ['eth0', 'eth1']}, + ) + ) + + self.assertEqual( + {'eth0': 'thathost', 'eth1': this_host}, + utils.default_rp_hypervisors( + hypervisors={'eth0': 'thathost'}, + device_mappings={'physnet0': ['eth0', 'eth1']}, + ) + ) diff --git a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py index eefb5cca9de..1571ba45254 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/mech_sriov/agent/test_sriov_nic_agent.py @@ -54,7 +54,7 @@ class TestSriovAgent(base.BaseTestCase): 'FixedIntervalLoopingCall', new=MockFixedIntervalLoopingCall) - self.agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}) + self.agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) @mock.patch("neutron.plugins.ml2.drivers.mech_sriov.agent.eswitch_manager" ".ESwitchManager.get_assigned_devices_info", return_value=set()) @@ -82,7 +82,7 @@ class TestSriovAgent(base.BaseTestCase): "eswitch_manager.PciOsWrapper.is_assigned_vf", return_value=True) def test_treat_devices_removed_with_existed_device(self, *args): - agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}) + agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) devices = [(DEVICE_MAC, PCI_SLOT)] with mock.patch.object(agent.plugin_rpc, "update_device_down") as fn_udd: @@ -99,7 +99,7 @@ class TestSriovAgent(base.BaseTestCase): "eswitch_manager.PciOsWrapper.is_assigned_vf", return_value=True) def test_treat_devices_removed_with_not_existed_device(self, *args): - agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}) + agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) devices = [(DEVICE_MAC, PCI_SLOT)] with mock.patch.object(agent.plugin_rpc, "update_device_down") as fn_udd: @@ -119,7 +119,7 @@ class TestSriovAgent(base.BaseTestCase): "eswitch_manager.PciOsWrapper.is_assigned_vf", return_value=True) def test_treat_devices_removed_failed(self, *args): - agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}) + agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) devices = [(DEVICE_MAC, PCI_SLOT)] with mock.patch.object(agent.plugin_rpc, "update_device_down") as fn_udd: @@ -546,7 +546,7 @@ class TestSriovAgent(base.BaseTestCase): def test_configurations_has_rp_bandwidth(self): rp_bandwidth = {'ens7': {'egress': 10000, 'ingress': 10000}} agent = sriov_nic_agent.SriovNicSwitchAgent( - {}, {}, 0, rp_bandwidth, {}) + {}, {}, 0, rp_bandwidth, {}, {}) self.assertIn(constants.RP_BANDWIDTHS, agent.agent_state['configurations']) @@ -562,7 +562,7 @@ class TestSriovAgent(base.BaseTestCase): 'reserved': 0 } agent = sriov_nic_agent.SriovNicSwitchAgent( - {}, {}, 0, {}, rp_inventory_values) + {}, {}, 0, {}, rp_inventory_values, {}) self.assertIn(constants.RP_INVENTORY_DEFAULTS, agent.agent_state['configurations']) @@ -706,7 +706,7 @@ class TestSRIOVAgentExtensionConfig(base.BaseTestCase): def test_report_loaded_extension(self, *args): with mock.patch.object(agent_rpc.PluginReportStateAPI, 'report_state') as mock_report_state: - agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}) + agent = sriov_nic_agent.SriovNicSwitchAgent({}, {}, 0, {}, {}, {}) agent._report_state() mock_report_state.assert_called_with( agent.context, agent.agent_state) diff --git a/neutron/tests/unit/services/placement_report/test_plugin.py b/neutron/tests/unit/services/placement_report/test_plugin.py index 2f5d3fe84e5..ea5ea48c74f 100644 --- a/neutron/tests/unit/services/placement_report/test_plugin.py +++ b/neutron/tests/unit/services/placement_report/test_plugin.py @@ -53,7 +53,9 @@ class PlacementReportPluginTestCases(test_plugin.Ml2PluginV2TestCase): # looking all good agent = { 'agent_type': 'test_mechanism_driver_agent', - 'configurations': {'resource_provider_bandwidths': {}}, + 'configurations': { + 'resource_provider_bandwidths': {'some iface': ''}, + }, 'host': 'fake host', } agent_db = mock.Mock() @@ -76,7 +78,8 @@ class PlacementReportPluginTestCases(test_plugin.Ml2PluginV2TestCase): # looking all good agent = { 'agent_type': 'test_mechanism_driver_agent', - 'configurations': {'resource_provider_bandwidths': {}}, + 'configurations': { + 'resource_provider_bandwidths': {'some iface': ''}}, 'host': 'fake host', } agent_db = mock.Mock() @@ -201,7 +204,7 @@ class PlacementReportPluginTestCases(test_plugin.Ml2PluginV2TestCase): self.assertEqual(1, mock_get_agent.call_count) self.assertEqual(1, mock_sync.call_count) - def test__sync_placement_state(self): + def test__sync_placement_state_legacy(self): agent = { 'agent_type': 'test_mechanism_driver_agent', 'configurations': { @@ -222,6 +225,30 @@ class PlacementReportPluginTestCases(test_plugin.Ml2PluginV2TestCase): self.assertEqual(1, mock_queue_event.call_count) + def test__sync_placement_state_rp_hypervisors(self): + agent = { + 'agent_type': 'test_mechanism_driver_agent', + 'configurations': { + 'resource_provider_bandwidths': {}, + 'resource_provider_inventory_defaults': {}, + 'resource_provider_hypervisors': {'eth0': 'hypervisor0'}, + }, + 'host': 'fake host', + } + agent_db = mock.Mock() + + with mock.patch.object(self.service_plugin._batch_notifier, + 'queue_event') as mock_queue_event, \ + mock.patch.object(self.service_plugin._placement_client, + 'list_resource_providers', + return_value={'resource_providers': [ + {'uuid': 'fake uuid'}]}) as mock_list_rps: + + self.service_plugin._sync_placement_state(agent, agent_db) + + self.assertEqual(1, mock_queue_event.call_count) + mock_list_rps.assert_called_once_with(name='hypervisor0') + class PlacementReporterAgentsTestCases(test_plugin.Ml2PluginV2TestCase): diff --git a/releasenotes/notes/locate-rp-tree-parent-by-hypervisor-name-3244ed87dc57f950.yaml b/releasenotes/notes/locate-rp-tree-parent-by-hypervisor-name-3244ed87dc57f950.yaml new file mode 100644 index 00000000000..36a830c26dd --- /dev/null +++ b/releasenotes/notes/locate-rp-tree-parent-by-hypervisor-name-3244ed87dc57f950.yaml @@ -0,0 +1,23 @@ +--- +fixes: + - | + Neutron now locates the root resource provider of the resource provider + tree it creates by using the hypervisor name instead of the hostname. + These are different in rare cases only. The hypervisor name can be set + per physical network device in config option + ``resource_provider_hypervisors`` which is located in the ``[ovs]`` + ini-section for ``ovs-agent`` and ``[sriov_nic]`` ini-section for + ``sriov-agent``. Hypervisor names default to ``socket.gethostname()`` + which works out of the box with ``libvirt`` even when the ``DEFAULT.host`` + config option is set to a non-default value. We believe this change fixes + `bug 696600 `_. +upgrade: + - | + For users affected by `bug 696600 + `_ the hypervisor name + now can be set per physical network device in config option + ``resource_provider_hypervisors`` which is located in the ``[ovs]`` + ini-section for ``ovs-agent`` and ``[sriov_nic]`` ini-section for + ``sriov-agent``. Hypervisor names default to ``socket.gethostname()`` + which works out of the box with ``libvirt`` even when the + ``DEFAULT.host`` config option is set to a non-default value.