Skip to content

Commit

Permalink
Simple tenant usage pagination
Browse files Browse the repository at this point in the history
Add optional parameters 'limit' and 'marker' to the
os-simple-tenant-usage endpoints for pagaination.

  /os-simple-tenant-usage?limit={limit}&marker={instance_uuid}
  /os-simple-tenant-usage/{tenant}?limit={limit}&marker={instance_uuid}

The aggregate usage totals may no longer reflect all instances for a
tenant, but rather just the instances for a given page. API consumers
will need to stitch the aggregate data back together (add the totals)
if a tenant's instances span several pages.

Implements blueprint paginate-simple-tenant-usage
Change-Id: Ic8e9f869f1b855f968967bedbf77542f287f26c0
  • Loading branch information
dianaclarke committed Dec 14, 2016
1 parent 199d3d8 commit 8340401
Show file tree
Hide file tree
Showing 30 changed files with 608 additions and 61 deletions.
12 changes: 8 additions & 4 deletions api-ref/source/os-simple-tenant-usage.inc
Expand Up @@ -27,6 +27,8 @@ Request
- detailed: detailed_simple_tenant_usage
- end: end_simple_tenant_usage
- start: start_simple_tenant_usage
- limit: usage_limit
- marker: usage_marker

Response
--------
Expand Down Expand Up @@ -60,20 +62,20 @@ Response
If the ``detailed`` query parameter is not specified or
is set to other than 1 (e.g. ``detailed=0``), the response is as follows:

.. literalinclude:: ../../doc/api_samples/os-simple-tenant-usage/simple-tenant-usage-get.json
.. literalinclude:: ../../doc/api_samples/os-simple-tenant-usage/v2.40/simple-tenant-usage-get.json
:language: javascript

If the ``detailed`` query parameter is set to one (``detailed=1``),
the response includes ``server_usages`` information for each tenant.
The response is as follows:

.. literalinclude:: ../../doc/api_samples/os-simple-tenant-usage/simple-tenant-usage-get-detail.json
.. literalinclude:: ../../doc/api_samples/os-simple-tenant-usage/v2.40/simple-tenant-usage-get-detail.json
:language: javascript

Show Usage Statistics For Tenant
================================

.. rest_method:: GET /os-simple-tenant-usage/{tenant_id}
.. rest_method:: GET /os-simple-tenant-usage/v2.40/{tenant_id}

Shows usage statistics for a tenant.

Expand All @@ -89,6 +91,8 @@ Request
- tenant_id: tenant_id
- end: end_simple_tenant_usage
- start: start_simple_tenant_usage
- limit: usage_limit
- marker: usage_marker

Response
--------
Expand Down Expand Up @@ -119,5 +123,5 @@ Response

**Example Show Usage Details For Tenant: JSON response**

.. literalinclude:: ../../doc/api_samples/os-simple-tenant-usage/simple-tenant-usage-get-specific.json
.. literalinclude:: ../../doc/api_samples/os-simple-tenant-usage/v2.40/simple-tenant-usage-get-specific.json
:language: javascript
19 changes: 19 additions & 0 deletions api-ref/source/parameters.yaml
Expand Up @@ -717,6 +717,25 @@ tags_query:
all tags in this list will be returned. Boolean expression in this
case is 't1 AND t2'. Tags in query must be separated by comma.
min_version: 2.26
usage_limit:
description: |
Requests a page size of items. Calculate usage for the limited number of
instances. Use the ``limit`` parameter to make an initial limited request
and use the last-seen instance UUID from the response as the ``marker``
parameter value in a subsequent limited request.
in: query
required: false
type: integer
min_version: 2.40
usage_marker:
description: |
The last-seen item. Use the ``limit`` parameter to make an initial limited
request and use the last-seen instance UUID from the response as the
``marker`` parameter value in a subsequent limited request.
in: query
required: false
type: string
min_version: 2.40
user_id_query_quota:
description: |
ID of user to list the quotas for.
Expand Down
Expand Up @@ -11,7 +11,7 @@
],
"keypairs_links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/keypairs?user_id=user2&limit=1&marker=keypair-5d935425-31d5-48a7-a0f1-e76e9813f2c3",
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/keypairs?limit=1&marker=keypair-5d935425-31d5-48a7-a0f1-e76e9813f2c3&user_id=user2",
"rel": "next"
}
]
Expand Down
@@ -0,0 +1,35 @@
{
"tenant_usages": [
{
"start": "2012-10-08T20:10:44.587336",
"stop": "2012-10-08T21:10:44.587336",
"tenant_id": "6f70656e737461636b20342065766572",
"total_hours": 1.0,
"total_local_gb_usage": 1.0,
"total_memory_mb_usage": 512.0,
"total_vcpus_usage": 1.0,
"server_usages": [
{
"ended_at": null,
"flavor": "m1.tiny",
"hours": 1.0,
"instance_id": "1f1deceb-17b5-4c04-84c7-e0d4499c8fe0",
"local_gb": 1,
"memory_mb": 512,
"name": "instance-2",
"started_at": "2012-10-08T20:10:44.541277",
"state": "active",
"tenant_id": "6f70656e737461636b20342065766572",
"uptime": 3600,
"vcpus": 1
}
]
}
],
"tenant_usages_links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/os-simple-tenant-usage?detailed=1&end=2016-10-12+18%3A22%3A04.868106&limit=1&marker=1f1deceb-17b5-4c04-84c7-e0d4499c8fe0&start=2016-10-12+18%3A22%3A04.868106",
"rel": "next"
}
]
}
@@ -0,0 +1,33 @@
{
"tenant_usage": {
"server_usages": [
{
"ended_at": null,
"flavor": "m1.tiny",
"hours": 1.0,
"instance_id": "1f1deceb-17b5-4c04-84c7-e0d4499c8fe0",
"local_gb": 1,
"memory_mb": 512,
"name": "instance-2",
"started_at": "2012-10-08T20:10:44.541277",
"state": "active",
"tenant_id": "6f70656e737461636b20342065766572",
"uptime": 3600,
"vcpus": 1
}
],
"start": "2012-10-08T20:10:44.587336",
"stop": "2012-10-08T21:10:44.587336",
"tenant_id": "6f70656e737461636b20342065766572",
"total_hours": 1.0,
"total_local_gb_usage": 1.0,
"total_memory_mb_usage": 512.0,
"total_vcpus_usage": 1.0
},
"tenant_usage_links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/os-simple-tenant-usage/6f70656e737461636b20342065766572?end=2016-10-12+18%3A22%3A04.868106&limit=1&marker=1f1deceb-17b5-4c04-84c7-e0d4499c8fe0&start=2016-10-12+18%3A22%3A04.868106",
"rel": "next"
}
]
}
@@ -0,0 +1,19 @@
{
"tenant_usages": [
{
"start": "2012-10-08T21:10:44.587336",
"stop": "2012-10-08T22:10:44.587336",
"tenant_id": "6f70656e737461636b20342065766572",
"total_hours": 1.0,
"total_local_gb_usage": 1.0,
"total_memory_mb_usage": 512.0,
"total_vcpus_usage": 1.0
}
],
"tenant_usages_links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/os-simple-tenant-usage?end=2016-10-12+18%3A22%3A04.868106&limit=1&marker=1f1deceb-17b5-4c04-84c7-e0d4499c8fe0&start=2016-10-12+18%3A22%3A04.868106",
"rel": "next"
}
]
}
2 changes: 1 addition & 1 deletion doc/api_samples/versions/v21-version-get-resp.json
Expand Up @@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.39",
"version": "2.40",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}
Expand Down
2 changes: 1 addition & 1 deletion doc/api_samples/versions/versions-get-resp.json
Expand Up @@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.39",
"version": "2.40",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}
Expand Down
3 changes: 2 additions & 1 deletion nova/api/openstack/api_version_request.py
Expand Up @@ -96,6 +96,7 @@
* 2.38 - Add a condition to return HTTPBadRequest if invalid status is
provided for listing servers.
* 2.39 - Deprecates image-metadata proxy API
* 2.40 - Adds simple tenant usage pagination support.
"""

# The minimum and maximum versions of the API supported
Expand All @@ -104,7 +105,7 @@
# Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.39"
_MAX_API_VERSION = "2.40"
DEFAULT_API_VERSION = _MIN_API_VERSION

# Almost all proxy APIs which related to network, images and baremetal
Expand Down
2 changes: 1 addition & 1 deletion nova/api/openstack/common.py
Expand Up @@ -401,7 +401,7 @@ def _get_links(self, request, identifier, collection_name):

def _get_next_link(self, request, identifier, collection_name):
"""Return href string with proper limit and marker params."""
params = request.params.copy()
params = collections.OrderedDict(sorted(request.params.items()))
params["marker"] = identifier
prefix = self._update_compute_link_prefix(request.application_url)
url = url_join(prefix,
Expand Down
99 changes: 81 additions & 18 deletions nova/api/openstack/compute/simple_tenant_usage.py
Expand Up @@ -21,13 +21,17 @@
import six.moves.urllib.parse as urlparse
from webob import exc

from nova.api.openstack import common
from nova.api.openstack.compute.views import usages as usages_view
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
import nova.conf
from nova import exception
from nova.i18n import _
from nova import objects
from nova.policies import simple_tenant_usage as stu_policies

CONF = nova.conf.CONF
ALIAS = "os-simple-tenant-usage"


Expand All @@ -39,6 +43,9 @@ def parse_strtime(dstr, fmt):


class SimpleTenantUsageController(wsgi.Controller):

_view_builder_class = usages_view.ViewBuilder

def _hours_for(self, instance, period_start, period_stop):
launched_at = instance.launched_at
terminated_at = instance.terminated_at
Expand Down Expand Up @@ -97,14 +104,16 @@ def _get_flavor(self, context, instance, flavors_cache):

return flavor_ref

def _tenant_usages_for_period(self, context, period_start,
period_stop, tenant_id=None, detailed=True):
def _tenant_usages_for_period(self, context, period_start, period_stop,
tenant_id=None, detailed=True, limit=None,
marker=None):

instances = objects.InstanceList.get_active_by_window_joined(
context, period_start, period_stop, tenant_id,
expected_attrs=['flavor'])
expected_attrs=['flavor'], limit=limit, marker=marker)
rval = {}
flavors = {}
all_server_usages = []

for instance in instances:
info = {}
Expand Down Expand Up @@ -170,10 +179,11 @@ def _tenant_usages_for_period(self, context, period_start,
info['hours'])

summary['total_hours'] += info['hours']
all_server_usages.append(info)
if detailed:
summary['server_usages'].append(info)

return rval.values()
return list(rval.values()), all_server_usages

def _parse_datetime(self, dtstr):
if not dtstr:
Expand Down Expand Up @@ -216,9 +226,31 @@ def _get_datetime_range(self, req):
detailed = env.get('detailed', ['0'])[0] == '1'
return (period_start, period_stop, detailed)

@wsgi.Controller.api_version("2.40")
@extensions.expected_errors(400)
def index(self, req):
"""Retrieve tenant_usage for all tenants."""
return self._index(req, links=True)

@wsgi.Controller.api_version("2.1", "2.39") # noqa
@extensions.expected_errors(400)
def index(self, req):
"""Retrieve tenant_usage for all tenants."""
return self._index(req)

@wsgi.Controller.api_version("2.40")
@extensions.expected_errors(400)
def show(self, req, id):
"""Retrieve tenant_usage for a specified tenant."""
return self._show(req, id, links=True)

@wsgi.Controller.api_version("2.1", "2.39") # noqa
@extensions.expected_errors(400)
def show(self, req, id):
"""Retrieve tenant_usage for a specified tenant."""
return self._show(req, id)

def _index(self, req, links=False):
context = req.environ['nova.context']

context.can(stu_policies.POLICY_ROOT % 'list')
Expand All @@ -232,15 +264,29 @@ def index(self, req):
now = timeutils.parse_isotime(timeutils.utcnow().isoformat())
if period_stop > now:
period_stop = now
usages = self._tenant_usages_for_period(context,
period_start,
period_stop,
detailed=detailed)
return {'tenant_usages': usages}

@extensions.expected_errors(400)
def show(self, req, id):
"""Retrieve tenant_usage for a specified tenant."""
marker = None
limit = CONF.api.max_limit
if links:
limit, marker = common.get_limit_and_marker(req)

try:
usages, server_usages = self._tenant_usages_for_period(
context, period_start, period_stop, detailed=detailed,
limit=limit, marker=marker)
except exception.MarkerNotFound as e:
raise exc.HTTPBadRequest(explanation=e.format_message())

tenant_usages = {'tenant_usages': usages}

if links:
usages_links = self._view_builder.get_links(req, server_usages)
if usages_links:
tenant_usages['tenant_usages_links'] = usages_links

return tenant_usages

def _show(self, req, id, links=False):
tenant_id = id
context = req.environ['nova.context']

Expand All @@ -256,16 +302,33 @@ def show(self, req, id):
now = timeutils.parse_isotime(timeutils.utcnow().isoformat())
if period_stop > now:
period_stop = now
usage = self._tenant_usages_for_period(context,
period_start,
period_stop,
tenant_id=tenant_id,
detailed=True)

marker = None
limit = CONF.api.max_limit
if links:
limit, marker = common.get_limit_and_marker(req)

try:
usage, server_usages = self._tenant_usages_for_period(
context, period_start, period_stop, tenant_id=tenant_id,
detailed=True, limit=limit, marker=marker)
except exception.MarkerNotFound as e:
raise exc.HTTPBadRequest(explanation=e.format_message())

if len(usage):
usage = list(usage)[0]
else:
usage = {}
return {'tenant_usage': usage}

tenant_usage = {'tenant_usage': usage}

if links:
usages_links = self._view_builder.get_links(
req, server_usages, tenant_id=tenant_id)
if usages_links:
tenant_usage['tenant_usage_links'] = usages_links

return tenant_usage


class SimpleTenantUsage(extensions.V21APIExtensionBase):
Expand Down

0 comments on commit 8340401

Please sign in to comment.