def a_snooze(self, event, action, name): """ Snooze event checks :param dict event: event to be snoozed :param dict action: action :param str name: name of the rule :returns: True if a snooze has been sent, False otherwise :rtype: boolean """ if event.get('event_type') == 'snooze': return False # Only check events can trigger an auto-snooze if event.get('event_type') != 'check': return False # A check OK cannot trigger an auto-snooze if event.get('state') == 0: return False # Alerts manager caching if not hasattr(self, 'am'): self.am = Alerts(*Alerts.provide_default_basics()) # Context manager caching if not hasattr(self, 'cm'): self.cm = ContextGraph(self.logger) entity_id = self.cm.get_id(event) current_alarm = self.am.get_current_alarm(entity_id) if current_alarm is None: snooze = { 'connector': event.get('connector', ''), 'connector_name': event.get('connector_name', ''), 'source_type': event.get('source_type', ''), 'component': event.get('component', ''), 'event_type': 'snooze', 'duration': action['duration'], 'author': 'event_filter', 'output': 'Auto snooze generated by rule "{}"'.format(name), 'timestamp': int(time.time()) } if event.get('resource', ''): snooze['resource'] = event['resource'] try: self.work_amqp_publisher.direct_event(snooze, 'Engine_event_filter') except Exception as e: self.logger.exception("Unable to send snooze event") return True return False
def exports(ws): el_kwargs = {'el_storage': EventsLog.provide_default_basics()} manager = singleton_per_scope(EventsLog, kwargs=el_kwargs) am = Alerts(*Alerts.provide_default_basics()) @ws.application.post('/api/v2/event') def send_event_post(): try: events = request.json except ValueError as verror: return gen_json_error( {'description': 'malformed JSON : {0}'.format(verror)}, HTTP_ERROR) if events is None: return gen_json_error({'description': 'nothing to return'}, HTTPError) return send_events(ws, am, events) @route(ws.application.post, name='event', payload=['event', 'url']) @route(ws.application.put, name='event', payload=['event', 'url']) def send_event(event, url=None): if ws.enable_crossdomain_send_events and url is not None: payload = {'event': json.dumps(event)} response = requests.post(url, data=payload) if response.status_code != 200: api_response = json.loads(response.text) return (api_response['data'], api_response['total']) return HTTPError(response.status_code, response.text) return send_events(ws, am, event) @route(ws.application.get, name='eventslog/count', payload=['tstart', 'tstop', 'limit', 'select']) def get_event_count_per_day(tstart, tstop, limit=100, select={}): """ get eventslog log count for each days in a given period :param tstart: timestamp of the begin period :param tstop: timestamp of the end period :param limit: limit the count number per day :param select: filter for eventslog collection :return: list in which each item contains an interval and the related count :rtype: list """ results = manager.get_eventlog_count_by_period(tstart, tstop, limit=limit, query=select) return results
def a_snooze(self, event, action, name): """ Snooze event checks :param dict event: event to be snoozed :param dict action: action :param str name: name of the rule :returns: True if a snooze has been sent, False otherwise :rtype: boolean """ if event.get('event_type') == 'snooze': return False # Only check events can trigger an auto-snooze if event.get('event_type') != 'check': return False # A check OK cannot trigger an auto-snooze if event.get('state') == 0: return False # Alerts manager caching if not hasattr(self, 'am'): self.am = Alerts(*Alerts.provide_default_basics()) # Context manager caching if not hasattr(self, 'cm'): self.cm = ContextGraph(self.logger) entity_id = self.cm.get_id(event) current_alarm = self.am.get_current_alarm(entity_id) if current_alarm is None: snooze = { 'connector': event.get('connector', ''), 'connector_name': event.get('connector_name', ''), 'source_type': event.get('source_type', ''), 'component': event.get('component', ''), 'event_type': 'snooze', 'duration': action['duration'], 'author': 'event_filter', 'output': 'Auto snooze generated by rule "{}"'.format(name), 'timestamp': int(time.time()) } if event.get('resource', ''): snooze['resource'] = event['resource'] try: self.work_amqp_publisher.direct_event( snooze, 'Engine_event_filter') except Exception as e: self.logger.exception("Unable to send snooze event") return True return False
def setUp(self): self.logger = logging.getLogger('alerts') self.alerts_storage = Middleware.get_middleware_by_uri( 'storage-periodical-testalarm://') self.config_storage = Middleware.get_middleware_by_uri( 'storage-default-testconfig://') self.config_storage.put_element(element={ '_id': 'test_config', 'crecord_type': 'statusmanagement', 'bagot_time': 3600, 'bagot_freq': 10, 'stealthy_time': 300, 'restore_event': True, 'auto_snooze': False, 'snooze_default_time': 300, }, _id='test_config') self.filter_storage = Middleware.get_middleware_by_uri( 'storage-default-testalarmfilter://') self.context_graph_storage = Middleware.get_middleware_by_uri( 'storage-default-testentities://') self.cg_manager = ContextGraph(self.logger) self.cg_manager.ent_storage = self.context_graph_storage self.watcher_manager = Watcher() conf = Configuration.load(Alerts.CONF_PATH, Ini) filter_ = {'crecord_type': 'statusmanagement'} self.config_data = EtherealData(collection=MongoCollection( self.config_storage._backend), filter_=filter_) self.event_publisher = Mock(spec=StatEventPublisher) self.manager = Alerts(config=conf, logger=self.logger, alerts_storage=self.alerts_storage, config_data=self.config_data, filter_storage=self.filter_storage, context_graph=self.cg_manager, watcher=self.watcher_manager, event_publisher=self.event_publisher)
def a_snooze(self, event, action, name): """ Snooze event checks :param dict event: event to be snoozed :param dict action: action :param str name: name of the rule :returns: True if a snooze has been sent, False otherwise :rtype: boolean """ # Only check events can trigger an auto-snooze if event['event_type'] != 'check': return False # A check OK cannot trigger an auto-snooze if event['state'] == 0: return False # Alerts manager caching if not hasattr(self, 'am'): self.am = Alerts() # Context manager caching if not hasattr(self, 'cm'): self.cm = Context() entity = self.cm.get_entity(event) entity_id = self.cm.get_entity_id(entity) current_alarm = self.am.get_current_alarm(entity_id) if current_alarm is None: snooze = { 'connector': event.get('connector', ''), 'connector_name': event.get('connector_name', ''), 'source_type': event.get('source_type', ''), 'component': event.get('component', ''), 'event_type': 'snooze', 'duration': action['duration'], 'author': 'event_filter', 'output': 'Auto snooze generated by rule "{}"'.format(name), } if 'resource' in event: snooze['resource'] = event['resource'] publish(event=snooze, publisher=self.amqp) return True return False
# --------------------------------- from __future__ import unicode_literals from canopsis.alarms.adapters import AlarmAdapter from canopsis.alarms.services import AlarmService from canopsis.alerts.manager import Alerts from canopsis.alerts.reader import AlertsReader from canopsis.task.core import register_task from canopsis.watcher.manager import Watcher from canopsis.common.mongo_store import MongoStore # import this module so tasks are registered in canopsis.tasks.core import canopsis.alerts.tasks as __alerts_tasks work_alerts_manager = Alerts(*Alerts.provide_default_basics()) beat_alerts_manager = Alerts(*Alerts.provide_default_basics()) alertsreader_manager = AlertsReader(*AlertsReader.provide_default_basics()) mongo_store = MongoStore.get_default() @register_task def event_processing(engine, event, alertsmgr=None, **kwargs): """ AMQP Event processing. """ if alertsmgr is None: alertsmgr = work_alerts_manager encoded_event = {}
from bottle import request import json as j import os from uuid import uuid4 from canopsis.common import root_path from canopsis.common.ws import route from canopsis.alerts.manager import Alerts from canopsis.common.converters import id_filter from canopsis.confng import Configuration, Ini from canopsis.context_graph.import_ctx import ImportKey, Manager from canopsis.context_graph.manager import ContextGraph from canopsis.webcore.utils import gen_json, gen_json_error, HTTP_ERROR import_col_man = Manager(Configuration.load(Manager.CONF_FILE, Ini)) alerts_manager = Alerts(*Alerts.provide_default_basics()) __IMPORT_ID = "import_id" __ERROR = "error" __OTHER_ERROR = "An error occured : {0}." __EVT_ERROR = "error while sending a event to the task : {0}." __STORE_ERROR = "Impossible to store the import: {0}." event_body = {ImportKey.EVT_IMPORT_UUID: None, ImportKey.EVT_JOBID: None} RK = "task_importctx"
class BaseTest(TestCase): def setUp(self): self.logger = logging.getLogger('alerts') self.alerts_storage = Middleware.get_middleware_by_uri( 'storage-periodical-testalarm://' ) self.config_storage = Middleware.get_middleware_by_uri( 'storage-default-testconfig://' ) self.config_storage.put_element( element={ '_id': 'test_config', 'crecord_type': 'statusmanagement', 'bagot_time': 3600, 'bagot_freq': 10, 'stealthy_time': 300, 'restore_event': True, 'auto_snooze': False, 'snooze_default_time': 300, }, _id='test_config' ) self.filter_storage = Middleware.get_middleware_by_uri( 'storage-default-testalarmfilter://' ) self.context_graph_storage = Middleware.get_middleware_by_uri( 'storage-default-testentities://' ) self.cg_manager = ContextGraph(self.logger) self.cg_manager.ent_storage = self.context_graph_storage self.watcher_manager = Watcher() conf = Configuration.load(Alerts.CONF_PATH, Ini) filter_ = {'crecord_type': 'statusmanagement'} self.config_data = EtherealData( collection=MongoCollection(self.config_storage._backend), filter_=filter_) self.event_publisher = Mock(spec=StatEventPublisher) mongo = MongoStore.get_default() collection = mongo.get_collection("default_testpbehavior") pb_collection = MongoCollection(collection) logger = Logger.get('test_pb', None, output_cls=OutputNull) config = Configuration.load(PBehaviorManager.CONF_PATH, Ini) self.pbm = PBehaviorManager(config=config, logger=logger, pb_collection=pb_collection) self.manager = Alerts(config=conf, logger=self.logger, alerts_storage=self.alerts_storage, config_data=self.config_data, filter_storage=self.filter_storage, context_graph=self.cg_manager, watcher=self.watcher_manager, event_publisher=self.event_publisher, pbehavior=self.pbm) def tearDown(self): """Teardown""" self.alerts_storage.remove_elements() self.config_storage.remove_elements() self.filter_storage.remove_elements() self.context_graph_storage.remove_elements() def gen_fake_alarm(self, update={}, moment=None): """ Generate a fake alarm/value. """ if moment is None: moment = int(time.mktime(datetime.now().timetuple())) alarm_id = '/fake/alarm/id' alarm = self.manager.make_alarm( alarm_id, { 'connector': 'fake-connector', 'connector_name': 'fake-connector-name', 'component': 'c', 'output': 'out', 'timestamp': moment } ) value = alarm[self.manager.alerts_storage.VALUE] value[AlarmField.state.value] = { 't': moment, 'val': Check.MINOR } value[AlarmField.steps.value] = [ { '_t': 'stateinc', 't': moment, 'a': 'fake-author', 'm': 'fake-message', 'val': Check.MINOR } ] dictio = merge_two_dicts(alarm, update) return dictio, value def gen_alarm_filter(self, update={}, storage=None): """ Generate a standard alarm filter. """ base = { AlarmFilter.LIMIT: 180.0, AlarmFilter.FILTER: '', AlarmFilter.CONDITION: {}, AlarmFilter.TASKS: ['alerts.systemaction.state_increase'], } dictio = merge_two_dicts(base, update) return AlarmFilter(dictio, logger=self.logger, storage=storage)
def exports(ws): ws.application.router.add_filter('id_filter', id_filter) context_manager = ContextGraph(ws.logger) am = Alerts(*Alerts.provide_default_basics()) ar = AlertsReader(*AlertsReader.provide_default_basics()) ma_rule_manager = MetaAlarmRuleManager( *MetaAlarmRuleManager.provide_default_basics()) pbm = PBehaviorManager(*PBehaviorManager.provide_default_basics()) @route(ws.application.get, name='alerts/get-alarms', payload=[ 'authkey', 'tstart', 'tstop', 'opened', 'resolved', 'lookups', 'filter', 'search', 'sort_key', 'sort_dir', 'skip', 'limit', 'with_steps', 'natural_search', 'active_columns', 'hide_resources', 'with_consequences', 'with_causes', 'correlation' ]) def get_alarms(authkey=None, tstart=None, tstop=None, opened=True, resolved=False, lookups=[], filter={}, search='', sort_key='opened', sort_dir='DESC', skip=0, limit=None, with_steps=False, natural_search=False, active_columns=None, hide_resources=False, with_consequences=False, with_causes=False, correlation=False): """ Return filtered, sorted and paginated alarms. :param tstart: Beginning timestamp of requested period :param tstop: End timestamp of requested period :type tstart: int or None :type tstop: int or None :param bool opened: If True, consider alarms that are currently opened :param bool resolved: If True, consider alarms that have been resolved :param list lookups: List of extra columns to compute for each returned alarm. Extra columns are "pbehaviors". :param dict filter: Mongo filter. Keys are UI column names. :param str search: Search expression in custom DSL :param str sort_key: Name of the column to sort :param str sort_dir: Either "ASC" or "DESC" :param int skip: Number of alarms to skip (pagination) :param int limit: Maximum number of alarms to return :param list active_columns: list of active columns on the brick listalarm . :param bool hide_resources: hide_resources if component has an alarm :returns: List of sorted alarms + pagination informations :rtype: dict """ if isinstance(search, int): search = str(search) try: alarms = ar.get(tstart=tstart, tstop=tstop, opened=opened, resolved=resolved, lookups=lookups, filter_=filter, search=search.strip(), sort_key=sort_key, sort_dir=sort_dir, skip=skip, limit=limit, with_steps=with_steps, natural_search=natural_search, active_columns=active_columns, hide_resources=hide_resources, with_consequences=with_consequences, correlation=correlation) except OperationFailure as of_err: message = 'Operation failure on get-alarms: {}'.format(of_err) raise WebServiceError(message) alarms_ids, consequences_children = [], [] alarm_children = {'alarms': [], 'total': 0} for alarm in alarms['alarms']: if with_consequences: consequences_children.extend( alarm.get('consequences', {}).get('data', [])) elif with_causes and alarm.get('v') and alarm['v'].get('parents'): consequences_children.extend(alarm['v']['parents']) tmp_id = alarm.get('d') if tmp_id: alarms_ids.append(tmp_id) entities = context_manager.get_entities_by_id(alarms_ids, with_links=False) entity_dict = {} for entity in entities: entity_dict[entity.get('_id')] = entity if consequences_children: alarm_children = ar.get( tstart=tstart, tstop=tstop, opened=True, resolved=True, lookups=lookups, filter_={'d': { '$in': consequences_children }}, sort_key=sort_key, sort_dir=sort_dir, skip=skip, limit=None, natural_search=natural_search, active_columns=active_columns, hide_resources=hide_resources, correlation=correlation, consequences_children=True) list_alarm = [] rule_ids = set() if 'rules' in alarms: for alarm_rules in alarms['rules'].values(): for v in alarm_rules: rule_ids.add(v) named_rules = ma_rule_manager.read_rules_with_names(list(rule_ids)) for d, alarm_rules in alarms['rules'].items(): alarm_named_rules = [] for v in alarm_rules: alarm_named_rules.append({ 'id': v, 'name': named_rules.get(v, "") }) alarms['rules'][d] = alarm_named_rules else: alarms['rules'] = dict() children_ent_ids = set() for alarm in alarms['alarms']: rules = alarms['rules'].get(alarm['d'], []) if 'd' in alarm and 'v' in alarm and \ alarm['v'].get('parents') else None if rules: if with_causes: alarm['causes'] = { 'total': len(alarm_children['alarms']), 'data': alarm_children['alarms'], } for al_child in alarm_children['alarms']: children_ent_ids.add(al_child['d']) else: alarm['causes'] = { 'total': len(rules), 'rules': rules, } if alarm.get('v') is None: alarm['v'] = dict() if alarm.get('v').get('meta'): del alarm['v']['meta'] if isinstance(alarm.get('rule'), basestring) and alarm['rule'] != "": alarm['rule'] = { 'id': alarm['rule'], 'name': named_rules.get(alarm['rule'], alarm['rule']) } now = int(time()) alarm_end = alarm.get('v', {}).get('resolved') if not alarm_end: alarm_end = now alarm["v"]['duration'] = ( alarm_end - alarm.get('v', {}).get('creation_date', alarm_end)) state_time = alarm.get('v', {}).get('state', {}).get('t', now) alarm["v"]['current_state_duration'] = now - state_time tmp_entity_id = alarm['d'] if alarm['d'] in entity_dict: alarm[ 'links'] = context_manager.enrich_links_to_entity_with_alarm( entity_dict[alarm['d']], alarm) # TODO: 'infos' is already present in entity. # Remove this one if unused. if tmp_entity_id in entity_dict: data = entity_dict[alarm['d']]['infos'] if alarm.get('infos'): alarm['infos'].update(data) else: alarm['infos'] = data alarm = compat_go_crop_states(alarm) if with_consequences and isinstance( alarm.get('consequences'), dict) and alarm_children['total'] > 0: map( lambda al_ch: al_ch.update( {'causes': { 'rules': [alarm['rule']], 'total': 1 }}), alarm_children['alarms']) alarm['consequences']['data'] = alarm_children['alarms'] alarm['consequences']['total'] = alarm_children['total'] for al_child in alarm_children['alarms']: children_ent_ids.add(al_child['d']) list_alarm.append(alarm) if children_ent_ids: children_entities = context_manager.get_entities_by_id( list(children_ent_ids), with_links=False) for entity in children_entities: entity_dict[entity.get('_id')] = entity for alarm in alarms['alarms']: for cat in ('causes', 'consequences'): if cat in alarm and alarm[cat].get('data'): for child in alarm[cat]['data']: if child['d'] in entity_dict: child[ 'links'] = context_manager.enrich_links_to_entity_with_alarm( entity_dict[child['d']], child) del alarms['rules'] alarms['alarms'] = list_alarm return alarms @route(ws.application.get, name='alerts/get-counters', payload=[ 'tstart', 'tstop', 'opened', 'resolved', 'lookups', 'filter', 'search', 'sort_key', 'sort_dir', 'skip', 'limit', 'with_steps', 'natural_search', 'active_columns', 'hide_resources' ]) def get_counters(tstart=None, tstop=None, opened=True, resolved=False, lookups=[], filter={}, search='', sort_key='opened', sort_dir='DESC', skip=0, limit=None, with_steps=False, natural_search=False, active_columns=None, hide_resources=False): if isinstance(search, int): search = str(search) try: alarms = ar.get(tstart=tstart, tstop=tstop, opened=opened, resolved=resolved, lookups=lookups, filter_=filter, search=search.strip(), sort_key=sort_key, sort_dir=sort_dir, skip=skip, limit=limit, with_steps=with_steps, natural_search=natural_search, active_columns=active_columns, hide_resources=hide_resources, add_pbh_filter=False) except OperationFailure as of_err: message = 'Operation failure on get-alarms: {}'.format(of_err) raise WebServiceError(message) counters = { "total": len(alarms['alarms']), "total_active": 0, "snooze": 0, "ack": 0, "ticket": 0, "pbehavior_active": 0 } alarms_ids = [] for alarm in alarms['alarms']: tmp_id = alarm.get('d') if tmp_id: alarms_ids.append(tmp_id) entities = context_manager.get_entities_by_id(alarms_ids, with_links=True) entity_id = [] for entity in entities: _id = entity.get('_id') if _id: entity_id.append(_id) active_pbh = pbm.get_active_pbehaviors_on_entities(entity_id) enabled_pbh_entity_dict = set() for pbh in active_pbh: if pbh[PBehavior.ENABLED]: for eid in pbh.get(PBehavior.EIDS, []): if eid in entity_id: enabled_pbh_entity_dict.add(eid) pbehavior_active_snooze = 0 for alarm in alarms['alarms']: v = alarm.get('v') snoozed = False if isinstance(v, dict): if v.get('ack', {}).get('_t') == 'ack': counters['ack'] += 1 snoozed = v.get('snooze', {}).get('_t') == 'snooze' if snoozed: counters['snooze'] += 1 if v.get('ticket', {}).get('_t') in ['declareticket', 'assocticket']: counters['ticket'] += 1 d = alarm.get('d') if d in enabled_pbh_entity_dict: counters['pbehavior_active'] += 1 if snoozed: pbehavior_active_snooze += 1 counters['total_active'] = counters['total'] - counters['pbehavior_active'] - counters['snooze'] + \ pbehavior_active_snooze return counters @route(ws.application.get, name='alerts/search/validate', payload=['expression']) def validate_search(expression): """ Tell if a search expression is valid from a grammatical propespective. :param str expression: Search expression :returns: True if valid, False otherwise :rtype: bool """ try: ar.interpret_search(expression) except Exception: return False else: return True @route( ws.application.get, name='alerts/count', payload=['start', 'stop', 'limit', 'select'], ) def count_by_period( start, stop, limit=100, select=None, ): """ Count alarms that have been opened during (stop - start) period. :param start: Beginning timestamp of period :type start: int :param stop: End timestamp of period :type stop: int :param limit: Counts cannot exceed this value :type limit: int :param query: Custom mongodb filter for alarms :type query: dict :return: List in which each item contains a time interval and the related count :rtype: list """ return ar.count_alarms_by_period( start, stop, limit=limit, query=select, ) @route( ws.application.get, name='alerts/get-current-alarm', payload=['entity_id'], ) def get_current_alarm(entity_id): """ Get current unresolved alarm for a entity. :param str entity_id: Entity ID of the alarm :returns: Alarm as dict if something is opened, else None """ return am.get_current_alarm(entity_id) @ws.application.get('/api/v2/alerts/filters/<entity_id:id_filter>') def get_filter(entity_id): """ Get all filters linked with an alarm. :param str entity_id: Entity ID of the alarm-filter :returns: a list of <AlarmFilter> """ filters = am.alarm_filters.get_filter(entity_id) if filters is None: return gen_json_error({'description': 'nothing to return'}, HTTP_ERROR) return gen_json([l.serialize() for l in filters]) @ws.application.post('/api/v2/alerts/filters') def create_filter(): """ Create a new alarm filter. :returns: an <AlarmFilter> """ # element is a full AlarmFilter (dict) to insert element = request.json if element is None: return gen_json_error({'description': 'nothing to insert'}, HTTP_ERROR) new = am.alarm_filters.create_filter(element=element) new.save() return gen_json(new.serialize()) @ws.application.put('/api/v2/alerts/filters/<entity_id:id_filter>') def update_filter(entity_id): """ Update an existing alam filter. :param entity_id: Entity ID of the alarm-filter :type entity_id: str :returns: <AlarmFilter> :rtype: dict """ dico = request.json if dico is None or not isinstance(dico, dict) or len(dico) <= 0: return gen_json_error({'description': 'wrong update dict'}, HTTP_ERROR) af = am.alarm_filters.update_filter(filter_id=entity_id, values=dico) if not isinstance(af, AlarmFilter): return gen_json_error({'description': 'failed to update filter'}, HTTP_ERROR) return gen_json(af.serialize()) @ws.application.delete('/api/v2/alerts/filters/<entity_id:id_filter>') def delete_id(entity_id): """ Delete a filter, based on his id. :param entity_id: Entity ID of the alarm-filter :type entity_id: str :rtype: dict """ ws.logger.info('Delete alarm-filter : {}'.format(entity_id)) return gen_json(am.alarm_filters.delete_filter(entity_id)) @ws.application.delete('/api/v2/alerts/<mfilter>') def delete_filter(mfilter): """ :param str mfilter: mongo filter :rtype: dict """ return gen_json(ar.alarm_storage._backend.remove(json.loads(mfilter))) @ws.application.post('/api/v2/alerts/done') def done_action(): """ Trigger done action. For json payload, see doc/docs/fr/guide_developpeur/apis/v2/alerts.md :rtype: dict """ dico = request.json if dico is None or not isinstance(dico, dict) or len(dico) <= 0: return gen_json_error({'description': 'wrong done dict'}, HTTP_ERROR) author = dico.get(am.AUTHOR) event = forger(event_type=Check.EVENT_TYPE, author=author, connector=dico.get('connector'), connector_name=dico.get('connector_name'), component=dico.get('component'), output=dico.get('comment')) if dico.get('source_type', None) == 'resource': event['resource'] = dico['resource'] event['source_type'] = 'resource' ws.logger.debug('Received done action: {}'.format(event)) entity_id = am.context_manager.get_id(event) retour = am.execute_task('alerts.useraction.done', event=event, author=author, entity_id=entity_id) return gen_json(retour)
class engine(Engine): etype = 'event_filter' def __init__(self, *args, **kargs): super(engine, self).__init__(*args, **kargs) account = Account(user="******", group="root") self.storage = get_storage(logging_level=self.logging_level, account=account) self.derogations = [] self.name = kargs['name'] self.drop_event_count = 0 self.pass_event_count = 0 def pre_run(self): self.beat() def time_conditions(self, derogation): conditions = derogation.get('time_conditions', None) if not isinstance(conditions, list): self.logger.error(("Invalid time conditions field in '%s': %s" % (derogation['_id'], conditions))) self.logger.debug(derogation) return False result = False now = time() for condition in conditions: if (condition['type'] == 'time_interval' and condition['startTs'] and condition['stopTs']): always = condition.get('always', False) if always: self.logger.debug(" + 'time_interval' is 'always'") result = True elif (now >= condition['startTs'] and now < condition['stopTs']): self.logger.debug(" + 'time_interval' Match") result = True return result def a_override(self, event, action): """Override a field from event or add a new one if it does not have one. """ afield = action.get('field', None) avalue = action.get('value', None) # This must be a hard check because value can be a boolean or a null # integer if afield is None or avalue is None: self.logger.error( "Malformed action ('field' and 'value' required): {}".format( action ) ) return False if afield not in event: self.logger.debug("Overriding: '{}' -> '{}'".format( afield, avalue)) event[afield] = avalue return True # afield is in event if not isinstance(avalue, list): if isinstance(event[afield], list): self.logger.debug("Appending: '{}' to '{}'".format( avalue, afield)) event[afield].append(avalue) else: self.logger.debug("Overriding: '{}' -> '{}'".format( afield, avalue)) event[afield] = avalue return True else: # operation field is supported only for list values op = action.get('operation', 'append') if op == 'override': self.logger.debug("Overriding: '{}' -> '{}'".format( afield, avalue)) event[afield] = avalue return True elif op == 'append': self.logger.debug("Appending: '{}' to '{}'".format( avalue, afield)) if isinstance(event[afield], list): event[afield] += avalue else: event[afield] = [event[afield]] + avalue return True else: self.logger.error( "Operation '{}' unsupported (action '{}')".format( op, action ) ) return False def a_remove(self, event, action): """Remove an event from a field in event or the whole field if no element is specified. """ akey = action.get('key', None) aelement = action.get('element', None) del_met = action.get('met', 0) if akey: if aelement: if del_met: for i, met in enumerate(event[akey]): if met['name'] == aelement: del event[akey][i] break elif isinstance(event[akey], dict): del event[akey][aelement] elif isinstance(event[akey], list): del event[akey][event[akey].index(aelement)] self.logger.debug(u" + {}: Removed: '{}' from '{}'".format( event['rk'], aelement, akey)) else: del event[akey] self.logger.debug(u" + {}: Removed: '{}'".format( event['rk'], akey)) return True else: self.logger.error( u"Action malformed (needs 'key' and/or 'element'): {}".format( action)) return False def a_modify(self, event, action, name): """ Args: event map of the event to be modified action map of type action _name of the rule Returns: ``None`` """ derogated = False atype = action.get('type') actionMap = { 'override': self.a_override, 'remove': self.a_remove } if atype in actionMap: derogated = actionMap[atype](event, action) else: self.logger.warning(u"Unknown action '{}'".format(atype)) # If the event was derogated, fill some informations if derogated: self.logger.debug(u"Event changed by rule '{}'".format(name)) return None def a_drop(self, event, action, name): """ Drop the event. Args: event map of the event to be modified action map of type action _name of the rule Returns: ``None`` """ self.logger.debug(u"Event dropped by rule '{}'".format(name)) self.drop_event_count += 1 return DROP def a_pass(self, event, action, name): """Pass the event to the next queue. Args: event map of the event to be modified action map of type action _name of the rule Returns: ``None`` """ self.logger.debug(u"Event passed by rule '{}'".format(name)) self.pass_event_count += 1 return event def a_route(self, event, action, name): """ Change the route to which an event will be sent Args: event: map of the event to be modified action: map of type action name: of the rule Returns: ``None`` """ if "route" in action: self.next_amqp_queues = [action["route"]] self.logger.debug(u"Event re-routed by rule '{}'".format(name)) else: self.logger.error( u"Action malformed (needs 'route'): {}".format(action)) return None def a_exec_job(self, event, action, name): records = self.storage.find( {'crecord_type': 'job', '_id': action['job']} ) for record in records: job = record.dump() job['context'] = event publish( publisher=self.amqp, event=job, rk='Engine_scheduler', exchange='amq.direct' ) # publish(publisher=self.amqp, event=job, rk='Engine_scheduler') return True def a_snooze(self, event, action, name): """ Snooze event checks :param dict event: event to be snoozed :param dict action: action :param str name: name of the rule :returns: True if a snooze has been sent, False otherwise :rtype: boolean """ # Only check events can trigger an auto-snooze if event['event_type'] != 'check': return False # A check OK cannot trigger an auto-snooze if event['state'] == 0: return False # Alerts manager caching if not hasattr(self, 'am'): self.am = Alerts() # Context manager caching if not hasattr(self, 'cm'): self.cm = Context() entity = self.cm.get_entity(event) entity_id = self.cm.get_entity_id(entity) current_alarm = self.am.get_current_alarm(entity_id) if current_alarm is None: snooze = { 'connector': event.get('connector', ''), 'connector_name': event.get('connector_name', ''), 'source_type': event.get('source_type', ''), 'component': event.get('component', ''), 'event_type': 'snooze', 'duration': action['duration'], 'author': 'event_filter', 'output': 'Auto snooze generated by rule "{}"'.format(name), } if 'resource' in event: snooze['resource'] = event['resource'] publish(event=snooze, publisher=self.amqp) return True return False def apply_actions(self, event, actions): pass_event = False actionMap = { 'drop': self.a_drop, 'pass': self.a_pass, 'override': self.a_modify, 'remove': self.a_modify, 'execjob': self.a_exec_job, 'route': self.a_route, 'snooze': self.a_snooze, } for name, action in actions: if action['type'] in actionMap: ret = actionMap[action['type'].lower()](event, action, name) if ret: pass_event = True else: self.logger.warning(u"Unknown action '{}'".format(action)) return pass_event def work(self, event, *xargs, **kwargs): rk = get_routingkey(event) default_action = self.configuration.get('default_action', 'pass') # list of supported actions rules = self.configuration.get('rules', []) to_apply = [] self.logger.debug(u'event {}'.format(event)) # When list configuration then check black and # white lists depending on json configuration for filterItem in rules: actions = filterItem.get('actions') name = filterItem.get('name', 'no_name') self.logger.debug(u'rule {}'.format(filterItem)) self.logger.debug(u'filter is {}'.format(filterItem['mfilter'])) # Try filter rules on current event if filterItem['mfilter'] and check(filterItem['mfilter'], event): self.logger.debug( u'Event: {}, filter matches'.format(event.get('rk', event)) ) for action in actions: if action['type'].lower() == 'drop': self.apply_actions(event, to_apply) return self.a_drop(event, None, name) to_apply.append((name, action)) if filterItem.get('break', 0): self.logger.debug( u' + Filter {} broke the next filters processing' .format( filterItem.get('name', 'filter') ) ) break if len(to_apply): if self.apply_actions(event, to_apply): self.logger.debug( u'Event before sent to next engine: %s' % event ) event['rk'] = event['_id'] = get_routingkey(event) return event # No rules matched if default_action == 'drop': self.logger.debug("Event '%s' dropped by default action" % (rk)) self.drop_event_count += 1 return DROP self.logger.debug("Event '%s' passed by default action" % (rk)) self.pass_event_count += 1 self.logger.debug(u'Event before sent to next engine: %s' % event) event['rk'] = event['_id'] = get_routingkey(event) return event def beat(self, *args, **kargs): """ Configuration reload for realtime ui changes handling """ self.derogations = [] self.configuration = { 'rules': [], 'default_action': self.find_default_action() } self.logger.debug(u'Reload configuration rules') records = self.storage.find( {'crecord_type': 'filter', 'enable': True}, sort='priority' ) for record in records: record_dump = record.dump() self.set_loaded(record_dump) try: record_dump["mfilter"] = loads(record_dump["mfilter"]) except Exception: self.logger.info(u'Invalid mfilter {}, filter {}'.format( record_dump['mfilter'], record_dump['name'], )) self.logger.debug(u'Loading record_dump:') self.logger.debug(record_dump) self.configuration['rules'].append(record_dump) self.logger.info( 'Loaded {} rules'.format(len(self.configuration['rules'])) ) self.send_stat_event() def set_loaded(self, record): if 'run_once' in record and not record['run_once']: self.storage.update(record['_id'], {'run_once': True}) self.logger.info( 'record {} has been run once'.format(record['_id']) ) def send_stat_event(self): """ Send AMQP Event for drop and pass metrics """ message_dropped = '{} event dropped since {}'.format( self.drop_event_count, self.beat_interval ) message_passed = '{} event passed since {}'.format( self.pass_event_count, self.beat_interval ) event = forger( connector='Engine', connector_name='engine', event_type='check', source_type='resource', resource=self.amqp_queue + '_data', state=0, state_type=1, output=message_dropped, perf_data_array=[ {'metric': 'pass_event', 'value': self.pass_event_count, 'type': 'GAUGE'}, {'metric': 'drop_event', 'value': self.drop_event_count, 'type': 'GAUGE'} ] ) self.logger.debug(message_dropped) self.logger.debug(message_passed) publish(publisher=self.amqp, event=event) self.drop_event_count = 0 self.pass_event_count = 0 def find_default_action(self): """Find the default action stored and returns it, else assume it default action is pass. """ records = self.storage.find({'crecord_type': 'defaultrule'}) if records: return records[0].dump()["action"] self.logger.debug( "No default action found. Assuming default action is pass" ) return 'pass'
class ComputeState(BaseTest): def setUp(self): super(ComputeState, self).setUp() pbehavior_storage = Middleware.get_middleware_by_uri( 'storage-default-testpbehavior://' ) filter_storage = Middleware.get_middleware_by_uri( 'storage-default-testalarmfilter://' ) config_storage = Middleware.get_middleware_by_uri( 'storage-default-testconfig://' ) config_storage.put_element( element={ '_id': 'test_config', 'crecord_type': 'statusmanagement', 'bagot_time': 3600, 'bagot_freq': 10, 'stealthy_time': 300, 'restore_event': True, 'auto_snooze': False, 'snooze_default_time': 300, }, _id='test_config' ) logger = Logger.get('test_pb', None, output_cls=OutputNull) config = Configuration.load(PBehaviorManager.CONF_PATH, Ini) self.pbm = PBehaviorManager(config=config, logger=logger, pb_storage=pbehavior_storage) self.pbm.context = self.context_graph_manager self.manager.pbehavior_manager = self.pbm conf = Configuration.load(Alerts.CONF_PATH, Ini) filter_ = {'crecord_type': 'statusmanagement'} config_data = EtherealData(collection=config_storage._backend, filter_=filter_) event_publisher = Mock(spec=StatEventPublisher) self.alert_manager = Alerts(config=conf, logger=logger, alerts_storage=self.alerts_storage, config_data=config_data, filter_storage=filter_storage, context_graph=self.context_graph_manager, watcher=self.manager, event_publisher=event_publisher) # Creating entity self.type_ = 'resource' self.name = 'morticia' entity = ContextGraph.create_entity_dict( id=self.name, etype=self.type_, name=self.name ) self.context_graph_manager.create_entity(entity) # Creating coresponding alarm event = { 'connector': self.type_, 'connector_name': 'connector_name', 'component': self.name, 'output': 'tadaTaDA tic tic', 'timestamp': 0 } alarm = self.alert_manager.make_alarm(self.name, event) self.state = 2 alarm = self.alert_manager.update_state(alarm, self.state, event) new_value = alarm[self.alert_manager.alerts_storage.VALUE] self.alert_manager.update_current_alarm(alarm, new_value) def tearDown(self): super(ComputeState, self).tearDown() self.pbm.pb_storage.remove_elements() def test_compute_state_issue427(self): # Aka: state desyncro watcher_id = 'addams' watcher = { '_id': watcher_id, 'mfilter': '{"name": {"$in": ["morticia"]}}', 'display_name': 'family' } self.assertTrue(self.manager.create_watcher(watcher)) res = self.manager.get_watcher(watcher_id) self.assertEqual(res['state'], self.state) # Creating pbehavior on it now = datetime.utcnow() self.pbm.create( name='addam', filter=loads('{"name": "morticia"}'), author='addams', tstart=timegm(now.timetuple()), tstop=timegm((now + timedelta(seconds=2)).timetuple()), rrule=None, enabled=True ) self.pbm.compute_pbehaviors_filters() res = self.manager.get_watcher(watcher_id) self.assertEqual(res['state'], self.state) self.manager.compute_watchers() res = self.manager.get_watcher(watcher_id) self.assertEqual(res['state'], 0) sleep(3) self.pbm.compute_pbehaviors_filters() self.manager.compute_watchers() res = self.manager.get_watcher(watcher_id) self.assertEqual(res['state'], self.state)
def exports(ws): ws.application.router.add_filter('id_filter', id_filter) context_manager = ContextGraph(ws.logger) am = Alerts(*Alerts.provide_default_basics()) ar = AlertsReader(*AlertsReader.provide_default_basics()) @route( ws.application.get, name='alerts/get-alarms', payload=[ 'tstart', 'tstop', 'opened', 'resolved', 'lookups', 'filter', 'search', 'sort_key', 'sort_dir', 'skip', 'limit', 'with_steps', 'natural_search', 'active_columns', 'hide_resources' ] ) def get_alarms( tstart=None, tstop=None, opened=True, resolved=False, lookups=[], filter={}, search='', sort_key='opened', sort_dir='DESC', skip=0, limit=50, with_steps=False, natural_search=False, active_columns=None, hide_resources=False ): """ Return filtered, sorted and paginated alarms. :param tstart: Beginning timestamp of requested period :param tstop: End timestamp of requested period :type tstart: int or None :type tstop: int or None :param bool opened: If True, consider alarms that are currently opened :param bool resolved: If True, consider alarms that have been resolved :param list lookups: List of extra columns to compute for each returned alarm. Extra columns are "pbehaviors". :param dict filter: Mongo filter. Keys are UI column names. :param str search: Search expression in custom DSL :param str sort_key: Name of the column to sort :param str sort_dir: Either "ASC" or "DESC" :param int skip: Number of alarms to skip (pagination) :param int limit: Maximum number of alarms to return :param list active_columns: list of active columns on the brick listalarm . :param bool hide_resources: hide_resources if component has an alarm :returns: List of sorted alarms + pagination informations :rtype: dict """ if isinstance(search, int): search = str(search) try: alarms = ar.get( tstart=tstart, tstop=tstop, opened=opened, resolved=resolved, lookups=lookups, filter_=filter, search=search.strip(), sort_key=sort_key, sort_dir=sort_dir, skip=skip, limit=limit, with_steps=with_steps, natural_search=natural_search, active_columns=active_columns, hide_resources=hide_resources ) except OperationFailure as of_err: message = 'Operation failure on get-alarms: {}'.format(of_err) raise WebServiceError(message) alarms_ids = [] for alarm in alarms['alarms']: tmp_id = alarm.get('d') if tmp_id: alarms_ids.append(tmp_id) entities = context_manager.get_entities_by_id(alarms_ids, with_links=True) entity_dict = {} for entity in entities: entity_dict[entity.get('_id')] = entity list_alarm = [] for alarm in alarms['alarms']: now = int(time()) alarm["v"]['duration'] = now - alarm.get('v', {}).get('creation_date', now) state_time = alarm.get('v', {}).get('state', {}).get('t', now) alarm["v"]['current_state_duration'] = now - state_time tmp_entity_id = alarm['d'] if alarm['d'] in entity_dict: alarm['links'] = entity_dict[alarm['d']]['links'] # TODO: 'infos' is already present in entity. # Remove this one if unused. if tmp_entity_id in entity_dict: data = entity_dict[alarm['d']]['infos'] if alarm.get('infos'): alarm['infos'].update(data) else: alarm['infos'] = data alarm = compat_go_crop_states(alarm) list_alarm.append(alarm) alarms['alarms'] = list_alarm return alarms @route( ws.application.get, name='alerts/search/validate', payload=['expression'] ) def validate_search(expression): """ Tell if a search expression is valid from a grammatical propespective. :param str expression: Search expression :returns: True if valid, False otherwise :rtype: bool """ try: ar.interpret_search(expression) except Exception: return False else: return True @route( ws.application.get, name='alerts/count', payload=['start', 'stop', 'limit', 'select'], ) def count_by_period( start, stop, limit=100, select=None, ): """ Count alarms that have been opened during (stop - start) period. :param start: Beginning timestamp of period :type start: int :param stop: End timestamp of period :type stop: int :param limit: Counts cannot exceed this value :type limit: int :param query: Custom mongodb filter for alarms :type query: dict :return: List in which each item contains a time interval and the related count :rtype: list """ return ar.count_alarms_by_period( start, stop, limit=limit, query=select, ) @route( ws.application.get, name='alerts/get-current-alarm', payload=['entity_id'], ) def get_current_alarm(entity_id): """ Get current unresolved alarm for a entity. :param str entity_id: Entity ID of the alarm :returns: Alarm as dict if something is opened, else None """ return am.get_current_alarm(entity_id) @ws.application.get( '/api/v2/alerts/filters/<entity_id:id_filter>' ) def get_filter(entity_id): """ Get all filters linked with an alarm. :param str entity_id: Entity ID of the alarm-filter :returns: a list of <AlarmFilter> """ filters = am.alarm_filters.get_filter(entity_id) if filters is None: return gen_json_error({'description': 'nothing to return'}, HTTP_ERROR) return gen_json([l.serialize() for l in filters]) @ws.application.post( '/api/v2/alerts/filters' ) def create_filter(): """ Create a new alarm filter. :returns: an <AlarmFilter> """ # element is a full AlarmFilter (dict) to insert element = request.json if element is None: return gen_json_error( {'description': 'nothing to insert'}, HTTP_ERROR) new = am.alarm_filters.create_filter(element=element) new.save() return gen_json(new.serialize()) @ws.application.put( '/api/v2/alerts/filters/<entity_id:id_filter>' ) def update_filter(entity_id): """ Update an existing alam filter. :param entity_id: Entity ID of the alarm-filter :type entity_id: str :returns: <AlarmFilter> :rtype: dict """ dico = request.json if dico is None or not isinstance(dico, dict) or len(dico) <= 0: return gen_json_error( {'description': 'wrong update dict'}, HTTP_ERROR) af = am.alarm_filters.update_filter(filter_id=entity_id, values=dico) if not isinstance(af, AlarmFilter): return gen_json_error({'description': 'failed to update filter'}, HTTP_ERROR) return gen_json(af.serialize()) @ws.application.delete( '/api/v2/alerts/filters/<entity_id:id_filter>' ) def delete_id(entity_id): """ Delete a filter, based on his id. :param entity_id: Entity ID of the alarm-filter :type entity_id: str :rtype: dict """ ws.logger.info('Delete alarm-filter : {}'.format(entity_id)) return gen_json(am.alarm_filters.delete_filter(entity_id)) @ws.application.delete( '/api/v2/alerts/<mfilter>' ) def delete_filter(mfilter): """ :param str mfilter: mongo filter :rtype: dict """ return gen_json(ar.alarm_storage._backend.remove(json.loads(mfilter))) @ws.application.post( '/api/v2/alerts/done' ) def done_action(): """ Trigger done action. For json payload, see doc/docs/fr/guide_developpeur/apis/v2/alerts.md :rtype: dict """ dico = request.json if dico is None or not isinstance(dico, dict) or len(dico) <= 0: return gen_json_error( {'description': 'wrong done dict'}, HTTP_ERROR) author = dico.get(am.AUTHOR) event = forger( event_type=Check.EVENT_TYPE, author=author, connector=dico.get('connector'), connector_name=dico.get('connector_name'), component=dico.get('component'), output=dico.get('comment') ) if dico.get('source_type', None) == 'resource': event['resource'] = dico['resource'] event['source_type'] = 'resource' ws.logger.debug('Received done action: {}'.format(event)) entity_id = am.context_manager.get_id(event) retour = am.execute_task( 'alerts.useraction.done', event=event, author=author, entity_id=entity_id ) return gen_json(retour)
class engine(Engine): etype = 'event_filter' def __init__(self, *args, **kargs): super(engine, self).__init__(*args, **kargs) self.mg_store = MongoStore.get_default() self.collection = MongoCollection( self.mg_store.get_collection("object")) self.name = kargs['name'] self.drop_event_count = 0 self.pass_event_count = 0 self.__load_rules() def pre_run(self): self.beat() def a_override(self, event, action): """Override a field from event or add a new one if it does not have one. """ afield = action.get('field', None) avalue = action.get('value', None) # This must be a hard check because value can be a boolean or a null # integer if afield is None or avalue is None: self.logger.error( "Malformed action ('field' and 'value' required): {}".format( action)) return False if afield not in event: self.logger.debug("Overriding: '{}' -> '{}'".format( afield, avalue)) event[afield] = avalue return True # afield is in event if not isinstance(avalue, list): if isinstance(event[afield], list): self.logger.debug("Appending: '{}' to '{}'".format( avalue, afield)) event[afield].append(avalue) else: self.logger.debug("Overriding: '{}' -> '{}'".format( afield, avalue)) event[afield] = avalue return True else: # operation field is supported only for list values op = action.get('operation', 'append') if op == 'override': self.logger.debug("Overriding: '{}' -> '{}'".format( afield, avalue)) event[afield] = avalue return True elif op == 'append': self.logger.debug("Appending: '{}' to '{}'".format( avalue, afield)) if isinstance(event[afield], list): event[afield] += avalue else: event[afield] = [event[afield]] + avalue return True else: self.logger.error( "Operation '{}' unsupported (action '{}')".format( op, action)) return False def a_remove(self, event, action): """Remove an event from a field in event or the whole field if no element is specified. """ akey = action.get('key', None) aelement = action.get('element', None) del_met = action.get('met', 0) if akey: if aelement: if del_met: for i, met in enumerate(event[akey]): if met['name'] == aelement: del event[akey][i] break elif isinstance(event[akey], dict): del event[akey][aelement] elif isinstance(event[akey], list): del event[akey][event[akey].index(aelement)] self.logger.debug(u" + {}: Removed: '{}' from '{}'".format( event['rk'], aelement, akey)) else: del event[akey] self.logger.debug(u" + {}: Removed: '{}'".format( event['rk'], akey)) return True else: self.logger.error( u"Action malformed (needs 'key' and/or 'element'): {}".format( action)) return False def a_modify(self, event, action, name): """ Args: event map of the event to be modified action map of type action _name of the rule Returns: ``None`` """ derogated = False atype = action.get('type') actionMap = {'override': self.a_override, 'remove': self.a_remove} if atype in actionMap: derogated = actionMap[atype](event, action) else: self.logger.warning(u"Unknown action '{}'".format(atype)) # If the event was derogated, fill some informations if derogated: self.logger.debug(u"Event changed by rule '{}'".format(name)) return None def a_drop(self, event, action, name): """ Drop the event. Args: event map of the event to be modified action map of type action _name of the rule Returns: ``None`` """ self.logger.debug(u"Event dropped by rule '{}'".format(name)) self.drop_event_count += 1 return DROP def a_pass(self, event, action, name): """Pass the event to the next queue. Args: event map of the event to be modified action map of type action _name of the rule Returns: ``None`` """ self.logger.debug(u"Event passed by rule '{}'".format(name)) self.pass_event_count += 1 return event def a_route(self, event, action, name): """ Change the route to which an event will be sent Args: event: map of the event to be modified action: map of type action name: of the rule Returns: ``None`` """ if "route" in action: self.next_amqp_queues = [action["route"]] self.logger.debug(u"Event re-routed by rule '{}'".format(name)) else: self.logger.error( u"Action malformed (needs 'route'): {}".format(action)) return None def a_exec_job(self, event, action, name): records = self.collection.find({ 'crecord_type': 'job', '_id': action['job'] }) for record in records: job = copy.deepcopy(record) job['context'] = event try: self.work_amqp_publisher.direct_event(job, 'Engine_scheduler') except Exception as e: self.logger.exception("Unable to send job") time.sleep(1) return True def a_snooze(self, event, action, name): """ Snooze event checks :param dict event: event to be snoozed :param dict action: action :param str name: name of the rule :returns: True if a snooze has been sent, False otherwise :rtype: boolean """ if event.get('event_type') == 'snooze': return False # Only check events can trigger an auto-snooze if event.get('event_type') != 'check': return False # A check OK cannot trigger an auto-snooze if event.get('state') == 0: return False # Alerts manager caching if not hasattr(self, 'am'): self.am = Alerts(*Alerts.provide_default_basics()) # Context manager caching if not hasattr(self, 'cm'): self.cm = ContextGraph(self.logger) entity_id = self.cm.get_id(event) current_alarm = self.am.get_current_alarm(entity_id) if current_alarm is None: snooze = { 'connector': event.get('connector', ''), 'connector_name': event.get('connector_name', ''), 'source_type': event.get('source_type', ''), 'component': event.get('component', ''), 'event_type': 'snooze', 'duration': action['duration'], 'author': 'event_filter', 'output': 'Auto snooze generated by rule "{}"'.format(name), 'timestamp': int(time.time()) } if event.get('resource', ''): snooze['resource'] = event['resource'] try: self.work_amqp_publisher.direct_event(snooze, 'Engine_event_filter') except Exception as e: self.logger.exception("Unable to send snooze event") return True return False def a_baseline(self, event, actions, name): """a_baseline :param event: :param action: baseline conf in event filter :param name: """ event['baseline_name'] = actions['baseline_name'] event['check_frequency'] = actions['check_frequency'] try: self.work_amqp_publisher.direct_event(event, 'Engine_baseline') except Exception as e: self.logger.exception("Unable to send baseline event") def apply_actions(self, event, actions): pass_event = False actionMap = { 'drop': self.a_drop, 'pass': self.a_pass, 'override': self.a_modify, 'remove': self.a_modify, 'execjob': self.a_exec_job, 'route': self.a_route, 'snooze': self.a_snooze, 'baseline': self.a_baseline } for name, action in actions: if action['type'] in actionMap: ret = actionMap[action['type'].lower()](event, action, name) if ret: pass_event = True else: self.logger.warning(u"Unknown action '{}'".format(action)) return pass_event def work(self, event, *xargs, **kwargs): rk = get_routingkey(event) default_action = self.configuration.get('default_action', 'pass') # list of supported actions rules = self.configuration.get('rules', []) to_apply = [] self.logger.debug(u'event {}'.format(event)) # When list configuration then check black and # white lists depending on json configuration for filterItem in rules: actions = filterItem.get('actions') name = filterItem.get('name', 'no_name') self.logger.debug(u'rule {}'.format(filterItem)) self.logger.debug(u'filter is {}'.format(filterItem['mfilter'])) # Try filter rules on current event if filterItem['mfilter'] and check(filterItem['mfilter'], event): self.logger.debug(u'Event: {}, filter matches'.format( event.get('rk', event))) if 'pbehaviors' in filterItem: pbehaviors = filterItem.get('pbehaviors', {}) list_in = pbehaviors.get('in', []) list_out = pbehaviors.get('out', []) if list_in or list_out: pbm = singleton_per_scope(PBehaviorManager) cm = singleton_per_scope(ContextGraph) entity = cm.get_entity(event) entity_id = cm.get_entity_id(entity) result = pbm.check_pbehaviors(entity_id, list_in, list_out) if not result: break for action in actions: if action['type'].lower() == 'drop': self.apply_actions(event, to_apply) return self.a_drop(event, None, name) to_apply.append((name, action)) if filterItem.get('break', 0): self.logger.debug( u' + Filter {} broke the next filters processing'. format(filterItem.get('name', 'filter'))) break if len(to_apply): if self.apply_actions(event, to_apply): self.logger.debug(u'Event before sent to next engine: %s' % event) event['rk'] = event['_id'] = get_routingkey(event) return event # No rules matched if default_action == 'drop': self.logger.debug("Event '%s' dropped by default action" % (rk)) self.drop_event_count += 1 return DROP self.logger.debug("Event '%s' passed by default action" % (rk)) self.pass_event_count += 1 self.logger.debug(u'Event before sent to next engine: %s' % event) event['rk'] = event['_id'] = get_routingkey(event) return event def __load_rules(self): tmp_rules = [] records = self.collection.find({ 'crecord_type': 'filter', 'enable': True }) records.sort('priority', 1) for record in records: record_dump = copy.deepcopy(record) self.set_loaded(record_dump) try: record_dump["mfilter"] = loads(record_dump["mfilter"]) except Exception: self.logger.info(u'Invalid mfilter {}, filter {}'.format( record_dump['mfilter'], record_dump['name'], )) self.logger.debug(u'Loading record_dump:') self.logger.debug(record_dump) tmp_rules.append(record_dump) self.configuration = { 'rules': tmp_rules, 'default_action': self.find_default_action() } def beat(self, *args, **kargs): """ Configuration reload for realtime ui changes handling """ self.logger.debug(u'Reload configuration rules') self.__load_rules() self.logger.debug('Loaded {} rules'.format( len(self.configuration['rules']))) self.send_stat_event() def set_loaded(self, record): if 'run_once' in record and not record['run_once']: self.collection.update({"_id": record['_id']}, {"$set": { 'run_once': True }}) self.logger.info('record {} has been run once'.format( record['_id'])) def send_stat_event(self): """ Send AMQP Event for drop and pass metrics """ message_dropped = '{} event dropped since {}'.format( self.drop_event_count, self.beat_interval) message_passed = '{} event passed since {}'.format( self.pass_event_count, self.beat_interval) event = forger(connector='Engine', connector_name='engine', event_type='check', source_type='resource', resource=self.amqp_queue + '_data', state=0, state_type=1, output=message_dropped, perf_data_array=[{ 'metric': 'pass_event', 'value': self.pass_event_count, 'type': 'GAUGE' }, { 'metric': 'drop_event', 'value': self.drop_event_count, 'type': 'GAUGE' }]) self.logger.debug(message_dropped) self.logger.debug(message_passed) try: self.beat_amqp_publisher.canopsis_event(event) except Exception as e: self.logger.exception("Unable to send stat event") self.drop_event_count = 0 self.pass_event_count = 0 def find_default_action(self): """Find the default action stored and returns it, else assume it default action is pass. """ records = self.collection.find_one({'crecord_type': 'defaultrule'}) if records: return records[0]["action"] self.logger.debug( "No default action found. Assuming default action is pass") return 'pass'
def exports(ws): ws.application.router.add_filter('id_filter', id_filter) context_manager = ContextGraph(ws.logger) am = Alerts(*Alerts.provide_default_basics()) ar = AlertsReader(*AlertsReader.provide_default_basics()) @route( ws.application.get, name='alerts/get-alarms', payload=[ 'tstart', 'tstop', 'opened', 'resolved', 'lookups', 'filter', 'search', 'sort_key', 'sort_dir', 'skip', 'limit', 'with_steps', 'natural_search', 'active_columns', 'hide_resources' ] ) def get_alarms( tstart=None, tstop=None, opened=True, resolved=False, lookups=[], filter={}, search='', sort_key='opened', sort_dir='DESC', skip=0, limit=None, with_steps=False, natural_search=False, active_columns=None, hide_resources=False ): """ Return filtered, sorted and paginated alarms. :param tstart: Beginning timestamp of requested period :param tstop: End timestamp of requested period :type tstart: int or None :type tstop: int or None :param bool opened: If True, consider alarms that are currently opened :param bool resolved: If True, consider alarms that have been resolved :param list lookups: List of extra columns to compute for each returned alarm. Extra columns are "pbehaviors". :param dict filter: Mongo filter. Keys are UI column names. :param str search: Search expression in custom DSL :param str sort_key: Name of the column to sort :param str sort_dir: Either "ASC" or "DESC" :param int skip: Number of alarms to skip (pagination) :param int limit: Maximum number of alarms to return :param list active_columns: list of active columns on the brick listalarm . :param bool hide_resources: hide_resources if component has an alarm :returns: List of sorted alarms + pagination informations :rtype: dict """ if isinstance(search, int): search = str(search) try: alarms = ar.get( tstart=tstart, tstop=tstop, opened=opened, resolved=resolved, lookups=lookups, filter_=filter, search=search.strip(), sort_key=sort_key, sort_dir=sort_dir, skip=skip, limit=limit, with_steps=with_steps, natural_search=natural_search, active_columns=active_columns, hide_resources=hide_resources ) except OperationFailure as of_err: message = 'Operation failure on get-alarms: {}'.format(of_err) raise WebServiceError(message) alarms_ids = [] for alarm in alarms['alarms']: tmp_id = alarm.get('d') if tmp_id: alarms_ids.append(tmp_id) entities = context_manager.get_entities_by_id(alarms_ids, with_links=True) entity_dict = {} for entity in entities: entity_dict[entity.get('_id')] = entity list_alarm = [] for alarm in alarms['alarms']: now = int(time()) alarm["v"]['duration'] = now - alarm.get('v', {}).get('creation_date', now) state_time = alarm.get('v', {}).get('state', {}).get('t', now) alarm["v"]['current_state_duration'] = now - state_time tmp_entity_id = alarm['d'] if alarm['d'] in entity_dict: alarm['links'] = entity_dict[alarm['d']]['links'] # TODO: 'infos' is already present in entity. # Remove this one if unused. if tmp_entity_id in entity_dict: data = entity_dict[alarm['d']]['infos'] if alarm.get('infos'): alarm['infos'].update(data) else: alarm['infos'] = data alarm = compat_go_crop_states(alarm) list_alarm.append(alarm) alarms['alarms'] = list_alarm return alarms @route( ws.application.get, name='alerts/search/validate', payload=['expression'] ) def validate_search(expression): """ Tell if a search expression is valid from a grammatical propespective. :param str expression: Search expression :returns: True if valid, False otherwise :rtype: bool """ try: ar.interpret_search(expression) except Exception: return False else: return True @route( ws.application.get, name='alerts/count', payload=['start', 'stop', 'limit', 'select'], ) def count_by_period( start, stop, limit=100, select=None, ): """ Count alarms that have been opened during (stop - start) period. :param start: Beginning timestamp of period :type start: int :param stop: End timestamp of period :type stop: int :param limit: Counts cannot exceed this value :type limit: int :param query: Custom mongodb filter for alarms :type query: dict :return: List in which each item contains a time interval and the related count :rtype: list """ return ar.count_alarms_by_period( start, stop, limit=limit, query=select, ) @route( ws.application.get, name='alerts/get-current-alarm', payload=['entity_id'], ) def get_current_alarm(entity_id): """ Get current unresolved alarm for a entity. :param str entity_id: Entity ID of the alarm :returns: Alarm as dict if something is opened, else None """ return am.get_current_alarm(entity_id) @ws.application.get( '/api/v2/alerts/filters/<entity_id:id_filter>' ) def get_filter(entity_id): """ Get all filters linked with an alarm. :param str entity_id: Entity ID of the alarm-filter :returns: a list of <AlarmFilter> """ filters = am.alarm_filters.get_filter(entity_id) if filters is None: return gen_json_error({'description': 'nothing to return'}, HTTP_ERROR) return gen_json([l.serialize() for l in filters]) @ws.application.post( '/api/v2/alerts/filters' ) def create_filter(): """ Create a new alarm filter. :returns: an <AlarmFilter> """ # element is a full AlarmFilter (dict) to insert element = request.json if element is None: return gen_json_error( {'description': 'nothing to insert'}, HTTP_ERROR) new = am.alarm_filters.create_filter(element=element) new.save() return gen_json(new.serialize()) @ws.application.put( '/api/v2/alerts/filters/<entity_id:id_filter>' ) def update_filter(entity_id): """ Update an existing alam filter. :param entity_id: Entity ID of the alarm-filter :type entity_id: str :returns: <AlarmFilter> :rtype: dict """ dico = request.json if dico is None or not isinstance(dico, dict) or len(dico) <= 0: return gen_json_error( {'description': 'wrong update dict'}, HTTP_ERROR) af = am.alarm_filters.update_filter(filter_id=entity_id, values=dico) if not isinstance(af, AlarmFilter): return gen_json_error({'description': 'failed to update filter'}, HTTP_ERROR) return gen_json(af.serialize()) @ws.application.delete( '/api/v2/alerts/filters/<entity_id:id_filter>' ) def delete_id(entity_id): """ Delete a filter, based on his id. :param entity_id: Entity ID of the alarm-filter :type entity_id: str :rtype: dict """ ws.logger.info('Delete alarm-filter : {}'.format(entity_id)) return gen_json(am.alarm_filters.delete_filter(entity_id)) @ws.application.delete( '/api/v2/alerts/<mfilter>' ) def delete_filter(mfilter): """ :param str mfilter: mongo filter :rtype: dict """ return gen_json(ar.alarm_storage._backend.remove(json.loads(mfilter))) @ws.application.post( '/api/v2/alerts/done' ) def done_action(): """ Trigger done action. For json payload, see doc/docs/fr/guide_developpeur/apis/v2/alerts.md :rtype: dict """ dico = request.json if dico is None or not isinstance(dico, dict) or len(dico) <= 0: return gen_json_error( {'description': 'wrong done dict'}, HTTP_ERROR) author = dico.get(am.AUTHOR) event = forger( event_type=Check.EVENT_TYPE, author=author, connector=dico.get('connector'), connector_name=dico.get('connector_name'), component=dico.get('component'), output=dico.get('comment') ) if dico.get('source_type', None) == 'resource': event['resource'] = dico['resource'] event['source_type'] = 'resource' ws.logger.debug('Received done action: {}'.format(event)) entity_id = am.context_manager.get_id(event) retour = am.execute_task( 'alerts.useraction.done', event=event, author=author, entity_id=entity_id ) return gen_json(retour)
class ComputeState(BaseTest): def setUp(self): super(ComputeState, self).setUp() pbehavior_storage = Middleware.get_middleware_by_uri( 'storage-default-testpbehavior://') filter_storage = Middleware.get_middleware_by_uri( 'storage-default-testalarmfilter://') config_storage = Middleware.get_middleware_by_uri( 'storage-default-testconfig://') config_storage.put_element(element={ '_id': 'test_config', 'crecord_type': 'statusmanagement', 'bagot_time': 3600, 'bagot_freq': 10, 'stealthy_time': 300, 'restore_event': True, 'auto_snooze': False, 'snooze_default_time': 300, }, _id='test_config') logger = Logger.get('test_pb', None, output_cls=OutputNull) config = Configuration.load(PBehaviorManager.CONF_PATH, Ini) self.pbm = PBehaviorManager(config=config, logger=logger, pb_storage=pbehavior_storage) self.pbm.context = self.context_graph_manager self.manager.pbehavior_manager = self.pbm conf = Configuration.load(Alerts.CONF_PATH, Ini) filter_ = {'crecord_type': 'statusmanagement'} config_data = EtherealData(collection=config_storage._backend, filter_=filter_) event_publisher = Mock(spec=StatEventPublisher) self.alert_manager = Alerts(config=conf, logger=logger, alerts_storage=self.alerts_storage, config_data=config_data, filter_storage=filter_storage, context_graph=self.context_graph_manager, watcher=self.manager, event_publisher=event_publisher) # Creating entity self.type_ = 'resource' self.name = 'morticia' entity = ContextGraph.create_entity_dict(id=self.name, etype=self.type_, name=self.name) self.context_graph_manager.create_entity(entity) # Creating coresponding alarm event = { 'connector': self.type_, 'connector_name': 'connector_name', 'component': self.name, 'output': 'tadaTaDA tic tic', 'timestamp': 0 } alarm = self.alert_manager.make_alarm(self.name, event) self.state = 2 alarm = self.alert_manager.update_state(alarm, self.state, event) new_value = alarm[self.alert_manager.alerts_storage.VALUE] self.alert_manager.update_current_alarm(alarm, new_value) def tearDown(self): super(ComputeState, self).tearDown() self.pbm.pb_storage.remove_elements() def test_compute_state_issue427(self): # Aka: state desyncro watcher_id = 'addams' watcher = { '_id': watcher_id, 'mfilter': '{"name": {"$in": ["morticia"]}}', 'display_name': 'family' } self.assertTrue(self.manager.create_watcher(watcher)) res = self.manager.get_watcher(watcher_id) self.assertEqual(res['state'], self.state) # Creating pbehavior on it now = datetime.utcnow() self.pbm.create(name='addam', filter=loads('{"name": "morticia"}'), author='addams', tstart=timegm(now.timetuple()), tstop=timegm((now + timedelta(seconds=2)).timetuple()), rrule=None, enabled=True) self.pbm.compute_pbehaviors_filters() res = self.manager.get_watcher(watcher_id) self.assertEqual(res['state'], self.state) self.manager.compute_watchers() res = self.manager.get_watcher(watcher_id) self.assertEqual(res['state'], 0) sleep(3) self.pbm.compute_pbehaviors_filters() self.manager.compute_watchers() res = self.manager.get_watcher(watcher_id) self.assertEqual(res['state'], self.state)
def setUp(self): super(ComputeState, self).setUp() pbehavior_storage = Middleware.get_middleware_by_uri( 'storage-default-testpbehavior://') filter_storage = Middleware.get_middleware_by_uri( 'storage-default-testalarmfilter://') config_storage = Middleware.get_middleware_by_uri( 'storage-default-testconfig://') config_storage.put_element(element={ '_id': 'test_config', 'crecord_type': 'statusmanagement', 'bagot_time': 3600, 'bagot_freq': 10, 'stealthy_time': 300, 'restore_event': True, 'auto_snooze': False, 'snooze_default_time': 300, }, _id='test_config') logger = Logger.get('test_pb', None, output_cls=OutputNull) config = Configuration.load(PBehaviorManager.CONF_PATH, Ini) self.pbm = PBehaviorManager(config=config, logger=logger, pb_storage=pbehavior_storage) self.pbm.context = self.context_graph_manager self.manager.pbehavior_manager = self.pbm conf = Configuration.load(Alerts.CONF_PATH, Ini) filter_ = {'crecord_type': 'statusmanagement'} config_data = EtherealData(collection=config_storage._backend, filter_=filter_) event_publisher = Mock(spec=StatEventPublisher) self.alert_manager = Alerts(config=conf, logger=logger, alerts_storage=self.alerts_storage, config_data=config_data, filter_storage=filter_storage, context_graph=self.context_graph_manager, watcher=self.manager, event_publisher=event_publisher) # Creating entity self.type_ = 'resource' self.name = 'morticia' entity = ContextGraph.create_entity_dict(id=self.name, etype=self.type_, name=self.name) self.context_graph_manager.create_entity(entity) # Creating coresponding alarm event = { 'connector': self.type_, 'connector_name': 'connector_name', 'component': self.name, 'output': 'tadaTaDA tic tic', 'timestamp': 0 } alarm = self.alert_manager.make_alarm(self.name, event) self.state = 2 alarm = self.alert_manager.update_state(alarm, self.state, event) new_value = alarm[self.alert_manager.alerts_storage.VALUE] self.alert_manager.update_current_alarm(alarm, new_value)
def setUp(self): super(ComputeState, self).setUp() pbehavior_storage = Middleware.get_middleware_by_uri( 'storage-default-testpbehavior://' ) filter_storage = Middleware.get_middleware_by_uri( 'storage-default-testalarmfilter://' ) config_storage = Middleware.get_middleware_by_uri( 'storage-default-testconfig://' ) config_storage.put_element( element={ '_id': 'test_config', 'crecord_type': 'statusmanagement', 'bagot_time': 3600, 'bagot_freq': 10, 'stealthy_time': 300, 'restore_event': True, 'auto_snooze': False, 'snooze_default_time': 300, }, _id='test_config' ) logger = Logger.get('test_pb', None, output_cls=OutputNull) config = Configuration.load(PBehaviorManager.CONF_PATH, Ini) self.pbm = PBehaviorManager(config=config, logger=logger, pb_storage=pbehavior_storage) self.pbm.context = self.context_graph_manager self.manager.pbehavior_manager = self.pbm conf = Configuration.load(Alerts.CONF_PATH, Ini) filter_ = {'crecord_type': 'statusmanagement'} config_data = EtherealData(collection=config_storage._backend, filter_=filter_) event_publisher = Mock(spec=StatEventPublisher) self.alert_manager = Alerts(config=conf, logger=logger, alerts_storage=self.alerts_storage, config_data=config_data, filter_storage=filter_storage, context_graph=self.context_graph_manager, watcher=self.manager, event_publisher=event_publisher) # Creating entity self.type_ = 'resource' self.name = 'morticia' entity = ContextGraph.create_entity_dict( id=self.name, etype=self.type_, name=self.name ) self.context_graph_manager.create_entity(entity) # Creating coresponding alarm event = { 'connector': self.type_, 'connector_name': 'connector_name', 'component': self.name, 'output': 'tadaTaDA tic tic', 'timestamp': 0 } alarm = self.alert_manager.make_alarm(self.name, event) self.state = 2 alarm = self.alert_manager.update_state(alarm, self.state, event) new_value = alarm[self.alert_manager.alerts_storage.VALUE] self.alert_manager.update_current_alarm(alarm, new_value)