API extension for accessing instance_actions

Adds a new API extension for accessing the recorded actions and events
on an instance.  Usage is documented with api samples.

Additionally it modified the db api to retrieve actions by request_id
since the api does not return the db id.  This extension is the first
consumer of that method so there's no issue of changing behaviour
elsewhere.

Blueprint instance-actions
DocImpact

Change-Id: I74109586cc762a7f51d2b114896cf071ee0671cb
This commit is contained in:
Andrew Laski 2012-12-12 10:35:11 -05:00
parent 787e334a10
commit 9ed8d398c2
22 changed files with 639 additions and 13 deletions

View File

@ -312,6 +312,14 @@
"namespace": "http://docs.openstack.org/compute/ext/hypervisors/api/v1.1",
"updated": "2012-06-21T00:00:00+00:00"
},
{
"alias": "os-instance-actions",
"description": "View a log of actions taken on an instance",
"links": [],
"name": "InstanceActions",
"namespace": "http://docs.openstack.org/compute/ext/instance-actions/api/v1.1",
"updated": "2013-02-08T00:00:00+00:00"
},
{
"alias": "os-instance_usage_audit_log",
"description": "Admin-only Task Log Monitoring.",

View File

@ -135,6 +135,9 @@
<extension alias="os-hypervisors" updated="2012-06-21T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/hypervisors/api/v1.1" name="Hypervisors">
<description>Admin-only hypervisor administration.</description>
</extension>
<extension alias="os-instance-actions" updated="2013-02-08T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/instance-actions/api/v1.1" name="InstanceActions">
<description>View a log of actions taken on an instance</description>
</extension>
<extension alias="os-instance_usage_audit_log" updated="2012-07-06T01:00:00+00:00" namespace="http://docs.openstack.org/ext/services/api/v1.1" name="OSInstanceUsageAuditLog">
<description>Admin-only Task Log Monitoring.</description>
</extension>

View File

@ -0,0 +1,27 @@
{
"instanceAction": {
"action": "reboot",
"events": [
{
"event": "schedule",
"finish_time": "2012-12-05 01:02:00.000000",
"result": "Success",
"start_time": "2012-12-05 01:00:02.000000",
"traceback": ""
},
{
"event": "compute_create",
"finish_time": "2012-12-05 01:04:00.000000",
"result": "Success",
"start_time": "2012-12-05 01:03:00.000000",
"traceback": ""
}
],
"instance_uuid": "b48316c5-71e8-45e4-9884-6c78055b9b13",
"message": "",
"project_id": "147",
"request_id": "req-3293a3f1-b44c-4609-b8d2-d81b105636b8",
"start_time": "2012-12-05 00:00:00.000000",
"user_id": "789"
}
}

View File

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<instanceAction instance_uuid="b48316c5-71e8-45e4-9884-6c78055b9b13" user_id="789" start_time="2012-12-05 00:00:00.000000" request_id="req-3293a3f1-b44c-4609-b8d2-d81b105636b8" action="reboot" message="" project_id="147">
<events finish_time="2012-12-05 01:02:00.000000" start_time="2012-12-05 01:00:02.000000" traceback="" event="schedule" result="Success"/>
<events finish_time="2012-12-05 01:04:00.000000" start_time="2012-12-05 01:03:00.000000" traceback="" event="compute_create" result="Success"/>
</instanceAction>

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-05 01: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-05 00:00:00.000000",
"user_id": "789"
}
]
}

View File

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<instanceActions>
<instanceAction instance_uuid="b48316c5-71e8-45e4-9884-6c78055b9b13" user_id="789" start_time="2012-12-05 01:00:00.000000" request_id="req-25517360-b757-47d3-be45-0e8d2a01b36a" action="resize" message="" project_id="842"/>
<instanceAction instance_uuid="b48316c5-71e8-45e4-9884-6c78055b9b13" user_id="789" start_time="2012-12-05 00:00:00.000000" request_id="req-3293a3f1-b44c-4609-b8d2-d81b105636b8" action="reboot" message="" project_id="147"/>
</instanceActions>

View File

@ -66,6 +66,8 @@
"compute_extension:hide_server_addresses": "is_admin:False",
"compute_extension:hosts": "rule:admin_api",
"compute_extension:hypervisors": "rule:admin_api",
"compute_extension:instance_actions": "",
"compute_extension:instance_actions:events": "rule:admin_api",
"compute_extension:instance_usage_audit_log": "rule:admin_api",
"compute_extension:keypairs": "",
"compute_extension:multinic": "",

View File

@ -0,0 +1,128 @@
# Copyright 2013 Rackspace Hosting
# 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 webob import exc
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil
from nova import compute
from nova import db
authorize_actions = extensions.extension_authorizer('compute',
'instance_actions')
authorize_events = extensions.soft_extension_authorizer('compute',
'instance_actions:events')
ACTION_KEYS = ['action', 'instance_uuid', 'request_id', 'user_id',
'project_id', 'start_time', 'message']
EVENT_KEYS = ['event', 'start_time', 'finish_time', 'result', 'traceback']
def make_actions(elem):
for key in ACTION_KEYS:
elem.set(key)
def make_action(elem):
for key in ACTION_KEYS:
elem.set(key)
event = xmlutil.TemplateElement('events', selector='events')
for key in EVENT_KEYS:
event.set(key)
elem.append(event)
class InstanceActionsTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('instanceActions')
elem = xmlutil.SubTemplateElement(root, 'instanceAction',
selector='instanceActions')
make_actions(elem)
return xmlutil.MasterTemplate(root, 1)
class InstanceActionTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('instanceAction',
selector='instanceAction')
make_action(root)
return xmlutil.MasterTemplate(root, 1)
class InstanceActionsController(wsgi.Controller):
def __init__(self):
super(InstanceActionsController, self).__init__()
self.compute_api = compute.API()
def _format_action(self, action_raw):
action = {}
for key in ACTION_KEYS:
if key in action_raw:
action[key] = action_raw[key]
return action
def _format_event(self, event_raw):
event = {}
for key in EVENT_KEYS:
if key in event_raw:
event[key] = event_raw[key]
return event
@wsgi.serializers(xml=InstanceActionsTemplate)
def index(self, req, server_id):
"""Returns the list of actions recorded for a given instance."""
context = req.environ["nova.context"]
instance = self.compute_api.get(context, server_id)
authorize_actions(context, target=instance)
actions_raw = db.actions_get(context, server_id)
actions = [self._format_action(action) for action in actions_raw]
return {'instanceActions': actions}
@wsgi.serializers(xml=InstanceActionTemplate)
def show(self, req, server_id, id):
"""Return data about the given instance action."""
context = req.environ['nova.context']
instance = self.compute_api.get(context, server_id)
authorize_actions(context, target=instance)
action = db.action_get_by_request_id(context, server_id, id)
if action is None:
raise exc.HTTPNotFound()
action_id = action['id']
action = self._format_action(action)
if authorize_events(context):
events_raw = db.action_events_get(context, action_id)
action['events'] = [self._format_event(evt) for evt in events_raw]
return {'instanceAction': action}
class Instance_actions(extensions.ExtensionDescriptor):
"""View a log of actions and events taken on an instance."""
name = "InstanceActions"
alias = "os-instance-actions"
namespace = ("http://docs.openstack.org/compute/ext/"
"instance-actions/api/v1.1")
updated = "2013-02-08T00:00:00+00:00"
def get_resources(self):
ext = extensions.ResourceExtension('os-instance-actions',
InstanceActionsController(),
parent=dict(
member_name='server',
collection_name='servers'))
return [ext]

View File

@ -1630,9 +1630,9 @@ def actions_get(context, uuid):
return IMPL.actions_get(context, uuid)
def action_get_by_id(context, uuid, action_id):
"""Get the action by id and given instance."""
return IMPL.action_get_by_id(context, uuid, action_id)
def action_get_by_request_id(context, uuid, request_id):
"""Get the action by request_id and given instance."""
return IMPL.action_get_by_request_id(context, uuid, request_id)
def action_event_start(context, values):
@ -1646,6 +1646,7 @@ def action_event_finish(context, values):
def action_events_get(context, action_id):
"""Get the events by action id."""
return IMPL.action_events_get(context, action_id)

View File

@ -4586,13 +4586,9 @@ def actions_get(context, instance_uuid):
return actions
def action_get_by_id(context, instance_uuid, action_id):
"""Get the action by id and given instance."""
action = model_query(context, models.InstanceAction).\
filter_by(instance_uuid=instance_uuid).\
filter_by(id=action_id).\
first()
def action_get_by_request_id(context, instance_uuid, request_id):
"""Get the action by request_id and given instance."""
action = _action_get_by_request_id(context, instance_uuid, request_id)
return action

View File

@ -0,0 +1,231 @@
# Copyright 2013 Rackspace Hosting
# 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.
import copy
import uuid
from lxml import etree
from webob import exc
from nova.api.openstack.compute.contrib import instance_actions
from nova import db
from nova import exception
from nova.openstack.common import policy
from nova import test
from nova.tests.api.openstack import fakes
from nova.tests import fake_instance_actions
FAKE_UUID = fake_instance_actions.FAKE_UUID
FAKE_REQUEST_ID = fake_instance_actions.FAKE_REQUEST_ID1
def format_action(action):
'''Remove keys that aren't serialized.'''
if 'id' in action:
del(action['id'])
if 'finish_time' in action:
del(action['finish_time'])
return action
def format_event(event):
'''Remove keys that aren't serialized.'''
if 'id' in event:
del(event['id'])
return event
class InstanceActionsPolicyTest(test.TestCase):
def setUp(self):
super(InstanceActionsPolicyTest, self).setUp()
self.controller = instance_actions.InstanceActionsController()
def test_list_actions_restricted_by_project(self):
rules = policy.Rules({'compute:get': policy.parse_rule(''),
'compute_extension:instance_actions':
policy.parse_rule('project_id:%(project_id)s')})
policy.set_rules(rules)
def fake_instance_get_by_uuid(context, instance_id):
return {'name': 'fake', 'project_id': '%s_unequal' %
context.project_id}
self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid)
req = fakes.HTTPRequest.blank('/v2/123/servers/12/os-instance-actions')
self.assertRaises(exception.NotAuthorized, self.controller.index, req,
str(uuid.uuid4()))
def test_get_action_restricted_by_project(self):
rules = policy.Rules({'compute:get': policy.parse_rule(''),
'compute_extension:instance_actions':
policy.parse_rule('project_id:%(project_id)s')})
policy.set_rules(rules)
def fake_instance_get_by_uuid(context, instance_id):
return {'name': 'fake', 'project_id': '%s_unequal' %
context.project_id}
self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid)
req = fakes.HTTPRequest.blank(
'/v2/123/servers/12/os-instance-actions/1')
self.assertRaises(exception.NotAuthorized, self.controller.show, req,
str(uuid.uuid4()), '1')
class InstanceActionsTest(test.TestCase):
def setUp(self):
super(InstanceActionsTest, self).setUp()
self.controller = instance_actions.InstanceActionsController()
self.fake_actions = copy.deepcopy(fake_instance_actions.FAKE_ACTIONS)
self.fake_events = copy.deepcopy(fake_instance_actions.FAKE_EVENTS)
def fake_instance_get_by_uuid(context, instance_id):
return {'name': 'fake', 'project_id': context.project_id}
self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid)
def test_list_actions(self):
def fake_get_actions(context, uuid):
return self.fake_actions[uuid].values()
self.stubs.Set(db, 'actions_get', fake_get_actions)
req = fakes.HTTPRequest.blank('/v2/123/servers/12/os-instance-actions')
res_dict = self.controller.index(req, FAKE_UUID)
for res in res_dict['instanceActions']:
fake_action = self.fake_actions[FAKE_UUID][res['request_id']]
fake_action = format_action(fake_action)
self.assertEqual(fake_action, res)
def test_get_action_with_events_allowed(self):
def fake_get_action(context, uuid, request_id):
return self.fake_actions[uuid][request_id]
def fake_get_events(context, action_id):
return self.fake_events[action_id]
self.stubs.Set(db, 'action_get_by_request_id', fake_get_action)
self.stubs.Set(db, 'action_events_get', fake_get_events)
req = fakes.HTTPRequest.blank(
'/v2/123/servers/12/os-instance-actions/1',
use_admin_context=True)
res_dict = self.controller.show(req, FAKE_UUID, FAKE_REQUEST_ID)
fake_action = self.fake_actions[FAKE_UUID][FAKE_REQUEST_ID]
fake_events = self.fake_events[fake_action['id']]
fake_events = [format_event(event) for event in fake_events]
fake_action = format_action(fake_action)
fake_action['events'] = fake_events
self.assertEqual(fake_action, res_dict['instanceAction'])
def test_get_action_with_events_not_allowed(self):
def fake_get_action(context, uuid, request_id):
return self.fake_actions[uuid][request_id]
def fake_get_events(context, action_id):
return self.fake_events[action_id]
self.stubs.Set(db, 'action_get_by_request_id', fake_get_action)
self.stubs.Set(db, 'action_events_get', fake_get_events)
rules = policy.Rules({'compute:get': policy.parse_rule(''),
'compute_extension:instance_actions':
policy.parse_rule(''),
'compute_extension:instance_actions:events':
policy.parse_rule('is_admin:True')})
policy.set_rules(rules)
req = fakes.HTTPRequest.blank(
'/v2/123/servers/12/os-instance-actions/1')
res_dict = self.controller.show(req, FAKE_UUID, FAKE_REQUEST_ID)
fake_action = self.fake_actions[FAKE_UUID][FAKE_REQUEST_ID]
fake_action = format_action(fake_action)
self.assertEqual(fake_action, res_dict['instanceAction'])
def test_action_not_found(self):
def fake_no_action(context, uuid, action_id):
return None
self.stubs.Set(db, 'action_get_by_request_id', fake_no_action)
req = fakes.HTTPRequest.blank(
'/v2/123/servers/12/os-instance-actions/1')
self.assertRaises(exc.HTTPNotFound, self.controller.show, req,
FAKE_UUID, FAKE_REQUEST_ID)
class InstanceActionsSerializerTest(test.TestCase):
def setUp(self):
super(InstanceActionsSerializerTest, self).setUp()
self.fake_actions = copy.deepcopy(fake_instance_actions.FAKE_ACTIONS)
self.fake_events = copy.deepcopy(fake_instance_actions.FAKE_EVENTS)
def _verify_instance_action_attachment(self, attach, tree):
for key in attach.keys():
if key != 'events':
self.assertEqual(attach[key], tree.get(key),
'%s did not match' % key)
def _verify_instance_action_event_attachment(self, attach, tree):
for key in attach.keys():
self.assertEqual(attach[key], tree.get(key),
'%s did not match' % key)
def test_instance_action_serializer(self):
serializer = instance_actions.InstanceActionTemplate()
action = self.fake_actions[FAKE_UUID][FAKE_REQUEST_ID]
text = serializer.serialize({'instanceAction': action})
tree = etree.fromstring(text)
action = format_action(action)
self.assertEqual('instanceAction', tree.tag)
self._verify_instance_action_attachment(action, tree)
found_events = False
for child in tree:
if child.tag == 'events':
found_events = True
self.assertFalse(found_events)
def test_instance_action_events_serializer(self):
serializer = instance_actions.InstanceActionTemplate()
action = self.fake_actions[FAKE_UUID][FAKE_REQUEST_ID]
event = self.fake_events[action['id']][0]
action['events'] = [event, event]
text = serializer.serialize({'instanceAction': action})
tree = etree.fromstring(text)
action = format_action(action)
self.assertEqual('instanceAction', tree.tag)
self._verify_instance_action_attachment(action, tree)
event = format_event(event)
found_events = False
for child in tree:
if child.tag == 'events':
found_events = True
for key in event:
self.assertEqual(event[key], child.get(key))
self.assertTrue(found_events)
def test_instance_actions_serializer(self):
serializer = instance_actions.InstanceActionsTemplate()
action_list = self.fake_actions[FAKE_UUID].values()
text = serializer.serialize({'instanceActions': action_list})
tree = etree.fromstring(text)
action_list = [format_action(action) for action in action_list]
self.assertEqual('instanceActions', tree.tag)
self.assertEqual(len(action_list), len(tree))
for idx, child in enumerate(tree):
self.assertEqual('instanceAction', child.tag)
request_id = child.get('request_id')
self._verify_instance_action_attachment(
self.fake_actions[FAKE_UUID][request_id],
child)

View File

@ -185,6 +185,7 @@ class ExtensionControllerTest(ExtensionTestCase):
"FloatingIpsBulk",
"Fox In Socks",
"Hosts",
"InstanceActions",
"Keypairs",
"Multinic",
"MultipleCreate",

View File

@ -17,6 +17,64 @@
from nova import db
FAKE_UUID = 'b48316c5-71e8-45e4-9884-6c78055b9b13'
FAKE_REQUEST_ID1 = 'req-3293a3f1-b44c-4609-b8d2-d81b105636b8'
FAKE_REQUEST_ID2 = 'req-25517360-b757-47d3-be45-0e8d2a01b36a'
FAKE_ACTION_ID1 = 'f811a359-0c98-4daa-87a4-2948d4c21b78'
FAKE_ACTION_ID2 = '4e9594b5-4ac5-421c-ac60-2d802b11c798'
FAKE_ACTIONS = {
FAKE_UUID: {
FAKE_REQUEST_ID1: {'id': FAKE_ACTION_ID1,
'action': 'reboot',
'instance_uuid': FAKE_UUID,
'request_id': FAKE_REQUEST_ID1,
'project_id': '147',
'user_id': '789',
'start_time': '2012-12-05 00:00:00.000000',
'finish_time': '',
'message': '',
},
FAKE_REQUEST_ID2: {'id': FAKE_ACTION_ID2,
'action': 'resize',
'instance_uuid': FAKE_UUID,
'request_id': FAKE_REQUEST_ID2,
'user_id': '789',
'project_id': '842',
'start_time': '2012-12-05 01:00:00.000000',
'finish_time': '',
'message': '',
}
}
}
FAKE_EVENTS = {
FAKE_ACTION_ID1: [{'id': '1',
'event': 'schedule',
'start_time': '2012-12-05 01:00:02.000000',
'finish_time': '2012-12-05 01:02:00.000000',
'result': 'Success',
'traceback': '',
},
{'id': '2',
'event': 'compute_create',
'start_time': '2012-12-05 01:03:00.000000',
'finish_time': '2012-12-05 01:04:00.000000',
'result': 'Success',
'traceback': '',
}
],
FAKE_ACTION_ID2: [{'id': '3',
'event': 'schedule',
'start_time': '2012-12-05 03:00:00.000000',
'finish_time': '2012-12-05 03:02:00.000000',
'result': 'Error',
'traceback': ''
}
]
}
def fake_action_event_start(*args):
pass

View File

@ -143,6 +143,8 @@ policy_data = """
"compute_extension:hide_server_addresses": "",
"compute_extension:hosts": "",
"compute_extension:hypervisors": "",
"compute_extension:instance_actions": "",
"compute_extension:instance_actions:events": "is_admin:True",
"compute_extension:instance_usage_audit_log": "",
"compute_extension:keypairs": "",
"compute_extension:multinic": "",

View File

@ -463,6 +463,14 @@
"name": "Volumes",
"namespace": "http://docs.openstack.org/compute/ext/volumes/api/v1.1",
"updated": "%(timestamp)s"
},
{
"alias": "os-instance-actions",
"description": "%(text)s",
"links": [],
"name": "InstanceActions",
"namespace": "http://docs.openstack.org/compute/ext/instance-actions/api/v1.1",
"updated": "%(timestamp)s"
}
]
}

View File

@ -174,4 +174,7 @@
<extension alias="os-volumes" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/volumes/api/v1.1" name="Volumes">
<description>%(text)s</description>
</extension>
<extension alias="os-instance-actions" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/instance-actions/api/v1.1" name="InstanceActions">
<description>%(text)s</description>
</extension>
</extensions>

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": "%(start_time)s",
"message": "",
"events": [
{
"event": "%(event)s",
"start_time": "%(timestamp)s",
"finish_time": "%(timestamp)s",
"result": "%(result)s",
"traceback": ""
},
{
"event": "%(event)s",
"start_time": "%(timestamp)s",
"finish_time": "%(timestamp)s",
"result": "%(result)s",
"traceback": ""
}
]
}
}

View File

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<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="%(start_time)s" message="">
<events event="%(event)s" start_time="%(timestamp)s" finish_time="%(timestamp)s" result="%(result)s" traceback=""/>
<events event="%(event)s" start_time="%(timestamp)s" finish_time="%(timestamp)s" result="%(result)s" traceback=""/>
</instanceAction>

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": "%(timestamp)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": "%(timestamp)s",
"message": ""
}
]
}

View File

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<instanceActions>
<instanceAction action="%(action)s" instance_uuid="%(uuid)s" request_id="%(request_id)s" user_id="%(integer_id)s" project_id="%(integer_id)s" start_time="%(timestamp)s" message=""/>
<instanceAction action="%(action)s" instance_uuid="%(uuid)s" request_id="%(request_id)s" user_id="%(integer_id)s" project_id="%(integer_id)s" start_time="%(timestamp)s" message=""/>
</instanceActions>

View File

@ -14,6 +14,7 @@
# under the License.
import base64
import copy
import datetime
import inspect
import json
@ -48,9 +49,11 @@ from nova.tests.api.openstack.compute.contrib import test_fping
from nova.tests.api.openstack.compute.contrib import test_networks
from nova.tests.api.openstack.compute.contrib import test_services
from nova.tests.baremetal.db import base as bm_db_base
from nova.tests import fake_instance_actions
from nova.tests import fake_network
from nova.tests.image import fake
from nova.tests.integrated import integrated_helpers
from nova.tests import utils as test_utils
from nova import utils
CONF = cfg.CONF
@ -3106,3 +3109,67 @@ class FloatingIpDNSJsonTest(ApiSampleTestBase):
class FloatingIpDNSXmlTest(FloatingIpDNSJsonTest):
ctype = 'xml'
class InstanceActionsSampleJsonTest(ApiSampleTestBase):
extension_name = ('nova.api.openstack.compute.contrib.instance_actions.'
'Instance_actions')
def setUp(self):
super(InstanceActionsSampleJsonTest, self).setUp()
self.actions = fake_instance_actions.FAKE_ACTIONS
self.events = fake_instance_actions.FAKE_EVENTS
self.instance = test_utils.get_test_instance()
def fake_instance_action_get_by_request_id(context, uuid, request_id):
return copy.deepcopy(self.actions[uuid][request_id])
def fake_instance_actions_get(context, uuid):
return [copy.deepcopy(value) for value in
self.actions[uuid].itervalues()]
def fake_instance_action_events_get(context, action_id):
return copy.deepcopy(self.events[action_id])
def fake_instance_get_by_uuid(context, instance_id):
return self.instance
self.stubs.Set(db, 'action_get_by_request_id',
fake_instance_action_get_by_request_id)
self.stubs.Set(db, 'actions_get', fake_instance_actions_get)
self.stubs.Set(db, 'action_events_get',
fake_instance_action_events_get)
self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid)
def test_instance_action_get(self):
fake_uuid = fake_instance_actions.FAKE_UUID
fake_request_id = fake_instance_actions.FAKE_REQUEST_ID1
fake_action = self.actions[fake_uuid][fake_request_id]
response = self._do_get('servers/%s/os-instance-actions/%s' %
(fake_uuid, fake_request_id))
subs = self._get_regexes()
subs['action'] = '(reboot)|(resize)'
subs['instance_uuid'] = fake_uuid
subs['integer_id'] = '[0-9]+'
subs['request_id'] = fake_action['request_id']
subs['start_time'] = fake_action['start_time']
subs['result'] = '(Success)|(Error)'
subs['event'] = '(schedule)|(compute_create)'
return self._verify_response('instance-action-get-resp', subs,
response)
def test_instance_actions_list(self):
fake_uuid = fake_instance_actions.FAKE_UUID
response = self._do_get('servers/%s/os-instance-actions' % (fake_uuid))
subs = self._get_regexes()
subs['action'] = '(reboot)|(resize)'
subs['integer_id'] = '[0-9]+'
subs['request_id'] = ('req-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}'
'-[0-9a-f]{4}-[0-9a-f]{12}')
return self._verify_response('instance-actions-list-resp', subs,
response)
class InstanceActionsSampleXmlTest(InstanceActionsSampleJsonTest):
ctype = 'xml'

View File

@ -685,9 +685,9 @@ class DbApiTestCase(test.TestCase):
db.action_start(ctxt2, action_values)
actions = db.actions_get(ctxt1, uuid1)
action_id = actions[0]['id']
action = db.action_get_by_id(ctxt1, uuid1, action_id)
self.assertEqual('resize', action['action'])
request_id = actions[0]['request_id']
action = db.action_get_by_request_id(ctxt1, uuid1, request_id)
self.assertEqual('run_instance', action['action'])
self.assertEqual(ctxt1.request_id, action['request_id'])
def test_instance_action_event_start(self):