From 65658ed6fca868430a4d9e074e4345c7b7f4bfc9 Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Mon, 30 Nov 2015 19:44:11 +0200 Subject: [PATCH] [microversions] Add support for 2.10 2.10 - Added user_id parameter to os-keypairs plugin, as well as a new property in the request body, for the create operation. Administrators will be able to list, get details and delete keypairs owned by users other than themselves and to create new keypairs on behalf of their users. Change-Id: I13ca3f8a4dd9cf11bec79966bb8a2ab48847be22 --- novaclient/__init__.py | 2 +- .../tests/functional/v2/test_keypairs.py | 77 +++++++++++++++++++ novaclient/tests/unit/v2/test_shell.py | 1 - novaclient/v2/keypairs.py | 60 ++++++++++++++- novaclient/v2/shell.py | 58 +++++++++++++- 5 files changed, 194 insertions(+), 4 deletions(-) diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 94ff3e494..37c73086a 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1") # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. -API_MAX_VERSION = api_versions.APIVersion("2.9") +API_MAX_VERSION = api_versions.APIVersion("2.10") diff --git a/novaclient/tests/functional/v2/test_keypairs.py b/novaclient/tests/functional/v2/test_keypairs.py index 35768de7b..171902f6f 100644 --- a/novaclient/tests/functional/v2/test_keypairs.py +++ b/novaclient/tests/functional/v2/test_keypairs.py @@ -10,6 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. +import tempest_lib.cli.base + +from novaclient.tests.functional import base from novaclient.tests.functional.v2 import fake_crypto from novaclient.tests.functional.v2.legacy import test_keypairs @@ -42,3 +45,77 @@ class TestKeypairsNovaClientV22(test_keypairs.TestKeypairsNovaClient): keypair = self._test_import_keypair(fingerprint, key_type='x509', pub_key=pub_key_file) self.assertIn('x509', keypair) + + +class TestKeypairsNovaClientV210(base.ClientTestBase): + """Keypairs functional tests for v2.10 nova-api microversion. + """ + + COMPUTE_API_VERSION = "2.10" + + def setUp(self): + super(TestKeypairsNovaClientV210, self).setUp() + user_name = self.name_generate("v2.10") + password = "password" + user = self.cli_clients.keystone( + "user-create --name %(name)s --pass %(pass)s --tenant %(tenant)s" % + {"name": user_name, "pass": password, + "tenant": self.cli_clients.tenant_name}) + self.user_id = self._get_value_from_the_table(user, "id") + self.addCleanup(self.cli_clients.keystone, + "user-delete %s" % self.user_id) + self.cli_clients_2 = tempest_lib.cli.base.CLIClient( + username=user_name, + password=password, + tenant_name=self.cli_clients.tenant_name, + uri=self.cli_clients.uri, + cli_dir=self.cli_clients.cli_dir) + + def another_nova(self, action, flags='', params='', fail_ok=False, + endpoint_type='publicURL', merge_stderr=False): + flags += " --os-compute-api-version %s " % self.COMPUTE_API_VERSION + return self.cli_clients_2.nova(action, flags, params, fail_ok, + endpoint_type, merge_stderr) + + def test_create_and_list_keypair(self): + name = self.name_generate("v2_10") + self.nova("keypair-add %s --user %s" % (name, self.user_id)) + self.addCleanup(self.another_nova, "keypair-delete %s" % name) + output = self.nova("keypair-list") + self.assertRaises(ValueError, self._get_value_from_the_table, + output, name) + output_1 = self.another_nova("keypair-list") + output_2 = self.nova("keypair-list --user %s" % self.user_id) + self.assertEqual(output_1, output_2) + # it should be table with one key-pair + self.assertEqual(name, self._get_column_value_from_single_row_table( + output_1, "Name")) + + output_1 = self.another_nova("keypair-show %s " % name) + output_2 = self.nova("keypair-show --user %s %s" % (self.user_id, + name)) + self.assertEqual(output_1, output_2) + self.assertEqual(self.user_id, + self._get_value_from_the_table(output_1, "user_id")) + + def test_create_and_delete(self): + name = self.name_generate("v2_10") + + def cleanup(): + # We should check keypair existence and remove it from correct user + # if keypair is presented + o = self.another_nova("keypair-list") + if name in o: + self.another_nova("keypair-delete %s" % name) + + self.nova("keypair-add %s --user %s" % (name, self.user_id)) + self.addCleanup(cleanup) + output = self.another_nova("keypair-list") + self.assertEqual(name, self._get_column_value_from_single_row_table( + output, "Name")) + + self.nova("keypair-delete %s --user %s " % (name, self.user_id)) + output = self.another_nova("keypair-list") + self.assertRaises( + ValueError, + self._get_column_value_from_single_row_table, output, "Name") diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index 0518e8191..36093ef7c 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -2535,7 +2535,6 @@ class ShellTest(utils.TestCase): 5, # Not implemented when test added, should not apply to adds. 7, # doesn't require any changes in novaclient 9, # doesn't require any changes in novaclient - 10, # Not implemented when test added, should not apply to adds. ]) versions_supported = set(range(0, novaclient.API_MAX_VERSION.ver_minor + 1)) diff --git a/novaclient/v2/keypairs.py b/novaclient/v2/keypairs.py index 021960517..70ab8da57 100644 --- a/novaclient/v2/keypairs.py +++ b/novaclient/v2/keypairs.py @@ -56,6 +56,7 @@ class KeypairManager(base.ManagerWithFind): keypair_prefix = "os-keypairs" is_alphanum_id_allowed = True + @api_versions.wraps("2.0", "2.9") def get(self, keypair): """ Get a keypair. @@ -66,6 +67,20 @@ class KeypairManager(base.ManagerWithFind): return self._get("/%s/%s" % (self.keypair_prefix, base.getid(keypair)), "keypair") + @api_versions.wraps("2.10") + def get(self, keypair, user_id=None): + """ + Get a keypair. + + :param keypair: The ID of the keypair to get. + :param user_id: Id of key-pair owner (Admin only). + :rtype: :class:`Keypair` + """ + query_string = "?user_id=%s" % user_id if user_id else "" + url = "/%s/%s%s" % (self.keypair_prefix, base.getid(keypair), + query_string) + return self._get(url, "keypair") + @api_versions.wraps("2.0", "2.1") def create(self, name, public_key=None): """ @@ -79,7 +94,7 @@ class KeypairManager(base.ManagerWithFind): body['keypair']['public_key'] = public_key return self._create('/%s' % self.keypair_prefix, body, 'keypair') - @api_versions.wraps("2.2") + @api_versions.wraps("2.2", "2.9") def create(self, name, public_key=None, key_type="ssh"): """ Create a keypair @@ -94,6 +109,25 @@ class KeypairManager(base.ManagerWithFind): body['keypair']['public_key'] = public_key return self._create('/%s' % self.keypair_prefix, body, 'keypair') + @api_versions.wraps("2.10") + def create(self, name, public_key=None, key_type="ssh", user_id=None): + """ + Create a keypair + + :param name: name for the keypair to create + :param public_key: existing public key to import + :param key_type: keypair type to create + :param user_id: user to add. + """ + body = {'keypair': {'name': name, + 'type': key_type}} + if public_key: + body['keypair']['public_key'] = public_key + if user_id: + body['keypair']['user_id'] = user_id + return self._create('/%s' % self.keypair_prefix, body, 'keypair') + + @api_versions.wraps("2.0", "2.9") def delete(self, key): """ Delete a keypair @@ -102,8 +136,32 @@ class KeypairManager(base.ManagerWithFind): """ self._delete('/%s/%s' % (self.keypair_prefix, base.getid(key))) + @api_versions.wraps("2.10") + def delete(self, key, user_id=None): + """ + Delete a keypair + + :param key: The :class:`Keypair` (or its ID) to delete. + :param user_id: Id of key-pair owner (Admin only). + """ + query_string = "?user_id=%s" % user_id if user_id else "" + url = '/%s/%s%s' % (self.keypair_prefix, base.getid(key), query_string) + self._delete(url) + + @api_versions.wraps("2.0", "2.9") def list(self): """ Get a list of keypairs. """ return self._list('/%s' % self.keypair_prefix, 'keypairs') + + @api_versions.wraps("2.10") + def list(self, user_id=None): + """ + Get a list of keypairs. + + :param user_id: Id of key-pairs owner (Admin only). + """ + query_string = "?user_id=%s" % user_id if user_id else "" + url = '/%s%s' % (self.keypair_prefix, query_string) + return self._list(url, 'keypairs') diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 5296084de..aee1b8c48 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -2977,11 +2977,17 @@ def _keypair_create(cs, args, name, pub_key): return cs.keypairs.create(name, pub_key) -@api_versions.wraps("2.2") +@api_versions.wraps("2.2", "2.9") def _keypair_create(cs, args, name, pub_key): return cs.keypairs.create(name, pub_key, key_type=args.key_type) +@api_versions.wraps("2.10") +def _keypair_create(cs, args, name, pub_key): + return cs.keypairs.create(name, pub_key, key_type=args.key_type, + user_id=args.user) + + @cliutils.arg('name', metavar='', help=_('Name of key.')) @cliutils.arg( '--pub-key', @@ -2997,6 +3003,12 @@ def _keypair_create(cs, args, name, pub_key): default='ssh', help=_('Keypair type. Can be ssh or x509.'), start_version="2.2") +@cliutils.arg( + '--user', + metavar='', + default=None, + help=_('ID of user to whom to add key-pair (Admin only).'), + start_version="2.10") def do_keypair_add(cs, args): """Create a new key pair for use with servers.""" name = args.name @@ -3021,6 +3033,7 @@ def do_keypair_add(cs, args): print(private_key) +@api_versions.wraps("2.0", "2.9") @cliutils.arg('name', metavar='', help=_('Keypair name to delete.')) def do_keypair_delete(cs, args): """Delete keypair given by its name.""" @@ -3028,6 +3041,18 @@ def do_keypair_delete(cs, args): cs.keypairs.delete(name) +@api_versions.wraps("2.10") +@cliutils.arg('name', metavar='', help=_('Keypair name to delete.')) +@cliutils.arg( + '--user', + metavar='', + default=None, + help=_('Id of key-pair owner (Admin only).')) +def do_keypair_delete(cs, args): + """Delete keypair given by its name.""" + cs.keypairs.delete(args.name, args.user) + + @api_versions.wraps("2.0", "2.1") def _get_keypairs_list_columns(cs, args): return ['Name', 'Fingerprint'] @@ -3038,6 +3063,7 @@ def _get_keypairs_list_columns(cs, args): return ['Name', 'Type', 'Fingerprint'] +@api_versions.wraps("2.0", "2.9") def do_keypair_list(cs, args): """Print a list of keypairs for a user""" keypairs = cs.keypairs.list() @@ -3045,6 +3071,19 @@ def do_keypair_list(cs, args): utils.print_list(keypairs, columns) +@api_versions.wraps("2.10") +@cliutils.arg( + '--user', + metavar='', + default=None, + help=_('List key-pairs of specified user id (Admin only).')) +def do_keypair_list(cs, args): + """Print a list of keypairs for a user""" + keypairs = cs.keypairs.list(args.user) + columns = _get_keypairs_list_columns(cs, args) + utils.print_list(keypairs, columns) + + def _print_keypair(keypair): kp = keypair._info.copy() pk = kp.pop('public_key') @@ -3052,6 +3091,7 @@ def _print_keypair(keypair): print(_("Public key: %s") % pk) +@api_versions.wraps("2.0", "2.9") @cliutils.arg( 'keypair', metavar='', @@ -3062,6 +3102,22 @@ def do_keypair_show(cs, args): _print_keypair(keypair) +@api_versions.wraps("2.10") +@cliutils.arg( + 'keypair', + metavar='', + help=_("Name of keypair.")) +@cliutils.arg( + '--user', + metavar='', + default=None, + help=_('Id of key-pair owner (Admin only).')) +def do_keypair_show(cs, args): + """Show details about the given keypair.""" + keypair = cs.keypairs.get(args.keypair, args.user) + _print_keypair(keypair) + + def _find_keypair(cs, keypair): """Get a keypair by name.""" return utils.find_resource(cs.keypairs, keypair)