class AlarmCombinationRule(base.AlarmRule): """Alarm Combinarion Rule Describe when to trigger the alarm based on combining the state of other alarms. """ operator = base.AdvEnum('operator', str, 'or', 'and', default='and') "How to combine the sub-alarms" alarm_ids = wsme.wsattr([wtypes.text], mandatory=True) "List of alarm identifiers to combine" @property def default_description(self): joiner = ' %s ' % self.operator return _('Combined state of alarms %s') % joiner.join(self.alarm_ids) def as_dict(self): return self.as_dict_from_keys(['operator', 'alarm_ids']) @staticmethod def validate(rule): rule.alarm_ids = sorted(set(rule.alarm_ids), key=rule.alarm_ids.index) if len(rule.alarm_ids) <= 1: raise base.ClientSideError( _('Alarm combination rule should ' 'contain at least two different ' 'alarm ids.')) return rule @staticmethod def validate_alarm(alarm): project = v2_utils.get_auth_project( alarm.project_id if alarm.project_id != wtypes.Unset else None) for id in alarm.combination_rule.alarm_ids: alarms = list( pecan.request.alarm_storage_conn.get_alarms(alarm_id=id, project=project)) if not alarms: raise AlarmNotFound(id, project) @staticmethod def update_hook(alarm): # should check if there is any circle in the dependency, but for # efficiency reason, here only check alarm cannot depend on itself if alarm.alarm_id in alarm.combination_rule.alarm_ids: raise base.ClientSideError( _('Cannot specify alarm %s itself in combination rule') % alarm.alarm_id) @classmethod def sample(cls): return cls(operator='or', alarm_ids=[ '739e99cb-c2ec-4718-b900-332502355f38', '153462d0-a9b8-4b5b-8175-9e4b05e9b856' ])
class AlarmGnocchiThresholdRule(base.AlarmRule): comparison_operator = base.AdvEnum('comparison_operator', str, 'lt', 'le', 'eq', 'ne', 'ge', 'gt', default='eq') "The comparison against the alarm threshold" threshold = wsme.wsattr(float, mandatory=True) "The threshold of the alarm" aggregation_method = wsme.wsattr(wtypes.text, mandatory=True) "The aggregation_method to compare to the threshold" evaluation_periods = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1) "The number of historical periods to evaluate the threshold" granularity = wsme.wsattr(wtypes.IntegerType(minimum=1), default=60) "The time range in seconds over which query" @classmethod def validate_alarm(cls, alarm): alarm_rule = getattr(alarm, "%s_rule" % alarm.type) aggregation_method = alarm_rule.aggregation_method if aggregation_method not in cls._get_aggregation_methods(): raise base.ClientSideError( 'aggregation_method should be in %s not %s' % (cls._get_aggregation_methods(), aggregation_method)) # NOTE(sileht): once cachetools is in the requirements # enable it # @cachetools.ttl_cache(maxsize=1, ttl=600) @staticmethod def _get_aggregation_methods(): ks_client = keystone_client.get_client() gnocchi_url = cfg.CONF.alarms.gnocchi_url headers = { 'Content-Type': "application/json", 'X-Auth-Token': ks_client.auth_token } try: r = requests.get("%s/v1/capabilities" % gnocchi_url, headers=headers) except requests.ConnectionError as e: raise GnocchiUnavailable(e) if r.status_code // 200 != 1: raise GnocchiUnavailable(r.text) return jsonutils.loads(r.text).get('aggregation_methods', [])
class Alarm(base.Base): """Representation of an alarm. .. note:: combination_rule and threshold_rule are mutually exclusive. The *type* of the alarm should be set to *threshold* or *combination* and the appropriate rule should be filled. """ alarm_id = wtypes.text "The UUID of the alarm" name = wsme.wsattr(wtypes.text, mandatory=True) "The name for the alarm" _description = None # provide a default def get_description(self): rule = getattr(self, '%s_rule' % self.type, None) if not self._description: if hasattr(rule, 'default_description'): return six.text_type(rule.default_description) return "%s alarm rule" % self.type return self._description def set_description(self, value): self._description = value description = wsme.wsproperty(wtypes.text, get_description, set_description) "The description of the alarm" enabled = wsme.wsattr(bool, default=True) "This alarm is enabled?" ok_actions = wsme.wsattr([wtypes.text], default=[]) "The actions to do when alarm state change to ok" alarm_actions = wsme.wsattr([wtypes.text], default=[]) "The actions to do when alarm state change to alarm" insufficient_data_actions = wsme.wsattr([wtypes.text], default=[]) "The actions to do when alarm state change to insufficient data" repeat_actions = wsme.wsattr(bool, default=False) "The actions should be re-triggered on each evaluation cycle" type = base.AdvEnum('type', str, *ALARMS_RULES.names(), mandatory=True) "Explicit type specifier to select which rule to follow below." time_constraints = wtypes.wsattr([AlarmTimeConstraint], default=[]) """Describe time constraints for the alarm""" # These settings are ignored in the PUT or POST operations, but are # filled in for GET project_id = wtypes.text "The ID of the project or tenant that owns the alarm" user_id = wtypes.text "The ID of the user who created the alarm" timestamp = datetime.datetime "The date of the last alarm definition update" state = base.AdvEnum('state', str, *state_kind, default='insufficient data') "The state offset the alarm" state_timestamp = datetime.datetime "The date of the last alarm state changed" severity = base.AdvEnum('severity', str, *severity_kind, default='low') "The severity of the alarm" def __init__(self, rule=None, time_constraints=None, **kwargs): super(Alarm, self).__init__(**kwargs) if rule: setattr(self, '%s_rule' % self.type, ALARMS_RULES[self.type].plugin(**rule)) if time_constraints: self.time_constraints = [ AlarmTimeConstraint(**tc) for tc in time_constraints ] @staticmethod def validate(alarm): Alarm.check_rule(alarm) Alarm.check_alarm_actions(alarm) ALARMS_RULES[alarm.type].plugin.validate_alarm(alarm) if alarm.time_constraints: tc_names = [tc.name for tc in alarm.time_constraints] if len(tc_names) > len(set(tc_names)): error = _("Time constraint names must be " "unique for a given alarm.") raise base.ClientSideError(error) return alarm @staticmethod def check_rule(alarm): rule = '%s_rule' % alarm.type if getattr(alarm, rule) in (wtypes.Unset, None): error = _("%(rule)s must be set for %(type)s" " type alarm") % { "rule": rule, "type": alarm.type } raise base.ClientSideError(error) rule_set = None for ext in ALARMS_RULES: name = "%s_rule" % ext.name if getattr(alarm, name): if rule_set is None: rule_set = name else: error = _("%(rule1)s and %(rule2)s cannot be set at the " "same time") % { 'rule1': rule_set, 'rule2': name } raise base.ClientSideError(error) @staticmethod def check_alarm_actions(alarm): actions_schema = ceilometer_alarm.NOTIFIER_SCHEMAS for state in state_kind: actions_name = state.replace(" ", "_") + '_actions' actions = getattr(alarm, actions_name) if not actions: continue for action in actions: try: url = netutils.urlsplit(action) except Exception: error = _("Unable to parse action %s") % action raise base.ClientSideError(error) if url.scheme not in actions_schema: error = _("Unsupported action %s") % action raise base.ClientSideError(error) @classmethod def sample(cls): return cls( alarm_id=None, name="SwiftObjectAlarm", description="An alarm", type='combination', time_constraints=[AlarmTimeConstraint.sample().as_dict()], user_id="c96c887c216949acbdfbd8b494863567", project_id="c96c887c216949acbdfbd8b494863567", enabled=True, timestamp=datetime.datetime.utcnow(), state="ok", severity="moderate", state_timestamp=datetime.datetime.utcnow(), ok_actions=["http://site:8000/ok"], alarm_actions=["http://site:8000/alarm"], insufficient_data_actions=["http://site:8000/nodata"], repeat_actions=False, combination_rule=combination.AlarmCombinationRule.sample(), ) def as_dict(self, db_model): d = super(Alarm, self).as_dict(db_model) for k in d: if k.endswith('_rule'): del d[k] d['rule'] = getattr(self, "%s_rule" % self.type).as_dict() if self.time_constraints: d['time_constraints'] = [ tc.as_dict() for tc in self.time_constraints ] return d
class AlarmThresholdRule(base.AlarmRule): """Alarm Threshold Rule Describe when to trigger the alarm based on computed statistics """ meter_name = wsme.wsattr(wtypes.text, mandatory=True) "The name of the meter" # FIXME(sileht): default doesn't work # workaround: default is set in validate method query = wsme.wsattr([base.Query], default=[]) """The query to find the data for computing statistics. Ownership settings are automatically included based on the Alarm owner. """ period = wsme.wsattr(wtypes.IntegerType(minimum=1), default=60) "The time range in seconds over which query" comparison_operator = base.AdvEnum('comparison_operator', str, 'lt', 'le', 'eq', 'ne', 'ge', 'gt', default='eq') "The comparison against the alarm threshold" threshold = wsme.wsattr(float, mandatory=True) "The threshold of the alarm" statistic = base.AdvEnum('statistic', str, 'max', 'min', 'avg', 'sum', 'count', default='avg') "The statistic to compare to the threshold" evaluation_periods = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1) "The number of historical periods to evaluate the threshold" exclude_outliers = wsme.wsattr(bool, default=False) "Whether datapoints with anomalously low sample counts are excluded" def __init__(self, query=None, **kwargs): if query: query = [base.Query(**q) for q in query] super(AlarmThresholdRule, self).__init__(query=query, **kwargs) @staticmethod def validate(threshold_rule): # note(sileht): wsme default doesn't work in some case # workaround for https://bugs.launchpad.net/wsme/+bug/1227039 if not threshold_rule.query: threshold_rule.query = [] # Timestamp is not allowed for AlarmThresholdRule query, as the alarm # evaluator will construct timestamp bounds for the sequence of # statistics queries as the sliding evaluation window advances # over time. v2_utils.validate_query(threshold_rule.query, storage.SampleFilter.__init__, allow_timestamps=False) return threshold_rule @staticmethod def validate_alarm(alarm): # ensure an implicit constraint on project_id is added to # the query if not already present alarm.threshold_rule.query = v2_utils.sanitize_query( alarm.threshold_rule.query, storage.SampleFilter.__init__, on_behalf_of=alarm.project_id) @property def default_description(self): return (_('Alarm when %(meter_name)s is %(comparison_operator)s a ' '%(statistic)s of %(threshold)s over %(period)s seconds') % dict(comparison_operator=self.comparison_operator, statistic=self.statistic, threshold=self.threshold, meter_name=self.meter_name, period=self.period)) def as_dict(self): rule = self.as_dict_from_keys([ 'period', 'comparison_operator', 'threshold', 'statistic', 'evaluation_periods', 'meter_name', 'exclude_outliers' ]) rule['query'] = [q.as_dict() for q in self.query] return rule @classmethod def sample(cls): return cls(meter_name='cpu_util', period=60, evaluation_periods=1, threshold=300.0, statistic='avg', comparison_operator='gt', query=[{ 'field': 'resource_id', 'value': '2a4d689b-f0b8-49c1-9eef-87cae58d80db', 'op': 'eq', 'type': 'string' }])
class Alarm(base.Base): """Representation of an alarm. .. note:: combination_rule and threshold_rule are mutually exclusive. The *type* of the alarm should be set to *threshold* or *combination* and the appropriate rule should be filled. """ alarm_id = wtypes.text "The UUID of the alarm" name = wsme.wsattr(wtypes.text, mandatory=True) "The name for the alarm" _description = None # provide a default def get_description(self): rule = getattr(self, '%s_rule' % self.type, None) if not self._description: if hasattr(rule, 'default_description'): return six.text_type(rule.default_description) return "%s alarm rule" % self.type return self._description def set_description(self, value): self._description = value description = wsme.wsproperty(wtypes.text, get_description, set_description) "The description of the alarm" enabled = wsme.wsattr(bool, default=True) "This alarm is enabled?" ok_actions = wsme.wsattr([wtypes.text], default=[]) "The actions to do when alarm state change to ok" alarm_actions = wsme.wsattr([wtypes.text], default=[]) "The actions to do when alarm state change to alarm" insufficient_data_actions = wsme.wsattr([wtypes.text], default=[]) "The actions to do when alarm state change to insufficient data" repeat_actions = wsme.wsattr(bool, default=False) "The actions should be re-triggered on each evaluation cycle" type = base.AdvEnum('type', str, *ALARMS_RULES.names(), mandatory=True) "Explicit type specifier to select which rule to follow below." time_constraints = wtypes.wsattr([AlarmTimeConstraint], default=[]) """Describe time constraints for the alarm""" # These settings are ignored in the PUT or POST operations, but are # filled in for GET project_id = wtypes.text "The ID of the project or tenant that owns the alarm" user_id = wtypes.text "The ID of the user who created the alarm" timestamp = datetime.datetime "The date of the last alarm definition update" state = base.AdvEnum('state', str, *state_kind, default='insufficient data') "The state offset the alarm" state_timestamp = datetime.datetime "The date of the last alarm state changed" severity = base.AdvEnum('severity', str, *severity_kind, default='low') "The severity of the alarm" def __init__(self, rule=None, time_constraints=None, **kwargs): super(Alarm, self).__init__(**kwargs) if rule: setattr(self, '%s_rule' % self.type, ALARMS_RULES[self.type].plugin(**rule)) if time_constraints: self.time_constraints = [ AlarmTimeConstraint(**tc) for tc in time_constraints ] @staticmethod def validate(alarm): Alarm.check_rule(alarm) Alarm.check_alarm_actions(alarm) ALARMS_RULES[alarm.type].plugin.validate_alarm(alarm) if alarm.time_constraints: tc_names = [tc.name for tc in alarm.time_constraints] if len(tc_names) > len(set(tc_names)): error = _("Time constraint names must be " "unique for a given alarm.") raise base.ClientSideError(error) return alarm @staticmethod def check_rule(alarm): rule = '%s_rule' % alarm.type if getattr(alarm, rule) in (wtypes.Unset, None): error = _("%(rule)s must be set for %(type)s" " type alarm") % { "rule": rule, "type": alarm.type } raise base.ClientSideError(error) rule_set = None for ext in ALARMS_RULES: name = "%s_rule" % ext.name if getattr(alarm, name): if rule_set is None: rule_set = name else: error = _("%(rule1)s and %(rule2)s cannot be set at the " "same time") % { 'rule1': rule_set, 'rule2': name } raise base.ClientSideError(error) @staticmethod def check_alarm_actions(alarm): actions_schema = ceilometer_alarm.NOTIFIER_SCHEMAS max_actions = cfg.CONF.alarm.alarm_max_actions for state in state_kind: actions_name = state.replace(" ", "_") + '_actions' actions = getattr(alarm, actions_name) if not actions: continue action_set = set(actions) if len(actions) != len(action_set): LOG.info( _LI('duplicate actions are found: %s, ' 'remove duplicate ones') % actions) actions = list(action_set) setattr(alarm, actions_name, actions) if 0 < max_actions < len(actions): error = _('%(name)s count exceeds maximum value ' '%(maximum)d') % { "name": actions_name, "maximum": max_actions } raise base.ClientSideError(error) limited = rbac.get_limited_to_project(pecan.request.headers) for action in actions: try: url = netutils.urlsplit(action) except Exception: error = _("Unable to parse action %s") % action raise base.ClientSideError(error) if url.scheme not in actions_schema: error = _("Unsupported action %s") % action raise base.ClientSideError(error) if limited and url.scheme in ('log', 'test'): error = _('You are not authorized to create ' 'action: %s') % action raise base.ClientSideError(error, status_code=401) @classmethod def sample(cls): return cls( alarm_id=None, name="SwiftObjectAlarm", description="An alarm", type='combination', time_constraints=[AlarmTimeConstraint.sample().as_dict()], user_id="c96c887c216949acbdfbd8b494863567", project_id="c96c887c216949acbdfbd8b494863567", enabled=True, timestamp=datetime.datetime.utcnow(), state="ok", severity="moderate", state_timestamp=datetime.datetime.utcnow(), ok_actions=["http://site:8000/ok"], alarm_actions=["http://site:8000/alarm"], insufficient_data_actions=["http://site:8000/nodata"], repeat_actions=False, combination_rule=combination.AlarmCombinationRule.sample(), ) def as_dict(self, db_model): d = super(Alarm, self).as_dict(db_model) for k in d: if k.endswith('_rule'): del d[k] d['rule'] = getattr(self, "%s_rule" % self.type).as_dict() if self.time_constraints: d['time_constraints'] = [ tc.as_dict() for tc in self.time_constraints ] return d @staticmethod def _is_trust_url(url): return url.scheme in ('trust+http', 'trust+https') def update_actions(self, old_alarm=None): trustor_user_id = pecan.request.headers.get('X-User-Id') trustor_project_id = pecan.request.headers.get('X-Project-Id') roles = pecan.request.headers.get('X-Roles', '') if roles: roles = roles.split(',') else: roles = [] auth_plugin = pecan.request.environ.get('keystone.token_auth') for actions in (self.ok_actions, self.alarm_actions, self.insufficient_data_actions): if actions is not None: for index, action in enumerate(actions[:]): url = netutils.urlsplit(action) if self._is_trust_url(url): if '@' not in url.netloc: # We have a trust action without a trust ID, # create it trust_id = keystone_client.create_trust_id( trustor_user_id, trustor_project_id, roles, auth_plugin) netloc = '%s:delete@%s' % (trust_id, url.netloc) url = list(url) url[1] = netloc actions[index] = urlparse.urlunsplit(url) if old_alarm: new_actions = list( itertools.chain(self.ok_actions or [], self.alarm_actions or [], self.insufficient_data_actions or [])) for action in itertools.chain( old_alarm.ok_actions or [], old_alarm.alarm_actions or [], old_alarm.insufficient_data_actions or []): if action not in new_actions: self.delete_trust(action) def delete_actions(self): for action in itertools.chain(self.ok_actions or [], self.alarm_actions or [], self.insufficient_data_actions or []): self.delete_trust(action) def delete_trust(self, action): auth_plugin = pecan.request.environ.get('keystone.token_auth') url = netutils.urlsplit(action) if self._is_trust_url(url) and url.password: keystone_client.delete_trust_id(url.username, auth_plugin)
class dummybase(wsme.types.Base): ae = v2_base.AdvEnum("name", str, "one", "other", default="other")