diff --git a/releasenotes/notes/get-usage-72d249ff790d1b8f.yaml b/releasenotes/notes/get-usage-72d249ff790d1b8f.yaml new file mode 100644 index 000000000..4b447f4d4 --- /dev/null +++ b/releasenotes/notes/get-usage-72d249ff790d1b8f.yaml @@ -0,0 +1,3 @@ +--- +features: + - Allow to retrieve the usage of a specific project diff --git a/shade/_normalize.py b/shade/_normalize.py index 944df2d52..05aeded72 100644 --- a/shade/_normalize.py +++ b/shade/_normalize.py @@ -662,3 +662,15 @@ class Normalizer(object): for key, val in ret['properties'].items(): ret.setdefault(key, val) return ret + + def _normalize_usage(self, usage): + """ Normalize a usage object """ + + # Discard noise + usage.pop('links', None) + usage.pop('NAME_ATTR', None) + usage.pop('HUMAN_ID', None) + usage.pop('human_id', None) + usage.pop('request_ids', None) + + return munch.Munch(usage) diff --git a/shade/_tasks.py b/shade/_tasks.py index f420c479f..625401938 100644 --- a/shade/_tasks.py +++ b/shade/_tasks.py @@ -891,6 +891,11 @@ class NovaQuotasDelete(task_manager.Task): return client.nova_client.quotas.delete(**self.args) +class NovaUsageGet(task_manager.Task): + def main(self, client): + return client.nova_client.usage.get(**self.args) + + class CinderQuotasSet(task_manager.Task): def main(self, client): return client.cinder_client.quotas.update(**self.args) diff --git a/shade/operatorcloud.py b/shade/operatorcloud.py index 11d1dfb9b..6bca44fbd 100644 --- a/shade/operatorcloud.py +++ b/shade/operatorcloud.py @@ -2022,6 +2022,29 @@ class OperatorCloud(openstackcloud.OpenStackCloud): except nova_exceptions.BadRequest: raise OpenStackCloudException("nova client call failed") + def get_compute_usage(self, name_or_id, start, end): + """ Get usage for a specific project + + :param name_or_id: project name or id + :param start: :class:`datetime.datetime` Start date in UTC + :param end: :class:`datetime.datetime` End date in UTCs + :raises: OpenStackCloudException if it's not a valid project + + :returns: Munch object with the usage + """ + proj = self.get_project(name_or_id) + if not proj: + raise OpenStackCloudException("project does not exist: {}".format( + name=proj.id)) + + with _utils.shade_exceptions( + "Unable to get resources usage for project: {name}".format( + name=proj.id)): + usage = self.manager.submit_task( + _tasks.NovaUsageGet(tenant_id=proj.id, start=start, end=end)) + + return self._normalize_usage(usage) + def set_volume_quotas(self, name_or_id, **kwargs): """ Set a volume quota in a project diff --git a/shade/tests/functional/test_usage.py b/shade/tests/functional/test_usage.py new file mode 100644 index 000000000..e175c1711 --- /dev/null +++ b/shade/tests/functional/test_usage.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +test_usage +---------------------------------- + +Functional tests for `shade` usage method +""" +import datetime + +from shade.tests.functional import base + + +class TestUsage(base.BaseFunctionalTestCase): + + def test_get_usage(self): + '''Test quotas functionality''' + usage = self.operator_cloud.get_compute_usage('demo', + datetime.datetime.now(), + datetime.datetime.now()) + self.assertIsNotNone(usage) + self.assertTrue(hasattr(usage, 'total_hours')) diff --git a/shade/tests/unit/test_usage.py b/shade/tests/unit/test_usage.py new file mode 100644 index 000000000..c0366acb0 --- /dev/null +++ b/shade/tests/unit/test_usage.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import datetime +import mock + +import shade +from shade.tests.unit import base +from shade.tests import fakes + + +class TestUsage(base.TestCase): + + @mock.patch.object(shade.OpenStackCloud, 'nova_client') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_get_usage(self, mock_keystone, mock_nova): + project = fakes.FakeProject('project_a') + start = end = datetime.datetime.now() + mock_keystone.tenants.list.return_value = [project] + self.op_cloud.get_compute_usage(project, start, end) + + mock_nova.usage.get.assert_called_once_with(start=start, end=end, + tenant_id='project_a')