diff --git a/api-ref/source/os-keypairs.inc b/api-ref/source/os-keypairs.inc index 6880054c2d77..033df258715f 100644 --- a/api-ref/source/os-keypairs.inc +++ b/api-ref/source/os-keypairs.inc @@ -23,6 +23,8 @@ Request .. rest_parameters:: parameters.yaml - user_id: keypair_user + - limit: keypair_limit + - marker: keypair_marker Response -------- diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index c49bd94ed84e..e49113a6c480 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -477,6 +477,25 @@ ip_query: in: query required: false type: string +keypair_limit: + description: | + Requests a page size of items. Returns a number of items up to a limit value. + Use the ``limit`` parameter to make an initial limited request and use the + last-seen item from the response as the ``marker`` parameter value in a + subsequent limited request. + in: query + required: false + type: integer + min_version: 2.35 +keypair_marker: + description: | + The last-seen item. Use the ``limit`` parameter to make an initial limited + request and use the last-seen item from the response as the ``marker`` + parameter value in a subsequent limited request. + in: query + required: false + type: string + min_version: 2.35 keypair_type_in: in: query required: false diff --git a/doc/api_samples/keypairs/v2.35/keypairs-list-resp.json b/doc/api_samples/keypairs/v2.35/keypairs-list-resp.json new file mode 100644 index 000000000000..69c8ec4f143c --- /dev/null +++ b/doc/api_samples/keypairs/v2.35/keypairs-list-resp.json @@ -0,0 +1,18 @@ +{ + "keypairs": [ + { + "keypair": { + "fingerprint": "7e:eb:ab:24:ba:d1:e1:88:ae:9a:fb:66:53:df:d3:bd", + "name": "keypair-5d935425-31d5-48a7-a0f1-e76e9813f2c3", + "type": "ssh", + "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkF3MX59OrlBs3dH5CU7lNmvpbrgZxSpyGjlnE8Flkirnc/Up22lpjznoxqeoTAwTW034k7Dz6aYIrZGmQwe2TkE084yqvlj45Dkyoj95fW/sZacm0cZNuL69EObEGHdprfGJQajrpz22NQoCD8TFB8Wv+8om9NH9Le6s+WPe98WC77KLw8qgfQsbIey+JawPWl4O67ZdL5xrypuRjfIPWjgy/VH85IXg/Z/GONZ2nxHgSShMkwqSFECAC5L3PHB+0+/12M/iikdatFSVGjpuHvkLOs3oe7m6HlOfluSJ85BzLWBbvva93qkGmLg4ZAc8rPh2O+YIsBUHNLLMM/oQp Generated-by-Nova\n" + } + } + ], + "keypairs_links": [ + { + "href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/keypairs?limit=1&marker=keypair-5d935425-31d5-48a7-a0f1-e76e9813f2c3", + "rel": "next" + } + ] +} \ No newline at end of file diff --git a/doc/api_samples/keypairs/v2.35/keypairs-list-user1-resp.json b/doc/api_samples/keypairs/v2.35/keypairs-list-user1-resp.json new file mode 100644 index 000000000000..547cb000c950 --- /dev/null +++ b/doc/api_samples/keypairs/v2.35/keypairs-list-user1-resp.json @@ -0,0 +1,12 @@ +{ + "keypairs": [ + { + "keypair": { + "fingerprint": "7e:eb:ab:24:ba:d1:e1:88:ae:9a:fb:66:53:df:d3:bd", + "name": "keypair-5d935425-31d5-48a7-a0f1-e76e9813f2c3", + "type": "ssh", + "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkF3MX59OrlBs3dH5CU7lNmvpbrgZxSpyGjlnE8Flkirnc/Up22lpjznoxqeoTAwTW034k7Dz6aYIrZGmQwe2TkE084yqvlj45Dkyoj95fW/sZacm0cZNuL69EObEGHdprfGJQajrpz22NQoCD8TFB8Wv+8om9NH9Le6s+WPe98WC77KLw8qgfQsbIey+JawPWl4O67ZdL5xrypuRjfIPWjgy/VH85IXg/Z/GONZ2nxHgSShMkwqSFECAC5L3PHB+0+/12M/iikdatFSVGjpuHvkLOs3oe7m6HlOfluSJ85BzLWBbvva93qkGmLg4ZAc8rPh2O+YIsBUHNLLMM/oQp Generated-by-Nova\n" + } + } + ] +} \ No newline at end of file diff --git a/doc/api_samples/keypairs/v2.35/keypairs-list-user2-resp.json b/doc/api_samples/keypairs/v2.35/keypairs-list-user2-resp.json new file mode 100644 index 000000000000..939a1c2c3d5c --- /dev/null +++ b/doc/api_samples/keypairs/v2.35/keypairs-list-user2-resp.json @@ -0,0 +1,18 @@ +{ + "keypairs": [ + { + "keypair": { + "fingerprint": "7e:eb:ab:24:ba:d1:e1:88:ae:9a:fb:66:53:df:d3:bd", + "name": "keypair-5d935425-31d5-48a7-a0f1-e76e9813f2c3", + "type": "ssh", + "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkF3MX59OrlBs3dH5CU7lNmvpbrgZxSpyGjlnE8Flkirnc/Up22lpjznoxqeoTAwTW034k7Dz6aYIrZGmQwe2TkE084yqvlj45Dkyoj95fW/sZacm0cZNuL69EObEGHdprfGJQajrpz22NQoCD8TFB8Wv+8om9NH9Le6s+WPe98WC77KLw8qgfQsbIey+JawPWl4O67ZdL5xrypuRjfIPWjgy/VH85IXg/Z/GONZ2nxHgSShMkwqSFECAC5L3PHB+0+/12M/iikdatFSVGjpuHvkLOs3oe7m6HlOfluSJ85BzLWBbvva93qkGmLg4ZAc8rPh2O+YIsBUHNLLMM/oQp Generated-by-Nova\n" + } + } + ], + "keypairs_links": [ + { + "href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/keypairs?user_id=user2&limit=1&marker=keypair-5d935425-31d5-48a7-a0f1-e76e9813f2c3", + "rel": "next" + } + ] +} \ No newline at end of file diff --git a/doc/api_samples/keypairs/v2.35/keypairs-post-req.json b/doc/api_samples/keypairs/v2.35/keypairs-post-req.json new file mode 100644 index 000000000000..005a3f504530 --- /dev/null +++ b/doc/api_samples/keypairs/v2.35/keypairs-post-req.json @@ -0,0 +1,7 @@ +{ + "keypair": { + "name": "keypair-ab9ff2e6-a6d7-4915-a241-044c369c07f9", + "type": "ssh", + "user_id": "fake" + } +} diff --git a/doc/api_samples/keypairs/v2.35/keypairs-post-resp.json b/doc/api_samples/keypairs/v2.35/keypairs-post-resp.json new file mode 100644 index 000000000000..394960868bf1 --- /dev/null +++ b/doc/api_samples/keypairs/v2.35/keypairs-post-resp.json @@ -0,0 +1,10 @@ +{ + "keypair": { + "fingerprint": "7e:eb:ab:24:ba:d1:e1:88:ae:9a:fb:66:53:df:d3:bd", + "name": "keypair-ab9ff2e6-a6d7-4915-a241-044c369c07f9", + "type": "ssh", + "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEApBdzF+fTq5QbN3R+QlO5TZr6W64GcUqcho5ZxPBZZIq53P1K\ndtpaY856ManqEwME1tN+JOw8+mmCK2RpkMHtk5BNPOMqr5Y+OQ5MqI/eX1v7GWnJ\ntHGTbi+vRDmxBh3aa3xiUGo66c9tjUKAg/ExQfFr/vKJvTR/S3urPlj3vfFgu+yi\n8PKoH0LGyHsviWsD1peDuu2XS+ca8qbkY3yD1o4Mv1R/OSF4P2fxjjWdp8R4EkoT\nJMKkhRAgAuS9zxwftPv9djP4opHWrRUlRo6bh75CzrN6Hu5uh5Tn5bkifOQcy1gW\n772vd6pBpi4OGQHPKz4djvmCLAVBzSyzDP6EKQIDAQABAoIBAQCB+tU/ZXKlIe+h\nMNTmoz1QfOe+AY625Rwx9cakGqMk4kKyC62VkgcxshfXCToSjzyhEuyEQOFYloT2\n7FY2xXb0gcS861Efv0pQlcQhbbz/GnQ/wC13ktPu3zTdPTm9l54xsFiMTGmYVaf4\n0mnMmhyjmKIsVGDJEDGZUD/oZj7wJGOFha5M4FZrZlJIrEZC0rGGlcC0kGF2no6B\nj1Mu7HjyK3pTKf4dlp+jeRikUF5Pct+qT+rcv2rZ3fl3inxtlLEwZeFPbp/njf/U\nIGxFzZsuLmiFlsJar6M5nEckTB3p25maWWaR8/0jvJRgsPnuoUrUoGDq87DMKCdk\nlw6by9fRAoGBANhnS9ko7Of+ntqIFR7xOG9p/oPATztgHkFxe4GbQ0leaDRTx3vE\ndQmUCnn24xtyVECaI9a4IV+LP1npw8niWUJ4pjgdAlkF4cCTu9sN+cBO15SfdACI\nzD1DaaHmpFCAWlpTo68VWlvWll6i2ncCkRJR1+q/C/yQz7asvl4AakElAoGBAMId\nxqMT2Sy9xLuHsrAoMUvBOkwaMYZH+IAb4DvUDjVIiKWjmonrmopS5Lpb+ALBKqZe\neVfD6HwWQqGwCFItToaEkZvrNfTapoNCHWWg001D49765UV5lMrArDbM1vXtFfM4\nDRYM6+Y6o/6QH8EBgXtyBxcYthIDBM3wBJa67xG1AoGAKTm8fFlMkIG0N4N3Kpbf\nnnH915GaRoBwIx2AXtd6QQ7oIRfYx95MQY/fUw7SgxcLr+btbulTCkWXwwRClUI2\nqPAdElGMcfMp56r9PaTy8EzUyu55heSJrB4ckIhEw0VAcTa/1wnlVduSd+LkZYmq\no2fOD11n5iycNXvBJF1F4LUCgYAMaRbwCi7SW3eefbiA5rDwJPRzNSGBckyC9EVL\nzezynyaNYH5a3wNMYKxa9dJPasYtSND9OXs9o7ay26xMhLUGiKc+jrUuaGRI9Asp\nGjUoNXT2JphN7s4CgHsCLep4YqYKnMTJah4S5CDj/5boIg6DM/EcGupZEHRYLkY8\n1MrAGQKBgQCi9yeC39ctLUNn+Ix604gttWWChdt3ozufTZ7HybJOSRA9Gh3iD5gm\nzlz0xqpGShKpOY2k+ftvja0poMdGeJLt84P3r2q01IgI7w0LmOj5m0W10dHysH27\nBWpCnHdBJMxnBsMRPoM4MKkmKWD9l5PSTCTWtkIpsyuDCko6D9UwZA==\n-----END RSA PRIVATE KEY-----\n", + "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkF3MX59OrlBs3dH5CU7lNmvpbrgZxSpyGjlnE8Flkirnc/Up22lpjznoxqeoTAwTW034k7Dz6aYIrZGmQwe2TkE084yqvlj45Dkyoj95fW/sZacm0cZNuL69EObEGHdprfGJQajrpz22NQoCD8TFB8Wv+8om9NH9Le6s+WPe98WC77KLw8qgfQsbIey+JawPWl4O67ZdL5xrypuRjfIPWjgy/VH85IXg/Z/GONZ2nxHgSShMkwqSFECAC5L3PHB+0+/12M/iikdatFSVGjpuHvkLOs3oe7m6HlOfluSJ85BzLWBbvva93qkGmLg4ZAc8rPh2O+YIsBUHNLLMM/oQp Generated-by-Nova\n", + "user_id": "fake" + } +} diff --git a/doc/api_samples/versions/v21-version-get-resp.json b/doc/api_samples/versions/v21-version-get-resp.json index 84be6f62b027..15d4d28d2f90 100644 --- a/doc/api_samples/versions/v21-version-get-resp.json +++ b/doc/api_samples/versions/v21-version-get-resp.json @@ -19,7 +19,7 @@ } ], "status": "CURRENT", - "version": "2.34", + "version": "2.35", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/doc/api_samples/versions/versions-get-resp.json b/doc/api_samples/versions/versions-get-resp.json index 7bef94b2d4d5..83eedf97d4b9 100644 --- a/doc/api_samples/versions/versions-get-resp.json +++ b/doc/api_samples/versions/versions-get-resp.json @@ -22,7 +22,7 @@ } ], "status": "CURRENT", - "version": "2.34", + "version": "2.35", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/nova/api/openstack/api_version_request.py b/nova/api/openstack/api_version_request.py index f394c38b3394..702aeb74433e 100644 --- a/nova/api/openstack/api_version_request.py +++ b/nova/api/openstack/api_version_request.py @@ -87,6 +87,7 @@ REST_API_VERSION_HISTORY = """REST API Version History: os-Migratelive Action does not throw badRequest in case of pre-checks failure. Verification result is available over instance-actions. + * 2.35 - Adds keypairs pagination support. """ # The minimum and maximum versions of the API supported @@ -95,7 +96,7 @@ REST_API_VERSION_HISTORY = """REST API Version History: # 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.34" +_MAX_API_VERSION = "2.35" DEFAULT_API_VERSION = _MIN_API_VERSION diff --git a/nova/api/openstack/compute/keypairs.py b/nova/api/openstack/compute/keypairs.py index 935ae16144bf..fbfd23cb99f2 100644 --- a/nova/api/openstack/compute/keypairs.py +++ b/nova/api/openstack/compute/keypairs.py @@ -18,8 +18,10 @@ import webob import webob.exc +from nova.api.openstack import api_version_request from nova.api.openstack import common from nova.api.openstack.compute.schemas import keypairs +from nova.api.openstack.compute.views import keypairs as keypairs_view from nova.api.openstack import extensions from nova.api.openstack import wsgi from nova.api import validation @@ -36,8 +38,12 @@ ALIAS = 'os-keypairs' class KeypairController(wsgi.Controller): """Keypair API controller for the OpenStack API.""" + + _view_builder_class = keypairs_view.ViewBuilder + def __init__(self): self.api = compute_api.KeypairAPI() + super(KeypairController, self).__init__() def _filter_keypair(self, keypair, **attrs): # TODO(claudiub): After v2 and v2.1 is no longer supported, @@ -221,7 +227,13 @@ class KeypairController(wsgi.Controller): # behaviors in this keypair resource. return {'keypair': keypair} - @wsgi.Controller.api_version("2.10") + @wsgi.Controller.api_version("2.35") + @extensions.expected_errors(400) + def index(self, req): + user_id = self._get_user_id(req) + return self._index(req, links=True, type=True, user_id=user_id) + + @wsgi.Controller.api_version("2.10", "2.34") # noqa @extensions.expected_errors(()) def index(self, req): # handle optional user-id for admin only @@ -238,20 +250,38 @@ class KeypairController(wsgi.Controller): def index(self, req): return self._index(req) - def _index(self, req, user_id=None, **keypair_filters): + def _index(self, req, user_id=None, links=False, **keypair_filters): """List of keypairs for a user.""" context = req.environ['nova.context'] user_id = user_id or context.user_id context.can(kp_policies.POLICY_ROOT % 'index', target={'user_id': user_id, 'project_id': context.project_id}) - key_pairs = self.api.get_key_pairs(context, user_id) - rval = [] - for key_pair in key_pairs: - rval.append({'keypair': self._filter_keypair(key_pair, - **keypair_filters)}) - return {'keypairs': rval} + if api_version_request.is_supported(req, min_version='2.35'): + limit, marker = common.get_limit_and_marker(req) + else: + limit = marker = None + + try: + key_pairs = self.api.get_key_pairs( + context, user_id, limit=limit, marker=marker) + except exception.MarkerNotFound as e: + raise webob.exc.HTTPBadRequest(explanation=e.format_message()) + + key_pairs = [self._filter_keypair(key_pair, **keypair_filters) + for key_pair in key_pairs] + + keypairs_list = [{'keypair': key_pair} for key_pair in key_pairs] + keypairs_dict = {'keypairs': keypairs_list} + + if links: + keypairs_links = self._view_builder.get_links(req, key_pairs) + + if keypairs_links: + keypairs_dict['keypairs_links'] = keypairs_links + + return keypairs_dict class Controller(wsgi.Controller): diff --git a/nova/api/openstack/compute/views/keypairs.py b/nova/api/openstack/compute/views/keypairs.py new file mode 100644 index 000000000000..020c7a0ac865 --- /dev/null +++ b/nova/api/openstack/compute/views/keypairs.py @@ -0,0 +1,25 @@ +# Copyright 2016 Mirantis Inc +# All Rights Reserved. +# +# 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 common + + +class ViewBuilder(common.ViewBuilder): + + _collection_name = "keypairs" + + def get_links(self, request, keypairs): + return self._get_collection_links(request, keypairs, + self._collection_name, 'name') diff --git a/nova/api/openstack/rest_api_version_history.rst b/nova/api/openstack/rest_api_version_history.rst index 4194ca929435..fed6afa5b435 100644 --- a/nova/api/openstack/rest_api_version_history.rst +++ b/nova/api/openstack/rest_api_version_history.rst @@ -350,3 +350,14 @@ user documentation. Checks in ``os-migrateLive`` before live-migration actually starts are now made in background. ``os-migrateLive`` is not throwing `400 Bad Request` if pre-live-migration checks fail. + +2.35 +---- + + Added pagination support for keypairs. + + Optional parameters 'limit' and 'marker' were added to GET /os-keypairs + request, the default sort_key was changed to 'name' field as ASC order, + the generic request format is:: + + GET /os-keypairs?limit={limit}&marker={kp_name} diff --git a/nova/compute/api.py b/nova/compute/api.py index f898acda802c..2b44137f5f8d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -3981,9 +3981,10 @@ class KeypairAPI(base.Base): objects.KeyPair.destroy_by_name(context, user_id, key_name) self._notify(context, 'delete.end', key_name) - def get_key_pairs(self, context, user_id): + def get_key_pairs(self, context, user_id, limit=None, marker=None): """List key pairs.""" - return objects.KeyPairList.get_by_user(context, user_id) + return objects.KeyPairList.get_by_user( + context, user_id, limit=limit, marker=marker) def get_key_pair(self, context, user_id, key_name): """Get a keypair by name.""" diff --git a/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-resp.json.tpl new file mode 100644 index 000000000000..0b2bf63e7f5b --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-resp.json.tpl @@ -0,0 +1,18 @@ +{ + "keypairs": [ + { + "keypair": { + "fingerprint": "%(fingerprint)s", + "name": "%(keypair_name)s", + "type": "%(keypair_type)s", + "public_key": "%(public_key)s" + } + } + ], + "keypairs_links": [ + { + "href": "%(versioned_compute_endpoint)s/keypairs?limit=1&marker=%(keypair_name)s", + "rel": "next" + } + ] +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-user1-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-user1-resp.json.tpl new file mode 100644 index 000000000000..8e0963bc7a39 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-user1-resp.json.tpl @@ -0,0 +1,12 @@ +{ + "keypairs": [ + { + "keypair": { + "fingerprint": "%(fingerprint)s", + "name": "%(keypair_name)s", + "type": "%(keypair_type)s", + "public_key": "%(public_key)s" + } + } + ] +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-user2-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-user2-resp.json.tpl new file mode 100644 index 000000000000..6c3402b24ce8 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-user2-resp.json.tpl @@ -0,0 +1,18 @@ +{ + "keypairs": [ + { + "keypair": { + "fingerprint": "%(fingerprint)s", + "name": "%(keypair_name)s", + "type": "%(keypair_type)s", + "public_key": "%(public_key)s" + } + } + ], + "keypairs_links": [ + { + "href": "%(versioned_compute_endpoint)s/keypairs?user_id=user2&limit=1&marker=%(keypair_name)s", + "rel": "next" + } + ] +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-post-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-post-req.json.tpl new file mode 100644 index 000000000000..f6a6d47b56ec --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-post-req.json.tpl @@ -0,0 +1,7 @@ +{ + "keypair": { + "name": "%(keypair_name)s", + "type": "%(keypair_type)s", + "user_id": "%(user_id)s" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-post-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-post-resp.json.tpl new file mode 100644 index 000000000000..ee5eb23f77dd --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-post-resp.json.tpl @@ -0,0 +1,10 @@ +{ + "keypair": { + "fingerprint": "%(fingerprint)s", + "name": "%(keypair_name)s", + "type": "%(keypair_type)s", + "private_key": "%(private_key)s", + "public_key": "%(public_key)s", + "user_id": "%(user_id)s" + } +} diff --git a/nova/tests/functional/api_sample_tests/test_keypairs.py b/nova/tests/functional/api_sample_tests/test_keypairs.py index 50bc9931abf7..65f97ffe124e 100644 --- a/nova/tests/functional/api_sample_tests/test_keypairs.py +++ b/nova/tests/functional/api_sample_tests/test_keypairs.py @@ -210,3 +210,76 @@ class KeyPairsV210SampleJsonTestNotAdmin(KeyPairsV210SampleJsonTest): response = self._do_post('os-keypairs', 'keypairs-post-req', subs) self.assertEqual(403, response.status_code) + + +class KeyPairsV235SampleJsonTest(api_sample_base.ApiSampleTestBaseV21): + ADMIN_API = True + sample_dir = "keypairs" + microversion = '2.35' + expected_post_status_code = 201 + scenarios = [('v2_35', {'api_major_version': 'v2.1'})] + + def setUp(self): + super(KeyPairsV235SampleJsonTest, self).setUp() + self.api.microversion = self.microversion + + # TODO(pkholkin): this is only needed because we randomly choose the + # uuid each time. + def generalize_subs(self, subs, vanilla_regexes): + subs['keypair_name'] = 'keypair-[0-9a-f-]+' + return subs + + def test_keypairs_post(self, user="admin", kp_name=None): + return self._check_keypairs_post( + keypair_type=keypair_obj.KEYPAIR_TYPE_SSH, + user_id=user, kp_name=kp_name) + + def _check_keypairs_post(self, **kwargs): + """Get api sample of key pairs post request.""" + key_name = kwargs.pop('kp_name') + if not key_name: + key_name = 'keypair-' + str(uuid.uuid4()) + + subs = dict(keypair_name=key_name, **kwargs) + response = self._do_post('os-keypairs', 'keypairs-post-req', subs) + subs = {'keypair_name': key_name} + + self._verify_response('keypairs-post-resp', subs, response, + self.expected_post_status_code) + return key_name + + def test_keypairs_list(self): + # Get api sample of key pairs list request. + + # sort key_pairs by name before paging + keypairs = sorted([self.test_keypairs_post() for i in range(3)]) + + response = self._do_get('os-keypairs?marker=%s&limit=1' % keypairs[1]) + subs = {'keypair_name': keypairs[2]} + self._verify_response('keypairs-list-resp', subs, response, 200) + + def test_keypairs_list_for_different_users(self): + # Get api sample of key pairs list request. + + # create common kp_names for two users + kp_names = ['keypair-' + str(uuid.uuid4()) for i in range(3)] + + # sort key_pairs by name before paging + keypairs_user1 = sorted([self.test_keypairs_post( + user="user1", kp_name=kp_name) for kp_name in kp_names]) + keypairs_user2 = sorted([self.test_keypairs_post( + user="user2", kp_name=kp_name) for kp_name in kp_names]) + + # get all keypairs after the second for user1 + response = self._do_get('os-keypairs?user_id=user1&marker=%s' + % keypairs_user1[1]) + subs = {'keypair_name': keypairs_user1[2]} + self._verify_response( + 'keypairs-list-user1-resp', subs, response, 200) + + # get only one keypair after the second for user2 + response = self._do_get('os-keypairs?user_id=user2&marker=%s&limit=1' + % keypairs_user2[1]) + subs = {'keypair_name': keypairs_user2[2]} + self._verify_response( + 'keypairs-list-user2-resp', subs, response, 200) diff --git a/nova/tests/unit/api/openstack/compute/test_keypairs.py b/nova/tests/unit/api/openstack/compute/test_keypairs.py index 2f8aeb515ffe..3c971107fa05 100644 --- a/nova/tests/unit/api/openstack/compute/test_keypairs.py +++ b/nova/tests/unit/api/openstack/compute/test_keypairs.py @@ -562,3 +562,43 @@ class KeypairsTestV210(KeypairsTestV22): self.assertRaises(exception.PolicyNotAuthorized, self.controller.create, req, body=body) + + +class KeypairsTestV235(test.TestCase): + base_url = '/v2/fake' + wsgi_api_version = '2.35' + + def _setup_app_and_controller(self): + self.app_server = fakes.wsgi_app_v21(init_only=('os-keypairs')) + self.controller = keypairs_v21.KeypairController() + + def setUp(self): + super(KeypairsTestV235, self).setUp() + self._setup_app_and_controller() + + @mock.patch("nova.db.key_pair_get_all_by_user") + def test_keypair_list_limit_and_marker(self, mock_kp_get): + mock_kp_get.side_effect = db_key_pair_get_all_by_user + + req = fakes.HTTPRequest.blank( + self.base_url + '/os-keypairs?limit=3&marker=fake_marker', + version=self.wsgi_api_version, use_admin_context=True) + + res_dict = self.controller.index(req) + + mock_kp_get.assert_called_once_with( + req.environ['nova.context'], 'fake_user', + limit=3, marker='fake_marker') + response = {'keypairs': [{'keypair': dict(keypair_data, name='FAKE', + type='ssh')}]} + self.assertEqual(res_dict, response) + + @mock.patch('nova.compute.api.KeypairAPI.get_key_pairs') + def test_keypair_list_limit_and_marker_invalid_marker(self, mock_kp_get): + mock_kp_get.side_effect = exception.MarkerNotFound(marker='unknown_kp') + + req = fakes.HTTPRequest.blank( + self.base_url + '/os-keypairs?limit=3&marker=unknown_kp', + version=self.wsgi_api_version, use_admin_context=True) + + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.index, req) diff --git a/releasenotes/notes/bp-keypairs-pagination-634c46aaa1058161.yaml b/releasenotes/notes/bp-keypairs-pagination-634c46aaa1058161.yaml new file mode 100644 index 000000000000..c83c62089e1a --- /dev/null +++ b/releasenotes/notes/bp-keypairs-pagination-634c46aaa1058161.yaml @@ -0,0 +1,5 @@ +--- +features: + - Added microversion v2.35 that adds pagination support for keypairs with + the help of new optional parameters 'limit' and 'marker' which were added + to GET /os-keypairs request.