Set cfn-hup to send events to the metadata server

Fixes #107

This is implemented using a logging handler that sends events to the metadata
server.

Signed-off-by: Tomas Sedovic <tomas@sedovic.cz>
This commit is contained in:
Tomas Sedovic 2012-05-14 13:56:27 +02:00
parent 95e3a0165d
commit 596b74d72a
3 changed files with 108 additions and 49 deletions

View File

@ -54,7 +54,7 @@ if args.verbose:
else:
logging.basicConfig(format=log_format, level=logging.INFO)
logger = logging.getLogger('cfn-hup')
logger = logging.getLogger('cfntools')
log_file_name = "/var/log/cfn-hup.log"
file_handler = logging.FileHandler(log_file_name)
file_handler.setFormatter(logging.Formatter(log_format))
@ -99,6 +99,13 @@ if not mainconfig.unique_resources_get():
args.config_dir))
exit(1)
metadata_handler = MetadataLoggingHandler(metadata_server_url(),
mainconfig.stack,
mainconfig.unique_resources_get()[0])
logger.addHandler(metadata_handler)
for r in mainconfig.unique_resources_get():
print r
metadata = Metadata(mainconfig.stack,

View File

@ -41,10 +41,13 @@ import rpmUtils.updates as rpmupdates
import rpmUtils.miscutils as rpmutils
import subprocess
import sys
from urllib2 import urlopen
from urllib2 import urlopen, Request
from urlparse import urlparse, urlunparse
logger = logging.getLogger('cfntools')
def to_boolean(b):
val = b.lower().strip() if isinstance(b, basestring) else b
return val in [True, 'true', 'yes', '1', 1]
@ -119,7 +122,7 @@ class Hook(object):
ev_name in self.triggers:
CommandRunner(self.action).run(user=self.runas)
else:
logging.debug('event: {%s, %s, %s} did not match %s' % \
logger.debug('event: {%s, %s, %s} did not match %s' % \
(ev_name, ev_object, ev_resource, self.__str__()))
def __str__(self):
@ -160,7 +163,7 @@ class CommandRunner(object):
Returns:
self
"""
logging.debug("Running command: %s" % self._command)
logger.debug("Running command: %s" % self._command)
cmd = ['su', user, '-c', self._command]
subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
@ -299,14 +302,14 @@ class RpmHelper(object):
if rpms:
cmd = "rpm -U --force --nosignature "
cmd += " ".join(packages)
logging.info("Installing packages: %s" % cmd)
logger.info("Installing packages: %s" % cmd)
else:
cmd = "yum -y install "
cmd += " ".join(packages)
logging.info("Installing packages: %s" % cmd)
logger.info("Installing packages: %s" % cmd)
command = CommandRunner(cmd).run()
if command.status:
logging.warn("Failed to install packages: %s" % cmd)
logger.warn("Failed to install packages: %s" % cmd)
@classmethod
def downgrade(cls, packages, rpms=True):
@ -330,10 +333,10 @@ class RpmHelper(object):
else:
cmd = "yum -y downgrade "
cmd += " ".join(packages)
logging.info("Downgrading packages: %s" % cmd)
logger.info("Downgrading packages: %s" % cmd)
command = Command(cmd).run()
if command.status:
logging.warn("Failed to downgrade packages: %s" % cmd)
logger.warn("Failed to downgrade packages: %s" % cmd)
class PackagesHandler(object):
@ -418,7 +421,7 @@ class PackagesHandler(object):
# FIXME:print non-error, but skipping pkg
pass
elif not RpmHelper.yum_package_available(pkg):
logging.warn("Skipping package '%s'. Not available via yum" % \
logger.warn("Skipping package '%s'. Not available via yum" % \
pkg)
elif not ver:
installs.append(pkg)
@ -495,7 +498,7 @@ class PackagesHandler(object):
for manager, package_entries in packages:
handler = self._package_handler(manager)
if not handler:
logging.warn("Skipping invalid package type: %s" % manager)
logger.warn("Skipping invalid package type: %s" % manager)
else:
handler(self, package_entries)
@ -513,9 +516,9 @@ class FilesHandler(object):
os.makedirs(os.path.dirname(dest))
except OSError as e:
if e.errno == errno.EEXIST:
logging.debug(str(e))
logger.debug(str(e))
else:
logging.exception(e)
logger.exception(e)
if 'content' in meta:
if isinstance(meta['content'], basestring):
@ -529,7 +532,7 @@ class FilesHandler(object):
elif 'source' in meta:
CommandRunner('wget -O %s %s' % (dest, meta['source'])).run()
else:
logging.error('%s %s' % (dest, str(meta)))
logger.error('%s %s' % (dest, str(meta)))
continue
uid = -1
@ -600,9 +603,9 @@ class SourcesHandler(object):
os.makedirs(dest)
except OSError as e:
if e.errno == errno.EEXIST:
logging.debug(str(e))
logger.debug(str(e))
else:
logging.exception(e)
logger.exception(e)
self._decompress(tmp_name, dest)
@ -654,10 +657,10 @@ class ServicesHandler(object):
if "enabled" in properties:
enable = to_boolean(properties["enabled"])
if enable:
logging.info("Enabling service %s" % service)
logger.info("Enabling service %s" % service)
handler(self, service, "enable")
else:
logging.info("Disabling service %s" % service)
logger.info("Disabling service %s" % service)
handler(self, service, "disable")
if "ensureRunning" in properties:
@ -665,10 +668,10 @@ class ServicesHandler(object):
command = handler(self, service, "status")
running = command.status == 0
if ensure_running and not running:
logging.info("Starting service %s" % service)
logger.info("Starting service %s" % service)
handler(self, service, "start")
elif not ensure_running and running:
logging.info("Stopping service %s" % service)
logger.info("Stopping service %s" % service)
handler(self, service, "stop")
def _monitor_service(self, handler, service, properties):
@ -677,10 +680,10 @@ class ServicesHandler(object):
command = handler(self, service, "status")
running = command.status == 0
if ensure_running and not running:
logging.info("Restarting service %s" % service)
logger.warn("Restarting service %s" % service)
start_cmd = handler(self, service, "start")
if start_cmd.status != 0:
logging.warning('Service %s did not start. STDERR: %s' %
logger.warning('Service %s did not start. STDERR: %s' %
(service, start_cmd.stderr))
return
for h in self.hooks:
@ -716,7 +719,7 @@ class ServicesHandler(object):
for manager, service_entries in self._services.iteritems():
handler = self._service_handler(manager)
if not handler:
logging.warn("Skipping invalid service type: %s" % manager)
logger.warn("Skipping invalid service type: %s" % manager)
else:
self._initialize_services(handler, service_entries)
@ -729,11 +732,56 @@ class ServicesHandler(object):
for manager, service_entries in self._services.iteritems():
handler = self._service_handler(manager)
if not handler:
logging.warn("Skipping invalid service type: %s" % manager)
logger.warn("Skipping invalid service type: %s" % manager)
else:
self._monitor_services(handler, service_entries)
def metadata_server_url():
"""
Return the url to the metadata server.
"""
try:
f = open("/var/lib/cloud/data/cfn-metadata-server")
server_url = f.read().strip()
f.close()
if not server_url[-1] == '/':
server_url += '/'
return server_url
except IOError:
return None
class MetadataLoggingHandler(logging.Handler):
def __init__(self, metadata_server_url, stack, resource):
super(MetadataLoggingHandler, self).__init__(level=logging.WARNING)
self.stack = stack
self.resource = resource
if metadata_server_url:
self.events_url = metadata_server_url + 'events/'
else:
logger.info('Metadata server URL not found')
self.events_url = None
def handle(self, record):
if not self.events_url:
return
event = {
'message': record.message,
'stack': self.stack,
'resource': self.resource,
'resource_type': 'AWS::EC2::Instance',
'reason': 'cfntools notification',
}
req = Request(self.events_url, json.dumps(event))
req.headers['Content-Type'] = 'application/json'
try:
urlopen(req)
except:
pass
class MetadataServerConnectionError(Exception):
pass
@ -756,31 +804,23 @@ class Metadata(object):
self._is_local_metadata = True
self._metadata = None
def metadata_server_url(self):
"""
Return the url to the metadata server.
"""
try:
f = open("/var/lib/cloud/data/cfn-metadata-server")
server_url = f.read()
f.close()
except IOError:
return None
url_parts = list(urlparse(server_url))
url_parts[2] = '/stacks/%s/resources/%s' % (self.stack, self.resource)
return urlunparse(url_parts)
def metadata_resource_url(self):
server_url = metadata_server_url()
if not server_url:
return
return server_url + 'stacks/%s/resources/%s' % (self.stack,
self.resource)
def remote_metadata(self):
"""
Connect to the metadata server and retreive the metadata from there.
"""
server_url = self.metadata_server_url()
if not server_url:
url = self.metadata_resource_url()
if not url:
raise MetadataServerConnectionError()
try:
return urlopen(server_url).read()
return urlopen(url).read()
except:
raise MetadataServerConnectionError()

View File

@ -240,17 +240,29 @@ class EngineManager(manager.Manager):
return {'events': [self.parse_event(e) for e in events]}
def event_create(self, context, event):
stack_name = event['stack']
resource_name = event['resource']
stack = db_api.stack_get(None, stack_name)
resource = db_api.resource_get_by_name_and_stack(None, resource_name,
stack.id)
if not resource:
return ['Unknown resource', None]
new_event = {
'name': event['message'],
'resource_status_reason': event['reason'],
'stack_id': stack.id,
'logical_resource_id': resource.name,
'physical_resource_id': None,
'resource_type': event['resource_type'],
'resource_properties': {},
}
try:
result = db_api.event_create(None, event)
event['id'] = result.id
return [None, event]
result = db_api.event_create(None, new_event)
new_event['id'] = result.id
return [None, new_event]
except Exception as ex:
logger.warn('db error %s' % str(ex))
try:
# This returns the error message without the entire SQL request
msg = ex.inner_exception.orig[1]
except:
msg = 'Error creating event'
msg = 'Error creating event'
return [msg, None]
def metadata_register_address(self, context, url):