<filename>monasca_api/v2/reference/alarming.py
# 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 falcon
from monasca_common.simport import simport
from oslo_config import cfg
from oslo_log import log

from monasca_api.common.messaging import (
    exceptions as message_queue_exceptions)
import monasca_api.expression_parser.alarm_expr_parser
from monasca_api.v2.reference import helpers

LOG = log.getLogger(__name__)


class Alarming(object):
    """Super class for Alarms and AlarmDefinitions.

    Shared attributes and methods for classes Alarms and AlarmDefinitions.
    """

    def __init__(self):

        super(Alarming, self).__init__()

        self.events_message_queue = simport.load(
            cfg.CONF.messaging.driver)(cfg.CONF.kafka.events_topic)

        self.alarm_state_transitions_message_queue = simport.load(
            cfg.CONF.messaging.driver)(cfg.CONF.kafka.alarm_state_transitions_topic)

    def _send_alarm_transitioned_event(self, tenant_id, alarm_id,
                                       alarm_definition_row,
                                       alarm_metric_rows,
                                       old_state, new_state,
                                       link, lifecycle_state,
                                       time_ms):

        # This is a change via the API, so there is no SubAlarm info to add
        sub_alarms = []
        metrics = []
        alarm_transitioned_event_msg = {u'alarm-transitioned': {
            u'tenantId': tenant_id,
            u'alarmId': alarm_id,
            u'alarmDefinitionId': alarm_definition_row['id'],
            u'alarmName': alarm_definition_row['name'],
            u'alarmDescription': alarm_definition_row['description'],
            u'actionsEnabled': alarm_definition_row['actions_enabled'] == 1,
            u'stateChangeReason': 'Alarm state updated via API',
            u'severity': alarm_definition_row['severity'],
            u'link': link,
            u'lifecycleState': lifecycle_state,
            u'oldState': old_state,
            u'newState': new_state,
            u'timestamp': time_ms,
            u'subAlarms': sub_alarms,
            u'metrics': metrics}
        }

        for alarm_metric_row in alarm_metric_rows:
            metric = self._build_metric(alarm_metric_row)
            metrics.append(metric)

        self.send_event(self.alarm_state_transitions_message_queue,
                        alarm_transitioned_event_msg)

    def _build_metric(self, alarm_metric_row):

        dimensions = {}

        metric = {u'name': alarm_metric_row['name'],
                  u'dimensions': dimensions}

        if alarm_metric_row['dimensions']:
            for dimension in alarm_metric_row['dimensions'].split(','):
                parsed_dimension = dimension.split('=')
                dimensions[parsed_dimension[0]] = parsed_dimension[1]

        return metric

    def _send_alarm_event(self, event_type, tenant_id, alarm_definition_id,
                          alarm_metric_rows, sub_alarm_rows, link, lifecycle_state,
                          extra_info=None):

        if not alarm_metric_rows:
            return

        # Build a dict mapping alarm id -> list of sub alarms.
        sub_alarm_dict = {}
        for sub_alarm_row in sub_alarm_rows:
            if sub_alarm_row['alarm_id'] in sub_alarm_dict:
                sub_alarm_dict[sub_alarm_row['alarm_id']] += [sub_alarm_row]
            else:
                sub_alarm_dict[sub_alarm_row['alarm_id']] = [sub_alarm_row]

        # Forward declaration.
        alarm_event_msg = {}
        prev_alarm_id = None
        for alarm_metric_row in alarm_metric_rows:
            if prev_alarm_id != alarm_metric_row['alarm_id']:
                if prev_alarm_id is not None:
                    sub_alarms_event_msg = (
                        self._build_sub_alarm_event_msg(sub_alarm_dict,
                                                        prev_alarm_id))
                    alarm_event_msg[event_type][u'subAlarms'] = sub_alarms_event_msg
                    self.send_event(self.events_message_queue,
                                    alarm_event_msg)

                alarm_metrics_event_msg = []
                alarm_event_msg = {event_type: {u'tenantId': tenant_id,
                                                u'alarmDefinitionId':
                                                    alarm_definition_id,
                                                u'alarmId': alarm_metric_row[
                                                    'alarm_id'],
                                                u'link': link,
                                                u'lifecycleState': lifecycle_state,
                                                u'alarmMetrics':
                                                    alarm_metrics_event_msg}}
                if extra_info:
                    alarm_event_msg[event_type].update(extra_info)

                prev_alarm_id = alarm_metric_row['alarm_id']

            metric = self._build_metric(alarm_metric_row)
            alarm_metrics_event_msg.append(metric)

        # Finish last alarm
        sub_alarms_event_msg = self._build_sub_alarm_event_msg(sub_alarm_dict,
                                                               prev_alarm_id)
        alarm_event_msg[event_type][u'subAlarms'] = sub_alarms_event_msg

        self.send_event(self.events_message_queue,
                        alarm_event_msg)

    def _build_sub_alarm_event_msg(self, sub_alarm_dict, alarm_id):

        sub_alarms_event_msg = {}

        if alarm_id not in sub_alarm_dict:
            return sub_alarms_event_msg

        for sub_alarm in sub_alarm_dict[alarm_id]:
            # There's only one expr in a sub alarm, so just take the first.
            sub_expr = (
                monasca_api.expression_parser.alarm_expr_parser.
                AlarmExprParser(sub_alarm['expression']).sub_expr_list[0])
            dimensions = {}
            sub_alarms_event_msg[sub_alarm['sub_alarm_id']] = {
                u'function': sub_expr.normalized_func,
                u'metricDefinition': {u'name': sub_expr.metric_name,
                                      u'dimensions': dimensions},
                u'operator': sub_expr.normalized_operator,
                u'threshold': sub_expr.threshold, u'period': sub_expr.period,
                u'periods': sub_expr.periods,
                u'expression': sub_expr.fmtd_sub_expr_str}

            for dimension in sub_expr.dimensions_as_list:
                parsed_dimension = dimension.split('=')
                dimensions[parsed_dimension[0]] = parsed_dimension[1]

        return sub_alarms_event_msg

    def send_event(self, message_queue, event_msg):
        try:
            message_queue.send_message(helpers.to_json(event_msg))
        except message_queue_exceptions.MessageQueueException as ex:
            LOG.exception(ex)
            raise falcon.HTTPInternalServerError(
                'Message queue service unavailable'.encode('utf8'),
                str(ex).encode('utf8'))

<filename>monasca_api/v2/reference/alarming.py
# 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 falcon
from monasca_common.simport import simport
from oslo_config import cfg
from oslo_log import log

from monasca_api.common.messaging import (
    exceptions as message_queue_exceptions)
import monasca_api.expression_parser.alarm_expr_parser
from monasca_api.v2.reference import helpers

LOG = log.getLogger(__name__)


class Alarming(object):
    """Super class for Alarms and AlarmDefinitions.

    Shared attributes and methods for classes Alarms and AlarmDefinitions.
    """

    def __init__(self):

        super(Alarming, self).__init__()

        self.events_message_queue = simport.load(
            cfg.CONF.messaging.driver)(cfg.CONF.kafka.events_topic)

        self.alarm_state_transitions_message_queue = simport.load(
            cfg.CONF.messaging.driver)(cfg.CONF.kafka.alarm_state_transitions_topic)

    def _send_alarm_transitioned_event(self, tenant_id, alarm_id,
                                       alarm_definition_row,
                                       alarm_metric_rows,
                                       old_state, new_state,
                                       link, lifecycle_state,
                                       time_ms):

        # This is a change via the API, so there is no SubAlarm info to add
        sub_alarms = []
        metrics = []
        alarm_transitioned_event_msg = {u'alarm-transitioned': {
            u'tenantId': tenant_id,
            u'alarmId': alarm_id,
            u'alarmDefinitionId': alarm_definition_row['id'],
            u'alarmName': alarm_definition_row['name'],
            u'alarmDescription': alarm_definition_row['description'],
            u'actionsEnabled': alarm_definition_row['actions_enabled'] == 1,
            u'stateChangeReason': 'Alarm state updated via API',
            u'severity': alarm_definition_row['severity'],
            u'link': link,
            u'lifecycleState': lifecycle_state,
            u'oldState': old_state,
            u'newState': new_state,
            u'timestamp': time_ms,
            u'subAlarms': sub_alarms,
            u'metrics': metrics}
        }

        for alarm_metric_row in alarm_metric_rows:
            metric = self._build_metric(alarm_metric_row)
            metrics.append(metric)

        self.send_event(self.alarm_state_transitions_message_queue,
                        alarm_transitioned_event_msg)

    def _build_metric(self, alarm_metric_row):

        dimensions = {}

        metric = {u'name': alarm_metric_row['name'],
                  u'dimensions': dimensions}

        if alarm_metric_row['dimensions']:
            for dimension in alarm_metric_row['dimensions'].split(','):
                parsed_dimension = dimension.split('=')
                dimensions[parsed_dimension[0]] = parsed_dimension[1]

        return metric

    def _send_alarm_event(self, event_type, tenant_id, alarm_definition_id,
                          alarm_metric_rows, sub_alarm_rows, link, lifecycle_state,
                          extra_info=None):

        if not alarm_metric_rows:
            return

        # Build a dict mapping alarm id -> list of sub alarms.
        sub_alarm_dict = {}
        for sub_alarm_row in sub_alarm_rows:
            if sub_alarm_row['alarm_id'] in sub_alarm_dict:
                sub_alarm_dict[sub_alarm_row['alarm_id']] += [sub_alarm_row]
            else:
                sub_alarm_dict[sub_alarm_row['alarm_id']] = [sub_alarm_row]

        # Forward declaration.
        alarm_event_msg = {}
        prev_alarm_id = None
        for alarm_metric_row in alarm_metric_rows:
            if prev_alarm_id != alarm_metric_row['alarm_id']:
                if prev_alarm_id is not None:
                    sub_alarms_event_msg = (
                        self._build_sub_alarm_event_msg(sub_alarm_dict,
                                                        prev_alarm_id))
                    alarm_event_msg[event_type][u'subAlarms'] = sub_alarms_event_msg
                    self.send_event(self.events_message_queue,
                                    alarm_event_msg)

                alarm_metrics_event_msg = []
                alarm_event_msg = {event_type: {u'tenantId': tenant_id,
                                                u'alarmDefinitionId':
                                                    alarm_definition_id,
                                                u'alarmId': alarm_metric_row[
                                                    'alarm_id'],
                                                u'link': link,
                                                u'lifecycleState': lifecycle_state,
                                                u'alarmMetrics':
                                                    alarm_metrics_event_msg}}
                if extra_info:
                    alarm_event_msg[event_type].update(extra_info)

                prev_alarm_id = alarm_metric_row['alarm_id']

            metric = self._build_metric(alarm_metric_row)
            alarm_metrics_event_msg.append(metric)

        # Finish last alarm
        sub_alarms_event_msg = self._build_sub_alarm_event_msg(sub_alarm_dict,
                                                               prev_alarm_id)
        alarm_event_msg[event_type][u'subAlarms'] = sub_alarms_event_msg

        self.send_event(self.events_message_queue,
                        alarm_event_msg)

    def _build_sub_alarm_event_msg(self, sub_alarm_dict, alarm_id):

        sub_alarms_event_msg = {}

        if alarm_id not in sub_alarm_dict:
            return sub_alarms_event_msg

        for sub_alarm in sub_alarm_dict[alarm_id]:
            # There's only one expr in a sub alarm, so just take the first.
            sub_expr = (
                monasca_api.expression_parser.alarm_expr_parser.
                AlarmExprParser(sub_alarm['expression']).sub_expr_list[0])
            dimensions = {}
            sub_alarms_event_msg[sub_alarm['sub_alarm_id']] = {
                u'function': sub_expr.normalized_func,
                u'metricDefinition': {u'name': sub_expr.metric_name,
                                      u'dimensions': dimensions},
                u'operator': sub_expr.normalized_operator,
                u'threshold': sub_expr.threshold, u'period': sub_expr.period,
                u'periods': sub_expr.periods,
                u'expression': sub_expr.fmtd_sub_expr_str}

            for dimension in sub_expr.dimensions_as_list:
                parsed_dimension = dimension.split('=')
                dimensions[parsed_dimension[0]] = parsed_dimension[1]

        return sub_alarms_event_msg

    def send_event(self, message_queue, event_msg):
        try:
            message_queue.send_message(helpers.to_json(event_msg))
        except message_queue_exceptions.MessageQueueException as ex:
            LOG.exception(ex)
            raise falcon.HTTPInternalServerError(
                'Message queue service unavailable'.encode('utf8'),
                str(ex).encode('utf8'))

# 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. Super class for Alarms and AlarmDefinitions.      Shared attributes and methods for classes Alarms and AlarmDefinitions. # This is a change via the API, so there is no SubAlarm info to add # Build a dict mapping alarm id -> list of sub alarms. # Forward declaration. # Finish last alarm # There's only one expr in a sub alarm, so just take the first.