From: Angus Salkeld Date: Wed, 21 Mar 2012 23:18:43 +0000 (+1100) Subject: Record events and retrieve them via "heat events_list " X-Git-Tag: 2014.1~2168 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=d1daeb01d0acfa593e9d12026e3f02d1a99c9cf1;p=openstack-build%2Fheat-build.git Record events and retrieve them via "heat events_list " Signed-off-by: Angus Salkeld --- diff --git a/bin/heat b/bin/heat index fe14eba0..8d7fc813 100755 --- 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' diff --git a/heat/api/v1/__init__.py b/heat/api/v1/__init__.py index c12ea55f..a906f169 100644 --- a/heat/api/v1/__init__.py +++ b/heat/api/v1/__init__.py @@ -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) diff --git a/heat/api/v1/stacks.py b/heat/api/v1/stacks.py index 4dca077a..1b1a3b4a 100644 --- a/heat/api/v1/stacks.py +++ b/heat/api/v1/stacks.py @@ -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() diff --git a/heat/client.py b/heat/client.py index b9a2e027..fb5e5218 100644 --- a/heat/client.py +++ b/heat/client.py @@ -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 diff --git a/heat/engine/api/v1/__init__.py b/heat/engine/api/v1/__init__.py index 5c55523f..170f7af8 100644 --- a/heat/engine/api/v1/__init__.py +++ b/heat/engine/api/v1/__init__.py @@ -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 index 00000000..994398e3 --- /dev/null +++ b/heat/engine/api/v1/events.py @@ -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) diff --git a/heat/engine/api/v1/stacks.py b/heat/engine/api/v1/stacks.py index 16c85ad2..ba3af0c4 100644 --- a/heat/engine/api/v1/stacks.py +++ b/heat/engine/api/v1/stacks.py @@ -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) diff --git a/heat/engine/capelistener.py b/heat/engine/capelistener.py index 04a821b7..4752574b 100644 --- a/heat/engine/capelistener.py +++ b/heat/engine/capelistener.py @@ -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 - - diff --git a/heat/engine/client.py b/heat/engine/client.py index 76ec44de..c9935492 100644 --- a/heat/engine/client.py +++ b/heat/engine/client.py @@ -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 index 00000000..7921ab65 --- /dev/null +++ b/heat/engine/simpledb.py @@ -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 +