Add get_usages_counts_for_quota to SchedulerReportClient
This adds a method for requesting /usages from placement for the purpose of counting quota usage for cores and ram. It is used in the next patch in the series. Part of blueprint count-quota-usage-from-placement Change-Id: I35f98f88f8353602e1bfc135f35d1b7bc9ba42a4
This commit is contained in:
parent
85e287d3ef
commit
54408d0ce4
@ -2257,6 +2257,11 @@ class InventoryInUse(InvalidInventory):
|
||||
pass
|
||||
|
||||
|
||||
class UsagesRetrievalFailed(NovaException):
|
||||
msg_fmt = _("Failed to retrieve usages for project '%(project_id)s' and "
|
||||
"user '%(user_id)s'.")
|
||||
|
||||
|
||||
class UnsupportedPointerModelRequested(Invalid):
|
||||
msg_fmt = _("Pointer model '%(model)s' requested is not supported by "
|
||||
"host.")
|
||||
|
@ -49,6 +49,7 @@ POST_RPS_RETURNS_PAYLOAD_API_VERSION = '1.20'
|
||||
AGGREGATE_GENERATION_VERSION = '1.19'
|
||||
NESTED_PROVIDER_API_VERSION = '1.14'
|
||||
POST_ALLOCATIONS_API_VERSION = '1.13'
|
||||
GET_USAGES_VERSION = '1.9'
|
||||
|
||||
AggInfo = collections.namedtuple('AggInfo', ['aggregates', 'generation'])
|
||||
TraitInfo = collections.namedtuple('TraitInfo', ['traits', 'generation'])
|
||||
@ -2315,3 +2316,67 @@ class SchedulerReportClient(object):
|
||||
new_aggs = existing_aggs - set([agg_uuid])
|
||||
self.set_aggregates_for_provider(
|
||||
context, rp_uuid, new_aggs, use_cache=False, generation=gen)
|
||||
|
||||
@staticmethod
|
||||
def _handle_usages_error_from_placement(resp, project_id, user_id=None):
|
||||
msg = ('[%(placement_req_id)s] Failed to retrieve usages for project '
|
||||
'%(project_id)s and user %(user_id)s. Got %(status_code)d: '
|
||||
'%(err_text)s')
|
||||
args = {'placement_req_id': get_placement_request_id(resp),
|
||||
'project_id': project_id,
|
||||
'user_id': user_id or 'N/A',
|
||||
'status_code': resp.status_code,
|
||||
'err_text': resp.text}
|
||||
LOG.error(msg, args)
|
||||
raise exception.UsagesRetrievalFailed(project_id=project_id,
|
||||
user_id=user_id or 'N/A')
|
||||
|
||||
@retrying.retry(stop_max_attempt_number=4,
|
||||
retry_on_exception=lambda e: isinstance(
|
||||
e, ks_exc.ConnectFailure))
|
||||
def _get_usages(self, context, project_id, user_id=None):
|
||||
url = '/usages?project_id=%s' % project_id
|
||||
if user_id:
|
||||
url = ''.join([url, '&user_id=%s' % user_id])
|
||||
return self.get(url, version=GET_USAGES_VERSION,
|
||||
global_request_id=context.global_id)
|
||||
|
||||
def get_usages_counts_for_quota(self, context, project_id, user_id=None):
|
||||
"""Get the usages counts for the purpose of counting quota usage.
|
||||
|
||||
:param context: The request context
|
||||
:param project_id: The project_id to count across
|
||||
:param user_id: The user_id to count across
|
||||
:returns: A dict containing the project-scoped and user-scoped counts
|
||||
if user_id is specified. For example:
|
||||
{'project': {'cores': <count across project>,
|
||||
'ram': <count across project>},
|
||||
{'user': {'cores': <count across user>,
|
||||
'ram': <count across user>},
|
||||
:raises: `exception.UsagesRetrievalFailed` if a placement API call
|
||||
fails
|
||||
"""
|
||||
total_counts = {'project': {}}
|
||||
# First query counts across all users of a project
|
||||
resp = self._get_usages(context, project_id)
|
||||
if resp:
|
||||
data = resp.json()
|
||||
# The response from placement will not contain a resource class if
|
||||
# there is no usage. We can consider a missing class to be 0 usage.
|
||||
cores = data['usages'].get(orc.VCPU, 0)
|
||||
ram = data['usages'].get(orc.MEMORY_MB, 0)
|
||||
total_counts['project'] = {'cores': cores, 'ram': ram}
|
||||
else:
|
||||
self._handle_usages_error_from_placement(resp, project_id)
|
||||
# If specified, second query counts across one user in the project
|
||||
if user_id:
|
||||
resp = self._get_usages(context, project_id, user_id=user_id)
|
||||
if resp:
|
||||
data = resp.json()
|
||||
cores = data['usages'].get(orc.VCPU, 0)
|
||||
ram = data['usages'].get(orc.MEMORY_MB, 0)
|
||||
total_counts['user'] = {'cores': cores, 'ram': ram}
|
||||
else:
|
||||
self._handle_usages_error_from_placement(resp, project_id,
|
||||
user_id=user_id)
|
||||
return total_counts
|
||||
|
@ -4071,3 +4071,89 @@ class TestAggregateAddRemoveHost(SchedulerReportClientTestCase):
|
||||
mock_set_aggs.assert_has_calls([mock.call(
|
||||
self.context, uuids.cn1, set([]), use_cache=False,
|
||||
generation=gen) for gen in gens])
|
||||
|
||||
|
||||
class TestUsages(SchedulerReportClientTestCase):
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.get')
|
||||
def test_get_usages_counts_for_quota_fail(self, mock_get):
|
||||
# First call with project fails
|
||||
mock_get.return_value = fake_requests.FakeResponse(500, content='err')
|
||||
self.assertRaises(exception.UsagesRetrievalFailed,
|
||||
self.client.get_usages_counts_for_quota,
|
||||
self.context, 'fake-project')
|
||||
mock_get.assert_called_once_with(
|
||||
'/usages?project_id=fake-project', version='1.9',
|
||||
global_request_id=self.context.global_id)
|
||||
# Second call with project + user fails
|
||||
mock_get.reset_mock()
|
||||
fake_good_response = fake_requests.FakeResponse(
|
||||
200, content=jsonutils.dumps(
|
||||
{'usages': {orc.VCPU: 2,
|
||||
orc.MEMORY_MB: 512}}))
|
||||
mock_get.side_effect = [fake_good_response,
|
||||
fake_requests.FakeResponse(500, content='err')]
|
||||
self.assertRaises(exception.UsagesRetrievalFailed,
|
||||
self.client.get_usages_counts_for_quota,
|
||||
self.context, 'fake-project', user_id='fake-user')
|
||||
self.assertEqual(2, mock_get.call_count)
|
||||
call1 = mock.call(
|
||||
'/usages?project_id=fake-project', version='1.9',
|
||||
global_request_id=self.context.global_id)
|
||||
call2 = mock.call(
|
||||
'/usages?project_id=fake-project&user_id=fake-user', version='1.9',
|
||||
global_request_id=self.context.global_id)
|
||||
mock_get.assert_has_calls([call1, call2])
|
||||
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.get')
|
||||
def test_get_usages_counts_for_quota_retries(self, mock_get):
|
||||
# Two attempts have a ConnectFailure and the third succeeds
|
||||
fake_project_response = fake_requests.FakeResponse(
|
||||
200, content=jsonutils.dumps(
|
||||
{'usages': {orc.VCPU: 2,
|
||||
orc.MEMORY_MB: 512}}))
|
||||
mock_get.side_effect = [ks_exc.ConnectFailure,
|
||||
ks_exc.ConnectFailure,
|
||||
fake_project_response]
|
||||
counts = self.client.get_usages_counts_for_quota(self.context,
|
||||
'fake-project')
|
||||
self.assertEqual(3, mock_get.call_count)
|
||||
expected = {'project': {'cores': 2, 'ram': 512}}
|
||||
self.assertDictEqual(expected, counts)
|
||||
|
||||
# Project query succeeds, first project + user query has a
|
||||
# ConnectFailure, second project + user query succeeds
|
||||
mock_get.reset_mock()
|
||||
fake_user_response = fake_requests.FakeResponse(
|
||||
200, content=jsonutils.dumps(
|
||||
{'usages': {orc.VCPU: 1,
|
||||
orc.MEMORY_MB: 256}}))
|
||||
mock_get.side_effect = [fake_project_response,
|
||||
ks_exc.ConnectFailure,
|
||||
fake_user_response]
|
||||
counts = self.client.get_usages_counts_for_quota(
|
||||
self.context, 'fake-project', user_id='fake-user')
|
||||
self.assertEqual(3, mock_get.call_count)
|
||||
expected['user'] = {'cores': 1, 'ram': 256}
|
||||
self.assertDictEqual(expected, counts)
|
||||
|
||||
# Three attempts in a row have a ConnectFailure
|
||||
mock_get.reset_mock()
|
||||
mock_get.side_effect = [ks_exc.ConnectFailure] * 4
|
||||
self.assertRaises(ks_exc.ConnectFailure,
|
||||
self.client.get_usages_counts_for_quota,
|
||||
self.context, 'fake-project')
|
||||
|
||||
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.get')
|
||||
def test_get_usages_counts_default_zero(self, mock_get):
|
||||
# A project and user are not yet consuming any resources.
|
||||
fake_response = fake_requests.FakeResponse(
|
||||
200, content=jsonutils.dumps({'usages': {}}))
|
||||
mock_get.side_effect = [fake_response, fake_response]
|
||||
|
||||
counts = self.client.get_usages_counts_for_quota(
|
||||
self.context, 'fake-project', user_id='fake-user')
|
||||
|
||||
self.assertEqual(2, mock_get.call_count)
|
||||
expected = {'project': {'cores': 0, 'ram': 0},
|
||||
'user': {'cores': 0, 'ram': 0}}
|
||||
self.assertDictEqual(expected, counts)
|
||||
|
Loading…
x
Reference in New Issue
Block a user