Skip to content

Commit

Permalink
GET servers API sorting REST API updates
Browse files Browse the repository at this point in the history
Updates the v2 and v3 /servers and /servers/detail APIs to support the
multiple sort keys and sort directions (using the 'sort_key' and
'sort_dir' parameters); these parameters can be specified multiple
times to create a list of sort keys and directions. These parameters
are passed from the API layer to the compute layer, then to the
instance layer (with updated version), then to the database layers,
and then to the common paginate_query function; the paginate_query
function already supports multiple sort keys and directions. The
function signatures in these various layers are updated with new
'sort_keys' and 'sort_dirs' parameters that represent the sort keys
and directions information as lists.

This support is enabled on the v2 API by the existence of a new
'os-server-sort-keys' API extension and is always enabled in the v3
API. The extension API sample issues 2 server creates and then ensures
that both servers are listed (name is unique) in the list reply.

DocImpact: The existing v2 and v3 servers API documentation needs to
reflect these new parameters.

The nova client will also be updated to use these parameters.

Change-Id: I02baf6c3cc7d29abab132ef1726140c57e17d9b6
Partially implements: blueprint nova-pagination
  • Loading branch information
kaufers committed Dec 1, 2014
1 parent 59acb16 commit f268be9
Show file tree
Hide file tree
Showing 32 changed files with 454 additions and 157 deletions.
8 changes: 8 additions & 0 deletions doc/api_samples/all_extensions/extensions-get-resp.json
Expand Up @@ -711,6 +711,14 @@
"name": "Volumes",
"namespace": "http://docs.openstack.org/compute/ext/volumes/api/v1.1",
"updated": "2011-03-25T00:00:00Z"
},
{
"alias": "os-server-sort-keys",
"description": "Add sorting support in get Server v2 API.",
"links": [],
"name": "ServerSortKeys",
"namespace": "http://docs.openstack.org/compute/ext/server_sort_keys/api/v2",
"updated": "2014-05-22T00:00:00Z"
}
]
}
3 changes: 3 additions & 0 deletions doc/api_samples/all_extensions/extensions-get-resp.xml
Expand Up @@ -287,4 +287,7 @@
<extension alias="os-volumes" updated="2011-03-25T00:00:00Z" namespace="http://docs.openstack.org/compute/ext/volumes/api/v1.1" name="Volumes">
<description>Volumes support.</description>
</extension>
<extension alias="os-server-sort-keys" updated="2014-05-22T00:00:00Z" namespace="http://docs.openstack.org/compute/ext/server_sort_keys/api/v2" name="ServerSortKeys">
<description>Add sorting support in get Server v2 API.</description>
</extension>
</extensions>
7 changes: 7 additions & 0 deletions doc/api_samples/os-server-sort-keys/server-post-req.json
@@ -0,0 +1,7 @@
{
"server" : {
"name" : "new-server-test",
"imageRef" : "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
"flavorRef" : "http://openstack.example.com/openstack/flavors/1"
}
}
3 changes: 3 additions & 0 deletions doc/api_samples/os-server-sort-keys/server-post-req.xml
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b" flavorRef="http://openstack.example.com/openstack/flavors/1" name="new-server-test">
</server>
16 changes: 16 additions & 0 deletions doc/api_samples/os-server-sort-keys/server-post-resp.json
@@ -0,0 +1,16 @@
{
"server": {
"adminPass": "jDje6SdBHGfQ",
"id": "e08e6d34-fcc1-480e-b11e-24a675b479f8",
"links": [
{
"href": "http://openstack.example.com/v2/openstack/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8",
"rel": "self"
},
{
"href": "http://openstack.example.com/openstack/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8",
"rel": "bookmark"
}
]
}
}
6 changes: 6 additions & 0 deletions doc/api_samples/os-server-sort-keys/server-post-resp.xml
@@ -0,0 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="e08e6d34-fcc1-480e-b11e-24a675b479f8" adminPass="jDje6SdBHGfQ">
<metadata/>
<atom:link href="http://openstack.example.com/v2/openstack/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8" rel="self"/>
<atom:link href="http://openstack.example.com/openstack/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8" rel="bookmark"/>
</server>
@@ -0,0 +1,18 @@
{
"servers": [
{
"id": "e08e6d34-fcc1-480e-b11e-24a675b479f8",
"links": [
{
"href": "http://openstack.example.com/v2/openstack/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8",
"rel": "self"
},
{
"href": "http://openstack.example.com/openstack/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8",
"rel": "bookmark"
}
],
"name": "new-server-test"
}
]
}
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<servers xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1">
<server name="new-server-test" id="e08e6d34-fcc1-480e-b11e-24a675b479f8">
<atom:link href="http://openstack.example.com/v2/openstack/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8" rel="self"/>
<atom:link href="http://openstack.example.com/openstack/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8" rel="bookmark"/>
</server>
</servers>
7 changes: 7 additions & 0 deletions doc/v3/api_samples/servers-sort/server-post-req.json
@@ -0,0 +1,7 @@
{
"server" : {
"name" : "new-server-test",
"imageRef" : "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
"flavorRef" : "http://openstack.example.com/openstack/flavors/1"
}
}
16 changes: 16 additions & 0 deletions doc/v3/api_samples/servers-sort/server-post-resp.json
@@ -0,0 +1,16 @@
{
"server": {
"adminPass": "jDje6SdBHGfQ",
"id": "e08e6d34-fcc1-480e-b11e-24a675b479f8",
"links": [
{
"href": "http://openstack.example.com/v3/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8",
"rel": "bookmark"
}
]
}
}
18 changes: 18 additions & 0 deletions doc/v3/api_samples/servers-sort/server-sort-keys-list-resp.json
@@ -0,0 +1,18 @@
{
"servers": [
{
"id": "e08e6d34-fcc1-480e-b11e-24a675b479f8",
"links": [
{
"href": "http://openstack.example.com/v3/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/e08e6d34-fcc1-480e-b11e-24a675b479f8",
"rel": "bookmark"
}
],
"name": "new-server-test"
}
]
}
25 changes: 25 additions & 0 deletions nova/api/openstack/compute/contrib/server_sort_keys.py
@@ -0,0 +1,25 @@
# Copyright 2014 IBM Corp.
#
# 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.

from nova.api.openstack import extensions


class Server_sort_keys(extensions.ExtensionDescriptor):
"""Add sort keys and directions to the Server GET v2 API."""

name = "ServerSortKeys"
alias = "os-server-sort-keys"
namespace = ("http://docs.openstack.org/compute/ext/"
"server_sort_keys/api/v2")
updated = "2014-05-22T00:00:00Z"
4 changes: 3 additions & 1 deletion nova/api/openstack/compute/plugins/v3/servers.py
Expand Up @@ -350,10 +350,12 @@ def _get_servers(self, req, is_detail):
search_opts['user_id'] = context.user_id

limit, marker = common.get_limit_and_marker(req)
sort_keys, sort_dirs = common.get_sort_params(req.params)
try:
instance_list = self.compute_api.get_all(context,
search_opts=search_opts, limit=limit, marker=marker,
want_objects=True, expected_attrs=['pci_devices'])
want_objects=True, expected_attrs=['pci_devices'],
sort_keys=sort_keys, sort_dirs=sort_dirs)
except exception.MarkerNotFound:
msg = _('marker [%s] not found') % marker
raise exc.HTTPBadRequest(explanation=msg)
Expand Down
8 changes: 7 additions & 1 deletion nova/api/openstack/compute/servers.py
Expand Up @@ -596,12 +596,18 @@ def _get_servers(self, req, is_detail):
search_opts['user_id'] = context.user_id

limit, marker = common.get_limit_and_marker(req)
# Sorting by multiple keys and directions is conditionally enabled
sort_keys, sort_dirs = None, None
if self.ext_mgr.is_loaded('os-server-sort-keys'):
sort_keys, sort_dirs = common.get_sort_params(req.params)
try:
instance_list = self.compute_api.get_all(context,
search_opts=search_opts,
limit=limit,
marker=marker,
want_objects=True)
want_objects=True,
sort_keys=sort_keys,
sort_dirs=sort_dirs)
except exception.MarkerNotFound:
msg = _('marker [%s] not found') % marker
raise exc.HTTPBadRequest(explanation=msg)
Expand Down
Expand Up @@ -711,6 +711,14 @@
"name": "ServerGroupQuotas",
"namespace": "http://docs.openstack.org/compute/ext/server-group-quotas/api/v2",
"updated": "%(isotime)s"
},
{
"alias": "os-server-sort-keys",
"description": "%(text)s",
"links": [],
"name": "ServerSortKeys",
"namespace": "http://docs.openstack.org/compute/ext/server_sort_keys/api/v2",
"updated": "%(isotime)s"
}
]
}
Expand Up @@ -266,4 +266,7 @@
<extension alias="os-server-group-quotas" updated="%(isotime)s" namespace="http://docs.openstack.org/compute/ext/server-group-quotas/api/v2" name="ServerGroupQuotas">
<description>%(text)s</description>
</extension>
<extension alias="os-server-sort-keys" updated="%(isotime)s" namespace="http://docs.openstack.org/compute/ext/server_sort_keys/api/v2" name="ServerSortKeys">
<description>%(text)s</description>
</extension>
</extensions>
@@ -0,0 +1,7 @@
{
"server" : {
"name" : "new-server-test",
"imageRef" : "%(host)s/openstack/images/%(image_id)s",
"flavorRef" : "%(host)s/openstack/flavors/1"
}
}
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="%(host)s/openstack/images/%(image_id)s" flavorRef="%(host)s/openstack/flavors/1" name="new-server-test">
</server>
@@ -0,0 +1,16 @@
{
"server": {
"adminPass": "%(password)s",
"id": "%(id)s",
"links": [
{
"href": "%(host)s/v2/openstack/servers/%(uuid)s",
"rel": "self"
},
{
"href": "%(host)s/openstack/servers/%(uuid)s",
"rel": "bookmark"
}
]
}
}
@@ -0,0 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="%(id)s" adminPass="%(password)s">
<metadata/>
<atom:link href="%(host)s/v2/openstack/servers/%(uuid)s" rel="self"/>
<atom:link href="%(host)s/openstack/servers/%(uuid)s" rel="bookmark"/>
</server>
@@ -0,0 +1,18 @@
{
"servers": [
{
"id": "%(id)s",
"links": [
{
"href": "%(host)s/v2/openstack/servers/%(id)s",
"rel": "self"
},
{
"href": "%(host)s/openstack/servers/%(id)s",
"rel": "bookmark"
}
],
"name": "new-server-test"
}
]
}
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<servers xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1">
<server name="new-server-test" id="%(id)s">
<atom:link href="%(host)s/v2/openstack/servers/%(id)s" rel="self"/>
<atom:link href="%(host)s/openstack/servers/%(id)s" rel="bookmark"/>
</server>
</servers>
16 changes: 16 additions & 0 deletions nova/tests/functional/test_api_samples.py
Expand Up @@ -4433,3 +4433,19 @@ class ServerGroupQuotas_QuotaClassesSampleXmlTests(
"server_group_quotas.Server_group_quotas")
extends_name = ("nova.api.openstack.compute.contrib.quota_classes."
"Quota_classes")


class ServerSortKeysJsonTests(ServersSampleBase):
extension_name = ("nova.api.openstack.compute.contrib.server_sort_keys"
".Server_sort_keys")

def test_servers_list(self):
self._post_server()
response = self._do_get('servers?sort_key=display_name&sort_dir=asc')
subs = self._get_regexes()
self._verify_response('server-sort-keys-list-resp', subs, response,
200)


class ServerSortKeysXmlTests(ServerSortKeysJsonTests):
ctype = 'xml'
@@ -0,0 +1,7 @@
{
"server" : {
"name" : "new-server-test",
"imageRef" : "%(host)s/openstack/images/%(image_id)s",
"flavorRef" : "%(host)s/openstack/flavors/1"
}
}
@@ -0,0 +1,16 @@
{
"server": {
"adminPass": "%(password)s",
"id": "%(id)s",
"links": [
{
"href": "%(host)s/v3/servers/%(uuid)s",
"rel": "self"
},
{
"href": "%(host)s/servers/%(uuid)s",
"rel": "bookmark"
}
]
}
}
@@ -0,0 +1,18 @@
{
"servers": [
{
"id": "%(id)s",
"links": [
{
"href": "%(host)s/v3/servers/%(id)s",
"rel": "self"
},
{
"href": "%(host)s/servers/%(id)s",
"rel": "bookmark"
}
],
"name": "new-server-test"
}
]
}
11 changes: 11 additions & 0 deletions nova/tests/functional/v3/test_servers.py
Expand Up @@ -64,6 +64,17 @@ def test_servers_details(self):
self._verify_response('servers-details-resp', subs, response, 200)


class ServerSortKeysJsonTests(ServersSampleBase):
sample_dir = 'servers-sort'

def test_servers_list(self):
self._post_server()
response = self._do_get('servers?sort_key=display_name&sort_dir=asc')
subs = self._get_regexes()
self._verify_response('server-sort-keys-list-resp', subs, response,
200)


class ServersSampleAllExtensionJsonTest(ServersSampleJsonTest):
all_extensions = True

Expand Down
Expand Up @@ -69,8 +69,13 @@ def test_show(self):
self.assertIn('config_drive', res_dict['server'])

def test_detail_servers(self):
# Sort is disabled in v2 without an extension so stub out
# the non-sorted DB get
self.stubs.Set(db, 'instance_get_all_by_filters',
fakes.fake_instance_get_all_by_filters())
# But it is enabled in v3 so stub out the sorted function
self.stubs.Set(db, 'instance_get_all_by_filters_sort',
fakes.fake_instance_get_all_by_filters())
req = fakes.HTTPRequest.blank(self.base_url + 'detail')
res = req.get_response(self.app)
server_dicts = jsonutils.loads(res.body)['servers']
Expand Down
Expand Up @@ -312,8 +312,13 @@ def test_show_server(self):
self.assertEqual(res_dict['server']['key_name'], '')

def test_detail_servers(self):
# Sort is disabled in v2 without an extension so stub out
# the non-sorted DB get
self.stubs.Set(db, 'instance_get_all_by_filters',
fakes.fake_instance_get_all_by_filters())
fakes.fake_instance_get_all_by_filters())
# But it is enabled in v3 so stub out the sorted function
self.stubs.Set(db, 'instance_get_all_by_filters_sort',
fakes.fake_instance_get_all_by_filters())
req = fakes.HTTPRequest.blank(self.base_url + '/servers/detail')
res = req.get_response(self.app_server)
server_dicts = jsonutils.loads(res.body)['servers']
Expand Down

0 comments on commit f268be9

Please sign in to comment.