Make os-instance-action read deleted instances.
Add a microversion change to the os-instance-actions API so that we mutate the context to set 'read_deleted="yes"' when looking up the instance. Blueprint: os-instance-actions-read-deleted-instances Change-Id: I607a28bbe06e20e17ee47a283e06b1d42b5c0e84
This commit is contained in:
parent
07573aff23
commit
934a0e4ede
@ -24,4 +24,4 @@
|
||||
"start_time": "2012-12-05T00:00:00.000000",
|
||||
"user_id": "789"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
{
|
||||
"instanceActions": [
|
||||
{
|
||||
"action": "resize",
|
||||
"instance_uuid": "b48316c5-71e8-45e4-9884-6c78055b9b13",
|
||||
"message": "",
|
||||
"project_id": "842",
|
||||
"request_id": "req-25517360-b757-47d3-be45-0e8d2a01b36a",
|
||||
"start_time": "2012-12-05T01:00:00.000000",
|
||||
"user_id": "789"
|
||||
},
|
||||
{
|
||||
"action": "reboot",
|
||||
"instance_uuid": "b48316c5-71e8-45e4-9884-6c78055b9b13",
|
||||
"message": "",
|
||||
"project_id": "147",
|
||||
"request_id": "req-3293a3f1-b44c-4609-b8d2-d81b105636b8",
|
||||
"start_time": "2012-12-05T00:00:00.000000",
|
||||
"user_id": "789"
|
||||
}
|
||||
]
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.20",
|
||||
"version": "2.21",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.20",
|
||||
"version": "2.21",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||
* 2.19 - Allow user to set and get the server description
|
||||
* 2.20 - Add attach and detach volume operations for instances in shelved
|
||||
and shelved_offloaded state
|
||||
* 2.21 - Make os-instance-actions read deleted instances
|
||||
|
||||
"""
|
||||
|
||||
@ -72,7 +73,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.20"
|
||||
_MAX_API_VERSION = "2.21"
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@ from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova import compute
|
||||
from nova.i18n import _
|
||||
from nova import utils
|
||||
|
||||
ALIAS = "os-instance-actions"
|
||||
authorize = extensions.os_compute_authorizer(ALIAS)
|
||||
@ -49,11 +50,20 @@ class InstanceActionsController(wsgi.Controller):
|
||||
event[key] = event_raw.get(key)
|
||||
return event
|
||||
|
||||
@wsgi.Controller.api_version("2.1", "2.20")
|
||||
def _get_instance(self, req, context, server_id):
|
||||
return common.get_instance(self.compute_api, context, server_id)
|
||||
|
||||
@wsgi.Controller.api_version("2.21") # noqa
|
||||
def _get_instance(self, req, context, server_id):
|
||||
with utils.temporary_mutation(context, read_deleted='yes'):
|
||||
return common.get_instance(self.compute_api, context, server_id)
|
||||
|
||||
@extensions.expected_errors(404)
|
||||
def index(self, req, server_id):
|
||||
"""Returns the list of actions recorded for a given instance."""
|
||||
context = req.environ["nova.context"]
|
||||
instance = common.get_instance(self.compute_api, context, server_id)
|
||||
instance = self._get_instance(req, context, server_id)
|
||||
authorize(context, target=instance)
|
||||
actions_raw = self.action_api.actions_get(context, instance)
|
||||
actions = [self._format_action(action) for action in actions_raw]
|
||||
@ -63,7 +73,7 @@ class InstanceActionsController(wsgi.Controller):
|
||||
def show(self, req, server_id, id):
|
||||
"""Return data about the given instance action."""
|
||||
context = req.environ['nova.context']
|
||||
instance = common.get_instance(self.compute_api, context, server_id)
|
||||
instance = self._get_instance(req, context, server_id)
|
||||
authorize(context, target=instance)
|
||||
action = self.action_api.action_get_by_request_id(context, instance,
|
||||
id)
|
||||
|
@ -176,6 +176,10 @@ user documentation.
|
||||
|
||||
2.20
|
||||
----
|
||||
|
||||
From this version of the API user can call detach and attach volumes for
|
||||
instances which are in shelved and shelved_offloaded state.
|
||||
|
||||
2.21
|
||||
----
|
||||
The ``os-instance-actions`` API now returns information from deleted
|
||||
instances.
|
||||
|
@ -193,8 +193,11 @@ class TestOpenStackClient(object):
|
||||
resp.body = jsonutils.loads(response.content)
|
||||
return resp
|
||||
|
||||
def api_get(self, relative_uri, **kwargs):
|
||||
def api_get(self, relative_uri, api_version=None, **kwargs):
|
||||
kwargs.setdefault('check_response_status', [200])
|
||||
if api_version:
|
||||
headers = kwargs.setdefault('headers', {})
|
||||
headers['X-OpenStack-Nova-API-Version'] = api_version
|
||||
return APIResponse(self.api_request(relative_uri, **kwargs))
|
||||
|
||||
def api_post(self, relative_uri, body, api_version=None, **kwargs):
|
||||
@ -360,6 +363,6 @@ class TestOpenStackClient(object):
|
||||
def delete_server_group(self, group_id):
|
||||
self.api_delete('/os-server-groups/%s' % group_id)
|
||||
|
||||
def get_instance_actions(self, server_id):
|
||||
def get_instance_actions(self, server_id, api_version=None):
|
||||
return self.api_get('/servers/%s/os-instance-actions' %
|
||||
(server_id)).body['instanceActions']
|
||||
(server_id), api_version).body['instanceActions']
|
||||
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"instanceAction": {
|
||||
"action": "%(action)s",
|
||||
"instance_uuid": "%(instance_uuid)s",
|
||||
"request_id": "%(request_id)s",
|
||||
"user_id": "%(integer_id)s",
|
||||
"project_id": "%(integer_id)s",
|
||||
"start_time": "%(strtime)s",
|
||||
"message": "",
|
||||
"events": [
|
||||
{
|
||||
"event": "%(event)s",
|
||||
"start_time": "%(strtime)s",
|
||||
"finish_time": "%(strtime)s",
|
||||
"result": "%(result)s",
|
||||
"traceback": ""
|
||||
},
|
||||
{
|
||||
"event": "%(event)s",
|
||||
"start_time": "%(strtime)s",
|
||||
"finish_time": "%(strtime)s",
|
||||
"result": "%(result)s",
|
||||
"traceback": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
{
|
||||
"instanceActions": [
|
||||
{
|
||||
"action": "%(action)s",
|
||||
"instance_uuid": "%(uuid)s",
|
||||
"request_id": "%(request_id)s",
|
||||
"user_id": "%(integer_id)s",
|
||||
"project_id": "%(integer_id)s",
|
||||
"start_time": "%(strtime)s",
|
||||
"message": ""
|
||||
},
|
||||
{
|
||||
"action": "%(action)s",
|
||||
"instance_uuid": "%(uuid)s",
|
||||
"request_id": "%(request_id)s",
|
||||
"user_id": "%(integer_id)s",
|
||||
"project_id": "%(integer_id)s",
|
||||
"start_time": "%(strtime)s",
|
||||
"message": ""
|
||||
}
|
||||
]
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.20",
|
||||
"version": "2.21",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.20",
|
||||
"version": "2.21",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ CONF.import_opt('osapi_compute_extension',
|
||||
|
||||
|
||||
class ServerActionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
microversion = None
|
||||
ADMIN_API = True
|
||||
extension_name = 'os-instance-actions'
|
||||
|
||||
@ -39,6 +40,11 @@ class ServerActionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
'contrib.instance_actions.Instance_actions')
|
||||
return f
|
||||
|
||||
def _fake_get(self, context, instance_uuid, expected_attrs=None,
|
||||
want_objects=True):
|
||||
return fake_instance.fake_instance_obj(
|
||||
None, **{'uuid': instance_uuid})
|
||||
|
||||
def setUp(self):
|
||||
super(ServerActionsSampleJsonTest, self).setUp()
|
||||
self.actions = fake_server_actions.FAKE_ACTIONS
|
||||
@ -58,11 +64,6 @@ class ServerActionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
def fake_instance_get_by_uuid(context, instance_id):
|
||||
return self.instance
|
||||
|
||||
def fake_get(self, context, instance_uuid, expected_attrs=None,
|
||||
want_objects=True):
|
||||
return fake_instance.fake_instance_obj(
|
||||
None, **{'uuid': instance_uuid})
|
||||
|
||||
self.stub_out('nova.db.action_get_by_request_id',
|
||||
fake_instance_action_get_by_request_id)
|
||||
self.stub_out('nova.db.actions_get', fake_server_actions_get)
|
||||
@ -70,7 +71,7 @@ class ServerActionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
fake_instance_action_events_get)
|
||||
self.stub_out('nova.db.instance_get_by_uuid',
|
||||
fake_instance_get_by_uuid)
|
||||
self.stub_out('nova.compute.api.API.get', fake_get)
|
||||
self.stub_out('nova.compute.api.API.get', self._fake_get)
|
||||
|
||||
def test_instance_action_get(self):
|
||||
fake_uuid = fake_server_actions.FAKE_UUID
|
||||
@ -78,7 +79,8 @@ class ServerActionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
fake_action = self.actions[fake_uuid][fake_request_id]
|
||||
|
||||
response = self._do_get('servers/%s/os-instance-actions/%s' %
|
||||
(fake_uuid, fake_request_id))
|
||||
(fake_uuid, fake_request_id),
|
||||
api_version=self.microversion)
|
||||
subs = {}
|
||||
subs['action'] = '(reboot)|(resize)'
|
||||
subs['instance_uuid'] = fake_uuid
|
||||
@ -91,7 +93,8 @@ class ServerActionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
|
||||
def test_instance_actions_list(self):
|
||||
fake_uuid = fake_server_actions.FAKE_UUID
|
||||
response = self._do_get('servers/%s/os-instance-actions' % (fake_uuid))
|
||||
response = self._do_get('servers/%s/os-instance-actions' % (fake_uuid),
|
||||
api_version=self.microversion)
|
||||
subs = {}
|
||||
subs['action'] = '(reboot)|(resize)'
|
||||
subs['integer_id'] = '[0-9]+'
|
||||
@ -99,3 +102,14 @@ class ServerActionsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
'-[0-9a-f]{4}-[0-9a-f]{12}')
|
||||
self._verify_response('instance-actions-list-resp', subs,
|
||||
response, 200)
|
||||
|
||||
|
||||
class ServerActionsV221SampleJsonTest(ServerActionsSampleJsonTest):
|
||||
microversion = '2.21'
|
||||
scenarios = [('v2_21', {'api_major_version': 'v2.1'})]
|
||||
|
||||
def _fake_get(self, context, instance_uuid, expected_attrs=None,
|
||||
want_objects=True):
|
||||
self.assertEqual('yes', context.read_deleted)
|
||||
return fake_instance.fake_instance_obj(
|
||||
None, **{'uuid': instance_uuid})
|
||||
|
76
nova/tests/functional/test_instance_actions.py
Normal file
76
nova/tests/functional/test_instance_actions.py
Normal file
@ -0,0 +1,76 @@
|
||||
# Copyright 2016 IBM Corp.
|
||||
#
|
||||
# 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.tests.functional.api import client
|
||||
from nova.tests.functional import test_servers
|
||||
from nova.tests.unit import fake_network
|
||||
|
||||
|
||||
class InstanceActionsTestV2(test_servers.ServersTestBase):
|
||||
"""Tests Instance Actions API"""
|
||||
|
||||
def _create_server(self):
|
||||
"""Creates a minimal test server via the compute API
|
||||
|
||||
Ensures the server is created and can be retrieved from the compute API
|
||||
and waits for it to be ACTIVE.
|
||||
|
||||
:returns: created server (dict)
|
||||
"""
|
||||
# TODO(mriedem): We should pull this up into the parent class so we
|
||||
# don't have so much copy/paste in these functional tests.
|
||||
fake_network.set_stub_network_methods(self)
|
||||
|
||||
# Create a server
|
||||
server = self._build_minimal_create_server_request()
|
||||
created_server = self.api.post_server({'server': server})
|
||||
self.assertTrue(created_server['id'])
|
||||
created_server_id = created_server['id']
|
||||
|
||||
# Check it's there
|
||||
found_server = self.api.get_server(created_server_id)
|
||||
self.assertEqual(created_server_id, found_server['id'])
|
||||
|
||||
found_server = self._wait_for_state_change(found_server, 'BUILD')
|
||||
# It should be available...
|
||||
self.assertEqual('ACTIVE', found_server['status'])
|
||||
return found_server
|
||||
|
||||
def test_get_instance_actions(self):
|
||||
server = self._create_server()
|
||||
actions = self.api.get_instance_actions(server['id'])
|
||||
self.assertEqual('create', actions[0]['action'])
|
||||
|
||||
def test_get_instance_actions_deleted(self):
|
||||
server = self._create_server()
|
||||
self._delete_server(server['id'])
|
||||
self.assertRaises(client.OpenStackApiNotFoundException,
|
||||
self.api.get_instance_actions,
|
||||
server['id'])
|
||||
|
||||
|
||||
class InstanceActionsTestV21(InstanceActionsTestV2):
|
||||
api_major_version = 'v2.1'
|
||||
|
||||
|
||||
class InstanceActionsTestV221(InstanceActionsTestV21):
|
||||
microversion = '2.21'
|
||||
|
||||
def test_get_instance_actions_deleted(self):
|
||||
server = self._create_server()
|
||||
self._delete_server(server['id'])
|
||||
actions = self.api.get_instance_actions(server['id'],
|
||||
api_version=self.microversion)
|
||||
self.assertEqual('delete', actions[0]['action'])
|
||||
self.assertEqual('create', actions[1]['action'])
|
@ -23,6 +23,7 @@ from webob import exc
|
||||
from nova.api.openstack.compute import instance_actions as instance_actions_v21
|
||||
from nova.api.openstack.compute.legacy_v2.contrib import instance_actions \
|
||||
as instance_actions_v2
|
||||
from nova.api.openstack import wsgi as os_wsgi
|
||||
from nova.compute import api as compute_api
|
||||
from nova.db.sqlalchemy import models
|
||||
from nova import exception
|
||||
@ -130,6 +131,11 @@ class InstanceActionsPolicyTestV2(InstanceActionsPolicyTestV21):
|
||||
|
||||
class InstanceActionsTestV21(test.NoDBTestCase):
|
||||
instance_actions = instance_actions_v21
|
||||
wsgi_api_version = os_wsgi.DEFAULT_API_VERSION
|
||||
|
||||
def fake_get(self, context, instance_uuid, expected_attrs=None,
|
||||
want_objects=False):
|
||||
return objects.Instance(uuid=instance_uuid)
|
||||
|
||||
def setUp(self):
|
||||
super(InstanceActionsTestV21, self).setUp()
|
||||
@ -137,22 +143,19 @@ class InstanceActionsTestV21(test.NoDBTestCase):
|
||||
self.fake_actions = copy.deepcopy(fake_server_actions.FAKE_ACTIONS)
|
||||
self.fake_events = copy.deepcopy(fake_server_actions.FAKE_EVENTS)
|
||||
|
||||
def fake_get(self, context, instance_uuid, expected_attrs=None,
|
||||
want_objects=False):
|
||||
return objects.Instance(uuid=instance_uuid)
|
||||
|
||||
def fake_instance_get_by_uuid(context, instance_id, use_slave=False):
|
||||
return fake_instance.fake_instance_obj(None,
|
||||
**{'name': 'fake', 'project_id': context.project_id})
|
||||
|
||||
self.stubs.Set(compute_api.API, 'get', fake_get)
|
||||
self.stubs.Set(compute_api.API, 'get', self.fake_get)
|
||||
self.stub_out('nova.db.instance_get_by_uuid',
|
||||
fake_instance_get_by_uuid)
|
||||
|
||||
def _get_http_req(self, action, use_admin_context=False):
|
||||
fake_url = '/123/servers/12/%s' % action
|
||||
return fakes.HTTPRequest.blank(fake_url,
|
||||
use_admin_context=use_admin_context)
|
||||
use_admin_context=use_admin_context,
|
||||
version=self.wsgi_api_version)
|
||||
|
||||
def _set_policy_rules(self):
|
||||
rules = {'compute:get': '',
|
||||
@ -246,6 +249,15 @@ class InstanceActionsTestV21(test.NoDBTestCase):
|
||||
FAKE_UUID, 'fake')
|
||||
|
||||
|
||||
class InstanceActionsTestV221(InstanceActionsTestV21):
|
||||
wsgi_api_version = "2.21"
|
||||
|
||||
def fake_get(self, context, instance_uuid, expected_attrs=None,
|
||||
want_objects=False):
|
||||
self.assertEqual('yes', context.read_deleted)
|
||||
return objects.Instance(uuid=instance_uuid)
|
||||
|
||||
|
||||
class InstanceActionsTestV2(InstanceActionsTestV21):
|
||||
instance_actions = instance_actions_v2
|
||||
|
||||
|
@ -66,7 +66,7 @@ EXP_VERSIONS = {
|
||||
"v2.1": {
|
||||
"id": "v2.1",
|
||||
"status": "CURRENT",
|
||||
"version": "2.20",
|
||||
"version": "2.21",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z",
|
||||
"links": [
|
||||
@ -128,7 +128,7 @@ class VersionsTestV20(test.NoDBTestCase):
|
||||
{
|
||||
"id": "v2.1",
|
||||
"status": "CURRENT",
|
||||
"version": "2.20",
|
||||
"version": "2.21",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z",
|
||||
"links": [
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
features:
|
||||
- The os-instance-actions methods now read actions from deleted instances.
|
||||
This means that
|
||||
'GET /v2.1/{tenant-id}/servers/{server-id}/os-instance-actions'
|
||||
and
|
||||
'GET /v2.1/{tenant-id}/servers/{server-id}/os-instance-actions/{req-id}'
|
||||
will return instance-action items even if the instance corresponding to
|
||||
'{server-id}' has been deleted.
|
Loading…
Reference in New Issue
Block a user