cloudwatch: set HA template to send watch data

The Wordpress HA template is now able to utilize the cfn-push-stats and send
alarm messages to the metadata server.

Change-Id: I52b615d3401dc2665e2b30e4a925d61ed204c827
Signed-off-by: Tomas Sedovic <tomas@sedovic.cz>
This commit is contained in:
Tomas Sedovic 2012-05-23 17:59:41 +02:00
parent 1d5aec19d7
commit f3913a9a3e
18 changed files with 125 additions and 83 deletions

View File

@ -20,6 +20,7 @@ include heat/cfntools/cfn-init
include heat/cfntools/cfn-hup include heat/cfntools/cfn-hup
include heat/cfntools/cfn-signal include heat/cfntools/cfn-signal
include heat/cfntools/cfn-get-metadata include heat/cfntools/cfn-get-metadata
include heat/cfntools/cfn-push-stats
include heat/cloudinit/config include heat/cloudinit/config
include heat/cloudinit/part-handler.py include heat/cloudinit/part-handler.py
include heat/db/sqlalchemy/migrate_repo/migrate.cfg include heat/db/sqlalchemy/migrate_repo/migrate.cfg

View File

@ -100,12 +100,6 @@ if not mainconfig.unique_resources_get():
exit(1) 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(): for r in mainconfig.unique_resources_get():
print r print r
metadata = Metadata(mainconfig.stack, metadata = Metadata(mainconfig.stack,

View File

@ -17,11 +17,20 @@ Implements cfn-signal CloudFormation functionality
""" """
import argparse import argparse
import logging import logging
import psutil
import os import os
import random import random
import sys import sys
log_format = '%(levelname)s [%(asctime)s] %(message)s'
logging.basicConfig(format=log_format, level=logging.DEBUG)
logger = logging.getLogger('cfn-push-stats')
try:
import psutil
except ImportError:
logger.warn("psutil not available. If you want process and memory "
"statistics, you need to install it.")
# #
# --aws-credential-file=PATH Specifies the location of the file with AWS # --aws-credential-file=PATH Specifies the location of the file with AWS
# credentials. # credentials.
@ -84,14 +93,10 @@ parser.add_argument('--disk-units', required=False, default='megabytes',
help='Specifies units for disk metrics.') help='Specifies units for disk metrics.')
parser.add_argument('--disk-path', required=False, default='/', parser.add_argument('--disk-path', required=False, default='/',
help='Selects the disk by the path on which to report.') help='Selects the disk by the path on which to report.')
parser.add_argument('url', parser.add_argument('--watch', required=True,
help='the url to post to') help='the name of the watch to post to.')
args = parser.parse_args() args = parser.parse_args()
log_format = '%(levelname)s [%(asctime)s] %(message)s'
logging.basicConfig(format=log_format, level=logging.DEBUG)
logger = logging.getLogger('cfn-push-stats')
data = {'Namespace': 'system/linux'} data = {'Namespace': 'system/linux'}
# service failure # service failure
@ -150,9 +155,15 @@ if args.disk_space_avail:
logger.info(str(data)) logger.info(str(data))
server_url = metadata_server_url()
if not server_url:
logger.error('can not get the metadata_server_url')
exit(1)
url = '%sstats/%s/data/' % (server_url, args.watch)
cmd_str = "curl -X POST -H \'Content-Type:\' --data-binary \'%s\' %s" % \ cmd_str = "curl -X POST -H \'Content-Type:\' --data-binary \'%s\' %s" % \
(json.dumps(data), args.url) (json.dumps(data), url)
cmd = CommandRunner(cmd_str) cmd = CommandRunner(cmd_str)
cmd.run() cmd.run()
print cmd.stdout logger.info(cmd.stdout)

View File

@ -767,36 +767,6 @@ def metadata_server_url():
return None 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): class MetadataServerConnectionError(Exception):
pass pass

View File

@ -19,6 +19,8 @@
import functools import functools
import urlparse import urlparse
import sys
from heat.openstack.common.exception import * from heat.openstack.common.exception import *

View File

@ -79,6 +79,10 @@ class CheckedDict(collections.MutableMapping):
if num > maxn or num < minn: if num > maxn or num < minn:
raise ValueError('%s is out of range' % key) raise ValueError('%s is out of range' % key)
elif self.data[key]['Type'] == 'List':
if not isinstance(value, list):
raise ValueError('%s Value must be a list' % (key))
elif self.data[key]['Type'] == 'CommaDelimitedList': elif self.data[key]['Type'] == 'CommaDelimitedList':
sp = value.split(',') sp = value.split(',')
if not isinstance(sp, list): if not isinstance(sp, list):

View File

@ -30,6 +30,7 @@ class CloudWatchAlarm(Resource):
'AllowedValues': ['GreaterThanOrEqualToThreshold', 'AllowedValues': ['GreaterThanOrEqualToThreshold',
'GreaterThanThreshold', 'LessThanThreshold', 'GreaterThanThreshold', 'LessThanThreshold',
'LessThanOrEqualToThreshold']}, 'LessThanOrEqualToThreshold']},
'AlarmDescription': {'Type': 'String'},
'EvaluationPeriods': {'Type': 'String'}, 'EvaluationPeriods': {'Type': 'String'},
'MetricName': {'Type': 'String'}, 'MetricName': {'Type': 'String'},
'Namespace': {'Type': 'String'}, 'Namespace': {'Type': 'String'},
@ -37,6 +38,7 @@ class CloudWatchAlarm(Resource):
'Statistic': {'Type': 'String', 'Statistic': {'Type': 'String',
'AllowedValues': ['SampleCount', 'Average', 'Sum', 'AllowedValues': ['SampleCount', 'Average', 'Sum',
'Minimum', 'Maximum']}, 'Minimum', 'Maximum']},
'AlarmActions': {'Type': 'List'},
'Threshold': {'Type': 'String'}, 'Threshold': {'Type': 'String'},
'Units': {'Type': 'String', 'Units': {'Type': 'String',
'AllowedValues': ['Seconds', 'Microseconds', 'Milliseconds', 'AllowedValues': ['Seconds', 'Microseconds', 'Milliseconds',
@ -90,3 +92,6 @@ class CloudWatchAlarm(Resource):
def FnGetRefId(self): def FnGetRefId(self):
return unicode(self.name) return unicode(self.name)
def strict_dependency(self):
return False

View File

@ -0,0 +1,64 @@
# 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 eventlet
import logging
import json
import os
from heat.common import exception
from heat.db import api as db_api
from heat.engine.resources import Resource
logger = logging.getLogger('heat.engine.escalation_policy')
class EscalationPolicy(Resource):
properties_schema = {
'Instance': {'Type': 'String'},
}
def __init__(self, name, json_snippet, stack):
super(EscalationPolicy, self).__init__(name, json_snippet, stack)
self.instance_id = ''
def validate(self):
'''
Validate the Properties
'''
return Resource.validate(self)
def create(self):
if self.state != None:
return
self.state_set(self.CREATE_IN_PROGRESS)
Resource.create(self)
self.state_set(self.CREATE_COMPLETE)
def delete(self):
if self.state == self.DELETE_IN_PROGRESS or \
self.state == self.DELETE_COMPLETE:
return
self.state_set(self.DELETE_IN_PROGRESS)
Resource.delete(self)
self.state_set(self.DELETE_COMPLETE)
def FnGetRefId(self):
return unicode(self.name)
def strict_dependency(self):
return False

View File

@ -21,6 +21,7 @@ from heat.common import exception
from heat.engine import checkeddict from heat.engine import checkeddict
from heat.engine import cloud_watch from heat.engine import cloud_watch
from heat.engine import eip from heat.engine import eip
from heat.engine import escalation_policy
from heat.engine import instance from heat.engine import instance
from heat.engine import resources from heat.engine import resources
from heat.engine import security_group from heat.engine import security_group
@ -39,6 +40,8 @@ logger = logging.getLogger(__file__)
'AWS::EC2::EIP': eip.ElasticIp, 'AWS::EC2::EIP': eip.ElasticIp,
'AWS::EC2::EIPAssociation': eip.ElasticIpAssociation, 'AWS::EC2::EIPAssociation': eip.ElasticIpAssociation,
'AWS::EC2::SecurityGroup': security_group.SecurityGroup, 'AWS::EC2::SecurityGroup': security_group.SecurityGroup,
'AWS::CloudWatch::Alarm': cloud_watch.CloudWatchAlarm,
'HEAT::Recovery::EscalationPolicy': escalation_policy.EscalationPolicy,
'AWS::CloudFormation::WaitConditionHandle': 'AWS::CloudFormation::WaitConditionHandle':
wait_condition.WaitConditionHandle, wait_condition.WaitConditionHandle,
'AWS::CloudFormation::WaitCondition': wait_condition.WaitCondition, 'AWS::CloudFormation::WaitCondition': wait_condition.WaitCondition,
@ -110,7 +113,6 @@ class Stack(object):
# TODO(sdake) Should return line number of invalid reference # TODO(sdake) Should return line number of invalid reference
response = None response = None
try: try:
order = self.get_create_order() order = self.get_create_order()
except KeyError: except KeyError:
@ -289,8 +291,9 @@ class Stack(object):
pass pass
elif i == 'Ref': elif i == 'Ref':
#print '%s Refences %s' % (r.name, s[i]) #print '%s Refences %s' % (r.name, s[i])
r.depends_on.append(s[i]) if r.strict_dependency():
elif i == 'DependsOn' or i == 'Ref': r.depends_on.append(s[i])
elif i == 'DependsOn':
#print '%s DependsOn on %s' % (r.name, s[i]) #print '%s DependsOn on %s' % (r.name, s[i])
r.depends_on.append(s[i]) r.depends_on.append(s[i])
else: else:

View File

@ -187,7 +187,7 @@ class Resource(object):
http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/ \ http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/ \
intrinsic-function-reference-getatt.html intrinsic-function-reference-getatt.html
''' '''
raise exception.InvalidTemplateAttribute(resource=self.name, key=key) return unicode(self.name)
def FnBase64(self, data): def FnBase64(self, data):
''' '''
@ -196,6 +196,12 @@ class Resource(object):
''' '''
return base64.b64encode(data) return base64.b64encode(data)
def strict_dependency(self):
'''
If True, this resource must be created before it can be referenced.
'''
return True
class GenericResource(Resource): class GenericResource(Resource):
properties_schema = {} properties_schema = {}

View File

@ -33,6 +33,7 @@ class WaitConditionHandle(Resource):
then the cfn-signal will use this url to post to and then the cfn-signal will use this url to post to and
WaitCondition will poll it to see if has been written to. WaitCondition will poll it to see if has been written to.
''' '''
properties_schema = {}
def __init__(self, name, json_snippet, stack): def __init__(self, name, json_snippet, stack):
super(WaitConditionHandle, self).__init__(name, json_snippet, stack) super(WaitConditionHandle, self).__init__(name, json_snippet, stack)

View File

@ -23,5 +23,6 @@ EOF
<file name='/opt/aws/bin/cfn-signal' type='base64'></file> <file name='/opt/aws/bin/cfn-signal' type='base64'></file>
<file name='/opt/aws/bin/cfn_helper.py' type='base64'></file> <file name='/opt/aws/bin/cfn_helper.py' type='base64'></file>
<file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file> <file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file>
<file name='/opt/aws/bin/cfn-push-stats' type='base64'></file>
</files> </files>
</template> </template>

View File

@ -23,5 +23,6 @@ EOF
<file name='/opt/aws/bin/cfn-signal' type='base64'></file> <file name='/opt/aws/bin/cfn-signal' type='base64'></file>
<file name='/opt/aws/bin/cfn_helper.py' type='base64'></file> <file name='/opt/aws/bin/cfn_helper.py' type='base64'></file>
<file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file> <file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file>
<file name='/opt/aws/bin/cfn-push-stats' type='base64'></file>
</files> </files>
</template> </template>

View File

@ -23,5 +23,6 @@ EOF
<file name='/opt/aws/bin/cfn-signal' type='base64'></file> <file name='/opt/aws/bin/cfn-signal' type='base64'></file>
<file name='/opt/aws/bin/cfn_helper.py' type='base64'></file> <file name='/opt/aws/bin/cfn_helper.py' type='base64'></file>
<file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file> <file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file>
<file name='/opt/aws/bin/cfn-push-stats' type='base64'></file>
</files> </files>
</template> </template>

View File

@ -23,6 +23,6 @@ EOF
<file name='/opt/aws/bin/cfn-signal' type='base64'></file> <file name='/opt/aws/bin/cfn-signal' type='base64'></file>
<file name='/opt/aws/bin/cfn_helper.py' type='base64'></file> <file name='/opt/aws/bin/cfn_helper.py' type='base64'></file>
<file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file> <file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file>
<file name='/opt/aws/bin/cfn-push-stats' type='base64'></file>
</files> </files>
</template> </template>

View File

@ -20,5 +20,6 @@ apt-get -y upgrade;apt-get -y install cloud-init;/usr/sbin/useradd -m ec2-user;e
<file name='/opt/aws/bin/cfn-signal' type='base64'></file> <file name='/opt/aws/bin/cfn-signal' type='base64'></file>
<file name='/opt/aws/bin/cfn_helper.py' type='base64'></file> <file name='/opt/aws/bin/cfn_helper.py' type='base64'></file>
<file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file> <file name='/opt/aws/bin/cfn-get-metadata' type='base64'></file>
<file name='/opt/aws/bin/cfn-push-stats' type='base64'></file>
</files> </files>
</template> </template>

View File

@ -140,8 +140,8 @@ def jeos_create(options, arguments, jeos_path, cfntools_path):
# and injecting them into the TDL at the appropriate place # and injecting them into the TDL at the appropriate place
if instance_type == 'cfntools': if instance_type == 'cfntools':
tdl_xml = etree.parse(tdl_path) tdl_xml = etree.parse(tdl_path)
cfn_tools = ['cfn-init', 'cfn-hup', 'cfn-signal', \ cfn_tools = ['cfn-init', 'cfn-hup', 'cfn-signal',
'cfn-get-metadata', 'cfn_helper.py'] 'cfn-get-metadata', 'cfn_helper.py', 'cfn-push-stats']
for cfnname in cfn_tools: for cfnname in cfn_tools:
f = open('%s/%s' % (cfntools_path, cfnname), 'r') f = open('%s/%s' % (cfntools_path, cfnname), 'r')
cfscript_e64 = base64.b64encode(f.read()) cfscript_e64 = base64.b64encode(f.read())

View File

@ -100,19 +100,19 @@
"WebServerRestartPolicy" : { "WebServerRestartPolicy" : {
"Type" : "HEAT::Recovery::EscalationPolicy", "Type" : "HEAT::Recovery::EscalationPolicy",
"Properties" : { "Properties" : {
"Instance" : { "Ref" : "WikiDatabase" }, "Instance" : { "Ref" : "WikiDatabase" }
} }
}, },
"HttpFailureAlarm": { "HttpFailureAlarm": {
"Type": "AWS::CloudWatch::Alarm", "Type": "AWS::CloudWatch::Alarm",
"Properties": { "Properties": {
"AlarmDescription": "Restart the WikiDatabase if httpd fails > 3 times in 10 minutes", "AlarmDescription": "Restart the WikiDatabase if httpd fails > 3 times in 10 minutes",
"MetricName": "ProcessRestartCount", "MetricName": "ServiceFailure",
"Namespace": "HEAT", "Namespace": "system/linux",
"Statistic": "Maximum", "Statistic": "Maximum",
"Period": "300", "Period": "300",
"EvaluationPeriods": "2", "EvaluationPeriods": "1",
"Threshold": "3", "Threshold": "2",
"AlarmActions": [ { "Ref": "WebServerRestartPolicy" } ], "AlarmActions": [ { "Ref": "WebServerRestartPolicy" } ],
"ComparisonOperator": "GreaterThanThreshold" "ComparisonOperator": "GreaterThanThreshold"
} }
@ -123,31 +123,6 @@
"AWS::CloudFormation::Init" : { "AWS::CloudFormation::Init" : {
"config" : { "config" : {
"files" : { "files" : {
"/opt/aws/bin/cfn-init" : {
"source" : "https://raw.github.com/heat-api/heat/master/heat/cfntools/cfn-init",
"mode" : "000755",
"owner" : "root",
"group" : "root"
},
"/opt/aws/bin/cfn-hup" : {
"source" : "https://raw.github.com/heat-api/heat/master/heat/cfntools/cfn-hup",
"mode" : "000755",
"owner" : "root",
"group" : "root"
},
"/opt/aws/bin/cfn-signal" : {
"source" : "https://raw.github.com/heat-api/heat/master/heat/cfntools/cfn-signal",
"mode" : "000755",
"owner" : "root",
"group" : "root"
},
"/opt/aws/bin/cfn_helper.py" : {
"source" : "https://raw.github.com/heat-api/heat/master/heat/cfntools/cfn_helper.py",
"mode" : "000644",
"owner" : "root",
"group" : "root"
},
"/etc/cfn/cfn-credentials" : { "/etc/cfn/cfn-credentials" : {
"content" : { "Fn::Join" : ["", [ "content" : { "Fn::Join" : ["", [
"AWSAccessKeyId=GobbleGobble\n", "AWSAccessKeyId=GobbleGobble\n",
@ -174,7 +149,9 @@
"/etc/cfn/notify-on-httpd-restarted" : { "/etc/cfn/notify-on-httpd-restarted" : {
"content" : { "Fn::Join" : ["", [ "content" : { "Fn::Join" : ["", [
"#!/bin/sh\n", "#!/bin/sh\n",
"logger -t cfn-event 'http got restarted'\n" "/opt/aws/bin/cfn-push-stats --watch ",
{ "Ref" : "HttpFailureAlarm" },
" --service-failure\n"
]]}, ]]},
"mode" : "000700", "mode" : "000700",
"owner" : "root", "owner" : "root",