1# Copyright (c) 2010 Reza Lotun http://reza.lotun.name 2# 3# Permission is hereby granted, free of charge, to any person obtaining a 4# copy of this software and associated documentation files (the 5# "Software"), to deal in the Software without restriction, including 6# without limitation the rights to use, copy, modify, merge, publish, dis- 7# tribute, sublicense, and/or sell copies of the Software, and to permit 8# persons to whom the Software is furnished to do so, subject to the fol- 9# lowing conditions: 10# 11# The above copyright notice and this permission notice shall be included 12# in all copies or substantial portions of the Software. 13# 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20# IN THE SOFTWARE. 21# 22 23from datetime import datetime 24from boto.ec2.cloudwatch.listelement import ListElement 25from boto.ec2.cloudwatch.dimension import Dimension 26from boto.compat import json 27from boto.compat import six 28 29 30class MetricAlarms(list): 31 def __init__(self, connection=None): 32 """ 33 Parses a list of MetricAlarms. 34 """ 35 list.__init__(self) 36 self.connection = connection 37 38 def startElement(self, name, attrs, connection): 39 if name == 'member': 40 metric_alarm = MetricAlarm(connection) 41 self.append(metric_alarm) 42 return metric_alarm 43 44 def endElement(self, name, value, connection): 45 pass 46 47 48class MetricAlarm(object): 49 50 OK = 'OK' 51 ALARM = 'ALARM' 52 INSUFFICIENT_DATA = 'INSUFFICIENT_DATA' 53 54 _cmp_map = { 55 '>=': 'GreaterThanOrEqualToThreshold', 56 '>': 'GreaterThanThreshold', 57 '<': 'LessThanThreshold', 58 '<=': 'LessThanOrEqualToThreshold', 59 } 60 _rev_cmp_map = dict((v, k) for (k, v) in six.iteritems(_cmp_map)) 61 62 def __init__(self, connection=None, name=None, metric=None, 63 namespace=None, statistic=None, comparison=None, 64 threshold=None, period=None, evaluation_periods=None, 65 unit=None, description='', dimensions=None, 66 alarm_actions=None, insufficient_data_actions=None, 67 ok_actions=None): 68 """ 69 Creates a new Alarm. 70 71 :type name: str 72 :param name: Name of alarm. 73 74 :type metric: str 75 :param metric: Name of alarm's associated metric. 76 77 :type namespace: str 78 :param namespace: The namespace for the alarm's metric. 79 80 :type statistic: str 81 :param statistic: The statistic to apply to the alarm's associated 82 metric. 83 Valid values: SampleCount|Average|Sum|Minimum|Maximum 84 85 :type comparison: str 86 :param comparison: Comparison used to compare statistic with threshold. 87 Valid values: >= | > | < | <= 88 89 :type threshold: float 90 :param threshold: The value against which the specified statistic 91 is compared. 92 93 :type period: int 94 :param period: The period in seconds over which the specified 95 statistic is applied. 96 97 :type evaluation_periods: int 98 :param evaluation_periods: The number of periods over which data is 99 compared to the specified threshold. 100 101 :type unit: str 102 :param unit: Allowed Values are: 103 Seconds|Microseconds|Milliseconds, 104 Bytes|Kilobytes|Megabytes|Gigabytes|Terabytes, 105 Bits|Kilobits|Megabits|Gigabits|Terabits, 106 Percent|Count| 107 Bytes/Second|Kilobytes/Second|Megabytes/Second| 108 Gigabytes/Second|Terabytes/Second, 109 Bits/Second|Kilobits/Second|Megabits/Second, 110 Gigabits/Second|Terabits/Second|Count/Second|None 111 112 :type description: str 113 :param description: Description of MetricAlarm 114 115 :type dimensions: dict 116 :param dimensions: A dictionary of dimension key/values where 117 the key is the dimension name and the value 118 is either a scalar value or an iterator 119 of values to be associated with that 120 dimension. 121 Example: { 122 'InstanceId': ['i-0123456', 'i-0123457'], 123 'LoadBalancerName': 'test-lb' 124 } 125 126 :type alarm_actions: list of strs 127 :param alarm_actions: A list of the ARNs of the actions to take in 128 ALARM state 129 130 :type insufficient_data_actions: list of strs 131 :param insufficient_data_actions: A list of the ARNs of the actions to 132 take in INSUFFICIENT_DATA state 133 134 :type ok_actions: list of strs 135 :param ok_actions: A list of the ARNs of the actions to take in OK state 136 """ 137 self.name = name 138 self.connection = connection 139 self.metric = metric 140 self.namespace = namespace 141 self.statistic = statistic 142 if threshold is not None: 143 self.threshold = float(threshold) 144 else: 145 self.threshold = None 146 self.comparison = self._cmp_map.get(comparison) 147 if period is not None: 148 self.period = int(period) 149 else: 150 self.period = None 151 if evaluation_periods is not None: 152 self.evaluation_periods = int(evaluation_periods) 153 else: 154 self.evaluation_periods = None 155 self.actions_enabled = None 156 self.alarm_arn = None 157 self.last_updated = None 158 self.description = description 159 self.dimensions = dimensions 160 self.state_reason = None 161 self.state_value = None 162 self.unit = unit 163 self.alarm_actions = alarm_actions 164 self.insufficient_data_actions = insufficient_data_actions 165 self.ok_actions = ok_actions 166 167 def __repr__(self): 168 return 'MetricAlarm:%s[%s(%s) %s %s]' % (self.name, self.metric, 169 self.statistic, 170 self.comparison, 171 self.threshold) 172 173 def startElement(self, name, attrs, connection): 174 if name == 'AlarmActions': 175 self.alarm_actions = ListElement() 176 return self.alarm_actions 177 elif name == 'InsufficientDataActions': 178 self.insufficient_data_actions = ListElement() 179 return self.insufficient_data_actions 180 elif name == 'OKActions': 181 self.ok_actions = ListElement() 182 return self.ok_actions 183 elif name == 'Dimensions': 184 self.dimensions = Dimension() 185 return self.dimensions 186 else: 187 pass 188 189 def endElement(self, name, value, connection): 190 if name == 'ActionsEnabled': 191 self.actions_enabled = value 192 elif name == 'AlarmArn': 193 self.alarm_arn = value 194 elif name == 'AlarmConfigurationUpdatedTimestamp': 195 self.last_updated = value 196 elif name == 'AlarmDescription': 197 self.description = value 198 elif name == 'AlarmName': 199 self.name = value 200 elif name == 'ComparisonOperator': 201 setattr(self, 'comparison', self._rev_cmp_map[value]) 202 elif name == 'EvaluationPeriods': 203 self.evaluation_periods = int(value) 204 elif name == 'MetricName': 205 self.metric = value 206 elif name == 'Namespace': 207 self.namespace = value 208 elif name == 'Period': 209 self.period = int(value) 210 elif name == 'StateReason': 211 self.state_reason = value 212 elif name == 'StateValue': 213 self.state_value = value 214 elif name == 'Statistic': 215 self.statistic = value 216 elif name == 'Threshold': 217 self.threshold = float(value) 218 elif name == 'Unit': 219 self.unit = value 220 else: 221 setattr(self, name, value) 222 223 def set_state(self, value, reason, data=None): 224 """ Temporarily sets the state of an alarm. 225 226 :type value: str 227 :param value: OK | ALARM | INSUFFICIENT_DATA 228 229 :type reason: str 230 :param reason: Reason alarm set (human readable). 231 232 :type data: str 233 :param data: Reason data (will be jsonified). 234 """ 235 return self.connection.set_alarm_state(self.name, reason, value, data) 236 237 def update(self): 238 return self.connection.update_alarm(self) 239 240 def enable_actions(self): 241 return self.connection.enable_alarm_actions([self.name]) 242 243 def disable_actions(self): 244 return self.connection.disable_alarm_actions([self.name]) 245 246 def describe_history(self, start_date=None, end_date=None, max_records=None, 247 history_item_type=None, next_token=None): 248 return self.connection.describe_alarm_history(self.name, start_date, 249 end_date, max_records, 250 history_item_type, 251 next_token) 252 253 def add_alarm_action(self, action_arn=None): 254 """ 255 Adds an alarm action, represented as an SNS topic, to this alarm. 256 What do do when alarm is triggered. 257 258 :type action_arn: str 259 :param action_arn: SNS topics to which notification should be 260 sent if the alarm goes to state ALARM. 261 """ 262 if not action_arn: 263 return # Raise exception instead? 264 self.actions_enabled = 'true' 265 self.alarm_actions.append(action_arn) 266 267 def add_insufficient_data_action(self, action_arn=None): 268 """ 269 Adds an insufficient_data action, represented as an SNS topic, to 270 this alarm. What to do when the insufficient_data state is reached. 271 272 :type action_arn: str 273 :param action_arn: SNS topics to which notification should be 274 sent if the alarm goes to state INSUFFICIENT_DATA. 275 """ 276 if not action_arn: 277 return 278 self.actions_enabled = 'true' 279 self.insufficient_data_actions.append(action_arn) 280 281 def add_ok_action(self, action_arn=None): 282 """ 283 Adds an ok action, represented as an SNS topic, to this alarm. What 284 to do when the ok state is reached. 285 286 :type action_arn: str 287 :param action_arn: SNS topics to which notification should be 288 sent if the alarm goes to state INSUFFICIENT_DATA. 289 """ 290 if not action_arn: 291 return 292 self.actions_enabled = 'true' 293 self.ok_actions.append(action_arn) 294 295 def delete(self): 296 self.connection.delete_alarms([self.name]) 297 298 299class AlarmHistoryItem(object): 300 def __init__(self, connection=None): 301 self.connection = connection 302 303 def __repr__(self): 304 return 'AlarmHistory:%s[%s at %s]' % (self.name, self.summary, self.timestamp) 305 306 def startElement(self, name, attrs, connection): 307 pass 308 309 def endElement(self, name, value, connection): 310 if name == 'AlarmName': 311 self.name = value 312 elif name == 'HistoryData': 313 self.data = json.loads(value) 314 elif name == 'HistoryItemType': 315 self.tem_type = value 316 elif name == 'HistorySummary': 317 self.summary = value 318 elif name == 'Timestamp': 319 try: 320 self.timestamp = datetime.strptime(value, 321 '%Y-%m-%dT%H:%M:%S.%fZ') 322 except ValueError: 323 self.timestamp = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ') 324