# -*- coding: utf-8 -*- # Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors # # 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 logging import sqlalchemy as sa import urllib.request, urllib.parse, urllib.error from datetime import timedelta from appenlight.models import Base from appenlight.lib.utils.date_utils import convert_date from sqlalchemy.dialects.postgresql import JSON from ziggurat_foundations.models.base import BaseModel log = logging.getLogger(__name__) # channel_rules_m2m_table = sa.Table( 'channels_actions', Base.metadata, sa.Column('channel_pkey', sa.Integer, sa.ForeignKey('alert_channels.pkey')), sa.Column('action_pkey', sa.Integer, sa.ForeignKey('alert_channels_actions.pkey')) ) channel_resources_m2m_table = sa.Table( 'channels_resources', Base.metadata, sa.Column('channel_pkey', sa.Integer, sa.ForeignKey('alert_channels.pkey')), sa.Column('resource_id', sa.Integer, sa.ForeignKey('resources.resource_id')) ) DATE_FRMT = '%Y-%m-%dT%H:%M' class AlertChannel(Base, BaseModel): """ Stores information about possible alerting options """ __tablename__ = 'alert_channels' __possible_channel_names__ = ['email'] __mapper_args__ = { 'polymorphic_on': 'channel_name', 'polymorphic_identity': 'integration' } owner_id = sa.Column(sa.Unicode(30), sa.ForeignKey('users.id', onupdate='CASCADE', ondelete='CASCADE')) channel_name = sa.Column(sa.Unicode(25), nullable=False) channel_value = sa.Column(sa.Unicode(80), nullable=False, default='') channel_json_conf = sa.Column(JSON(), nullable=False, default='') channel_validated = sa.Column(sa.Boolean, nullable=False, default=False) send_alerts = sa.Column(sa.Boolean, nullable=False, default=True) daily_digest = sa.Column(sa.Boolean, nullable=False, default=True) integration_id = sa.Column(sa.Integer, sa.ForeignKey('integrations.id'), nullable=True) pkey = sa.Column(sa.Integer(), nullable=False, primary_key=True) channel_actions = sa.orm.relationship('AlertChannelAction', cascade="all", passive_deletes=True, passive_updates=True, secondary=channel_rules_m2m_table, backref='channels') resources = sa.orm.relationship('Resource', cascade="all", passive_deletes=True, passive_updates=True, secondary=channel_resources_m2m_table, backref='resources') @property def channel_visible_value(self): if self.integration: return '{}: {}'.format( self.channel_name, self.integration.resource.resource_name ) return '{}: {}'.format( self.channel_name, self.channel_value ) def get_dict(self, exclude_keys=None, include_keys=None, extended_info=True): """ Returns dictionary with required information that will be consumed by angular """ instance_dict = super(AlertChannel, self).get_dict(exclude_keys, include_keys) exclude_keys_list = exclude_keys or [] include_keys_list = include_keys or [] instance_dict['supports_report_alerting'] = True instance_dict['channel_visible_value'] = self.channel_visible_value if extended_info: instance_dict['actions'] = [ rule.get_dict(extended_info=True) for rule in self.channel_actions] del instance_dict['channel_json_conf'] if self.integration: instance_dict[ 'supports_report_alerting'] = \ self.integration.supports_report_alerting d = {} for k in instance_dict.keys(): if (k not in exclude_keys_list and (k in include_keys_list or not include_keys)): d[k] = instance_dict[k] return d def __repr__(self): return '' % (self.channel_name, self.channel_value, self.user_name,) def send_digest(self, **kwargs): """ This should implement daily top error report notifications """ log.warning('send_digest NOT IMPLEMENTED') def notify_reports(self, **kwargs): """ This should implement notification of reports that occured in 1 min interval """ log.warning('notify_reports NOT IMPLEMENTED') def notify_alert(self, **kwargs): """ Notify user of report/uptime/chart threshold events based on events alert type Kwargs: application: application that the event applies for, event: event that is notified, user: user that should be notified request: request object """ alert_name = kwargs['event'].unified_alert_name() if alert_name in ['slow_report_alert', 'error_report_alert']: self.notify_report_alert(**kwargs) elif alert_name == 'uptime_alert': self.notify_uptime_alert(**kwargs) elif alert_name == 'chart_alert': self.notify_chart_alert(**kwargs) def notify_chart_alert(self, **kwargs): """ This should implement report open/close alerts notifications """ log.warning('notify_chart_alert NOT IMPLEMENTED') def notify_report_alert(self, **kwargs): """ This should implement report open/close alerts notifications """ log.warning('notify_report_alert NOT IMPLEMENTED') def notify_uptime_alert(self, **kwargs): """ This should implement uptime open/close alerts notifications """ log.warning('notify_uptime_alert NOT IMPLEMENTED') def get_notification_basic_vars(self, kwargs): """ Sets most common variables used later for rendering notifications for channel """ if 'event' in kwargs: kwargs['since_when'] = kwargs['event'].start_date url_start_date = kwargs.get('since_when') - timedelta(minutes=1) url_end_date = kwargs.get('since_when') + timedelta(minutes=4) tmpl_vars = { "timestamp": kwargs['since_when'], "user": kwargs['user'], "since_when": kwargs.get('since_when'), "url_start_date": url_start_date, "url_end_date": url_end_date } tmpl_vars["resource_name"] = kwargs['resource'].resource_name tmpl_vars["resource"] = kwargs['resource'] if 'event' in kwargs: tmpl_vars['event_values'] = kwargs['event'].values tmpl_vars['alert_type'] = kwargs['event'].unified_alert_name() tmpl_vars['alert_action'] = kwargs['event'].unified_alert_action() return tmpl_vars def report_alert_notification_vars(self, kwargs): tmpl_vars = self.get_notification_basic_vars(kwargs) reports = kwargs.get('reports', []) tmpl_vars["reports"] = reports tmpl_vars["confirmed_total"] = len(reports) tmpl_vars["report_type"] = "error reports" tmpl_vars["url_report_type"] = 'report/list' alert_type = tmpl_vars.get('alert_type', '') if 'slow_report' in alert_type: tmpl_vars["report_type"] = "slow reports" tmpl_vars["url_report_type"] = 'report/list_slow' app_url = kwargs['request'].registry.settings['_mail_url'] destination_url = kwargs['request'].route_url('/', _app_url=app_url) if alert_type: destination_url += 'ui/{}?resource={}&start_date={}&end_date={}'.format( tmpl_vars["url_report_type"], tmpl_vars['resource'].resource_id, tmpl_vars['url_start_date'].strftime(DATE_FRMT), tmpl_vars['url_end_date'].strftime(DATE_FRMT) ) else: destination_url += 'ui/{}?resource={}'.format( tmpl_vars["url_report_type"], tmpl_vars['resource'].resource_id ) tmpl_vars["destination_url"] = destination_url return tmpl_vars def uptime_alert_notification_vars(self, kwargs): tmpl_vars = self.get_notification_basic_vars(kwargs) app_url = kwargs['request'].registry.settings['_mail_url'] destination_url = kwargs['request'].route_url('/', _app_url=app_url) destination_url += 'ui/{}?resource={}'.format( 'uptime', tmpl_vars['resource'].resource_id) tmpl_vars['destination_url'] = destination_url reason = '' e_values = tmpl_vars.get('event_values') if e_values and e_values.get('response_time') == 0: reason += ' Response time was slower than 20 seconds.' elif e_values: code = e_values.get('status_code') reason += ' Response status code: %s.' % code tmpl_vars['reason'] = reason return tmpl_vars def chart_alert_notification_vars(self, kwargs): tmpl_vars = self.get_notification_basic_vars(kwargs) tmpl_vars['chart_name'] = tmpl_vars['event_values']['chart_name'] tmpl_vars['action_name'] = tmpl_vars['event_values'].get( 'action_name') or '' matched_values = tmpl_vars['event_values']['matched_step_values'] tmpl_vars['readable_values'] = [] for key, value in list(matched_values['values'].items()): matched_label = matched_values['labels'].get(key) if matched_label: tmpl_vars['readable_values'].append({ 'label': matched_label['human_label'], 'value': value }) tmpl_vars['readable_values'] = sorted(tmpl_vars['readable_values'], key=lambda x: x['label']) start_date = convert_date(tmpl_vars['event_values']['start_interval']) end_date = None if tmpl_vars['event_values'].get('end_interval'): end_date = convert_date(tmpl_vars['event_values']['end_interval']) app_url = kwargs['request'].registry.settings['_mail_url'] destination_url = kwargs['request'].route_url('/', _app_url=app_url) to_encode = { 'resource': tmpl_vars['event_values']['resource'], 'start_date': start_date.strftime(DATE_FRMT), } if end_date: to_encode['end_date'] = end_date.strftime(DATE_FRMT) destination_url += 'ui/{}?{}'.format( 'logs', urllib.parse.urlencode(to_encode) ) tmpl_vars['destination_url'] = destination_url return tmpl_vars