From c2bc676183a202743fa80f9cb0271af818958441 Mon Sep 17 00:00:00 2001 From: Przemyslaw Szczerbik Date: Tue, 6 Jul 2021 12:24:59 +0200 Subject: [PATCH] Report pkt processing capacity on Neutron agent RP Report the packet processing capacity on the Neutron agent resource provider to Placement as the new 'NET_PACKET_RATE_KILOPACKET_PER_SEC' or 'NET_PACKET_RATE_[E|I]GR_KILOPACKET_PER_SEC' resource inventory. This is similar to how the bandwidth resource is reported today. Partial-Bug: #1922237 See-Also: https://review.opendev.org/785236 Change-Id: I8deefbeed4b4b51dd20062df62c8891fee3ebf9d --- neutron/agent/common/placement_report.py | 46 ++++++++++- neutron/services/placement_report/plugin.py | 12 +++ .../agent/common/test_placement_report.py | 68 +++++++++++++++- .../services/placement_report/test_plugin.py | 80 +++++++++++++++++++ ...-processing-capacity-1c43fe49d7bb2193.yaml | 13 +++ 5 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/pkt-processing-capacity-1c43fe49d7bb2193.yaml diff --git a/neutron/agent/common/placement_report.py b/neutron/agent/common/placement_report.py index bf162821717..30d4b812af8 100644 --- a/neutron/agent/common/placement_report.py +++ b/neutron/agent/common/placement_report.py @@ -81,6 +81,8 @@ class PlacementState(object): def __init__(self, rp_bandwidths, rp_inventory_defaults, + rp_pkt_processing, + rp_pkt_processing_inventory_defaults, driver_uuid_namespace, agent_type, hypervisor_rps, @@ -89,6 +91,8 @@ class PlacementState(object): client): self._rp_bandwidths = rp_bandwidths self._rp_inventory_defaults = rp_inventory_defaults + self._rp_pp = rp_pkt_processing + self._rp_pp_inventory_defaults = rp_pkt_processing_inventory_defaults self._driver_uuid_namespace = driver_uuid_namespace self._agent_type = agent_type self._hypervisor_rps = hypervisor_rps @@ -219,7 +223,36 @@ class PlacementState(object): return rp_traits - def deferred_update_resource_provider_inventories(self): + def _deferred_update_rp_pp_inventory(self): + agent_rp_inventories = [] + + for hypervisor, pp_values in self._rp_pp.items(): + agent_rp_uuid = place_utils.agent_resource_provider_uuid( + self._driver_uuid_namespace, hypervisor) + + inventories = {} + for direction, rp_class in ( + (nlib_const.EGRESS_DIRECTION, + orc.NET_PACKET_RATE_EGR_KILOPACKET_PER_SEC), + (nlib_const.INGRESS_DIRECTION, + orc.NET_PACKET_RATE_IGR_KILOPACKET_PER_SEC), + (nlib_const.ANY_DIRECTION, + orc.NET_PACKET_RATE_KILOPACKET_PER_SEC)): + if pp_values.get(direction): + inventory = dict(self._rp_pp_inventory_defaults) + inventory['total'] = pp_values[direction] + inventories[rp_class] = inventory + + if inventories: + agent_rp_inventories.append( + DeferredCall( + self._client.update_resource_provider_inventories, + resource_provider_uuid=agent_rp_uuid, + inventories=inventories)) + + return agent_rp_inventories + + def _deferred_update_rp_bw_inventory(self): rp_inventories = [] for device, bw_values in self._rp_bandwidths.items(): @@ -234,7 +267,7 @@ class PlacementState(object): orc.NET_BW_EGR_KILOBIT_PER_SEC), (nlib_const.INGRESS_DIRECTION, orc.NET_BW_IGR_KILOBIT_PER_SEC)): - if bw_values[direction] is not None: + if bw_values.get(direction): inventory = dict(self._rp_inventory_defaults) inventory['total'] = bw_values[direction] inventories[rp_class] = inventory @@ -248,6 +281,15 @@ class PlacementState(object): return rp_inventories + def deferred_update_resource_provider_inventories(self): + bw_inventory = self._deferred_update_rp_bw_inventory() + pp_inventory = self._deferred_update_rp_pp_inventory() + + inventories = [] + inventories.extend(bw_inventory) + inventories.extend(pp_inventory) + return inventories + def deferred_sync(self): state = [] state += self.deferred_update_traits() diff --git a/neutron/services/placement_report/plugin.py b/neutron/services/placement_report/plugin.py index cd1ad712b37..3a570db82f6 100644 --- a/neutron/services/placement_report/plugin.py +++ b/neutron/services/placement_report/plugin.py @@ -18,6 +18,7 @@ from neutron_lib.api.definitions import agent_resources_synced from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources +from neutron_lib import constants as n_const from neutron_lib.placement import client as place_client from neutron_lib.plugins import directory from neutron_lib.services import base as service_base @@ -130,6 +131,17 @@ class PlacementReportPlugin(service_base.ServicePluginBase): 'resource_provider_bandwidths'], rp_inventory_defaults=configurations[ 'resource_provider_inventory_defaults'], + # RP_PP_WITHOUT_DIRECTION and RP_PP_WITH_DIRECTION are mutually + # exclusive options. Use the one that was provided or fallback to + # an empty dict. + rp_pkt_processing=( + configurations[n_const.RP_PP_WITHOUT_DIRECTION] + if configurations.get( + n_const.RP_PP_WITHOUT_DIRECTION) + else configurations.get( + n_const.RP_PP_WITH_DIRECTION, {})), + rp_pkt_processing_inventory_defaults=configurations.get( + n_const.RP_PP_INVENTORY_DEFAULTS, {}), driver_uuid_namespace=uuid_ns, agent_type=agent['agent_type'], hypervisor_rps=hypervisor_rps, diff --git a/neutron/tests/unit/agent/common/test_placement_report.py b/neutron/tests/unit/agent/common/test_placement_report.py index e7e391c9c1f..858a68759dc 100644 --- a/neutron/tests/unit/agent/common/test_placement_report.py +++ b/neutron/tests/unit/agent/common/test_placement_report.py @@ -54,6 +54,8 @@ class PlacementStateTestCase(base.BaseTestCase): self.kwargs = { 'rp_bandwidths': {}, 'rp_inventory_defaults': {}, + 'rp_pkt_processing': {}, + 'rp_pkt_processing_inventory_defaults': {}, 'driver_uuid_namespace': self.driver_uuid_namespace, 'agent_type': 'fake agent type', 'hypervisor_rps': { @@ -225,7 +227,7 @@ class PlacementStateTestCase(base.BaseTestCase): set(['CUSTOM_VNIC_TYPE_NORMAL'])], actual_traits) - def test_deferred_update_resource_provider_inventories(self): + def test_deferred_update_resource_provider_inventories_bw(self): self.kwargs.update({ 'device_mappings': { 'physnet0': ['eth0'], @@ -255,3 +257,67 @@ class PlacementStateTestCase(base.BaseTestCase): 'total': 100, 'step_size': 10, 'max_unit': 50}}) + + def test_deferred_update_resource_provider_inventories_pp_direction(self): + self.kwargs.update({ + 'rp_pkt_processing': { + 'fakehost': {'egress': 100, 'ingress': 200}, + }, + 'rp_pkt_processing_inventory_defaults': { + 'step_size': 10, + 'max_unit': 50, + }, + }) + state = placement_report.PlacementState(**self.kwargs) + + for deferred in state.deferred_update_resource_provider_inventories(): + deferred.execute() + + # uuid below generated by the following command: + # uuid -v5 '00000000-0000-0000-0000-000000000001' 'fakehost' + expected_calls = [ + mock.call( + resource_provider_uuid=uuid.UUID( + 'c0b4abe5-516f-54b8-b965-ff94060dcbcc'), + inventories={ + # TODO(przszc): Replace hard-coded resource classes names + # with os-resource-classes lib. + 'NET_PACKET_RATE_EGR_KILOPACKET_PER_SEC': { + 'total': 100, + 'step_size': 10, + 'max_unit': 50}, + 'NET_PACKET_RATE_IGR_KILOPACKET_PER_SEC': { + 'total': 200, + 'step_size': 10, + 'max_unit': 50}})] + self.client_mock.update_resource_provider_inventories.assert_has_calls( + expected_calls) + + def test_deferred_update_resource_provider_inventories_pp(self): + self.kwargs.update({ + 'rp_pkt_processing': { + 'fakehost': {'any': 300}, + }, + 'rp_pkt_processing_inventory_defaults': { + 'step_size': 1, + 'max_unit': 5, + }, + }) + state = placement_report.PlacementState(**self.kwargs) + + for deferred in state.deferred_update_resource_provider_inventories(): + deferred.execute() + + # uuid below generated by the following command: + # uuid -v5 '00000000-0000-0000-0000-000000000001' 'fakehost' + expected_calls = [ + mock.call( + resource_provider_uuid=uuid.UUID( + 'c0b4abe5-516f-54b8-b965-ff94060dcbcc'), + inventories={ + 'NET_PACKET_RATE_KILOPACKET_PER_SEC': { + 'total': 300, + 'step_size': 1, + 'max_unit': 5}})] + self.client_mock.update_resource_provider_inventories.assert_has_calls( + expected_calls) diff --git a/neutron/tests/unit/services/placement_report/test_plugin.py b/neutron/tests/unit/services/placement_report/test_plugin.py index ffe760c25f7..d172ea80346 100644 --- a/neutron/tests/unit/services/placement_report/test_plugin.py +++ b/neutron/tests/unit/services/placement_report/test_plugin.py @@ -249,6 +249,86 @@ class PlacementReportPluginTestCases(test_plugin.Ml2PluginV2TestCase): self.assertEqual(1, mock_queue_event.call_count) mock_list_rps.assert_called_once_with(name='hypervisor0') + def test__sync_placement_state_rp_pkt_processing_with_direction(self): + agent = { + 'agent_type': 'test_mechanism_driver_agent', + 'configurations': { + 'resource_provider_bandwidths': {}, + 'resource_provider_inventory_defaults': {}, + 'resource_provider_packet_processing_with_direction': { + 'fake host': {'egress': 1, 'ingress': 2} + }, + 'resource_provider_packet_processing_inventory_defaults': { + 'allocation_ratio': 1, 'min_unit': 1, 'step_size': 1 + }, + }, + 'host': 'fake host', + } + agent_db = mock.Mock() + mock_state = mock.Mock(return_value=[]) + + with mock.patch.object(self.service_plugin._batch_notifier, + 'queue_event') as mock_queue_event, \ + mock.patch('neutron.agent.common.placement_report.PlacementState', + return_value=mock_state) as mock_placement_state: + + self.service_plugin._sync_placement_state(agent, agent_db) + + self.assertEqual(1, mock_queue_event.call_count) + mock_placement_state.assert_called_once_with( + rp_pkt_processing={'fake host': {'egress': 1, 'ingress': 2}}, + rp_pkt_processing_inventory_defaults={ + 'allocation_ratio': 1, 'min_unit': 1, 'step_size': 1}, + rp_bandwidths=mock.ANY, + rp_inventory_defaults=mock.ANY, + driver_uuid_namespace=mock.ANY, + agent_type=mock.ANY, + hypervisor_rps=mock.ANY, + device_mappings=mock.ANY, + supported_vnic_types=mock.ANY, + client=mock.ANY) + mock_state.deferred_sync.assert_called_once() + + def test__sync_placement_state_rp_pkt_processing_without_direction(self): + agent = { + 'agent_type': 'test_mechanism_driver_agent', + 'configurations': { + 'resource_provider_bandwidths': {}, + 'resource_provider_inventory_defaults': {}, + 'resource_provider_packet_processing_without_direction': { + 'fake host': {'any': 1} + }, + 'resource_provider_packet_processing_inventory_defaults': { + 'allocation_ratio': 1, 'min_unit': 1, 'step_size': 1 + }, + }, + 'host': 'fake host', + } + agent_db = mock.Mock() + mock_state = mock.Mock(return_value=[]) + + with mock.patch.object(self.service_plugin._batch_notifier, + 'queue_event') as mock_queue_event, \ + mock.patch('neutron.agent.common.placement_report.PlacementState', + return_value=mock_state) as mock_placement_state: + + self.service_plugin._sync_placement_state(agent, agent_db) + + self.assertEqual(1, mock_queue_event.call_count) + mock_placement_state.assert_called_once_with( + rp_pkt_processing={'fake host': {'any': 1}}, + rp_pkt_processing_inventory_defaults={ + 'allocation_ratio': 1, 'min_unit': 1, 'step_size': 1}, + rp_bandwidths=mock.ANY, + rp_inventory_defaults=mock.ANY, + driver_uuid_namespace=mock.ANY, + agent_type=mock.ANY, + hypervisor_rps=mock.ANY, + device_mappings=mock.ANY, + supported_vnic_types=mock.ANY, + client=mock.ANY) + mock_state.deferred_sync.assert_called_once() + class PlacementReporterAgentsTestCases(test_plugin.Ml2PluginV2TestCase): diff --git a/releasenotes/notes/pkt-processing-capacity-1c43fe49d7bb2193.yaml b/releasenotes/notes/pkt-processing-capacity-1c43fe49d7bb2193.yaml new file mode 100644 index 00000000000..7151af73c61 --- /dev/null +++ b/releasenotes/notes/pkt-processing-capacity-1c43fe49d7bb2193.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + Report packet processing capacity on the OVS agent resource provider as + the new ``NET_PACKET_RATE_KILOPACKET_PER_SEC``, + ``NET_PACKET_RATE_EGR_KILOPACKET_PER_SEC`` or + ``NET_PACKET_RATE_IGR_KILOPACKET_PER_SEC`` resource inventory. This is + similar to how the bandwidth resource is reported today. The former + is used for non-hardware-offloaded OVS deployments, where packets + processed from both ingress and egress directions are handled by the same + set of CPU cores. Remaining inventories are used for hardware-offloaded + OVS, where the incoming and outgoing packets are handled by independent + hardware resources.