예제 #1
0
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'
                   ])
예제 #2
0
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', [])
예제 #3
0
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
예제 #4
0
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'
                   }])
예제 #5
0
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)
예제 #6
0
 class dummybase(wsme.types.Base):
     ae = v2_base.AdvEnum("name", str, "one", "other", default="other")