--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+#
+# 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 itertools
+from webob import exc
+
+from heat.api.openstack.v1 import util
+from heat.common import wsgi
+from heat.engine import api as engine_api
+from heat.engine import identifier
+from heat.engine import rpcapi as engine_rpcapi
+import heat.openstack.common.rpc.common as rpc_common
+from heat.openstack.common.gettextutils import _
+
+
+summary_keys = [
+ engine_api.EVENT_ID,
+ engine_api.EVENT_TIMESTAMP,
+ engine_api.EVENT_RES_NAME,
+ engine_api.EVENT_RES_STATUS,
+ engine_api.EVENT_RES_STATUS_DATA,
+ engine_api.EVENT_RES_PHYSICAL_ID,
+]
+
+
+def format_event(req, event, keys=None):
+ include_key = lambda k: k in keys if keys else True
+
+ def transform(key, value):
+ if not include_key(key):
+ return
+
+ if key == engine_api.EVENT_ID:
+ identity = identifier.EventIdentifier(**value)
+ yield ('id', identity.event_id)
+ yield ('links', [util.make_link(req, identity),
+ util.make_link(req, identity.resource(),
+ 'resource'),
+ util.make_link(req, identity.stack(),
+ 'stack')])
+ elif (key == engine_api.EVENT_STACK_ID or
+ key == engine_api.EVENT_STACK_NAME):
+ return
+ else:
+ yield (key, value)
+
+ return dict(itertools.chain.from_iterable(
+ transform(k, v) for k, v in event.items()))
+
+
+class EventController(object):
+ """
+ WSGI controller for Events in Heat v1 API
+ Implements the API actions
+ """
+
+ def __init__(self, options):
+ self.options = options
+ self.engine = engine_rpcapi.EngineAPI()
+
+ def _event_list(self, req, identity,
+ filter_func=lambda e: True, detail=False):
+ try:
+ result = self.engine.list_events(req.context,
+ identity)
+ except rpc_common.RemoteError as ex:
+ return util.remote_error(ex)
+
+ if 'events' not in result:
+ raise exc.HTTPInternalServerError()
+ ev_list = result['events']
+
+ keys = None if detail else summary_keys
+
+ return [format_event(req, e, keys) for e in ev_list if filter_func(e)]
+
+ @util.identified_stack
+ def index(self, req, identity, resource_name=None):
+ """
+ Lists summary information for all resources
+ """
+
+ if resource_name is None:
+ events = self._event_list(req, identity)
+ else:
+ res_match = lambda e: e[engine_api.EVENT_RES_NAME] == resource_name
+
+ events = self._event_list(req, identity, res_match)
+ if not events:
+ msg = _('No events found for resource %s') % resource_name
+ raise exc.HTTPNotFound(msg)
+
+ return {'events': events}
+
+ @util.identified_stack
+ def show(self, req, identity, resource_name, event_id):
+ """
+ Gets detailed information for a stack
+ """
+
+ def event_match(ev):
+ identity = identifier.EventIdentifier(**ev[engine_api.EVENT_ID])
+ return (ev[engine_api.EVENT_RES_NAME] == resource_name and
+ identity.event_id == event_id)
+
+ events = self._event_list(req, identity, event_match, True)
+ if not events:
+ raise exc.HTTPNotFound(_('No event %s found') % event_id)
+
+ return {'event': events[0]}
+
+
+def create_resource(options):
+ """
+ Events resource factory method.
+ """
+ # TODO(zaneb) handle XML based on Content-type/Accepts
+ deserializer = wsgi.JSONRequestDeserializer()
+ serializer = wsgi.JSONResponseSerializer()
+ return wsgi.Resource(EventController(options), deserializer, serializer)
import heat.api.openstack.v1.stacks as stacks
import heat.api.openstack.v1.resources as resources
+import heat.api.openstack.v1.events as events
@attr(tag=['unit', 'api-openstack-v1'])
self.m.VerifyAll()
+@attr(tag=['unit', 'api-openstack-v1', 'EventController'])
+@attr(speed='fast')
+class EventControllerTest(ControllerTest, unittest.TestCase):
+ '''
+ Tests the API class which acts as the WSGI controller,
+ the endpoint processing API requests after they are routed
+ '''
+
+ def setUp(self):
+ # Create WSGI controller instance
+ class DummyConfig():
+ bind_port = 8004
+ cfgopts = DummyConfig()
+ self.controller = events.EventController(options=cfgopts)
+
+ def test_resource_index(self):
+ event_id = '42'
+ res_name = 'WikiDatabase'
+ stack_identity = identifier.HeatIdentifier(self.tenant,
+ 'wordpress', '6')
+ res_identity = identifier.ResourceIdentifier(resource_name=res_name,
+ **stack_identity)
+ ev_identity = identifier.EventIdentifier(event_id=event_id,
+ **res_identity)
+
+ req = self._get(stack_identity._tenant_path() +
+ '/resources/' + res_name + '/events')
+
+ engine_resp = {u'events': [
+ {
+ u'stack_name': u'wordpress',
+ u'event_time': u'2012-07-23T13:05:39Z',
+ u'stack_identity': dict(stack_identity),
+ u'logical_resource_id': res_name,
+ u'resource_status_reason': u'state changed',
+ u'event_identity': dict(ev_identity),
+ u'resource_status': u'IN_PROGRESS',
+ u'physical_resource_id': None,
+ u'resource_properties': {u'UserData': u'blah'},
+ u'resource_type': u'AWS::EC2::Instance',
+ },
+ {
+ u'stack_name': u'wordpress',
+ u'event_time': u'2012-07-23T13:05:39Z',
+ u'stack_identity': dict(stack_identity),
+ u'logical_resource_id': 'SomeOtherResource',
+ u'resource_status_reason': u'state changed',
+ u'event_identity': dict(ev_identity),
+ u'resource_status': u'IN_PROGRESS',
+ u'physical_resource_id': None,
+ u'resource_properties': {u'UserData': u'blah'},
+ u'resource_type': u'AWS::EC2::Instance',
+ }
+ ]}
+ self.m.StubOutWithMock(rpc, 'call')
+ rpc.call(req.context, self.topic,
+ {'method': 'list_events',
+ 'args': {'stack_identity': stack_identity},
+ 'version': self.api_version},
+ None).AndReturn(engine_resp)
+ self.m.ReplayAll()
+
+ result = self.controller.index(req, tenant_id=self.tenant,
+ stack_name=stack_identity.stack_name,
+ stack_id=stack_identity.stack_id,
+ resource_name=res_name)
+
+ expected = {
+ 'events': [
+ {
+ 'id': event_id,
+ 'links': [
+ {'href': self._url(ev_identity), 'rel': 'self'},
+ {'href': self._url(res_identity), 'rel': 'resource'},
+ {'href': self._url(stack_identity), 'rel': 'stack'},
+ ],
+ u'logical_resource_id': res_name,
+ u'resource_status_reason': u'state changed',
+ u'event_time': u'2012-07-23T13:05:39Z',
+ u'resource_status': u'IN_PROGRESS',
+ u'physical_resource_id': None,
+ }
+ ]
+ }
+
+ self.assertEqual(result, expected)
+ self.m.VerifyAll()
+
+ def test_stack_index(self):
+ event_id = '42'
+ res_name = 'WikiDatabase'
+ stack_identity = identifier.HeatIdentifier(self.tenant,
+ 'wordpress', '6')
+ res_identity = identifier.ResourceIdentifier(resource_name=res_name,
+ **stack_identity)
+ ev_identity = identifier.EventIdentifier(event_id=event_id,
+ **res_identity)
+
+ req = self._get(stack_identity._tenant_path() + '/events')
+
+ engine_resp = {u'events': [
+ {
+ u'stack_name': u'wordpress',
+ u'event_time': u'2012-07-23T13:05:39Z',
+ u'stack_identity': dict(stack_identity),
+ u'logical_resource_id': res_name,
+ u'resource_status_reason': u'state changed',
+ u'event_identity': dict(ev_identity),
+ u'resource_status': u'IN_PROGRESS',
+ u'physical_resource_id': None,
+ u'resource_properties': {u'UserData': u'blah'},
+ u'resource_type': u'AWS::EC2::Instance',
+ }
+ ]}
+ self.m.StubOutWithMock(rpc, 'call')
+ rpc.call(req.context, self.topic,
+ {'method': 'list_events',
+ 'args': {'stack_identity': stack_identity},
+ 'version': self.api_version},
+ None).AndReturn(engine_resp)
+ self.m.ReplayAll()
+
+ result = self.controller.index(req, tenant_id=self.tenant,
+ stack_name=stack_identity.stack_name,
+ stack_id=stack_identity.stack_id)
+
+ expected = {
+ 'events': [
+ {
+ 'id': event_id,
+ 'links': [
+ {'href': self._url(ev_identity), 'rel': 'self'},
+ {'href': self._url(res_identity), 'rel': 'resource'},
+ {'href': self._url(stack_identity), 'rel': 'stack'},
+ ],
+ u'logical_resource_id': res_name,
+ u'resource_status_reason': u'state changed',
+ u'event_time': u'2012-07-23T13:05:39Z',
+ u'resource_status': u'IN_PROGRESS',
+ u'physical_resource_id': None,
+ }
+ ]
+ }
+
+ self.assertEqual(result, expected)
+ self.m.VerifyAll()
+
+ def test_index_stack_nonexist(self):
+ stack_identity = identifier.HeatIdentifier(self.tenant,
+ 'wibble', '6')
+
+ req = self._get(stack_identity._tenant_path() + '/events')
+
+ self.m.StubOutWithMock(rpc, 'call')
+ rpc.call(req.context, self.topic,
+ {'method': 'list_events',
+ 'args': {'stack_identity': stack_identity},
+ 'version': self.api_version},
+ None).AndRaise(rpc_common.RemoteError("AttributeError"))
+ self.m.ReplayAll()
+
+ self.assertRaises(webob.exc.HTTPNotFound,
+ self.controller.index,
+ req, tenant_id=self.tenant,
+ stack_name=stack_identity.stack_name,
+ stack_id=stack_identity.stack_id)
+ self.m.VerifyAll()
+
+ def test_index_resource_nonexist(self):
+ event_id = '42'
+ res_name = 'WikiDatabase'
+ stack_identity = identifier.HeatIdentifier(self.tenant,
+ 'wordpress', '6')
+ res_identity = identifier.ResourceIdentifier(resource_name=res_name,
+ **stack_identity)
+ ev_identity = identifier.EventIdentifier(event_id=event_id,
+ **res_identity)
+
+ req = self._get(stack_identity._tenant_path() +
+ '/resources/' + res_name + '/events')
+
+ engine_resp = {u'events': [
+ {
+ u'stack_name': u'wordpress',
+ u'event_time': u'2012-07-23T13:05:39Z',
+ u'stack_identity': dict(stack_identity),
+ u'logical_resource_id': 'SomeOtherResource',
+ u'resource_status_reason': u'state changed',
+ u'event_identity': dict(ev_identity),
+ u'resource_status': u'IN_PROGRESS',
+ u'physical_resource_id': None,
+ u'resource_properties': {u'UserData': u'blah'},
+ u'resource_type': u'AWS::EC2::Instance',
+ }
+ ]}
+ self.m.StubOutWithMock(rpc, 'call')
+ rpc.call(req.context, self.topic,
+ {'method': 'list_events',
+ 'args': {'stack_identity': stack_identity},
+ 'version': self.api_version},
+ None).AndReturn(engine_resp)
+ self.m.ReplayAll()
+
+ self.assertRaises(webob.exc.HTTPNotFound,
+ self.controller.index,
+ req, tenant_id=self.tenant,
+ stack_name=stack_identity.stack_name,
+ stack_id=stack_identity.stack_id,
+ resource_name=res_name)
+ self.m.VerifyAll()
+
+ def test_show(self):
+ event_id = '42'
+ res_name = 'WikiDatabase'
+ stack_identity = identifier.HeatIdentifier(self.tenant,
+ 'wordpress', '6')
+ res_identity = identifier.ResourceIdentifier(resource_name=res_name,
+ **stack_identity)
+ ev1_identity = identifier.EventIdentifier(event_id='41',
+ **res_identity)
+ ev_identity = identifier.EventIdentifier(event_id=event_id,
+ **res_identity)
+
+ req = self._get(stack_identity._tenant_path() +
+ '/resources/' + res_name + '/events/' + event_id)
+
+ engine_resp = {u'events': [
+ {
+ u'stack_name': u'wordpress',
+ u'event_time': u'2012-07-23T13:05:39Z',
+ u'stack_identity': dict(stack_identity),
+ u'logical_resource_id': res_name,
+ u'resource_status_reason': u'state changed',
+ u'event_identity': dict(ev1_identity),
+ u'resource_status': u'IN_PROGRESS',
+ u'physical_resource_id': None,
+ u'resource_properties': {u'UserData': u'blah'},
+ u'resource_type': u'AWS::EC2::Instance',
+ },
+ {
+ u'stack_name': u'wordpress',
+ u'event_time': u'2012-07-23T13:06:00Z',
+ u'stack_identity': dict(stack_identity),
+ u'logical_resource_id': res_name,
+ u'resource_status_reason': u'state changed',
+ u'event_identity': dict(ev_identity),
+ u'resource_status': u'CREATE_COMPLETE',
+ u'physical_resource_id':
+ u'a3455d8c-9f88-404d-a85b-5315293e67de',
+ u'resource_properties': {u'UserData': u'blah'},
+ u'resource_type': u'AWS::EC2::Instance',
+ }
+ ]}
+ self.m.StubOutWithMock(rpc, 'call')
+ rpc.call(req.context, self.topic,
+ {'method': 'list_events',
+ 'args': {'stack_identity': stack_identity},
+ 'version': self.api_version},
+ None).AndReturn(engine_resp)
+ self.m.ReplayAll()
+
+ result = self.controller.show(req, tenant_id=self.tenant,
+ stack_name=stack_identity.stack_name,
+ stack_id=stack_identity.stack_id,
+ resource_name=res_name,
+ event_id=event_id)
+
+ expected = {
+ 'event': {
+ 'id': event_id,
+ 'links': [
+ {'href': self._url(ev_identity), 'rel': 'self'},
+ {'href': self._url(res_identity), 'rel': 'resource'},
+ {'href': self._url(stack_identity), 'rel': 'stack'},
+ ],
+ u'logical_resource_id': res_name,
+ u'resource_status_reason': u'state changed',
+ u'event_time': u'2012-07-23T13:06:00Z',
+ u'resource_status': u'CREATE_COMPLETE',
+ u'physical_resource_id':
+ u'a3455d8c-9f88-404d-a85b-5315293e67de',
+ u'resource_type': u'AWS::EC2::Instance',
+ u'resource_properties': {u'UserData': u'blah'},
+ }
+ }
+
+ self.assertEqual(result, expected)
+ self.m.VerifyAll()
+
+ def test_show_nonexist(self):
+ event_id = '42'
+ res_name = 'WikiDatabase'
+ stack_identity = identifier.HeatIdentifier(self.tenant,
+ 'wordpress', '6')
+ res_identity = identifier.ResourceIdentifier(resource_name=res_name,
+ **stack_identity)
+ ev_identity = identifier.EventIdentifier(event_id='41',
+ **res_identity)
+
+ req = self._get(stack_identity._tenant_path() +
+ '/resources/' + res_name + '/events/' + event_id)
+
+ engine_resp = {u'events': [
+ {
+ u'stack_name': u'wordpress',
+ u'event_time': u'2012-07-23T13:05:39Z',
+ u'stack_identity': dict(stack_identity),
+ u'logical_resource_id': res_name,
+ u'resource_status_reason': u'state changed',
+ u'event_identity': dict(ev_identity),
+ u'resource_status': u'IN_PROGRESS',
+ u'physical_resource_id': None,
+ u'resource_properties': {u'UserData': u'blah'},
+ u'resource_type': u'AWS::EC2::Instance',
+ }
+ ]}
+ self.m.StubOutWithMock(rpc, 'call')
+ rpc.call(req.context, self.topic,
+ {'method': 'list_events',
+ 'args': {'stack_identity': stack_identity},
+ 'version': self.api_version},
+ None).AndReturn(engine_resp)
+ self.m.ReplayAll()
+
+ self.assertRaises(webob.exc.HTTPNotFound,
+ self.controller.show,
+ req, tenant_id=self.tenant,
+ stack_name=stack_identity.stack_name,
+ stack_id=stack_identity.stack_id,
+ resource_name=res_name, event_id=event_id)
+ self.m.VerifyAll()
+
+ def test_show_bad_resource(self):
+ event_id = '42'
+ res_name = 'WikiDatabase'
+ stack_identity = identifier.HeatIdentifier(self.tenant,
+ 'wordpress', '6')
+ res_identity = identifier.ResourceIdentifier(resource_name=res_name,
+ **stack_identity)
+ ev_identity = identifier.EventIdentifier(event_id='41',
+ **res_identity)
+
+ req = self._get(stack_identity._tenant_path() +
+ '/resources/' + res_name + '/events/' + event_id)
+
+ engine_resp = {u'events': [
+ {
+ u'stack_name': u'wordpress',
+ u'event_time': u'2012-07-23T13:05:39Z',
+ u'stack_identity': dict(stack_identity),
+ u'logical_resource_id': 'SomeOtherResourceName',
+ u'resource_status_reason': u'state changed',
+ u'event_identity': dict(ev_identity),
+ u'resource_status': u'IN_PROGRESS',
+ u'physical_resource_id': None,
+ u'resource_properties': {u'UserData': u'blah'},
+ u'resource_type': u'AWS::EC2::Instance',
+ }
+ ]}
+ self.m.StubOutWithMock(rpc, 'call')
+ rpc.call(req.context, self.topic,
+ {'method': 'list_events',
+ 'args': {'stack_identity': stack_identity},
+ 'version': self.api_version},
+ None).AndReturn(engine_resp)
+ self.m.ReplayAll()
+
+ self.assertRaises(webob.exc.HTTPNotFound,
+ self.controller.show,
+ req, tenant_id=self.tenant,
+ stack_name=stack_identity.stack_name,
+ stack_id=stack_identity.stack_id,
+ resource_name=res_name, event_id=event_id)
+ self.m.VerifyAll()
+
+ def test_show_stack_nonexist(self):
+ event_id = '42'
+ res_name = 'WikiDatabase'
+ stack_identity = identifier.HeatIdentifier(self.tenant,
+ 'wibble', '6')
+
+ req = self._get(stack_identity._tenant_path() +
+ '/resources/' + res_name + '/events/' + event_id)
+
+ self.m.StubOutWithMock(rpc, 'call')
+ rpc.call(req.context, self.topic,
+ {'method': 'list_events',
+ 'args': {'stack_identity': stack_identity},
+ 'version': self.api_version},
+ None).AndRaise(rpc_common.RemoteError("AttributeError"))
+ self.m.ReplayAll()
+
+ self.assertRaises(webob.exc.HTTPNotFound,
+ self.controller.show,
+ req, tenant_id=self.tenant,
+ stack_name=stack_identity.stack_name,
+ stack_id=stack_identity.stack_id,
+ resource_name=res_name, event_id=event_id)
+ self.m.VerifyAll()
+
+
if __name__ == '__main__':
sys.argv.append(__file__)
nose.main()