Merge "Microversion 2.35 adds keypairs pagination support"

This commit is contained in:
Jenkins 2016-07-12 19:41:34 +00:00 committed by Gerrit Code Review
commit 29f82ad191
22 changed files with 350 additions and 13 deletions

@ -23,6 +23,8 @@ Request
.. rest_parameters:: parameters.yaml
- user_id: keypair_user
- limit: keypair_limit
- marker: keypair_marker
Response
--------

@ -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

@ -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"
}
]
}

@ -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"
}
}
]
}

@ -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"
}
]
}

@ -0,0 +1,7 @@
{
"keypair": {
"name": "keypair-ab9ff2e6-a6d7-4915-a241-044c369c07f9",
"type": "ssh",
"user_id": "fake"
}
}

@ -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"
}
}

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.34",
"version": "2.35",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.34",
"version": "2.35",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

@ -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

@ -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):

@ -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')

@ -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}

@ -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."""

@ -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"
}
]
}

@ -0,0 +1,12 @@
{
"keypairs": [
{
"keypair": {
"fingerprint": "%(fingerprint)s",
"name": "%(keypair_name)s",
"type": "%(keypair_type)s",
"public_key": "%(public_key)s"
}
}
]
}

@ -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"
}
]
}

@ -0,0 +1,7 @@
{
"keypair": {
"name": "%(keypair_name)s",
"type": "%(keypair_type)s",
"user_id": "%(user_id)s"
}
}

@ -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"
}
}

@ -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)

@ -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)

@ -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.