Add placement client methods for allocations

Added placement client methods for listing and updating allocations
and for updating QoS minumum bandwidth allocation.

Change-Id: I9dab2e9e9728183ee209a038a0347eb7b4a83a1c
Related-Bug: #1882804
This commit is contained in:
elajkat 2020-07-10 17:57:36 +02:00 committed by Brian Haley
parent 3d6e16de32
commit 41e6b9bed8
4 changed files with 217 additions and 1 deletions

View File

@ -80,3 +80,17 @@ class AmbiguousResponsibilityForResourceProvider(exceptions.NeutronException):
"""Not clear who's responsible for resource provider."""
message = _("Expected one driver to be responsible for resource provider "
"%(rsc_provider)s, but got many: %(drivers)s")
class PlacementAllocationGenerationConflict(exceptions.Conflict):
message = _("Resource allocation has been changed for consumer "
"%(consumer)s in Placement while Neutron tried to update it.")
class PlacementAllocationRemoved(exceptions.BadRequest):
message = _("Resource allocation is deleted for consumer %(consumer)s")
class PlacementAllocationRpNotExists(exceptions.BadRequest):
message = _("Resource provider %(resource_provider)s for %(consumer)s "
"does not exist")

View File

@ -38,7 +38,8 @@ PLACEMENT_API_WITH_MEMBER_OF = 'placement 1.3'
PLACEMENT_API_WITH_NESTED_RESOURCES = 'placement 1.14'
PLACEMENT_API_RETURN_PROVIDER_BODY = 'placement 1.20'
PLACEMENT_API_ERROR_CODE = 'placement 1.23'
PLACEMENT_API_LATEST_SUPPORTED = PLACEMENT_API_ERROR_CODE
PLACEMENT_API_CONSUMER_GENERATION = 'placement 1.28'
PLACEMENT_API_LATEST_SUPPORTED = PLACEMENT_API_CONSUMER_GENERATION
GENERATION_CONFLICT_RETRIES = 10
@ -732,3 +733,60 @@ class PlacementAPIClient(object):
self._delete(url)
except ks_exc.NotFound:
raise n_exc.PlacementResourceClassNotFound(resource_class=name)
@_check_placement_api_available
def list_allocations(self, consumer_uuid):
"""List allocations for the consumer
:param consumer_uuid: The uuid of the consumer, in case of bound port
owned by a VM, the VM uuid.
:returns: All allocation records for the consumer.
"""
url = '/allocations/%s' % consumer_uuid
return self._get(url).json()
def update_qos_minbw_allocation(self, consumer_uuid, minbw_alloc_diff,
rp_uuid):
"""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.
"""
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
body['allocations'][rp_uuid]['resources'][drctn] = new_kbps
try:
# Update allocations has no return body, but leave the loop
return self.update_allocation(consumer_uuid, body)
except ks_exc.Conflict as e:
resp = e.response.json()
if resp['errors'][0]['code'] == 'placement.concurrent_update':
continue
else:
raise
raise n_exc.PlacementAllocationGenerationConflict(
consumer=consumer_uuid)
def update_allocation(self, consumer_uuid, allocations):
"""Update allocation record for given consumer and rp
:param consumer_uuid: The uuid of the consumer
:param allocations: Dict in the form described in placement API ref:
https://tinyurl.com/yxeuzn6l
"""
url = '/allocations/%s' % consumer_uuid
self._put(url, allocations)

View File

@ -26,6 +26,7 @@ from neutron_lib.tests import _base as base
RESOURCE_PROVIDER_UUID = uuidutils.generate_uuid()
CONSUMER_UUID = uuidutils.generate_uuid()
RESOURCE_PROVIDER_NAME = 'resource_provider_name'
RESOURCE_PROVIDER = {
'uuid': RESOURCE_PROVIDER_UUID,
@ -698,3 +699,138 @@ class TestPlacementAPIClient(base.BaseTestCase):
resource_provider_generation=None,
)
self.assertEqual(1, self.placement_fixture.mock_put.call_count)
def test_list_allocations(self):
self.placement_api_client.list_allocations(CONSUMER_UUID)
self.placement_fixture.mock_get.assert_called_once_with(
'/allocations/%s' % CONSUMER_UUID)
def test_update_allocation(self):
mock_rsp = mock.Mock()
mock_rsp.json = lambda: {
'allocations': {
RESOURCE_PROVIDER_UUID: {'resources': {'a': 10}}
}
}
self.placement_fixture.mock_get.side_effect = [mock_rsp]
self.placement_api_client.update_allocation(
CONSUMER_UUID,
{'allocations': {
RESOURCE_PROVIDER_UUID: {
'resources': {'a': 20}}
}})
self.placement_fixture.mock_put.assert_called_once_with(
'/allocations/%s' % CONSUMER_UUID,
{'allocations': {
RESOURCE_PROVIDER_UUID: {
'resources': {'a': 20}}
}}
)
def test_update_qos_minbw_allocation(self):
mock_rsp_get = mock.Mock()
mock_rsp_get.json = lambda: {
'allocations': {
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(
consumer_uuid=CONSUMER_UUID,
minbw_alloc_diff={'a': 2, 'b': 2},
rp_uuid=RESOURCE_PROVIDER_UUID
)
self.placement_fixture.mock_put.assert_called_once_with(
'/allocations/%s' % CONSUMER_UUID,
{'allocations': {
RESOURCE_PROVIDER_UUID: {
'resources': {'a': 5, 'b': 4}}
}}
)
def test_update_qos_minbw_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,
consumer_uuid=CONSUMER_UUID,
minbw_alloc_diff={'a': 1, 'b': 1},
rp_uuid=RESOURCE_PROVIDER_UUID
)
def test_update_qos_minbw_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,
consumer_uuid=CONSUMER_UUID,
minbw_alloc_diff={'a': 1, 'b': 1},
rp_uuid=RESOURCE_PROVIDER_UUID
)
def test_update_qos_minbw_allocation_max_retries(self):
mock_rsp_get = mock.Mock()
mock_rsp_get.json = lambda: {
'allocations': {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: {
'errors': [{'code': 'placement.concurrent_update'}]}
self.placement_fixture.mock_put.side_effect = ks_exc.Conflict(
response=mock_rsp_put)
self.assertRaises(
n_exc.PlacementAllocationGenerationConflict,
self.placement_api_client.update_qos_minbw_allocation,
consumer_uuid=CONSUMER_UUID,
minbw_alloc_diff={},
rp_uuid=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 = mock.Mock()
mock_rsp_get.json = lambda: {
'allocations': {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: {
'errors': [{'code': 'placement.concurrent_update'}]}
self.placement_fixture.mock_put.side_effect = [
ks_exc.Conflict(response=mock_rsp_put),
mock.Mock()
]
self.placement_api_client.update_qos_minbw_allocation(
consumer_uuid=CONSUMER_UUID,
minbw_alloc_diff={},
rp_uuid=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 = mock.Mock()
mock_rsp_get.json = lambda: {
'allocations': {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 = ''
mock_rsp_put.json = lambda: {
'errors': [{'code': 'some other error code'}]}
self.placement_fixture.mock_put.side_effect = ks_exc.Conflict(
response=mock_rsp_put)
self.assertRaises(
ks_exc.Conflict,
self.placement_api_client.update_qos_minbw_allocation,
consumer_uuid=CONSUMER_UUID,
minbw_alloc_diff={},
rp_uuid=RESOURCE_PROVIDER_UUID,
)
self.placement_fixture.mock_put.assert_called_once()

View File

@ -0,0 +1,8 @@
---
features:
- |
- Bump ``PlacementAPIClient's`` max supported microversion to ``1.28``,
as from that version ``allocations`` API handles generations in a general
way.
- Add ``list_allocations``, ``update_allocation`` and
``update_qos_minbw_allocation`` methods.