Record events and retrieve them via "heat events_list <stack_name>"

Signed-off-by: Angus Salkeld <asalkeld@redhat.com>
This commit is contained in:
Angus Salkeld 2012-03-22 10:18:43 +11:00
parent 4cea0f1589
commit d1daeb01d0
10 changed files with 208 additions and 11 deletions

View File

@ -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'

View 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)

View 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()

View 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

View 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)

View File

@ -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)

View 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)

View 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

View 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)

46
heat/engine/simpledb.py Normal file
View File

@ -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