"Fossies" - the Fresh Open Source Software Archive

Member "monasca-api-3.1.0/monasca_api/v2/reference/alarms.py" (27 Sep 2019, 21642 Bytes) of package /linux/misc/openstack/monasca-api-3.1.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. For more information about "alarms.py" see the Fossies "Dox" file reference documentation.

    1 # Copyright 2014-2017 Hewlett Packard Enterprise Development LP
    2 # Copyright 2018 OP5 AB
    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 
   16 import re
   17 
   18 import falcon
   19 from monasca_common.simport import simport
   20 from oslo_config import cfg
   21 from oslo_log import log
   22 import six
   23 
   24 from monasca_api.api import alarms_api_v2
   25 from monasca_api.common.repositories import exceptions
   26 from monasca_api.v2.common.exceptions import HTTPUnprocessableEntityError
   27 from monasca_api.v2.common.schemas import alarm_update_schema as schema_alarm
   28 from monasca_api.v2.common import validation
   29 from monasca_api.v2.reference import alarming
   30 from monasca_api.v2.reference import helpers
   31 from monasca_api.v2.reference import resource
   32 
   33 LOG = log.getLogger(__name__)
   34 
   35 
   36 class Alarms(alarms_api_v2.AlarmsV2API,
   37              alarming.Alarming):
   38     def __init__(self):
   39         try:
   40             super(Alarms, self).__init__()
   41             self._region = cfg.CONF.region
   42             self._alarms_repo = simport.load(
   43                 cfg.CONF.repositories.alarms_driver)()
   44 
   45         except Exception as ex:
   46             LOG.exception(ex)
   47             raise exceptions.RepositoryException(ex)
   48 
   49     @resource.resource_try_catch_block
   50     def on_put(self, req, res, alarm_id):
   51 
   52         helpers.validate_authorization(req, ['api:alarms:put'])
   53 
   54         alarm = helpers.from_json(req)
   55         schema_alarm.validate(alarm)
   56 
   57         # Validator makes state optional, so check it here
   58         if 'state' not in alarm or not alarm['state']:
   59             raise HTTPUnprocessableEntityError('Unprocessable Entity',
   60                                                "Field 'state' is required")
   61         if 'lifecycle_state' not in alarm or not alarm['lifecycle_state']:
   62             raise HTTPUnprocessableEntityError('Unprocessable Entity',
   63                                                "Field 'lifecycle_state' is required")
   64         if 'link' not in alarm or not alarm['link']:
   65             raise HTTPUnprocessableEntityError('Unprocessable Entity',
   66                                                "Field 'link' is required")
   67 
   68         self._alarm_update(req.project_id, alarm_id, alarm['state'],
   69                            alarm['lifecycle_state'], alarm['link'])
   70 
   71         result = self._alarm_show(req.uri, req.project_id, alarm_id)
   72 
   73         res.body = helpers.to_json(result)
   74         res.status = falcon.HTTP_200
   75 
   76     @resource.resource_try_catch_block
   77     def on_patch(self, req, res, alarm_id):
   78 
   79         helpers.validate_authorization(req, ['api:alarms:patch'])
   80 
   81         alarm = helpers.from_json(req)
   82         schema_alarm.validate(alarm)
   83 
   84         old_alarm = self._alarms_repo.get_alarm(req.project_id, alarm_id)[0]
   85 
   86         # if a field is not present or is None, replace it with the old value
   87         if 'state' not in alarm or not alarm['state']:
   88             alarm['state'] = old_alarm['state']
   89         if 'lifecycle_state' not in alarm or alarm['lifecycle_state'] is None:
   90             alarm['lifecycle_state'] = old_alarm['lifecycle_state']
   91         if 'link' not in alarm or alarm['link'] is None:
   92             alarm['link'] = old_alarm['link']
   93 
   94         self._alarm_patch(req.project_id, alarm_id, alarm['state'],
   95                           alarm['lifecycle_state'], alarm['link'])
   96 
   97         result = self._alarm_show(req.uri, req.project_id, alarm_id)
   98 
   99         res.body = helpers.to_json(result)
  100         res.status = falcon.HTTP_200
  101 
  102     @resource.resource_try_catch_block
  103     def on_delete(self, req, res, alarm_id):
  104 
  105         helpers.validate_authorization(req, ['api:alarms:delete'])
  106 
  107         self._alarm_delete(req.project_id, alarm_id)
  108 
  109         res.status = falcon.HTTP_204
  110 
  111     @resource.resource_try_catch_block
  112     def on_get(self, req, res, alarm_id=None):
  113         helpers.validate_authorization(req, ['api:alarms:get'])
  114 
  115         if alarm_id is None:
  116             query_parms = falcon.uri.parse_query_string(req.query_string)
  117             if 'state' in query_parms:
  118                 validation.validate_alarm_state(query_parms['state'])
  119                 query_parms['state'] = query_parms['state'].upper()
  120 
  121             if 'severity' in query_parms:
  122                 validation.validate_severity_query(query_parms['severity'])
  123                 query_parms['severity'] = query_parms['severity'].upper()
  124 
  125             if 'sort_by' in query_parms:
  126                 if isinstance(query_parms['sort_by'], six.string_types):
  127                     query_parms['sort_by'] = query_parms['sort_by'].split(',')
  128 
  129                 allowed_sort_by = {
  130                     'alarm_id', 'alarm_definition_id', 'alarm_definition_name',
  131                     'state', 'severity', 'lifecycle_state', 'link',
  132                     'state_updated_timestamp', 'updated_timestamp', 'created_timestamp'}
  133                 validation.validate_sort_by(query_parms['sort_by'], allowed_sort_by)
  134 
  135             query_parms['metric_dimensions'] = helpers.get_query_dimensions(
  136                 req, 'metric_dimensions')
  137             helpers.validate_query_dimensions(query_parms['metric_dimensions'])
  138 
  139             offset = helpers.get_query_param(req, 'offset')
  140             if offset is not None and not isinstance(offset, int):
  141                 try:
  142                     offset = int(offset)
  143                 except Exception as ex:
  144                     LOG.exception(ex)
  145                     raise HTTPUnprocessableEntityError(
  146                         "Unprocessable Entity",
  147                         "Offset value {} must be an integer".format(offset))
  148 
  149             result = self._alarm_list(req.uri, req.project_id,
  150                                       query_parms, offset,
  151                                       req.limit)
  152 
  153             res.body = helpers.to_json(result)
  154             res.status = falcon.HTTP_200
  155 
  156         else:
  157             result = self._alarm_show(req.uri, req.project_id, alarm_id)
  158 
  159             res.body = helpers.to_json(result)
  160             res.status = falcon.HTTP_200
  161 
  162     def _alarm_update(self, tenant_id, alarm_id, new_state, lifecycle_state,
  163                       link):
  164 
  165         alarm_metric_rows = self._alarms_repo.get_alarm_metrics(alarm_id)
  166         sub_alarm_rows = self._alarms_repo.get_sub_alarms(tenant_id, alarm_id)
  167 
  168         old_alarm, time_ms = self._alarms_repo.update_alarm(tenant_id, alarm_id,
  169                                                             new_state,
  170                                                             lifecycle_state, link)
  171         old_state = old_alarm['state']
  172         # alarm_definition_id is the same for all rows.
  173         alarm_definition_id = sub_alarm_rows[0]['alarm_definition_id']
  174 
  175         state_info = {u'alarmState': new_state, u'oldAlarmState': old_state}
  176 
  177         self._send_alarm_event(u'alarm-updated', tenant_id,
  178                                alarm_definition_id, alarm_metric_rows,
  179                                sub_alarm_rows, link, lifecycle_state, state_info)
  180 
  181         if old_state != new_state:
  182             try:
  183                 alarm_definition_row = self._alarms_repo.get_alarm_definition(
  184                     tenant_id, alarm_id)
  185             except exceptions.DoesNotExistException:
  186                 # Alarm definition does not exist. May have been deleted
  187                 # in another transaction. In that case, all associated
  188                 # alarms were also deleted, so don't send transition events.
  189                 pass
  190             else:
  191                 self._send_alarm_transitioned_event(tenant_id, alarm_id,
  192                                                     alarm_definition_row,
  193                                                     alarm_metric_rows,
  194                                                     old_state, new_state,
  195                                                     link, lifecycle_state,
  196                                                     time_ms)
  197 
  198     def _alarm_patch(self, tenant_id, alarm_id, new_state, lifecycle_state,
  199                      link):
  200 
  201         alarm_metric_rows = self._alarms_repo.get_alarm_metrics(alarm_id)
  202         sub_alarm_rows = self._alarms_repo.get_sub_alarms(tenant_id, alarm_id)
  203 
  204         old_alarm, time_ms = self._alarms_repo.update_alarm(tenant_id, alarm_id,
  205                                                             new_state,
  206                                                             lifecycle_state, link)
  207 
  208         # alarm_definition_id is the same for all rows.
  209         alarm_definition_id = sub_alarm_rows[0]['alarm_definition_id']
  210 
  211         state_info = {u'alarmState': new_state, u'oldAlarmState': old_alarm['state']}
  212 
  213         self._send_alarm_event(u'alarm-updated', tenant_id,
  214                                alarm_definition_id, alarm_metric_rows,
  215                                sub_alarm_rows, link, lifecycle_state, state_info)
  216 
  217         if old_alarm['state'] != new_state:
  218             try:
  219                 alarm_definition_row = self._alarms_repo.get_alarm_definition(
  220                     tenant_id, alarm_id)
  221             except exceptions.DoesNotExistException:
  222                 # Alarm definition does not exist. May have been deleted
  223                 # in another transaction. In that case, all associated
  224                 # alarms were also deleted, so don't send transition events.
  225                 pass
  226             else:
  227                 self._send_alarm_transitioned_event(tenant_id, alarm_id,
  228                                                     alarm_definition_row,
  229                                                     alarm_metric_rows,
  230                                                     old_alarm['state'], new_state,
  231                                                     link, lifecycle_state,
  232                                                     time_ms)
  233 
  234     def _alarm_delete(self, tenant_id, id):
  235 
  236         alarm_metric_rows = self._alarms_repo.get_alarm_metrics(id)
  237         sub_alarm_rows = self._alarms_repo.get_sub_alarms(tenant_id, id)
  238 
  239         self._alarms_repo.delete_alarm(tenant_id, id)
  240 
  241         # alarm_definition_id is the same for all rows.
  242         alarm_definition_id = sub_alarm_rows[0]['alarm_definition_id']
  243 
  244         self._send_alarm_event(u'alarm-deleted', tenant_id,
  245                                alarm_definition_id, alarm_metric_rows,
  246                                sub_alarm_rows, None, None)
  247 
  248     def _alarm_show(self, req_uri, tenant_id, alarm_id):
  249 
  250         alarm_rows = self._alarms_repo.get_alarm(tenant_id, alarm_id)
  251 
  252         req_uri_no_id = req_uri.replace('/' + alarm_id, "")
  253         first_row = True
  254         for alarm_row in alarm_rows:
  255             if first_row:
  256                 ad = {u'id': alarm_row['alarm_definition_id'],
  257                       u'name': alarm_row['alarm_definition_name'],
  258                       u'severity': alarm_row['severity'], }
  259                 helpers.add_links_to_resource(ad,
  260                                               re.sub('alarms',
  261                                                      'alarm-definitions',
  262                                                      req_uri_no_id))
  263 
  264                 metrics = []
  265                 alarm = {u'id': alarm_row['alarm_id'], u'metrics': metrics,
  266                          u'state': alarm_row['state'],
  267                          u'lifecycle_state': alarm_row['lifecycle_state'],
  268                          u'link': alarm_row['link'],
  269                          u'state_updated_timestamp':
  270                              alarm_row['state_updated_timestamp'].isoformat() +
  271                              'Z',
  272                          u'updated_timestamp':
  273                              alarm_row['updated_timestamp'].isoformat() + 'Z',
  274                          u'created_timestamp':
  275                              alarm_row['created_timestamp'].isoformat() + 'Z',
  276                          u'alarm_definition': ad}
  277                 helpers.add_links_to_resource(alarm, req_uri_no_id)
  278 
  279                 first_row = False
  280 
  281             dimensions = {}
  282             metric = {u'name': alarm_row['metric_name'],
  283                       u'dimensions': dimensions}
  284 
  285             if alarm_row['metric_dimensions']:
  286                 for dimension in alarm_row['metric_dimensions'].split(','):
  287                     parsed_dimension = dimension.split('=')
  288                     dimensions[parsed_dimension[0]] = parsed_dimension[1]
  289 
  290             metrics.append(metric)
  291 
  292         return alarm
  293 
  294     def _alarm_list(self, req_uri, tenant_id, query_parms, offset, limit):
  295 
  296         alarm_rows = self._alarms_repo.get_alarms(tenant_id, query_parms,
  297                                                   offset, limit)
  298 
  299         result = []
  300         if not alarm_rows:
  301             return helpers.paginate_alarming(result, req_uri, limit)
  302 
  303         # Forward declaration
  304         alarm = {}
  305         prev_alarm_id = None
  306         for alarm_row in alarm_rows:
  307             if prev_alarm_id != alarm_row['alarm_id']:
  308                 if prev_alarm_id is not None:
  309                     result.append(alarm)
  310 
  311                 ad = {u'id': alarm_row['alarm_definition_id'],
  312                       u'name': alarm_row['alarm_definition_name'],
  313                       u'severity': alarm_row['severity'], }
  314                 helpers.add_links_to_resource(ad,
  315                                               re.sub('alarms',
  316                                                      'alarm-definitions',
  317                                                      req_uri))
  318 
  319                 metrics = []
  320                 alarm = {u'id': alarm_row['alarm_id'], u'metrics': metrics,
  321                          u'state': alarm_row['state'],
  322                          u'lifecycle_state': alarm_row['lifecycle_state'],
  323                          u'link': alarm_row['link'],
  324                          u'state_updated_timestamp':
  325                              alarm_row['state_updated_timestamp'].isoformat() +
  326                              'Z',
  327                          u'updated_timestamp':
  328                              alarm_row['updated_timestamp'].isoformat() + 'Z',
  329                          u'created_timestamp':
  330                              alarm_row['created_timestamp'].isoformat() + 'Z',
  331                          u'alarm_definition': ad}
  332                 helpers.add_links_to_resource(alarm, req_uri)
  333 
  334                 prev_alarm_id = alarm_row['alarm_id']
  335 
  336             dimensions = {}
  337             metric = {u'name': alarm_row['metric_name'],
  338                       u'dimensions': dimensions}
  339 
  340             if alarm_row['metric_dimensions']:
  341                 for dimension in alarm_row['metric_dimensions'].split(','):
  342                     parsed_dimension = dimension.split('=')
  343                     dimensions[parsed_dimension[0]] = parsed_dimension[1]
  344 
  345             metrics.append(metric)
  346 
  347         result.append(alarm)
  348 
  349         return helpers.paginate_alarming(result, req_uri, limit)
  350 
  351 
  352 class AlarmsCount(alarms_api_v2.AlarmsCountV2API, alarming.Alarming):
  353 
  354     def __init__(self):
  355         try:
  356             super(AlarmsCount, self).__init__()
  357             self._region = cfg.CONF.region
  358             self._alarms_repo = simport.load(
  359                 cfg.CONF.repositories.alarms_driver)()
  360 
  361         except Exception as ex:
  362             LOG.exception(ex)
  363             raise exceptions.RepositoryException(ex)
  364 
  365     @resource.resource_try_catch_block
  366     def on_get(self, req, res):
  367         helpers.validate_authorization(req, ['api:alarms:count'])
  368         query_parms = falcon.uri.parse_query_string(req.query_string)
  369 
  370         if 'state' in query_parms:
  371             validation.validate_alarm_state(query_parms['state'])
  372             query_parms['state'] = query_parms['state'].upper()
  373 
  374         if 'severity' in query_parms:
  375             validation.validate_severity_query(query_parms['severity'])
  376             query_parms['severity'] = query_parms['severity'].upper()
  377 
  378         if 'group_by' in query_parms:
  379             if not isinstance(query_parms['group_by'], list):
  380                 query_parms['group_by'] = query_parms['group_by'].split(',')
  381             self._validate_group_by(query_parms['group_by'])
  382 
  383         query_parms['metric_dimensions'] = helpers.get_query_dimensions(req, 'metric_dimensions')
  384         helpers.validate_query_dimensions(query_parms['metric_dimensions'])
  385 
  386         offset = helpers.get_query_param(req, 'offset')
  387 
  388         if offset is not None:
  389             try:
  390                 offset = int(offset)
  391             except Exception:
  392                 raise HTTPUnprocessableEntityError(
  393                     "Unprocessable Entity",
  394                     "Offset must be a valid integer, was {}".format(offset))
  395 
  396         result = self._alarms_count(req.uri, req.project_id, query_parms, offset, req.limit)
  397 
  398         res.body = helpers.to_json(result)
  399         res.status = falcon.HTTP_200
  400 
  401     def _alarms_count(self, req_uri, tenant_id, query_parms, offset, limit):
  402 
  403         count_data = self._alarms_repo.get_alarms_count(tenant_id, query_parms, offset, limit)
  404         group_by = query_parms['group_by'] if 'group_by' in query_parms else []
  405 
  406         # result = count_data
  407         result = {
  408             'links': [
  409                 {
  410                     'rel': 'self',
  411                     'href': req_uri
  412                 }
  413             ],
  414             'columns': ['count']
  415         }
  416 
  417         if len(count_data) == 0 or count_data[0]['count'] == 0:
  418             count = [0]
  419             if 'group_by' in query_parms:
  420                 for field in query_parms['group_by']:
  421                     result['columns'].append(field)
  422                     count.append(None)
  423             result['counts'] = [count]
  424             return result
  425 
  426         if len(count_data) > limit:
  427             result['links'].append({
  428                 'rel': 'next',
  429                 'href': helpers.create_alarms_count_next_link(req_uri, offset, limit)})
  430             count_data = count_data[:limit]
  431 
  432         result['columns'].extend(group_by)
  433 
  434         result['counts'] = []
  435         for row in count_data:
  436             count_result = [row['count']]
  437             for field in group_by:
  438                 count_result.append(row[field])
  439             result['counts'].append(count_result)
  440 
  441         return result
  442 
  443     def _validate_group_by(self, group_by):
  444         allowed_values = {'alarm_definition_id', 'name', 'state', 'severity',
  445                           'link', 'lifecycle_state', 'metric_name',
  446                           'dimension_name', 'dimension_value'}
  447         if not set(group_by).issubset(allowed_values):
  448             raise HTTPUnprocessableEntityError(
  449                 "Unprocessable Entity",
  450                 "One or more group-by values from {} are not in {}"
  451                 .format(group_by, allowed_values))
  452 
  453 
  454 class AlarmsStateHistory(alarms_api_v2.AlarmsStateHistoryV2API,
  455                          alarming.Alarming):
  456     def __init__(self):
  457         try:
  458             super(AlarmsStateHistory, self).__init__()
  459             self._region = cfg.CONF.region
  460             self._alarms_repo = simport.load(
  461                 cfg.CONF.repositories.alarms_driver)()
  462             self._metrics_repo = simport.load(
  463                 cfg.CONF.repositories.metrics_driver)()
  464 
  465         except Exception as ex:
  466             LOG.exception(ex)
  467             raise exceptions.RepositoryException(ex)
  468 
  469     @resource.resource_try_catch_block
  470     def on_get(self, req, res, alarm_id=None):
  471         helpers.validate_authorization(req, ['api:alarms:state_history'])
  472         offset = helpers.get_query_param(req, 'offset')
  473 
  474         if alarm_id is None:
  475             start_timestamp = helpers.get_query_starttime_timestamp(req, False)
  476             end_timestamp = helpers.get_query_endtime_timestamp(req, False)
  477 
  478             dimensions = helpers.get_query_dimensions(req)
  479             helpers.validate_query_dimensions(dimensions)
  480 
  481             result = self._alarm_history_list(req.project_id, start_timestamp,
  482                                               end_timestamp, dimensions,
  483                                               req.uri, offset, req.limit)
  484 
  485         else:
  486             result = self._alarm_history(req.project_id, alarm_id,
  487                                          req.uri, offset,
  488                                          req.limit)
  489 
  490         res.body = helpers.to_json(result)
  491         res.status = falcon.HTTP_200
  492 
  493     def _alarm_history_list(self, tenant_id, start_timestamp,
  494                             end_timestamp, dimensions, req_uri, offset,
  495                             limit):
  496 
  497         # get_alarms expects 'metric_dimensions' for dimensions key.
  498         new_query_parms = {'metric_dimensions': dimensions}
  499 
  500         alarm_rows = self._alarms_repo.get_alarms(tenant_id, new_query_parms,
  501                                                   None, None)
  502         alarm_id_list = [alarm_row['alarm_id'] for alarm_row in alarm_rows]
  503 
  504         result = self._metrics_repo.alarm_history(tenant_id, alarm_id_list,
  505                                                   offset, limit,
  506                                                   start_timestamp,
  507                                                   end_timestamp)
  508 
  509         return helpers.paginate(result, req_uri, limit)
  510 
  511     def _alarm_history(self, tenant_id, alarm_id, req_uri, offset, limit):
  512 
  513         result = self._metrics_repo.alarm_history(tenant_id, [alarm_id], offset,
  514                                                   limit)
  515 
  516         return helpers.paginate(result, req_uri, limit)