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:
Mark Doffman 2015-12-01 15:45:11 -06:00 committed by Sean Dague
parent 07573aff23
commit 934a0e4ede
17 changed files with 228 additions and 28 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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'])

View File

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

View File

@ -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": [

View File

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