]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
cloudwatch: set HA template to send watch data
authorTomas Sedovic <tomas@sedovic.cz>
Wed, 23 May 2012 15:59:41 +0000 (17:59 +0200)
committerTomas Sedovic <tomas@sedovic.cz>
Fri, 25 May 2012 12:20:13 +0000 (14:20 +0200)
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>
18 files changed:
MANIFEST.in
heat/cfntools/cfn-hup
heat/cfntools/cfn-push-stats
heat/cfntools/cfn_helper.py
heat/common/exception.py
heat/engine/checkeddict.py
heat/engine/cloud_watch.py
heat/engine/escalation_policy.py [new file with mode: 0644]
heat/engine/parser.py
heat/engine/resources.py
heat/engine/wait_condition.py
heat/jeos/F16-i386-cfntools-jeos.tdl
heat/jeos/F16-x86_64-cfntools-jeos.tdl
heat/jeos/F17-i386-cfntools-jeos.tdl
heat/jeos/F17-x86_64-cfntools-jeos.tdl
heat/jeos/U10-amd64-cfntools-jeos.tdl
heat/utils.py
templates/WordPress_Single_Instance_With_HA.template

index 3e981ef6269a8f7df6cffbba0662a75776ff1c71..c6886005d079705b80842676560c538b710a0aa1 100644 (file)
@@ -20,6 +20,7 @@ include heat/cfntools/cfn-init
 include heat/cfntools/cfn-hup
 include heat/cfntools/cfn-signal
 include heat/cfntools/cfn-get-metadata
+include heat/cfntools/cfn-push-stats
 include heat/cloudinit/config
 include heat/cloudinit/part-handler.py
 include heat/db/sqlalchemy/migrate_repo/migrate.cfg
index b81a727b1ce6d6eedaa248ff1c18cb0beeb19ed2..9aa2613980d1abaceb8ac6387671a801881cf4c8 100755 (executable)
@@ -100,12 +100,6 @@ if not mainconfig.unique_resources_get():
     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,
index 996adbe718400ce41b3f9ff02f5875477925b164..148f41ac2714badda792bcd7b6cd2a60328dacc6 100755 (executable)
@@ -17,11 +17,20 @@ Implements cfn-signal CloudFormation functionality
 """
 import argparse
 import logging
-import psutil
 import os
 import random
 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
 #                              credentials.
@@ -84,14 +93,10 @@ parser.add_argument('--disk-units', required=False, default='megabytes',
                     help='Specifies units for disk metrics.')
 parser.add_argument('--disk-path', required=False, default='/',
                     help='Selects the disk by the path on which to report.')
-parser.add_argument('url',
-                    help='the url to post to')
+parser.add_argument('--watch', required=True,
+                    help='the name of the watch to post to.')
 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'}
 
 # service failure
@@ -150,9 +155,15 @@ if args.disk_space_avail:
 
 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" % \
-           (json.dumps(data), args.url)
+           (json.dumps(data), url)
 
 cmd = CommandRunner(cmd_str)
 cmd.run()
-print cmd.stdout
+logger.info(cmd.stdout)
index e6cd0ad11017a339b73dca55f11daff8bd4a011f..4429ee1c697d97fc2dd7f49f44129d5bfe1d7001 100644 (file)
@@ -767,36 +767,6 @@ def metadata_server_url():
         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
 
index dd2eed5ee90525c43c6f17fc2b23f5044d5a6dbc..be718ecd1094bc474612f8e2f360a0ceef214f6b 100644 (file)
@@ -19,6 +19,8 @@
 
 import functools
 import urlparse
+import sys
+
 from heat.openstack.common.exception import *
 
 
index 0a0cdb04889a8d89f1aff2a7b158eff4195a6250..ce9a41cfe979a807066fe83a17d27a5760203cf6 100644 (file)
@@ -79,6 +79,10 @@ class CheckedDict(collections.MutableMapping):
                 if num > maxn or num < minn:
                     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':
                 sp = value.split(',')
                 if not isinstance(sp, list):
index 6f5d38c6a6def889717424233f9795272170f6d3..8e1d4a1953b6c14babcd6d91c5417c78249cfb17 100644 (file)
@@ -30,6 +30,7 @@ class CloudWatchAlarm(Resource):
                          'AllowedValues': ['GreaterThanOrEqualToThreshold',
                          'GreaterThanThreshold', 'LessThanThreshold',
                          'LessThanOrEqualToThreshold']},
+        'AlarmDescription': {'Type': 'String'},
         'EvaluationPeriods': {'Type': 'String'},
         'MetricName': {'Type': 'String'},
         'Namespace': {'Type': 'String'},
@@ -37,6 +38,7 @@ class CloudWatchAlarm(Resource):
         'Statistic': {'Type': 'String',
                       'AllowedValues': ['SampleCount', 'Average', 'Sum',
                                         'Minimum', 'Maximum']},
+        'AlarmActions': {'Type': 'List'},
         'Threshold': {'Type': 'String'},
         'Units': {'Type': 'String',
                   'AllowedValues': ['Seconds', 'Microseconds', 'Milliseconds',
@@ -90,3 +92,6 @@ class CloudWatchAlarm(Resource):
 
     def FnGetRefId(self):
         return unicode(self.name)
+
+    def strict_dependency(self):
+        return False
diff --git a/heat/engine/escalation_policy.py b/heat/engine/escalation_policy.py
new file mode 100644 (file)
index 0000000..33b1d76
--- /dev/null
@@ -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
index 56fc524cdbc2269bc36c325f9763e2a2ceb66362..45d916c412d51ec7ba17933add4885ce92a1416a 100644 (file)
@@ -21,6 +21,7 @@ from heat.common import exception
 from heat.engine import checkeddict
 from heat.engine import cloud_watch
 from heat.engine import eip
+from heat.engine import escalation_policy
 from heat.engine import instance
 from heat.engine import resources
 from heat.engine import security_group
@@ -39,6 +40,8 @@ logger = logging.getLogger(__file__)
     'AWS::EC2::EIP': eip.ElasticIp,
     'AWS::EC2::EIPAssociation': eip.ElasticIpAssociation,
     'AWS::EC2::SecurityGroup': security_group.SecurityGroup,
+    'AWS::CloudWatch::Alarm': cloud_watch.CloudWatchAlarm,
+    'HEAT::Recovery::EscalationPolicy': escalation_policy.EscalationPolicy,
     'AWS::CloudFormation::WaitConditionHandle':
         wait_condition.WaitConditionHandle,
     'AWS::CloudFormation::WaitCondition': wait_condition.WaitCondition,
@@ -110,7 +113,6 @@ class Stack(object):
         # TODO(sdake) Should return line number of invalid reference
 
         response = None
-
         try:
             order = self.get_create_order()
         except KeyError:
@@ -289,8 +291,9 @@ class Stack(object):
                     pass
                 elif i == 'Ref':
                     #print '%s Refences %s' % (r.name, s[i])
-                    r.depends_on.append(s[i])
-                elif i == 'DependsOn' or i == 'Ref':
+                    if r.strict_dependency():
+                        r.depends_on.append(s[i])
+                elif i == 'DependsOn':
                     #print '%s DependsOn on %s' % (r.name, s[i])
                     r.depends_on.append(s[i])
                 else:
index 6a36e40dab1f938a3452f4c5b641f60b7e3316d5..02a0adce6e07a9153e9a66e61fc80aa932891027 100644 (file)
@@ -187,7 +187,7 @@ class Resource(object):
         http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/ \
         intrinsic-function-reference-getatt.html
         '''
-        raise exception.InvalidTemplateAttribute(resource=self.name, key=key)
+        return unicode(self.name)
 
     def FnBase64(self, data):
         '''
@@ -196,6 +196,12 @@ class Resource(object):
         '''
         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):
     properties_schema = {}
index 2371bc6144e44a516277347d4760ce1562c9209c..b847a0262f8812a6177c54e3afbf8c24c0fa3e22 100644 (file)
@@ -33,6 +33,7 @@ class WaitConditionHandle(Resource):
     then the cfn-signal will use this url to post to and
     WaitCondition will poll it to see if has been written to.
     '''
+    properties_schema = {}
 
     def __init__(self, name, json_snippet, stack):
         super(WaitConditionHandle, self).__init__(name, json_snippet, stack)
index d7f7b431ecdb675557bbe02122d0db63ab7f8d1b..0d110218304924a8b5e434058d245d73c361c77e 100644 (file)
@@ -23,5 +23,6 @@ EOF
     <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-get-metadata'  type='base64'></file>
+    <file name='/opt/aws/bin/cfn-push-stats'  type='base64'></file>
   </files>
 </template>
index 7b618a7591375f2d7207d8ca30cdb84df40ddbff..dc135adfb8bce9aa6e66d3311758ac2944794a68 100644 (file)
@@ -23,5 +23,6 @@ EOF
     <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-get-metadata'  type='base64'></file>
+    <file name='/opt/aws/bin/cfn-push-stats'  type='base64'></file>
   </files>
 </template>
index 9749e42aed0af7a78a19489b8230127051fa9729..aff94e9f179abf5f9cebb6368ea6e54a3165277d 100644 (file)
@@ -23,5 +23,6 @@ EOF
     <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-get-metadata'  type='base64'></file>
+    <file name='/opt/aws/bin/cfn-push-stats'  type='base64'></file>
   </files>
 </template>
index 17e8869d3e83387a008c61a54bf134f7eb92dd77..583bf9de48ec6b564cdb85e9d69264ce9484d1ad 100644 (file)
@@ -23,6 +23,6 @@ EOF
     <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-get-metadata'  type='base64'></file>
+    <file name='/opt/aws/bin/cfn-push-stats'  type='base64'></file>
   </files>
-
 </template>
index 5ea4db184b3a9ce9bfc1d4e512ccfd8c3401a947..316e39de26d7b5021d1205ed1dbdb0fe73a55515 100644 (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_helper.py'  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>
 </template>
index d008a10d4401a4ac0719a4690c5aedac68b79a3e..61d528b071b1c575231f5eac29a355730fdefa1a 100644 (file)
@@ -140,8 +140,8 @@ def jeos_create(options, arguments, jeos_path, cfntools_path):
     # and injecting them into the TDL at the appropriate place
     if instance_type == 'cfntools':
         tdl_xml = etree.parse(tdl_path)
-        cfn_tools = ['cfn-init', 'cfn-hup', 'cfn-signal', \
-                    'cfn-get-metadata', 'cfn_helper.py']
+        cfn_tools = ['cfn-init', 'cfn-hup', 'cfn-signal',
+                    'cfn-get-metadata', 'cfn_helper.py', 'cfn-push-stats']
         for cfnname in cfn_tools:
             f = open('%s/%s' % (cfntools_path, cfnname), 'r')
             cfscript_e64 = base64.b64encode(f.read())
index 34286dffd784d814493617ac06675df72201b8df..653668dbb2ec5ee9a2079b49f83375898c365248 100644 (file)
     "WebServerRestartPolicy" : {
       "Type" : "HEAT::Recovery::EscalationPolicy",
       "Properties" : {
-        "Instance" : { "Ref" : "WikiDatabase" },
+        "Instance" : { "Ref" : "WikiDatabase" }
       }
     },
     "HttpFailureAlarm": {
      "Type": "AWS::CloudWatch::Alarm",
      "Properties": {
         "AlarmDescription": "Restart the WikiDatabase if httpd fails > 3 times in 10 minutes",
-        "MetricName": "ProcessRestartCount",
-        "Namespace": "HEAT",
+        "MetricName": "ServiceFailure",
+        "Namespace": "system/linux",
         "Statistic": "Maximum",
         "Period": "300",
-        "EvaluationPeriods": "2",
-        "Threshold": "3",
+        "EvaluationPeriods": "1",
+        "Threshold": "2",
         "AlarmActions": [ { "Ref": "WebServerRestartPolicy" } ],
         "ComparisonOperator": "GreaterThanThreshold"
       }
         "AWS::CloudFormation::Init" : {
           "config" : {
             "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" : {
                 "content" : { "Fn::Join" : ["", [
                   "AWSAccessKeyId=GobbleGobble\n",
               "/etc/cfn/notify-on-httpd-restarted" : {
                 "content" : { "Fn::Join" : ["", [
                 "#!/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",
                 "owner"   : "root",