コード例 #1
0
ファイル: gnocchi.py プロジェクト: amar266/aodh
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"

    cache = cachetools.TTLCache(maxsize=1, ttl=3600)
    lock = threading.RLock()

    @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))

    @staticmethod
    @cachetools.cached(cache, lock=lock)
    def _get_aggregation_methods():
        conf = pecan.request.cfg
        gnocchi_client = client.Client('1',
                                       keystone_client.get_session(conf),
                                       adapter_options={
                                           'interface':
                                           conf.service_credentials.interface,
                                           'region_name':
                                           conf.service_credentials.region_name
                                       })
        try:
            return gnocchi_client.capabilities.list().get(
                'aggregation_methods', [])
        except exceptions.ClientException as e:
            raise base.ClientSideError(e.message, status_code=e.code)
        except Exception as e:
            raise GnocchiUnavailable(e)
コード例 #2
0
class AlarmCombinationRule(base.AlarmRule):
    """Alarm Combination 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.storage.get_alarms(
                alarm_id=id, project=project))
            if not alarms:
                raise base.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'])
コード例 #3
0
ファイル: gnocchi.py プロジェクト: sileht/aodh
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(pecan.request.cfg)
        gnocchi_url = pecan.request.cfg.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', [])
コード例 #4
0
ファイル: alarms.py プロジェクト: amar266/aodh
class Alarm(base.Base):
    """Representation of an alarm."""

    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"

    state_reason = wsme.wsattr(wtypes.text, default=ALARM_REASON_DEFAULT)
    "The reason of the current state"

    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):
        if alarm.type == 'threshold':
            warnings.simplefilter("always")
            debtcollector.deprecate(
                "Ceilometer's API is deprecated as of Ocata. Therefore, "
                " threshold rule alarms are no longer supported.",
                version="5.0.0")

        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):
        max_actions = pecan.request.cfg.api.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(
                    '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,
                                                  pecan.request.enforcer)

            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='threshold',
            time_constraints=[AlarmTimeConstraint.sample().as_dict()],
            user_id="c96c887c216949acbdfbd8b494863567",
            project_id="c96c887c216949acbdfbd8b494863567",
            enabled=True,
            timestamp=datetime.datetime(2015, 1, 1, 12, 0, 0, 0),
            state="ok",
            severity="moderate",
            state_reason="threshold over 90%",
            state_timestamp=datetime.datetime(2015, 1, 1, 12, 0, 0, 0),
            ok_actions=["http://site:8000/ok"],
            alarm_actions=["http://site:8000/alarm"],
            insufficient_data_actions=["http://site:8000/nodata"],
            repeat_actions=False,
        )

    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]
        rule = getattr(self, "%s_rule" % self.type)
        d['rule'] = rule if isinstance(rule, dict) else rule.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.startswith('trust+')

    def _get_existing_trust_ids(self):
        for action in itertools.chain(self.ok_actions or [], self.alarm_actions
                                      or [], self.insufficient_data_actions
                                      or []):
            url = netutils.urlsplit(action)
            if self._is_trust_url(url):
                trust_id = url.username
                if trust_id and url.password == 'delete':
                    yield trust_id

    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')

        if old_alarm:
            prev_trust_ids = set(old_alarm._get_existing_trust_ids())
        else:
            prev_trust_ids = set()
        trust_id = prev_trust_ids.pop() if prev_trust_ids else None
        trust_id_used = False

        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 '@' in url.netloc:
                            errmsg = _("trust URL cannot contain a trust ID.")
                            raise base.ClientSideError(errmsg)
                        if trust_id is None:
                            # We have a trust action without a trust ID,
                            # create it
                            trust_id = keystone_client.create_trust_id(
                                pecan.request.cfg, trustor_user_id,
                                trustor_project_id, roles, auth_plugin)
                        if trust_id_used:
                            pw = ''
                        else:
                            pw = ':delete'
                            trust_id_used = True
                        netloc = '%s%s@%s' % (trust_id, pw, url.netloc)
                        url = urlparse.SplitResult(url.scheme, netloc,
                                                   url.path, url.query,
                                                   url.fragment)
                        actions[index] = url.geturl()
        if trust_id is not None and not trust_id_used:
            prev_trust_ids.add(trust_id)
        for old_trust_id in prev_trust_ids:
            keystone_client.delete_trust_id(old_trust_id, auth_plugin)

    def delete_actions(self):
        auth_plugin = pecan.request.environ.get('keystone.token_auth')
        for trust_id in self._get_existing_trust_ids():
            keystone_client.delete_trust_id(trust_id, auth_plugin)
コード例 #5
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"

    ceilometer_sample_api_is_supported = None

    def __init__(self, query=None, **kwargs):
        query = [base.Query(**q) for q in query] if query else []
        super(AlarmThresholdRule, self).__init__(query=query, **kwargs)

    @classmethod
    def _check_ceilometer_sample_api(cls):
        # Check it only once
        if cls.ceilometer_sample_api_is_supported is None:

            auth_config = pecan.request.cfg.service_credentials
            client = ceiloclient.get_client(
                version=2,
                session=keystone_client.get_session(pecan.request.cfg),
                # ceiloclient adapter options
                region_name=auth_config.region_name,
                interface=auth_config.interface,
            )
            try:
                client.statistics.list(
                    meter_name="idontthinkthatexistsbutwhatever")
            except Exception as e:
                if isinstance(e, ceiloexc.HTTPException):
                    if e.code == 410:
                        cls.ceilometer_sample_api_is_supported = False
                    elif e.code < 500:
                        cls.ceilometer_sample_api_is_supported = True
                    else:
                        raise
                else:
                    raise
            else:
                # I don't think this meter can exist but how known
                cls.ceilometer_sample_api_is_supported = True

        if cls.ceilometer_sample_api_is_supported is False:
            raise base.ClientSideError(
                "This telemetry installation is not configured to support"
                "alarm of type 'threshold")

    @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

    @classmethod
    def validate_alarm(cls, alarm):
        cls._check_ceilometer_sample_api()
        # 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'}])
コード例 #6
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):
        query = [base.Query(**q) for q in query] if query else []
        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'}])
コード例 #7
0
ファイル: test_wsme_custom_type.py プロジェクト: sileht/aodh
 class dummybase(wsme.types.Base):
     ae = v2_base.AdvEnum("name", str, "one", "other", default="other")
コード例 #8
0
ファイル: alarms.py プロジェクト: sileht/aodh
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):
        max_actions = pecan.request.cfg.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(
                    _('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,
                                                  pecan.request.enforcer)

            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(
                                pecan.request.cfg, 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(pecan.request.cfg, url.username,
                                            auth_plugin)