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:
parent
787e334a10
commit
9ed8d398c2
@ -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.",
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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>
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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>
|
@ -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": "",
|
||||
|
128
nova/api/openstack/compute/contrib/instance_actions.py
Normal file
128
nova/api/openstack/compute/contrib/instance_actions.py
Normal 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]
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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)
|
@ -185,6 +185,7 @@ class ExtensionControllerTest(ExtensionTestCase):
|
||||
"FloatingIpsBulk",
|
||||
"Fox In Socks",
|
||||
"Hosts",
|
||||
"InstanceActions",
|
||||
"Keypairs",
|
||||
"Multinic",
|
||||
"MultipleCreate",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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": "",
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -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>
|
@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
@ -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>
|
@ -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'
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user