"Fossies" - the Fresh Open Source Software Archive

Member "aodh-15.0.0/aodh/api/controllers/v2/alarm_rules/gnocchi.py" (5 Oct 2022, 9125 Bytes) of package /linux/misc/openstack/aodh-15.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 #
    2 # Copyright 2015 eNovance
    3 #
    4 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 # not use this file except in compliance with the License. You may obtain
    6 # a copy of the License at
    7 #
    8 #      http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 # Unless required by applicable law or agreed to in writing, software
   11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 # License for the specific language governing permissions and limitations
   14 # under the License.
   15 import json
   16 import threading
   17 
   18 import cachetools
   19 from gnocchiclient import client
   20 from gnocchiclient import exceptions
   21 from keystoneauth1 import exceptions as ka_exceptions
   22 from oslo_config import cfg
   23 from oslo_log import log
   24 import pecan
   25 import wsme
   26 from wsme import types as wtypes
   27 
   28 from aodh.api.controllers.v2 import base
   29 from aodh.api.controllers.v2 import utils as v2_utils
   30 from aodh import keystone_client
   31 
   32 LOG = log.getLogger(__name__)
   33 
   34 GNOCCHI_OPTS = [
   35     cfg.StrOpt('gnocchi_external_project_owner',
   36                default="service",
   37                help='Project name of resources creator in Gnocchi. '
   38                '(For example the Ceilometer project name'),
   39     cfg.StrOpt('gnocchi_external_domain_name',
   40                default="Default",
   41                help='Domain name of resources creator in Gnocchi. '
   42                '(For example, default or service_domain'),
   43 ]
   44 
   45 
   46 class GnocchiUnavailable(Exception):
   47     code = 503
   48 
   49 
   50 class AlarmGnocchiThresholdRule(base.AlarmRule):
   51     comparison_operator = base.AdvEnum('comparison_operator', str,
   52                                        'lt', 'le', 'eq', 'ne', 'ge', 'gt',
   53                                        default='eq')
   54     "The comparison against the alarm threshold"
   55 
   56     threshold = wsme.wsattr(float, mandatory=True)
   57     "The threshold of the alarm"
   58 
   59     aggregation_method = wsme.wsattr(wtypes.text, mandatory=True)
   60     "The aggregation_method to compare to the threshold"
   61 
   62     evaluation_periods = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1)
   63     "The number of historical periods to evaluate the threshold"
   64 
   65     granularity = wsme.wsattr(wtypes.IntegerType(minimum=1), default=60)
   66     "The time range in seconds over which query"
   67 
   68     cache = cachetools.TTLCache(maxsize=1, ttl=3600)
   69     lock = threading.RLock()
   70 
   71     @classmethod
   72     def validate_alarm(cls, alarm):
   73         alarm_rule = getattr(alarm, "%s_rule" % alarm.type)
   74         aggregation_method = alarm_rule.aggregation_method
   75         if aggregation_method not in cls._get_aggregation_methods():
   76             raise base.ClientSideError(
   77                 'aggregation_method should be in %s not %s' % (
   78                     cls._get_aggregation_methods(), aggregation_method))
   79 
   80     @staticmethod
   81     @cachetools.cached(cache, lock=lock)
   82     def _get_aggregation_methods():
   83         conf = pecan.request.cfg
   84         gnocchi_client = client.Client(
   85             '1', keystone_client.get_session(conf),
   86             adapter_options={
   87                 'interface': conf.service_credentials.interface,
   88                 'region_name': conf.service_credentials.region_name})
   89         try:
   90             return gnocchi_client.capabilities.list().get(
   91                 'aggregation_methods', [])
   92         except exceptions.ClientException as e:
   93             raise base.ClientSideError(e.message, status_code=e.code)
   94         except Exception as e:
   95             raise GnocchiUnavailable(e)
   96 
   97 
   98 class MetricOfResourceRule(AlarmGnocchiThresholdRule):
   99     metric = wsme.wsattr(wtypes.text, mandatory=True)
  100     "The name of the metric"
  101 
  102     resource_id = wsme.wsattr(wtypes.text, mandatory=True)
  103     "The id of a resource"
  104 
  105     resource_type = wsme.wsattr(wtypes.text, mandatory=True)
  106     "The resource type"
  107 
  108     def as_dict(self):
  109         rule = self.as_dict_from_keys(['granularity', 'comparison_operator',
  110                                        'threshold', 'aggregation_method',
  111                                        'evaluation_periods',
  112                                        'metric',
  113                                        'resource_id',
  114                                        'resource_type'])
  115         return rule
  116 
  117 
  118 class AggregationMetricByResourcesLookupRule(AlarmGnocchiThresholdRule):
  119     metric = wsme.wsattr(wtypes.text, mandatory=True)
  120     "The name of the metric"
  121 
  122     query = wsme.wsattr(wtypes.text, mandatory=True)
  123     ('The query to filter the metric, Don\'t forget to filter out '
  124      'deleted resources (example: {"and": [{"=": {"ended_at": null}}, ...]}), '
  125      'Otherwise Gnocchi will try to create the aggregate against obsolete '
  126      'resources')
  127 
  128     resource_type = wsme.wsattr(wtypes.text, mandatory=True)
  129     "The resource type"
  130 
  131     def as_dict(self):
  132         rule = self.as_dict_from_keys(['granularity', 'comparison_operator',
  133                                        'threshold', 'aggregation_method',
  134                                        'evaluation_periods',
  135                                        'metric',
  136                                        'query',
  137                                        'resource_type'])
  138         return rule
  139 
  140     cache = cachetools.TTLCache(maxsize=1, ttl=3600)
  141     lock = threading.RLock()
  142 
  143     @staticmethod
  144     @cachetools.cached(cache, lock=lock)
  145     def get_external_project_owner():
  146         kc = keystone_client.get_client(pecan.request.cfg)
  147         project_name = pecan.request.cfg.api.gnocchi_external_project_owner
  148         domain_name = pecan.request.cfg.api.gnocchi_external_domain_name
  149         try:
  150             domains = kc.domains.list(name=domain_name)
  151             project = kc.projects.find(
  152                 name=project_name,
  153                 domain_id=domains[0].id)
  154             return project.id
  155         except ka_exceptions.NotFound:
  156             LOG.warning("Unable to get domain or project information. "
  157                         "domain_name : %(domain_name)s, "
  158                         "project_name : %(project_name)s",
  159                         {'domain_name': domain_name,
  160                          'project_name': project_name})
  161             return None
  162 
  163     @classmethod
  164     def validate_alarm(cls, alarm):
  165         super(AggregationMetricByResourcesLookupRule,
  166               cls).validate_alarm(alarm)
  167 
  168         rule = alarm.gnocchi_aggregation_by_resources_threshold_rule
  169 
  170         # check the query string is a valid json
  171         try:
  172             query = json.loads(rule.query)
  173         except ValueError:
  174             raise wsme.exc.InvalidInput('rule/query', rule.query)
  175 
  176         conf = pecan.request.cfg
  177 
  178         # Scope the alarm to the project id if needed
  179         auth_project = v2_utils.get_auth_project(alarm.project_id)
  180         if auth_project:
  181 
  182             perms_filter = {"=": {"created_by_project_id": auth_project}}
  183 
  184             external_project_owner = cls.get_external_project_owner()
  185             if external_project_owner:
  186                 perms_filter = {"or": [
  187                     perms_filter,
  188                     {"and": [
  189                         {"=": {"created_by_project_id":
  190                                external_project_owner}},
  191                         {"=": {"project_id": auth_project}}]}
  192                 ]}
  193 
  194             query = {"and": [perms_filter, query]}
  195             rule.query = json.dumps(query)
  196 
  197         gnocchi_client = client.Client(
  198             '1', keystone_client.get_session(conf),
  199             adapter_options={
  200                 'interface': conf.service_credentials.interface,
  201                 'region_name': conf.service_credentials.region_name})
  202         try:
  203             gnocchi_client.aggregates.fetch(
  204                 operations=[
  205                     'aggregate', rule.aggregation_method,
  206                     [
  207                         'metric', rule.metric,
  208                         rule.aggregation_method.lstrip('rate:')
  209                     ]
  210                 ],
  211                 search=query,
  212                 needed_overlap=0,
  213                 start="-1 day",
  214                 stop="now",
  215                 resource_type=rule.resource_type)
  216         except exceptions.ClientException as e:
  217             if e.code == 404:
  218                 # NOTE(sileht): We are fine here, we just want to ensure the
  219                 # 'query' payload is valid for Gnocchi If the metric
  220                 # doesn't exists yet, it doesn't matter.
  221                 return
  222             if e.code == 400 and 'Metrics not found' in e.message["cause"]:
  223                 # NOTE(tkajinam): Gnocchi<4.5 returns 400 instead of 404
  224                 return
  225             raise base.ClientSideError(e.message, status_code=e.code)
  226         except Exception as e:
  227             raise GnocchiUnavailable(e)
  228 
  229 
  230 class AggregationMetricsByIdLookupRule(AlarmGnocchiThresholdRule):
  231     metrics = wsme.wsattr([wtypes.text], mandatory=True)
  232     "A list of metric Ids"
  233 
  234     def as_dict(self):
  235         rule = self.as_dict_from_keys(['granularity', 'comparison_operator',
  236                                        'threshold', 'aggregation_method',
  237                                        'evaluation_periods',
  238                                        'metrics'])
  239         return rule