]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Record events and retrieve them via "heat events_list <stack_name>"
authorAngus Salkeld <asalkeld@redhat.com>
Wed, 21 Mar 2012 23:18:43 +0000 (10:18 +1100)
committerAngus Salkeld <asalkeld@redhat.com>
Wed, 21 Mar 2012 23:18:43 +0000 (10:18 +1100)
Signed-off-by: Angus Salkeld <asalkeld@redhat.com>
bin/heat
heat/api/v1/__init__.py
heat/api/v1/stacks.py
heat/client.py
heat/engine/api/v1/__init__.py
heat/engine/api/v1/events.py [new file with mode: 0644]
heat/engine/api/v1/stacks.py
heat/engine/capelistener.py
heat/engine/client.py
heat/engine/simpledb.py [new file with mode: 0644]

index fe14eba0daf005da87465da5edc77225d6a4940a..8d7fc8136b237ee46bab7efafd4fb4c3d49e1377 100755 (executable)
--- a/bin/heat
+++ b/bin/heat
@@ -187,6 +187,22 @@ def stack_describe(options, arguments):
     result = c.describe_stacks(**parameters)
     print json.dumps(result, indent=2)
 
+@catch_error('events_list')
+def stack_events_list(options, arguments):
+    '''
+    '''
+    parameters = {}
+    try:
+        parameters['StackName'] = arguments.pop(0)
+    except IndexError:
+        print "Please specify the stack name "
+        print "as the first argument"
+        return FAILURE
+
+    c = get_client(options)
+    result = c.list_stack_events(**parameters)
+    print json.dumps(result, indent=2)
+
 @catch_error('list')
 def stack_list(options, arguments):
     '''
@@ -449,6 +465,7 @@ def lookup_command(parser, command_name):
                 'update': stack_update,
                 'delete': stack_delete,
                 'list': stack_list,
+                'events_list': stack_events_list,
                 'validate': template_validate,
                 'gettemplate': get_template,
                 'describe': stack_describe,
@@ -492,6 +509,8 @@ Commands:
 
     jeos_create     Create a JEOS image
 
+    events_list     List events for a stack
+
 """
 
     oparser = optparse.OptionParser(version='%%prog %s'
index c12ea55fa499f7c9187dd59a4e15338a8b05aa22..a906f169aa9d77be3105150d0f7d9d60018291e2 100644 (file)
@@ -46,5 +46,7 @@ class API(wsgi.Router):
                        action="delete", conditions=dict(method=["DELETE"]))
         mapper.connect("/UpdateStack", controller=stacks_resource,
                        action="update", conditions=dict(method=["PUT"]))
+        mapper.connect("/DescribeStackEvents", controller=stacks_resource,
+                       action="events_list", conditions=dict(method=["GET"]))
 
         super(API, self).__init__(mapper)
index 4dca077a6f759caf7d78b2e9b01f2fc40c5d19a1..1b1a3b4a451f01707ac2c5f03380ddb7b4c0819e 100644 (file)
@@ -136,6 +136,20 @@ class StackController(object):
             return webob.exc.HTTPNotFound()
 
 
+    def events_list(self, req):
+        """
+        Returns the following information for all stacks:
+        """
+        c = engine.get_engine_client(req.context)
+        stack_list = c.get_stack_events(**req.params)
+
+        res = {'DescribeStackEventsResult': {'StackEvents': [] } }
+        summaries = res['DescribeStackEventsResult']['StackEvents']
+        for s in stack_list:
+            summaries.append(s)
+
+        return res
+
 def create_resource(options):
     """Stacks resource factory method."""
     deserializer = wsgi.JSONRequestDeserializer()
index b9a2e027ff2ef029cb6ca3aed12d64a0fcebc6ae..fb5e521811f019fd341761cd4f1da92d72c098f8 100644 (file)
@@ -80,6 +80,14 @@ class V1Client(base_client.BaseClient):
         data = json.loads(res.read())
         return data
 
+    def list_stack_events(self, **kwargs):
+        params = self._extract_params(kwargs, SUPPORTED_PARAMS)
+        self._insert_common_parameters(params)
+
+        res = self.do_request("GET", "/DescribeStackEvents", params=params)
+        data = json.loads(res.read())
+        return data
+
 Client = V1Client
 
 
index 5c55523f05d8ad423e7de17885f90c31ba7378c3..170f7af8b4adbdfea608e393b7bd07cf70882349 100644 (file)
@@ -17,6 +17,7 @@ import routes
 
 from heat.common import wsgi
 from heat.engine.api.v1 import stacks
+from heat.engine.api.v1 import events
 
 class API(wsgi.Router):
     """WSGI entry point for all stac requests."""
@@ -29,4 +30,9 @@ class API(wsgi.Router):
                         collection={'detail': 'GET'})
         mapper.connect("/", controller=stacks_resource, action="index")
 
+        events_resource = events.create_resource(conf)
+        mapper.resource("event", "events", controller=events_resource,
+                        parent_resource=dict(member_name='stack',
+                                             collection_name='stacks'))
+
         super(API, self).__init__(mapper)
diff --git a/heat/engine/api/v1/events.py b/heat/engine/api/v1/events.py
new file mode 100644 (file)
index 0000000..994398e
--- /dev/null
@@ -0,0 +1,54 @@
+# 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.
+
+"""
+Implementation of the stacks server WSGI controller.
+"""
+import json
+import logging
+
+import webob
+from webob.exc import (HTTPNotFound,
+                       HTTPConflict,
+                       HTTPBadRequest)
+
+from heat.common import exception
+from heat.common import wsgi
+
+from heat.engine import capelistener
+from heat.engine import simpledb
+
+logger = logging.getLogger('heat.engine.api.v1.events')
+
+
+class EventsController(object):
+    '''
+    The controller for the events child "resource"
+    stacks/events
+    '''
+
+    def __init__(self, conf):
+        self.conf = conf
+        self.event_db = {}
+        self.listener = capelistener.CapeEventListener()
+
+    def index(self, req, stack_id):
+        return simpledb.events_get(stack_id)
+
+def create_resource(conf):
+    """Events resource factory method."""
+    deserializer = wsgi.JSONRequestDeserializer()
+    serializer = wsgi.JSONResponseSerializer()
+    return wsgi.Resource(EventsController(conf), deserializer, serializer)
index 16c85ad206d39d3a322342bec37242fff976bb3a..ba3af0c40f34ac8f9fb9c2fc93554390237d942c 100644 (file)
@@ -36,7 +36,7 @@ logger = logging.getLogger('heat.engine.api.v1.stacks')
 
 stack_db = {}
 
-class Controller(object):
+class StacksController(object):
     '''
     bla
     '''
@@ -92,7 +92,7 @@ class Controller(object):
     def create(self, req, body=None):
 
         if body is None:
-            msg = _("TemplateBody or TemplateUrl were not given.")
+            msg = _("No Template provided.")
             return webob.exc.HTTPBadRequest(explanation=msg)
 
         if stack_db.has_key(body['StackName']):
@@ -125,4 +125,4 @@ def create_resource(conf):
     """Stacks resource factory method."""
     deserializer = wsgi.JSONRequestDeserializer()
     serializer = wsgi.JSONResponseSerializer()
-    return wsgi.Resource(Controller(conf), deserializer, serializer)
+    return wsgi.Resource(StacksController(conf), deserializer, serializer)
index 04a821b76c467b5646581502dae3c5f2d188f8f5..4752574b7cb1dab74f4b1a963ad51ce2900e171e 100644 (file)
@@ -17,11 +17,16 @@ import errno
 import eventlet
 from eventlet.green import socket
 import fcntl
+import libxml2
 import logging
 import os
 import stat
+from heat.engine import simpledb
 
-class CapeEventListener:
+
+logger = logging.getLogger('heat.engine.capelistener')
+
+class CapeEventListener(object):
 
     def __init__(self):
         self.backlog = 50
@@ -40,7 +45,8 @@ class CapeEventListener:
             if stat.S_ISSOCK(st.st_mode):
                 os.remove(self.file)
             else:
-                raise ValueError("File %s exists and is not a socket", self.file)
+                raise ValueError("File %s exists and is not a socket",
+                                 self.file)
         sock.bind(self.file)
         sock.listen(self.backlog)
         os.chmod(self.file, 0600)
@@ -50,12 +56,48 @@ class CapeEventListener:
     def cape_event_listner(self, sock):
         eventlet.serve(sock, self.cape_event_handle)
 
+    def store(self, xml_event):
+
+        try:
+            doc = libxml2.parseDoc(xml_event)
+        except:
+            return
+
+        event = {'EventId': ''}
+        root = doc.getRootElement()
+        child = root.children
+        while child is not None:
+            if child.type != "element":
+                child = child.next
+            elif child.name == 'event':
+                child = child.children
+            elif child.name == 'application':
+                event['StackId'] = child.prop('name')
+                event['StackName'] = child.prop('name')
+                child = child.children
+            elif child.name == 'node':
+                event['ResourceType'] = 'AWS::EC2::Instance'
+                event['LogicalResourceId'] = child.prop('name')
+                child = child.children
+            elif child.name == 'resource':
+                event['ResourceType'] = 'ORG::HA::Service'
+                event['LogicalResourceId'] = child.prop('name')
+                child = child.children
+            elif child.name == 'state':
+                event['ResourceStatus'] = child.content
+                child = child.next
+            elif child.name == 'reason':
+                event['ResourceStatusReason'] = child.content
+                child = child.next
+            else:
+                child = child.next
+
+        simpledb.event_append(event)
+        doc.freeDoc()
+
     def cape_event_handle(self, sock, client_addr):
         while True:
             x = sock.recv(4096)
-            # TODO(asalkeld) format this event "nicely"
-            logger.info('%s' % x.strip('\n'))
+            self.store(x.strip('\n'))
             if not x: break
 
-
-
index 76ec44de5a57a05a7bda838a6d3a77f07484432a..c9935492635190678518fc38f9d6dc7b64cdcc42 100644 (file)
@@ -110,13 +110,19 @@ class EngineClient(BaseClient):
         stack = data['stack']
         return stack
 
-    def delete_stack(self, stack_name):
+    def delete_stack(self, stack_id):
         """
         Deletes Engine's information about an stack
         """
-        res = self.do_request("DELETE", "/stacks/%s" % stack_name)
+        res = self.do_request("DELETE", "/stacks/%s" % stack_id)
         return res
 
+    def get_stack_events(self, **kwargs):
+        params = self._extract_params(kwargs, SUPPORTED_PARAMS)
+        res = self.do_request("GET", "/stacks/%s/events" % (params['StackName']),
+                              params=params)
+        return json.loads(res.read())['events']
+
 def get_engine_addr(conf):
     conf.register_opts(engine_addr_opts)
     return (conf.engine_host, conf.engine_port)
diff --git a/heat/engine/simpledb.py b/heat/engine/simpledb.py
new file mode 100644 (file)
index 0000000..7921ab6
--- /dev/null
@@ -0,0 +1,46 @@
+# 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 anydbm
+import json
+
+def event_append(event):
+    name = event['StackName']
+    d = anydbm.open('/var/lib/heat/%s.events.db' % name, 'c')
+    if d.has_key('lastid'):
+        newid = int(d['lastid']) + 1
+    else:
+        newid = 1
+    event['EventId'] = '%d' % newid
+    d['lastid'] = event['EventId']
+    d[event['EventId']] = json.dumps(event)
+
+    d.close()
+
+
+def events_get(stack_id):
+    events = {'events': []}
+    try:
+        d = anydbm.open('/var/lib/heat/%s.events.db' % stack_id, 'r')
+    except:
+        return events
+
+    for k, v in d.iteritems():
+        if k != 'lastid':
+            events['events'].append(json.loads(v))
+
+    d.close()
+    return events
+