Actually normalize nova usage data

Turns out usage reports are empty when there is no usage - so direct
passthrough is not so much a thing. Fix it.

Change-Id: I6a2f2e737f792ba74a191d688b3380dc333e34fe
This commit is contained in:
Monty Taylor 2017-02-15 13:39:53 -06:00
parent 71322c7bbc
commit 60ce27ea81
No known key found for this signature in database
GPG Key ID: 7BAE94BC7141A594
4 changed files with 132 additions and 12 deletions

View File

@ -228,6 +228,58 @@ Limits and current usage for a project in Nova
total_server_groups_used=int(),
properties=dict())
ComputeUsage
------------
Current usage for a project in Nova
.. code-block:: python
ComputeUsage = dict(
location=Location(),
started_at=str(),
stopped_at=str(),
server_usages=list(),
max_personality=int(),
max_personality_size=int(),
max_server_group_members=int(),
max_server_groups=int(),
max_server_meta=int(),
max_total_cores=int(),
max_total_instances=int(),
max_total_keypairs=int(),
max_total_ram_size=int(),
total_cores_used=int(),
total_hours=int(),
total_instances_used=int(),
total_local_gb_usage=int(),
total_memory_mb_usage=int(),
total_ram_used=int(),
total_server_groups_used=int(),
total_vcpus_usage=int(),
properties=dict())
ServerUsage
-----------
Current usage for a server in Nova
.. code-block:: python
ComputeUsage = dict(
started_at=str(),
ended_at=str(),
flavor=str(),
hours=int(),
instance_id=str(),
local_gb=int(),
memory_mb=int(),
name=str(),
state=str(),
uptime=int(),
vcpus=int(),
properties=dict())
Floating IP
-----------

View File

@ -762,8 +762,10 @@ class Normalizer(object):
ret.setdefault(key, val)
return ret
def _normalize_usage(self, usage):
""" Normalize a usage object """
def _normalize_compute_usage(self, usage):
""" Normalize a compute usage object """
usage = usage.copy()
# Discard noise
usage.pop('links', None)
@ -771,5 +773,66 @@ class Normalizer(object):
usage.pop('HUMAN_ID', None)
usage.pop('human_id', None)
usage.pop('request_ids', None)
project_id = usage.pop('tenant_id', None)
return munch.Munch(usage)
ret = munch.Munch(
location=self._get_current_location(project_id=project_id),
)
for key in (
'max_personality',
'max_personality_size',
'max_server_group_members',
'max_server_groups',
'max_server_meta',
'max_total_cores',
'max_total_instances',
'max_total_keypairs',
'max_total_ram_size',
'total_cores_used',
'total_hours',
'total_instances_used',
'total_local_gb_usage',
'total_memory_mb_usage',
'total_ram_used',
'total_server_groups_used',
'total_vcpus_usage'):
ret[key] = usage.pop(key, 0)
ret['started_at'] = usage.pop('start')
ret['stopped_at'] = usage.pop('stop')
ret['server_usages'] = self._normalize_server_usages(
usage.pop('server_usages', []))
ret['properties'] = usage
return ret
def _normalize_server_usage(self, server_usage):
""" Normalize a server usage object """
server_usage = server_usage.copy()
# TODO(mordred) Right now there is already a location on the usage
# object. Including one here seems verbose.
server_usage.pop('tenant_id')
ret = munch.Munch()
ret['ended_at'] = server_usage.pop('ended_at', None)
ret['started_at'] = server_usage.pop('started_at', None)
for key in (
'flavor',
'instance_id',
'name',
'state'):
ret[key] = server_usage.pop(key, '')
for key in (
'hours',
'local_gb',
'memory_mb',
'uptime',
'vcpus'):
ret[key] = server_usage.pop(key, 0)
ret['properties'] = server_usage
return ret
def _normalize_server_usages(self, server_usages):
ret = []
for server_usage in server_usages:
ret.append(self._normalize_server_usage(server_usage))
return ret

View File

@ -10,6 +10,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
import jsonpatch
from ironicclient import client as ironic_client
@ -2111,12 +2112,12 @@ class OperatorCloud(openstackcloud.OpenStackCloud):
except nova_exceptions.BadRequest:
raise OpenStackCloudException("nova client call failed")
def get_compute_usage(self, name_or_id, start, end):
def get_compute_usage(self, name_or_id, start, end=None):
""" 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
:param end: :class:`datetime.datetime` End date in UTC. Defaults to now
:raises: OpenStackCloudException if it's not a valid project
:returns: Munch object with the usage
@ -2125,6 +2126,8 @@ class OperatorCloud(openstackcloud.OpenStackCloud):
if not proj:
raise OpenStackCloudException("project does not exist: {}".format(
name=proj.id))
if not end:
end = datetime.datetime.now()
with _utils.shade_exceptions(
"Unable to get resources usage for project: {name}".format(
@ -2132,7 +2135,7 @@ class OperatorCloud(openstackcloud.OpenStackCloud):
usage = self.manager.submit_task(
_tasks.NovaUsageGet(tenant_id=proj.id, start=start, end=end))
return self._normalize_usage(usage)
return self._normalize_compute_usage(usage)
def set_volume_quotas(self, name_or_id, **kwargs):
""" Set a volume quota in a project

View File

@ -25,11 +25,13 @@ 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())
def test_get_compute_usage(self):
'''Test usage functionality'''
start = datetime.datetime.now() - datetime.timedelta(seconds=5)
usage = self.operator_cloud.get_compute_usage('demo', start)
self.add_info_on_exception('usage', usage)
self.assertIsNotNone(usage)
self.assertTrue(hasattr(usage, 'total_hours'))
self.assertIn('total_hours', usage)
self.assertIn('started_at', usage)
self.assertEqual(start.isoformat(), usage['started_at'])
self.assertIn('location', usage)