diff --git a/neutron_lib/placement/client.py b/neutron_lib/placement/client.py index f57c8360e..d4fa2d6e7 100644 --- a/neutron_lib/placement/client.py +++ b/neutron_lib/placement/client.py @@ -752,39 +752,40 @@ class PlacementAPIClient(object): url = '/allocations/%s' % consumer_uuid return self._get(url).json() - def update_qos_minbw_allocation(self, consumer_uuid, minbw_alloc_diff, - rp_uuid): + def update_qos_allocation(self, consumer_uuid, alloc_diff): """Update allocation for QoS minimum bandwidth consumer :param consumer_uuid: The uuid of the consumer, in case of bound port owned by a VM, the VM uuid. - :param minbw_alloc_diff: A dict which contains the fields to update - for the allocation under the given resource - provider. - :param rp_uuid: uuid of the resource provider for which the - allocations are to be updated. + :param alloc_diff: A dict which contains RP UUIDs as keys and + corresponding fields to update for the allocation + under the given resource provider. """ for i in range(GENERATION_CONFLICT_RETRIES): body = self.list_allocations(consumer_uuid) if not body['allocations']: raise n_exc.PlacementAllocationRemoved(consumer=consumer_uuid) - if rp_uuid not in body['allocations']: - raise n_exc.PlacementAllocationRpNotExists( - resource_provider=rp_uuid, consumer=consumer_uuid) - # Count new min_kbps values based on the diff in alloc_diff - for drctn, min_kbps_diff in minbw_alloc_diff.items(): - orig_kbps = body['allocations'][rp_uuid]['resources'][drctn] - new_kbps = orig_kbps + min_kbps_diff - if new_kbps > 0: - body['allocations'][rp_uuid]['resources'][drctn] = new_kbps - else: - # Remove the resource class if the new value for min_kbps - # is 0 - resources = body['allocations'][rp_uuid]['resources'] - if len(resources) > 1: - resources.pop(drctn, None) + # Count new values based on the diff in alloc_diff + for rp_uuid, diff in alloc_diff.items(): + if rp_uuid not in body['allocations']: + raise n_exc.PlacementAllocationRpNotExists( + resource_provider=rp_uuid, consumer=consumer_uuid) + for drctn, value in diff.items(): + orig_value = body['allocations'][rp_uuid][ + 'resources'].get(drctn, 0) + new_value = orig_value + value + if new_value > 0: + body['allocations'][rp_uuid]['resources'][ + drctn] = new_value else: - body['allocations'].pop(rp_uuid) + # Remove the resource class if the new value is 0 + resources = body['allocations'][rp_uuid]['resources'] + resources.pop(drctn, None) + + # Remove RPs without any resources + body['allocations'] = { + rp: alloc for rp, alloc in body['allocations'].items() + if alloc.get('resources')} try: # Update allocations has no return body, but leave the loop return self.update_allocation(consumer_uuid, body) diff --git a/neutron_lib/tests/unit/placement/test_client.py b/neutron_lib/tests/unit/placement/test_client.py index 8a0a9cc0b..52a5ebfa9 100644 --- a/neutron_lib/tests/unit/placement/test_client.py +++ b/neutron_lib/tests/unit/placement/test_client.py @@ -26,6 +26,7 @@ from neutron_lib.tests import _base as base RESOURCE_PROVIDER_UUID = uuidutils.generate_uuid() +SECOND_RESOURCE_PROVIDER_UUID = uuidutils.generate_uuid() CONSUMER_UUID = uuidutils.generate_uuid() RESOURCE_PROVIDER_NAME = 'resource_provider_name' RESOURCE_PROVIDER = { @@ -727,23 +728,20 @@ class TestPlacementAPIClient(base.BaseTestCase): }} ) - def _get_allocation_response(self, resources): + def _get_allocation_response(self, allocation): mock_rsp_get = mock.Mock() mock_rsp_get.json = lambda: { - 'allocations': { - RESOURCE_PROVIDER_UUID: resources - } + 'allocations': allocation } return mock_rsp_get - def test_update_qos_minbw_allocation(self): + def test_update_qos_allocation(self): mock_rsp_get = self._get_allocation_response( - {'resources': {'a': 3, 'b': 2}}) + {RESOURCE_PROVIDER_UUID: {'resources': {'a': 3, 'b': 2}}}) self.placement_fixture.mock_get.side_effect = [mock_rsp_get] - self.placement_api_client.update_qos_minbw_allocation( + self.placement_api_client.update_qos_allocation( consumer_uuid=CONSUMER_UUID, - minbw_alloc_diff={'a': 2, 'b': 2}, - rp_uuid=RESOURCE_PROVIDER_UUID + alloc_diff={RESOURCE_PROVIDER_UUID: {'a': 2, 'b': 2}}, ) self.placement_fixture.mock_put.assert_called_once_with( '/allocations/%s' % CONSUMER_UUID, @@ -753,32 +751,31 @@ class TestPlacementAPIClient(base.BaseTestCase): }} ) - def test_update_qos_minbw_allocation_removed(self): + def test_update_qos_allocation_removed(self): mock_rsp = mock.Mock() mock_rsp.json = lambda: {'allocations': {}} self.placement_fixture.mock_get.side_effect = [mock_rsp] self.assertRaises( n_exc.PlacementAllocationRemoved, - self.placement_api_client.update_qos_minbw_allocation, + self.placement_api_client.update_qos_allocation, consumer_uuid=CONSUMER_UUID, - minbw_alloc_diff={'a': 1, 'b': 1}, - rp_uuid=RESOURCE_PROVIDER_UUID + alloc_diff={RESOURCE_PROVIDER_UUID: {'a': 1, 'b': 1}}, ) - def test_update_qos_minbw_allocation_rp_not_exists(self): + def test_update_qos_allocation_rp_not_exists(self): mock_rsp = mock.Mock() mock_rsp.json = lambda: {'allocations': {'other:rp:uuid': {'c': 3}}} self.placement_fixture.mock_get.side_effect = [mock_rsp] self.assertRaises( n_exc.PlacementAllocationRpNotExists, - self.placement_api_client.update_qos_minbw_allocation, + self.placement_api_client.update_qos_allocation, consumer_uuid=CONSUMER_UUID, - minbw_alloc_diff={'a': 1, 'b': 1}, - rp_uuid=RESOURCE_PROVIDER_UUID + alloc_diff={RESOURCE_PROVIDER_UUID: {'a': 1, 'b': 1}}, ) - def test_update_qos_minbw_allocation_max_retries(self): - mock_rsp_get = self._get_allocation_response({'c': 3}) + def test_update_qos_allocation_max_retries(self): + mock_rsp_get = self._get_allocation_response( + {RESOURCE_PROVIDER_UUID: {'c': 3}}) self.placement_fixture.mock_get.side_effect = 10 * [mock_rsp_get] mock_rsp_put = mock.Mock() mock_rsp_put.json = lambda: { @@ -787,15 +784,15 @@ class TestPlacementAPIClient(base.BaseTestCase): response=mock_rsp_put) self.assertRaises( n_exc.PlacementAllocationGenerationConflict, - self.placement_api_client.update_qos_minbw_allocation, + self.placement_api_client.update_qos_allocation, consumer_uuid=CONSUMER_UUID, - minbw_alloc_diff={}, - rp_uuid=RESOURCE_PROVIDER_UUID, + alloc_diff={RESOURCE_PROVIDER_UUID: {}}, ) self.assertEqual(10, self.placement_fixture.mock_put.call_count) def test_update_qos_minbwallocation_generation_conflict_solved(self): - mock_rsp_get = self._get_allocation_response({'c': 3}) + mock_rsp_get = self._get_allocation_response( + {RESOURCE_PROVIDER_UUID: {'c': 3}}) self.placement_fixture.mock_get.side_effect = 2 * [mock_rsp_get] mock_rsp_put = mock.Mock() mock_rsp_put.json = lambda: { @@ -804,15 +801,15 @@ class TestPlacementAPIClient(base.BaseTestCase): ks_exc.Conflict(response=mock_rsp_put), mock.Mock() ] - self.placement_api_client.update_qos_minbw_allocation( + self.placement_api_client.update_qos_allocation( consumer_uuid=CONSUMER_UUID, - minbw_alloc_diff={}, - rp_uuid=RESOURCE_PROVIDER_UUID + alloc_diff={RESOURCE_PROVIDER_UUID: {}}, ) self.assertEqual(2, self.placement_fixture.mock_put.call_count) - def test_update_qos_minbw_allocation_other_conflict(self): - mock_rsp_get = self._get_allocation_response({'c': 3}) + def test_update_qos_allocation_other_conflict(self): + mock_rsp_get = self._get_allocation_response( + {RESOURCE_PROVIDER_UUID: {'c': 3}}) self.placement_fixture.mock_get.side_effect = 10*[mock_rsp_get] mock_rsp_put = mock.Mock() mock_rsp_put.text = '' @@ -822,34 +819,31 @@ class TestPlacementAPIClient(base.BaseTestCase): response=mock_rsp_put) self.assertRaises( ks_exc.Conflict, - self.placement_api_client.update_qos_minbw_allocation, + self.placement_api_client.update_qos_allocation, consumer_uuid=CONSUMER_UUID, - minbw_alloc_diff={}, - rp_uuid=RESOURCE_PROVIDER_UUID, + alloc_diff={RESOURCE_PROVIDER_UUID: {}}, ) self.placement_fixture.mock_put.assert_called_once() - def test_update_qos_minbw_allocation_to_zero(self): + def test_update_qos_allocation_to_zero(self): mock_rsp_get = self._get_allocation_response( - {'resources': {'a': 3, 'b': 2}}) + {RESOURCE_PROVIDER_UUID: {'resources': {'a': 3, 'b': 2}}}) self.placement_fixture.mock_get.side_effect = [mock_rsp_get] - self.placement_api_client.update_qos_minbw_allocation( + self.placement_api_client.update_qos_allocation( consumer_uuid=CONSUMER_UUID, - minbw_alloc_diff={'a': -3, 'b': -2}, - rp_uuid=RESOURCE_PROVIDER_UUID + alloc_diff={RESOURCE_PROVIDER_UUID: {'a': -3, 'b': -2}}, ) self.placement_fixture.mock_put.assert_called_once_with( '/allocations/%s' % CONSUMER_UUID, {'allocations': {}}) - def test_update_qos_minbw_allocation_one_class_to_zero(self): + def test_update_qos_allocation_one_class_to_zero(self): mock_rsp_get = self._get_allocation_response( - {'resources': {'a': 3, 'b': 2}}) + {RESOURCE_PROVIDER_UUID: {'resources': {'a': 3, 'b': 2}}}) self.placement_fixture.mock_get.side_effect = [mock_rsp_get] - self.placement_api_client.update_qos_minbw_allocation( + self.placement_api_client.update_qos_allocation( consumer_uuid=CONSUMER_UUID, - minbw_alloc_diff={'a': -3, 'b': 1}, - rp_uuid=RESOURCE_PROVIDER_UUID + alloc_diff={RESOURCE_PROVIDER_UUID: {'a': -3, 'b': 1}}, ) self.placement_fixture.mock_put.assert_called_once_with( '/allocations/%s' % CONSUMER_UUID, @@ -857,3 +851,41 @@ class TestPlacementAPIClient(base.BaseTestCase): RESOURCE_PROVIDER_UUID: { 'resources': {'b': 3}} }}) + + def test_update_qos_allocation_one_class_to_zero_and_new_class(self): + mock_rsp_get = self._get_allocation_response( + {RESOURCE_PROVIDER_UUID: {'resources': {'a': 3}}}) + self.placement_fixture.mock_get.side_effect = [mock_rsp_get] + self.placement_api_client.update_qos_allocation( + consumer_uuid=CONSUMER_UUID, + alloc_diff={RESOURCE_PROVIDER_UUID: {'a': -3, 'b': 1}}, + ) + self.placement_fixture.mock_put.assert_called_once_with( + '/allocations/%s' % CONSUMER_UUID, + {'allocations': { + RESOURCE_PROVIDER_UUID: { + 'resources': {'b': 1}} + }}) + + def test_update_qos_allocation_multiple_rps(self): + mock_rsp_get = self._get_allocation_response({ + RESOURCE_PROVIDER_UUID: {'resources': {'a': 3, 'b': 2}}, + SECOND_RESOURCE_PROVIDER_UUID: {'resources': {'c': 1, 'd': 5}}, + }) + self.placement_fixture.mock_get.side_effect = [mock_rsp_get] + self.placement_api_client.update_qos_allocation( + consumer_uuid=CONSUMER_UUID, + alloc_diff={ + RESOURCE_PROVIDER_UUID: {'a': -3, 'b': 2}, + SECOND_RESOURCE_PROVIDER_UUID: {'e': 3, 'd': -5}, + }, + ) + self.placement_fixture.mock_put.assert_called_once_with( + '/allocations/%s' % CONSUMER_UUID, + {'allocations': { + RESOURCE_PROVIDER_UUID: { + 'resources': {'b': 4}}, + SECOND_RESOURCE_PROVIDER_UUID: { + 'resources': {'c': 1, 'e': 3}}, + }} + )