# Copyright 2014,2016 Hewlett Packard Enterprise Development Company, L.P. # # 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 re import falcon from oslo_config import cfg from oslo_log import log import pyparsing import simport from monasca_api.api import alarm_definitions_api_v2 from monasca_api.common.repositories import exceptions import monasca_api.expression_parser.alarm_expr_parser from monasca_api.v2.common.exceptions import HTTPUnprocessableEntityError from monasca_api.v2.common.schemas import ( alarm_definition_request_body_schema as schema_alarms) from monasca_api.v2.common import validation from monasca_api.v2.reference import alarming from monasca_api.v2.reference import helpers from monasca_api.v2.reference import resource LOG = log.getLogger(__name__) class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API, alarming.Alarming): def __init__(self): try: super(AlarmDefinitions, self).__init__() self._region = cfg.CONF.region self._default_authorized_roles = ( cfg.CONF.security.default_authorized_roles) self._alarm_definitions_repo = simport.load( cfg.CONF.repositories.alarm_definitions_driver)() except Exception as ex: LOG.exception(ex) raise exceptions.RepositoryException(ex) def on_post(self, req, res): helpers.validate_authorization(req, self._default_authorized_roles) alarm_definition = helpers.read_json_msg_body(req) self._validate_alarm_definition(alarm_definition) tenant_id = helpers.get_tenant_id(req) name = get_query_alarm_definition_name(alarm_definition) expression = get_query_alarm_definition_expression(alarm_definition) description = get_query_alarm_definition_description(alarm_definition) severity = get_query_alarm_definition_severity(alarm_definition) match_by = get_query_alarm_definition_match_by(alarm_definition) alarm_actions = get_query_alarm_definition_alarm_actions( alarm_definition) undetermined_actions = get_query_alarm_definition_undetermined_actions( alarm_definition) ok_actions = get_query_ok_actions(alarm_definition) result = self._alarm_definition_create(tenant_id, name, expression, description, severity, match_by, alarm_actions, undetermined_actions, ok_actions) helpers.add_links_to_resource(result, req.uri) res.body = helpers.dumpit_utf8(result) res.status = falcon.HTTP_201 def on_get(self, req, res, alarm_definition_id=None): if alarm_definition_id is None: helpers.validate_authorization(req, self._default_authorized_roles) tenant_id = helpers.get_tenant_id(req) name = helpers.get_query_name(req) dimensions = helpers.get_query_dimensions(req) severity = helpers.get_query_param(req, "severity") sort_by = helpers.get_query_param(req, 'sort_by', default_val=None) if sort_by is not None: if isinstance(sort_by, basestring): sort_by = [sort_by] allowed_sort_by = {'id', 'name', 'severity', 'updated_at', 'created_at'} validation.validate_sort_by(sort_by, allowed_sort_by) offset = helpers.get_query_param(req, 'offset') if offset is not None and not isinstance(offset, int): try: offset = int(offset) except Exception: raise HTTPUnprocessableEntityError('Unprocessable Entity', 'Offset value {} must be an integer'.format(offset)) limit = helpers.get_limit(req) result = self._alarm_definition_list(tenant_id, name, dimensions, severity, req.uri, sort_by, offset, limit) res.body = helpers.dumpit_utf8(result) res.status = falcon.HTTP_200 else: helpers.validate_authorization(req, self._default_authorized_roles) tenant_id = helpers.get_tenant_id(req) result = self._alarm_definition_show(tenant_id, alarm_definition_id) helpers.add_links_to_resource(result, re.sub('/' + alarm_definition_id, '', req.uri)) res.body = helpers.dumpit_utf8(result) res.status = falcon.HTTP_200 def on_put(self, req, res, alarm_definition_id): helpers.validate_authorization(req, self._default_authorized_roles) alarm_definition = helpers.read_json_msg_body(req) self._validate_alarm_definition(alarm_definition, require_all=True) tenant_id = helpers.get_tenant_id(req) name = get_query_alarm_definition_name(alarm_definition) expression = get_query_alarm_definition_expression(alarm_definition) actions_enabled = ( get_query_alarm_definition_actions_enabled(alarm_definition)) description = get_query_alarm_definition_description(alarm_definition) alarm_actions = get_query_alarm_definition_alarm_actions(alarm_definition) ok_actions = get_query_ok_actions(alarm_definition) undetermined_actions = get_query_alarm_definition_undetermined_actions( alarm_definition) match_by = get_query_alarm_definition_match_by(alarm_definition) severity = get_query_alarm_definition_severity(alarm_definition) result = self._alarm_definition_update_or_patch(tenant_id, alarm_definition_id, name, expression, actions_enabled, description, alarm_actions, ok_actions, undetermined_actions, match_by, severity, patch=False) helpers.add_links_to_resource(result, req.uri) res.body = helpers.dumpit_utf8(result) res.status = falcon.HTTP_200 def on_patch(self, req, res, alarm_definition_id): helpers.validate_authorization(req, self._default_authorized_roles) alarm_definition = helpers.read_json_msg_body(req) tenant_id = helpers.get_tenant_id(req) # Optional args name = get_query_alarm_definition_name(alarm_definition, return_none=True) expression = get_query_alarm_definition_expression(alarm_definition, return_none=True) actions_enabled = ( get_query_alarm_definition_actions_enabled(alarm_definition, return_none=True)) description = get_query_alarm_definition_description(alarm_definition, return_none=True) alarm_actions = get_query_alarm_definition_alarm_actions( alarm_definition, return_none=True) ok_actions = get_query_ok_actions(alarm_definition, return_none=True) undetermined_actions = get_query_alarm_definition_undetermined_actions( alarm_definition, return_none=True) match_by = get_query_alarm_definition_match_by(alarm_definition, return_none=True) severity = get_query_alarm_definition_severity(alarm_definition, return_none=True) result = self._alarm_definition_update_or_patch(tenant_id, alarm_definition_id, name, expression, actions_enabled, description, alarm_actions, ok_actions, undetermined_actions, match_by, severity, patch=True) helpers.add_links_to_resource(result, req.uri) res.body = helpers.dumpit_utf8(result) res.status = falcon.HTTP_200 def on_delete(self, req, res, alarm_definition_id): helpers.validate_authorization(req, self._default_authorized_roles) tenant_id = helpers.get_tenant_id(req) self._alarm_definition_delete(tenant_id, alarm_definition_id) res.status = falcon.HTTP_204 def _validate_name_not_conflicting(self, tenant_id, name, expected_id=None): definitions = self._alarm_definitions_repo.get_alarm_definitions(tenant_id=tenant_id, name=name, dimensions=None, severity=None, sort_by=None, offset=None, limit=0) if definitions: if not expected_id: LOG.warn("Found existing definition for {} with tenant_id {}".format(name, tenant_id)) raise exceptions.AlreadyExistsException("An alarm definition with the name {} already exists" .format(name)) found_definition_id = definitions[0]['id'] if found_definition_id != expected_id: LOG.warn("Found existing alarm definition for {} with tenant_id {} with unexpected id {}" .format(name, tenant_id, found_definition_id)) raise exceptions.AlreadyExistsException( "An alarm definition with the name {} already exists with id {}" .format(name, found_definition_id)) @resource.resource_try_catch_block def _alarm_definition_show(self, tenant_id, id): alarm_definition_row = ( self._alarm_definitions_repo.get_alarm_definition(tenant_id, id)) return self._build_alarm_definition_show_result(alarm_definition_row) def _build_alarm_definition_show_result(self, alarm_definition_row): match_by = get_comma_separated_str_as_list( alarm_definition_row['match_by']) alarm_actions_list = get_comma_separated_str_as_list( alarm_definition_row['alarm_actions']) ok_actions_list = get_comma_separated_str_as_list( alarm_definition_row['ok_actions']) undetermined_actions_list = get_comma_separated_str_as_list( alarm_definition_row['undetermined_actions']) description = (alarm_definition_row['description'].decode('utf8') if alarm_definition_row['description'] is not None else None) result = { u'actions_enabled': alarm_definition_row['actions_enabled'] == 1, u'alarm_actions': alarm_actions_list, u'undetermined_actions': undetermined_actions_list, u'ok_actions': ok_actions_list, u'description': description, u'expression': alarm_definition_row['expression'].decode('utf8'), u'id': alarm_definition_row['id'].decode('utf8'), u'match_by': match_by, u'name': alarm_definition_row['name'].decode('utf8'), u'severity': alarm_definition_row['severity'].decode( 'utf8').upper()} return result @resource.resource_try_catch_block def _alarm_definition_delete(self, tenant_id, id): sub_alarm_definition_rows = ( self._alarm_definitions_repo.get_sub_alarm_definitions(id)) alarm_metric_rows = self._alarm_definitions_repo.get_alarm_metrics( tenant_id, id) sub_alarm_rows = self._alarm_definitions_repo.get_sub_alarms( tenant_id, id) if not self._alarm_definitions_repo.delete_alarm_definition( tenant_id, id): raise falcon.HTTPNotFound self._send_alarm_definition_deleted_event(id, sub_alarm_definition_rows) self._send_alarm_event(u'alarm-deleted', tenant_id, id, alarm_metric_rows, sub_alarm_rows, None, None) @resource.resource_try_catch_block def _alarm_definition_list(self, tenant_id, name, dimensions, severity, req_uri, sort_by, offset, limit): alarm_definition_rows = ( self._alarm_definitions_repo.get_alarm_definitions(tenant_id, name, dimensions, severity, sort_by, offset, limit)) result = [] for alarm_definition_row in alarm_definition_rows: match_by = get_comma_separated_str_as_list( alarm_definition_row['match_by']) alarm_actions_list = get_comma_separated_str_as_list( alarm_definition_row['alarm_actions']) ok_actions_list = get_comma_separated_str_as_list( alarm_definition_row['ok_actions']) undetermined_actions_list = get_comma_separated_str_as_list( alarm_definition_row['undetermined_actions']) ad = {u'id': alarm_definition_row['id'], u'name': alarm_definition_row['name'], u'description': alarm_definition_row['description'] if ( alarm_definition_row['description']) else u'', u'expression': alarm_definition_row['expression'], u'match_by': match_by, u'severity': alarm_definition_row['severity'].upper(), u'actions_enabled': alarm_definition_row['actions_enabled'] == 1, u'alarm_actions': alarm_actions_list, u'ok_actions': ok_actions_list, u'undetermined_actions': undetermined_actions_list} helpers.add_links_to_resource(ad, req_uri) result.append(ad) result = helpers.paginate(result, req_uri, limit) return result def _validate_alarm_definition(self, alarm_definition, require_all=False): try: schema_alarms.validate(alarm_definition, require_all=require_all) if 'match_by' in alarm_definition: for name in alarm_definition['match_by']: validation.dimension_key(name) except Exception as ex: LOG.debug(ex) raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message) @resource.resource_try_catch_block def _alarm_definition_update_or_patch(self, tenant_id, definition_id, name, expression, actions_enabled, description, alarm_actions, ok_actions, undetermined_actions, match_by, severity, patch): if expression: try: sub_expr_list = ( monasca_api.expression_parser.alarm_expr_parser. AlarmExprParser(expression).sub_expr_list) except pyparsing.ParseException as ex: LOG.exception(ex) title = "Invalid alarm expression".encode('utf8') msg = "parser failed on expression '{}' at column {}".format( expression.encode('utf8'), str(ex.column).encode('utf8')) raise HTTPUnprocessableEntityError(title, msg) else: sub_expr_list = None if name: self._validate_name_not_conflicting(tenant_id, name, expected_id=definition_id) alarm_def_row, sub_alarm_def_dicts = ( self._alarm_definitions_repo.update_or_patch_alarm_definition( tenant_id, definition_id, name, expression, sub_expr_list, actions_enabled, description, alarm_actions, ok_actions, undetermined_actions, match_by, severity, patch)) old_sub_alarm_def_event_dict = ( self._build_sub_alarm_def_update_dict( sub_alarm_def_dicts['old'])) new_sub_alarm_def_event_dict = ( self._build_sub_alarm_def_update_dict(sub_alarm_def_dicts[ 'new'])) changed_sub_alarm_def_event_dict = ( self._build_sub_alarm_def_update_dict(sub_alarm_def_dicts[ 'changed'])) unchanged_sub_alarm_def_event_dict = ( self._build_sub_alarm_def_update_dict(sub_alarm_def_dicts[ 'unchanged'])) alarm_def_event_dict = ( {u'tenantId': tenant_id, u'alarmDefinitionId': definition_id, u'alarmName': name, u'alarmDescription': description, u'alarmExpression': expression, u'severity': severity, u'matchBy': match_by, u'alarmActionsEnabled': actions_enabled, u'oldAlarmSubExpressions': old_sub_alarm_def_event_dict, u'changedSubExpressions': changed_sub_alarm_def_event_dict, u'unchangedSubExpressions': unchanged_sub_alarm_def_event_dict, u'newAlarmSubExpressions': new_sub_alarm_def_event_dict}) alarm_definition_updated_event = ( {u'alarm-definition-updated': alarm_def_event_dict}) self.send_event(self.events_message_queue, alarm_definition_updated_event) result = self._build_alarm_definition_show_result(alarm_def_row) return result def _build_sub_alarm_def_update_dict(self, sub_alarm_def_dict): sub_alarm_def_update_dict = {} for id, sub_alarm_def in sub_alarm_def_dict.items(): dimensions = {} for name, value in sub_alarm_def.dimensions.items(): dimensions[u'uname'] = value sub_alarm_def_update_dict[sub_alarm_def.id] = {} sub_alarm_def_update_dict[sub_alarm_def.id][u'function'] = ( sub_alarm_def.function) sub_alarm_def_update_dict[sub_alarm_def.id][ u'metricDefinition'] = ( {u'name': sub_alarm_def.metric_name, u'dimensions': dimensions}) sub_alarm_def_update_dict[sub_alarm_def.id][u'operator'] = ( sub_alarm_def.operator) sub_alarm_def_update_dict[sub_alarm_def.id][u'threshold'] = ( sub_alarm_def.threshold) sub_alarm_def_update_dict[sub_alarm_def.id][u'period'] = ( sub_alarm_def.period) sub_alarm_def_update_dict[sub_alarm_def.id][u'periods'] = ( sub_alarm_def.periods) sub_alarm_def_update_dict[sub_alarm_def.id][u'expression'] = ( sub_alarm_def.expression) return sub_alarm_def_update_dict @resource.resource_try_catch_block def _alarm_definition_create(self, tenant_id, name, expression, description, severity, match_by, alarm_actions, undetermined_actions, ok_actions): try: sub_expr_list = ( monasca_api.expression_parser.alarm_expr_parser. AlarmExprParser(expression).sub_expr_list) except pyparsing.ParseException as ex: LOG.exception(ex) title = "Invalid alarm expression".encode('utf8') msg = "parser failed on expression '{}' at column {}".format( expression.encode('utf8'), str(ex.column).encode('utf8')) raise HTTPUnprocessableEntityError(title, msg) self._validate_name_not_conflicting(tenant_id, name) alarm_definition_id = ( self._alarm_definitions_repo. create_alarm_definition(tenant_id, name, expression, sub_expr_list, description, severity, match_by, alarm_actions, undetermined_actions, ok_actions)) self._send_alarm_definition_created_event(tenant_id, alarm_definition_id, name, expression, sub_expr_list, description, match_by) result = ( {u'alarm_actions': alarm_actions, u'ok_actions': ok_actions, u'description': description, u'match_by': match_by, u'severity': severity, u'actions_enabled': u'true', u'undetermined_actions': undetermined_actions, u'expression': expression, u'id': alarm_definition_id, u'name': name}) return result def _send_alarm_definition_deleted_event(self, alarm_definition_id, sub_alarm_definition_rows): sub_alarm_definition_deleted_event_msg = {} alarm_definition_deleted_event_msg = {u"alarm-definition-deleted": { u"alarmDefinitionId": alarm_definition_id, u'subAlarmMetricDefinitions': sub_alarm_definition_deleted_event_msg}} for sub_alarm_definition in sub_alarm_definition_rows: sub_alarm_definition_deleted_event_msg[ sub_alarm_definition['id']] = { u'name': sub_alarm_definition['metric_name']} dimensions = {} sub_alarm_definition_deleted_event_msg[sub_alarm_definition['id']][ u'dimensions'] = dimensions if sub_alarm_definition['dimensions']: for dimension in sub_alarm_definition['dimensions'].split(','): parsed_dimension = dimension.split('=') dimensions[parsed_dimension[0]] = parsed_dimension[1] self.send_event(self.events_message_queue, alarm_definition_deleted_event_msg) def _send_alarm_definition_created_event(self, tenant_id, alarm_definition_id, name, expression, sub_expr_list, description, match_by): alarm_definition_created_event_msg = { u'alarm-definition-created': {u'tenantId': tenant_id, u'alarmDefinitionId': alarm_definition_id, u'alarmName': name, u'alarmDescription': description, u'alarmExpression': expression, u'matchBy': match_by}} sub_expr_event_msg = {} for sub_expr in sub_expr_list: sub_expr_event_msg[sub_expr.id] = { u'function': sub_expr.normalized_func} metric_definition = {u'name': sub_expr.normalized_metric_name} sub_expr_event_msg[sub_expr.id][ u'metricDefinition'] = metric_definition dimensions = {} for dimension in sub_expr.dimensions_as_list: parsed_dimension = dimension.split("=") dimensions[parsed_dimension[0]] = parsed_dimension[1] metric_definition[u'dimensions'] = dimensions sub_expr_event_msg[sub_expr.id][ u'operator'] = sub_expr.normalized_operator sub_expr_event_msg[sub_expr.id][u'threshold'] = sub_expr.threshold sub_expr_event_msg[sub_expr.id][u'period'] = sub_expr.period sub_expr_event_msg[sub_expr.id][u'periods'] = sub_expr.periods sub_expr_event_msg[sub_expr.id][ u'expression'] = sub_expr.fmtd_sub_expr_str alarm_definition_created_event_msg[u'alarm-definition-created'][ u'alarmSubExpressions'] = sub_expr_event_msg self.send_event(self.events_message_queue, alarm_definition_created_event_msg) def get_query_alarm_definition_name(alarm_definition, return_none=False): try: if 'name' in alarm_definition: name = alarm_definition['name'] return name else: if return_none: return None else: raise Exception("Missing name") except Exception as ex: LOG.debug(ex) raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message) def get_query_alarm_definition_expression(alarm_definition, return_none=False): try: if 'expression' in alarm_definition: expression = alarm_definition['expression'] return expression else: if return_none: return None else: raise Exception("Missing expression") except Exception as ex: LOG.debug(ex) raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message) def get_query_alarm_definition_description(alarm_definition, return_none=False): if 'description' in alarm_definition: return alarm_definition['description'] else: if return_none: return None else: return '' def get_query_alarm_definition_severity(alarm_definition, return_none=False): if 'severity' in alarm_definition: severity = alarm_definition['severity'] severity = severity.decode('utf8').upper() if severity not in ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']: raise HTTPUnprocessableEntityError('Unprocessable Entity', 'Invalid severity') return severity else: if return_none: return None else: return 'LOW' def get_query_alarm_definition_match_by(alarm_definition, return_none=False): if 'match_by' in alarm_definition: match_by = alarm_definition['match_by'] return match_by else: if return_none: return None else: return [] def get_query_alarm_definition_alarm_actions(alarm_definition, return_none=False): if 'alarm_actions' in alarm_definition: alarm_actions = alarm_definition['alarm_actions'] return alarm_actions else: if return_none: return None else: return [] def get_query_alarm_definition_undetermined_actions(alarm_definition, return_none=False): if 'undetermined_actions' in alarm_definition: undetermined_actions = alarm_definition['undetermined_actions'] return undetermined_actions else: if return_none: return None else: return [] def get_query_ok_actions(alarm_definition, return_none=False): if 'ok_actions' in alarm_definition: ok_actions = alarm_definition['ok_actions'] return ok_actions else: if return_none: return None else: return [] def get_query_alarm_definition_actions_enabled(alarm_definition, required=False, return_none=False): try: if 'actions_enabled' in alarm_definition: enabled_actions = alarm_definition['actions_enabled'] return enabled_actions else: if return_none: return None elif required: raise Exception("Missing actions-enabled") else: return '' except Exception as ex: LOG.debug(ex) raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message) def get_comma_separated_str_as_list(comma_separated_str): if not comma_separated_str: return [] else: return comma_separated_str.decode('utf8').split(',')