From 47358449d359a287d21426b4e1f18479a4d1fd36 Mon Sep 17 00:00:00 2001 From: Pavel Kholkin Date: Wed, 6 Jul 2016 15:02:11 +0300 Subject: [PATCH] Microversion 2.35 adds keypairs pagination support After this microversion Nova API allows to get several keypairs with the help of new optional parameters 'limit' and 'marker' which were added to GET /os-keypairs request. Partial-Bug: #1599904 Implements blueprint: keypairs-pagination Change-Id: Idd3757f5be90ec4af1bd1a7ca3f9c20319dbfd33 --- api-ref/source/os-keypairs.inc | 2 + api-ref/source/parameters.yaml | 19 +++++ .../keypairs/v2.35/keypairs-list-resp.json | 18 +++++ .../v2.35/keypairs-list-user1-resp.json | 12 +++ .../v2.35/keypairs-list-user2-resp.json | 18 +++++ .../keypairs/v2.35/keypairs-post-req.json | 7 ++ .../keypairs/v2.35/keypairs-post-resp.json | 10 +++ .../versions/v21-version-get-resp.json | 2 +- .../versions/versions-get-resp.json | 2 +- nova/api/openstack/api_version_request.py | 3 +- nova/api/openstack/compute/keypairs.py | 46 ++++++++++-- nova/api/openstack/compute/views/keypairs.py | 25 +++++++ .../openstack/rest_api_version_history.rst | 11 +++ nova/compute/api.py | 5 +- .../v2.35/keypairs-list-resp.json.tpl | 18 +++++ .../v2.35/keypairs-list-user1-resp.json.tpl | 12 +++ .../v2.35/keypairs-list-user2-resp.json.tpl | 18 +++++ .../keypairs/v2.35/keypairs-post-req.json.tpl | 7 ++ .../v2.35/keypairs-post-resp.json.tpl | 10 +++ .../api_sample_tests/test_keypairs.py | 73 +++++++++++++++++++ .../api/openstack/compute/test_keypairs.py | 40 ++++++++++ ...-keypairs-pagination-634c46aaa1058161.yaml | 5 ++ 22 files changed, 350 insertions(+), 13 deletions(-) create mode 100644 doc/api_samples/keypairs/v2.35/keypairs-list-resp.json create mode 100644 doc/api_samples/keypairs/v2.35/keypairs-list-user1-resp.json create mode 100644 doc/api_samples/keypairs/v2.35/keypairs-list-user2-resp.json create mode 100644 doc/api_samples/keypairs/v2.35/keypairs-post-req.json create mode 100644 doc/api_samples/keypairs/v2.35/keypairs-post-resp.json create mode 100644 nova/api/openstack/compute/views/keypairs.py create mode 100644 nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-user1-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-list-user2-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-post-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/keypairs/v2.35/keypairs-post-resp.json.tpl create mode 100644 releasenotes/notes/bp-keypairs-pagination-634c46aaa1058161.yaml 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.