Record events and retrieve them via "heat events_list <stack_name>"
Signed-off-by: Angus Salkeld <asalkeld@redhat.com>
This commit is contained in:
parent
4cea0f1589
commit
d1daeb01d0
19
bin/heat
19
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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue