# aodh.api.controllers.v2.alarm_rules.gnocchi

# Source code for aodh.api.controllers.v2.alarm_rules.gnocchi

    #
    # Copyright 2015 eNovance
    #
    # 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 json
    import threading

    import cachetools
    from gnocchiclient import client
    from gnocchiclient import exceptions
    from keystoneauth1 import exceptions as ka_exceptions
    from oslo_config import cfg
    from oslo_log import log
    import pecan
    import wsme
    from wsme import types as wtypes

    from aodh.api.controllers.v2 import base
    from aodh.api.controllers.v2 import utils as v2_utils
    from aodh import keystone_client

    LOG = log.getLogger(__name__)

    GNOCCHI_OPTS = [
        cfg.StrOpt('gnocchi_external_project_owner',
                   default="service",
                   help='Project name of resources creator in Gnocchi. '
                   '(For example the Ceilometer project name'),
        cfg.StrOpt('gnocchi_external_domain_name',
                   default="Default",
                   help='Domain name of resources creator in Gnocchi. '
                   '(For example, default or service_domain'),
    ]

    class GnocchiUnavailable(Exception):
        code = 503

    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, maximum=100), default=1)
        "The number of historical periods to evaluate the threshold"

        granularity = wsme.wsattr(wtypes.IntegerType(
            minimum=1, maximum=3600 * 24 * 365), 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 {} not {}'.format(
                        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)

    class MetricOfResourceRule(AlarmGnocchiThresholdRule):
        metric = wsme.wsattr(wtypes.text, mandatory=True)
        "The name of the metric"

        resource_id = wsme.wsattr(wtypes.text, mandatory=True)
        "The id of a resource"

        resource_type = wsme.wsattr(wtypes.text, mandatory=True)
        "The resource type"

        def as_dict(self):
            rule = self.as_dict_from_keys(['granularity', 'comparison_operator',
                                           'threshold', 'aggregation_method',
                                           'evaluation_periods',
                                           'metric',
                                           'resource_id',
                                           'resource_type'])
            return rule

    class AggregationMetricByResourcesLookupRule(AlarmGnocchiThresholdRule):
        metric = wsme.wsattr(wtypes.text, mandatory=True)
        "The name of the metric"

        query = wsme.wsattr(wtypes.text, mandatory=True)
        ('The query to filter the metric, Don\'t forget to filter out '
         'deleted resources (example: {"and": [{"=": {"ended_at": null}}, ...]}), '
         'Otherwise Gnocchi will try to create the aggregate against obsolete '
         'resources')

        resource_type = wsme.wsattr(wtypes.text, mandatory=True)
        "The resource type"

        def as_dict(self):
            rule = self.as_dict_from_keys(['granularity', 'comparison_operator',
                                           'threshold', 'aggregation_method',
                                           'evaluation_periods',
                                           'metric',
                                           'query',
                                           'resource_type'])
            return rule

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

        @staticmethod
        @cachetools.cached(cache, lock=lock)
        def get_external_project_owner():
            kc = keystone_client.get_client(pecan.request.cfg)
            project_name = pecan.request.cfg.api.gnocchi_external_project_owner
            domain_name = pecan.request.cfg.api.gnocchi_external_domain_name
            try:
                domains = kc.domains.list(name=domain_name)
                project = kc.projects.find(
                    name=project_name,
                    domain_id=domains[0].id)
                return project.id
            except ka_exceptions.NotFound:
                LOG.warning("Unable to get domain or project information. "
                            "domain_name : %(domain_name)s, "
                            "project_name : %(project_name)s",
                            {'domain_name': domain_name,
                             'project_name': project_name})
                return None

        @classmethod
        def validate_alarm(cls, alarm):
            super().validate_alarm(alarm)

            rule = alarm.gnocchi_aggregation_by_resources_threshold_rule

            # check the query string is a valid json
            try:
                query = json.loads(rule.query)
            except ValueError:
                raise wsme.exc.InvalidInput('rule/query', rule.query)

            conf = pecan.request.cfg

            # Scope the alarm to the project id if needed
            auth_project = v2_utils.get_auth_project(alarm.project_id)
            if auth_project:

                perms_filter = {"=": {"created_by_project_id": auth_project}}

                external_project_owner = cls.get_external_project_owner()
                if external_project_owner:
                    perms_filter = {"or": [
                        perms_filter,
                        {"and": [
                            {"=": {"created_by_project_id":
                                   external_project_owner}},
                            {"=": {"project_id": auth_project}}]}
                    ]}

                query = {"and": [perms_filter, query]}
                rule.query = json.dumps(query)

            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:
                gnocchi_client.aggregates.fetch(
                    operations=[
                        'aggregate', rule.aggregation_method,
                        [
                            'metric', rule.metric,
                            rule.aggregation_method.lstrip('rate:')
                        ]
                    ],
                    search=query,
                    needed_overlap=0,
                    start="-1 day",
                    stop="now",
                    resource_type=rule.resource_type)
            except exceptions.ClientException as e:
                if e.code == 404:
                    # NOTE(sileht): We are fine here, we just want to ensure the
                    # 'query' payload is valid for Gnocchi If the metric
                    # doesn't exists yet, it doesn't matter.
                    return
                if e.code == 400 and 'Metrics not found' in e.message["cause"]:
                    # NOTE(tkajinam): Gnocchi<4.5 returns 400 instead of 404
                    return
                raise base.ClientSideError(e.message, status_code=e.code)
            except Exception as e:
                raise GnocchiUnavailable(e)

    class AggregationMetricsByIdLookupRule(AlarmGnocchiThresholdRule):
        metrics = wsme.wsattr([wtypes.text], mandatory=True)
        "A list of metric Ids"

        def as_dict(self):
            rule = self.as_dict_from_keys(['granularity', 'comparison_operator',
                                           'threshold', 'aggregation_method',
                                           'evaluation_periods',
                                           'metrics'])
            return rule

this page last updated: 2026-08-15 16:35:58

Except where otherwise noted, this document is licensed under Creative Commons Attribution 3.0 License. See all OpenStack Legal Documents.

found an error? report a bug