From cd88097ff59ad40002ac4f624b53efe72bc84386 Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Wed, 10 Feb 2016 17:37:25 +0200 Subject: [PATCH] [microversions] Enable 2.21 2.21 - The os-instance-actions API now returns information from deleted instances. Change-Id: Iff514e4fa9135207c6f8e32e444d45b1b61d8c7c --- novaclient/__init__.py | 2 +- novaclient/tests/functional/base.py | 6 +- .../functional/v2/test_instance_action.py | 60 +++++++++++++++++++ novaclient/tests/unit/test_utils.py | 9 +++ novaclient/utils.py | 10 +++- novaclient/v2/contrib/instance_action.py | 43 +++++++++++-- 6 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 novaclient/tests/functional/v2/test_instance_action.py diff --git a/novaclient/__init__.py b/novaclient/__init__.py index b9f008cca..f5588d094 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.20") +API_MAX_VERSION = api_versions.APIVersion("2.21") diff --git a/novaclient/tests/functional/base.py b/novaclient/tests/functional/base.py index 42cef9e15..14a1e93a7 100644 --- a/novaclient/tests/functional/base.py +++ b/novaclient/tests/functional/base.py @@ -308,7 +308,8 @@ class ClientTestBase(testtools.TestCase): raise ValueError("Unable to find value for column '%s'.") - def _create_server(self, name=None, with_network=True, **kwargs): + def _create_server(self, name=None, with_network=True, add_cleanup=True, + **kwargs): name = name or self.name_generate(prefix='server') if with_network: nics = [{"net-id": self.network.id}] @@ -316,7 +317,8 @@ class ClientTestBase(testtools.TestCase): nics = None server = self.client.servers.create(name, self.image, self.flavor, nics=nics, **kwargs) - self.addCleanup(server.delete) + if add_cleanup: + self.addCleanup(server.delete) novaclient.v2.shell._poll_for_status( self.client.servers.get, server.id, 'building', ['active']) diff --git a/novaclient/tests/functional/v2/test_instance_action.py b/novaclient/tests/functional/v2/test_instance_action.py new file mode 100644 index 000000000..a4557277e --- /dev/null +++ b/novaclient/tests/functional/v2/test_instance_action.py @@ -0,0 +1,60 @@ +# 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. + +import uuid + +import six +from tempest_lib import exceptions + +from novaclient.tests.functional import base + + +class TestInstanceActionCLI(base.ClientTestBase): + + COMPUTE_API_VERSION = "2.21" + + def _test_cmd_with_not_existing_instance(self, cmd, args): + try: + self.nova("%s %s" % (cmd, args)) + except exceptions.CommandFailed as e: + self.assertIn("ERROR (NotFound):", six.text_type(e)) + else: + self.fail("%s is not failed on non existing instance." % cmd) + + def test_show_action_with_not_existing_instance(self): + name_or_uuid = str(uuid.uuid4()) + request_id = str(uuid.uuid4()) + self._test_cmd_with_not_existing_instance( + "instance-action", "%s %s" % (name_or_uuid, request_id)) + + def test_list_actions_with_not_existing_instance(self): + name_or_uuid = str(uuid.uuid4()) + self._test_cmd_with_not_existing_instance("instance-action-list", + name_or_uuid) + + def test_show_and_list_actions_on_deleted_instance(self): + server = self._create_server(add_cleanup=False) + server.delete() + self.wait_for_resource_delete(server, self.client.servers) + + output = self.nova("instance-action-list %s" % server.id) + # NOTE(andreykurilin): output is not a single row table, so we can + # obtain just "create" action. It should be enough for testing + # "nova instance-action " command + request_id = self._get_column_value_from_single_row_table( + output, "Request_ID") + + output = self.nova("instance-action %s %s" % (server.id, request_id)) + + # ensure that obtained action is "create". + self.assertEqual("create", + self._get_value_from_the_table(output, "action")) diff --git a/novaclient/tests/unit/test_utils.py b/novaclient/tests/unit/test_utils.py index cfe4fd767..f3d88c21e 100644 --- a/novaclient/tests/unit/test_utils.py +++ b/novaclient/tests/unit/test_utils.py @@ -158,6 +158,15 @@ class FindResourceTestCase(test_utils.TestCase): output = utils.find_resource(alphanum_manager, '01234') self.assertEqual(output, alphanum_manager.get('01234')) + def test_find_without_wrapping_exception(self): + alphanum_manager = FakeManager(True) + self.assertRaises(exceptions.NotFound, utils.find_resource, + alphanum_manager, 'not_exist', wrap_exception=False) + res = alphanum_manager.resources[0] + alphanum_manager.resources.append(res) + self.assertRaises(exceptions.NoUniqueMatch, utils.find_resource, + alphanum_manager, res.name, wrap_exception=False) + class _FakeResult(object): def __init__(self, name, value): diff --git a/novaclient/utils.py b/novaclient/utils.py index ef183030c..1e34e0783 100644 --- a/novaclient/utils.py +++ b/novaclient/utils.py @@ -273,7 +273,7 @@ def print_dict(d, dict_property="Property", dict_value="Value", wrap=0): print(result) -def find_resource(manager, name_or_id, **find_args): +def find_resource(manager, name_or_id, wrap_exception=True, **find_args): """Helper for the _find_* methods.""" # for str id which is not uuid (for Flavor, Keypair and hypervsior in cells # environments search currently) @@ -316,7 +316,9 @@ def find_resource(manager, name_or_id, **find_args): "to be more specific.") % {'class': manager.resource_class.__name__.lower(), 'name': name_or_id}) - raise exceptions.CommandError(msg) + if wrap_exception: + raise exceptions.CommandError(msg) + raise exceptions.NoUniqueMatch(msg) # finally try to get entity as integer id try: @@ -325,7 +327,9 @@ def find_resource(manager, name_or_id, **find_args): msg = (_("No %(class)s with a name or ID of '%(name)s' exists.") % {'class': manager.resource_class.__name__.lower(), 'name': name_or_id}) - raise exceptions.CommandError(msg) + if wrap_exception: + raise exceptions.CommandError(msg) + raise exceptions.NotFound(404, msg) def _format_servers_list_networks(server): diff --git a/novaclient/v2/contrib/instance_action.py b/novaclient/v2/contrib/instance_action.py index 2a7c6b8df..99f11a750 100644 --- a/novaclient/v2/contrib/instance_action.py +++ b/novaclient/v2/contrib/instance_action.py @@ -15,7 +15,11 @@ import pprint +import six + +from novaclient import api_versions from novaclient import base +from novaclient import exceptions from novaclient.i18n import _ from novaclient.openstack.common import cliutils from novaclient import utils @@ -41,17 +45,41 @@ class InstanceActionManager(base.ManagerWithFind): base.getid(server), 'instanceActions') +@api_versions.wraps("2.0", "2.20") +def _find_server(cs, args): + return utils.find_resource(cs.servers, args.server) + + +@api_versions.wraps("2.21") +def _find_server(cs, args): + try: + return utils.find_resource(cs.servers, args.server, + wrap_exception=False) + except exceptions.NoUniqueMatch as e: + raise exceptions.CommandError(six.text_type(e)) + except exceptions.NotFound: + # The server can be deleted + return args.server + + @cliutils.arg( 'server', metavar='', - help=_('Name or UUID of the server to show an action for.')) + help=_('Name or UUID of the server to show actions for.'), + start_version="2.0", end_version="2.20") +@cliutils.arg( + 'server', + metavar='', + help=_('Name or UUID of the server to show actions for. Only UUID can be ' + 'used to show actions for a deleted server.'), + start_version="2.21") @cliutils.arg( 'request_id', metavar='', help=_('Request ID of the action to get.')) def do_instance_action(cs, args): """Show an action.""" - server = utils.find_resource(cs.servers, args.server) + server = _find_server(cs, args) action_resource = cs.instance_action.get(server, args.request_id) action = action_resource._info if 'events' in action: @@ -62,10 +90,17 @@ def do_instance_action(cs, args): @cliutils.arg( 'server', metavar='', - help=_('Name or UUID of the server to list actions for.')) + help=_('Name or UUID of the server to list actions for.'), + start_version="2.0", end_version="2.20") +@cliutils.arg( + 'server', + metavar='', + help=_('Name or UUID of the server to list actions for. Only UUID can be ' + 'used to list actions on a deleted server.'), + start_version="2.21") def do_instance_action_list(cs, args): """List actions on a server.""" - server = utils.find_resource(cs.servers, args.server) + server = _find_server(cs, args) actions = cs.instance_action.list(server) utils.print_list(actions, ['Action', 'Request_ID', 'Message', 'Start_Time'],