def email_login_ux_summary(now, time_delta, site_host_args): """ generate and send a report with login event state counts and user experience stats for the site and host combination specified by `site_host_args` over the interval defined by `now` and `time_delta` :arg tuple site_host_args: the `site` and `host_name` :arg datetime.datetime now: the initial moment for calculating the data in the report :arg datetime.timedelta time_delta: the reporting interval used for calculating the data in the report :raises: a generic :exc:`Exception` This report used the subscription at `Citrix logon event and ux summary <../../../admin/ssl_cert_tracker/subscription/?q=Citrix+logon+event+and+ux+summary>`__ to render the emails being sent. """ try: return base_utils.borgs_are_hailing( data=login_states_by_site_host_hour(now=now, time_delta=time_delta, site=site_host_args[0], host_name=site_host_args[1]), subscription=base_utils.get_subscription( 'Citrix logon event and ux summary'), logger=LOGGER, time_delta=time_delta, site=site_host_args[0], host_name=site_host_args[1]) except Exception as error: raise error
def email_borg_login_summary_report(now=None, **dead_for): """ prepare and email a report with all the logon events generated by the `Citrix` bots The report includes events that occurred during the interval defined by the arguments `now` and `dead_for`. The report includes counts for `failed` and `successful` events. This task is almost identical to :func:`email_dead_borgs_alert` with the exception of the default value for the :class:`datetime.timedelta` object created when `dead_for` is not present. This value is picked from dynamic preference `Ignore events created older than <../../../admin/dynamic_preferences/globalpreferencemodel/?q=ignore_events_older_than>`__. This task used the subscription at `Citrix logon event summary <../../../admin/ssl_cert_tracker/subscription/?q=Citrix+logon+event+summary>`__ to render the emails being sent. """ if not dead_for: time_delta = get_preference( 'citrusborgevents__ignore_events_older_than') else: time_delta = base_utils.MomentOfTime.time_delta(**dead_for) try: return base_utils.borgs_are_hailing( data=get_logins_by_event_state_borg_hour( now=base_utils.MomentOfTime.now(now), time_delta=time_delta), subscription=base_utils.get_subscription( 'Citrix logon event summary'), logger=LOGGER, time_delta=time_delta) except Exception as error: raise error
def raise_site_not_configured_for_bot(): """ email alerts if there are exchange bots with mis-configured site info :returns: the result of the email send operation :rtype: str :raises: :exc:`Exception` if an exception was thrown while sending the alert """ data = models.MailHost.objects.filter( Q(site__isnull=True) | Q(site__site__iexact='site.not.exist')).\ exclude(host_name__iexact='host.not.exist') if data.exists(): data = base_utils.url_annotate(data) if not data and not get_preference('exchange__empty_alerts'): return 'all exchange bots are properly configured' try: ret = base_utils.borgs_are_hailing( data=data, subscription=base_utils.get_subscription('Exchange bot no site'), logger=LOGGER, level=get_preference('exchange__server_error')) except Exception as error: raise error if ret: return 'emailed alert for mis-configured Exchange bots' return 'cannot email alert for mis-configured Exchange bots'
def email_failed_logins_report(now=None, send_no_news=False, **dead_for): """ generate and email a report with all the failed `Citrix` logon events See the :func:`email_dead_borgs_alert` for details about the arguments to this task. This report uses the subscription at `Citrix Failed Logins Report <../../../admin/ssl_cert_tracker/subscription/?q=Citrix+Failed+Logins+Report>`__. """ if not dead_for: time_delta = get_preference('citrusborglogon__logon_report_period') else: time_delta = base_utils.MomentOfTime.time_delta(**dead_for) now = base_utils.MomentOfTime.now(now) data = get_failed_events(now=now, time_delta=time_delta) if not data and send_no_news: return ( 'there were no failed logon events between' ' {:%a %b %d, %Y %H:%M %Z} and {:%a %b %d, %Y %H:%M %Z}'.format( timezone.localtime(value=now), timezone.localtime(now - time_delta))) try: return base_utils.borgs_are_hailing( data=data, subscription=base_utils.get_subscription( 'Citrix Failed Logins Report'), logger=LOGGER, time_delta=time_delta) except Exception as error: raise error
def dead_mail_sites(subscription, time_delta_pref=None, level=None): """ task for site related alerts via email :returns: the return depends on whether the systems has detected alerts and on the value of the :class:`citrus_borg.dynamic_preferences_registry.ExchangeEmptyAlerts` dynamic setting * if there are alerts, this task will return the result of the email send op * otherwise the task will check the value of the :class:`citrus_borg.dynamic_preferences_registry.ExchangeEmptyAlerts` dynamic setting. * if the value is ``False``, the task will not send any emails and it will return this information * otherwise the task will send an email saying that there are no alerts and return the result of the email send op :rtype: str :raises: :exc:`Exception` if an exception was thrown while sending the alert """ if level is None: level = get_preference('exchange__default_level') subscription = base_utils.get_subscription(subscription) if time_delta_pref is None: time_delta = get_preference('exchange__default_error') else: time_delta = get_preference(time_delta_pref) not_seen_after = base_utils.MomentOfTime.past(time_delta=time_delta) data = queries.dead_mail_sites(not_seen_after=not_seen_after) if not data and not get_preference('exchange__empty_alerts'): return 'no %s data found for %s' % (level, subscription.subscription) try: ret = base_utils.borgs_are_hailing( data=data, subscription=subscription, logger=LOGGER, time_delta=time_delta, level=level) except Exception as error: raise error if ret: return 'emailed data for %s' % data.model._meta.verbose_name_plural return 'could not email data for %s' % data.model._meta.verbose_name_plural
def email_dead_servers_alert(now=None, send_no_news=None, **dead_for): """ send out alerts about `Citrix` application servers that have not service any requests during the time interval defined by arguments `now` and `dead_for` via email This task is almost identical to :func:`email_dead_borgs_alert` with the exception of the default value for the :class:`datetime.timedelta` object created when `dead_for` is not present. This value is picked from dynamic preference `Reporting period for dead nodes <../../../admin/dynamic_preferences/globalpreferencemodel/?q=node_forgotten_after>`__ This task used the subscription at `Missing Citrix farm hosts <../../../admin/ssl_cert_tracker/subscription/?q=Missing+Citrix+farm+hosts>`__ to render the emails being sent. """ now = base_utils.MomentOfTime.now(now) if not dead_for: time_delta = get_preference('citrusborgnode__node_forgotten_after') else: time_delta = base_utils.MomentOfTime.time_delta(**dead_for) if send_no_news is None: send_no_news = get_preference('citrusborgcommon__send_no_news') if not isinstance(send_no_news, bool): raise TypeError( 'object {} type {} is not valid. must be boolean'.format( send_no_news, type(send_no_news))) data = get_dead_brokers(now=now, time_delta=time_delta) if not data and send_no_news: return ( 'all known Cerner session servers were active between' ' {:%a %b %d, %Y %H:%M %Z} and {:%a %b %d, %Y %H:%M %Z}'.format( timezone.localtime(value=now), timezone.localtime(now - time_delta))) try: return base_utils.borgs_are_hailing( data=data, subscription=base_utils.get_subscription( 'Missing Citrix farm hosts'), logger=LOGGER, time_delta=time_delta) except Exception as error: raise error
def email_dead_sites_alert(now=None, send_no_news=None, **dead_for): """ send out alerts about remote sites where all the `Citrix` monitoring bots that have not been seen within the time interval defined by arguments `now` and `dead_for` via email This task is almost identical to :func:`email_dead_borgs_alert` with the exception of the default value for the :class:`datetime.timedelta` object created when `dead_for` is not present. This value is picked from dynamic preference `Site not seen alert threshold <../../../admin/dynamic_preferences/globalpreferencemodel/?q=dead_site_after>`__. This task used the subscription at `Dead Citrix client sites <../../../admin/ssl_cert_tracker/subscription/?q=Dead+Citrix+client+sites>`__ to render the emails being sent. """ now = base_utils.MomentOfTime.now(now) if not dead_for: time_delta = get_preference('citrusborgnode__dead_site_after') else: time_delta = base_utils.MomentOfTime.time_delta(**dead_for) if send_no_news is None: send_no_news = get_preference('citrusborgcommon__send_no_news') if not isinstance(send_no_news, bool): raise TypeError( 'object {} type {} is not valid. must be boolean'.format( send_no_news, type(send_no_news))) data = get_dead_sites(now=now, time_delta=time_delta) if not data and send_no_news: return ( 'at least one monitoring bot on each site was active between' ' {:%a %b %d, %Y %H:%M %Z} and {:%a %b %d, %Y %H:%M %Z}'.format( timezone.localtime(value=now), timezone.localtime(now - time_delta))) try: return base_utils.borgs_are_hailing( data=data, subscription=base_utils.get_subscription( 'Dead Citrix client sites'), logger=LOGGER, time_delta=time_delta) except Exception as error: raise error
def report_failed_events_by_bot(bot, report_interval): """ send out report for failed events for a bot over a given duration measured back from the moment of the call via email :arg str bot: the host (short) name of the bot to report on :arg `object` report_interval: the time interval for which the report is calculated. it is either a :class:`datetime.timedelta` instance or a :class:`dict` suitable for constructing a :classL`datetime.timedelta` instance, for instance {'hours': 1, 'seconds': 1} :returns: the result of the email send operation :rtype: str :raises: :exc:`Exception` if an exception was thrown while sending the alert """ subscription = base_utils.get_subscription( 'Exchange Failed Send Receive By Bot') data = queries.dead_bodies( data_source='mail_collector.mailbotmessage', filter_exp='event__event_registered_on__gte', not_seen_after=base_utils.MomentOfTime.past( time_delta=report_interval), event__source_host__host_name=bot, event__event_status__iexact='fail').\ order_by('-mail_message_identifier', 'event__event_type_sort') try: ret = base_utils.borgs_are_hailing( data=data, subscription=subscription, logger=LOGGER, time_delta=report_interval, level=get_preference('exchange__server_error'), bot=bot) except Exception as error: raise error if ret: return ( 'emailed exchange failed send receive events report for bot %s' % bot) return ( 'could not email exchange failed send receive events report for bot %s' % bot)
def email_failed_logins_alarm(now=None, failed_threshold=None, **dead_for): """ raise alert about failed `Citrix` logon events and send it via email :arg int failed_threshold: the number of failed logons that will trigger the alert By default, this will be retrieved from the dynamic preference `Failed logon events count alert threshold <../../../admin/dynamic_preferences/globalpreferencemodel/?q=logon_alert_threshold>`__ See the :func:`email_dead_borgs_alert` for details about the other arguments. This alert used the subscription at `Citrix logon alert <../../../admin/ssl_cert_tracker/subscription/?q=Citrix+logon+alert>`__. """ if failed_threshold is None: failed_threshold = get_preference( 'citrusborglogon__logon_alert_threshold') if not dead_for: time_delta = get_preference('citrusborglogon__logon_alert_after') else: time_delta = base_utils.MomentOfTime.time_delta(**dead_for) now = base_utils.MomentOfTime.now(now) data = raise_failed_logins_alarm(now=now, time_delta=time_delta, failed_threshold=failed_threshold) if not data: return ( 'there were less than {} failed logon events between' ' {:%a %b %d, %Y %H:%M %Z} and {:%a %b %d, %Y %H:%M %Z}'.format( failed_threshold, timezone.localtime(value=now), timezone.localtime(now - time_delta))) try: return base_utils.borgs_are_hailing( data=data, subscription=base_utils.get_subscription('Citrix logon alert'), logger=LOGGER, time_delta=time_delta, failed_threshold=failed_threshold) except Exception as error: raise error
def email_failed_login_site_report(now, time_delta, send_no_news, site_host_args): """ prepare and email a report with the `Citrix` failed logins details for the `site` and `bot` combination specified in `site_host_args` :arg tuple site_host_args: the `site` and 'bot` for which the report is prepared :arg now: see :func:`email_dead_borgs_alert` :arg bool send_no_news: see :func:`email_dead_borgs_alert` :arg dict reporting_period: see the `dead_for` argument of the :func:`email_dead_borgs_alert` task This report uses the subscription at `Citrix Failed Logins per Report <../../../admin/ssl_cert_tracker/subscription/?q=Citrix+Failed+Logins+per+Report>`__ to render the emails being sent. """ now = base_utils.MomentOfTime.now(now) site, host_name = site_host_args data = get_failed_events(now=now, time_delta=time_delta, site=site, host_name=host_name) if not data and send_no_news: return ( 'there were no failed logon events on the {} bot in {} between' ' {:%a %b %d, %Y %H:%M %Z} and {:%a %b %d, %Y %H:%M %Z}'.format( host_name, site, timezone.localtime(value=now), timezone.localtime(now - time_delta))) try: return base_utils.borgs_are_hailing( data=data, subscription=base_utils.get_subscription( 'Citrix Failed Logins per Site Report'), logger=LOGGER, time_delta=time_delta, site=site, host_name=host_name) except Exception as error: raise error
def report_events_by_site(site, report_interval, report_level): """ send out report with events for a site via email :arg str site: the site to report on :arg `object` report_interval: the time interval for which the report is calculated. it is either a :class:`datetime.timedelta` instance or a :class:`dict` suitable for constructing a :class:`datetime.timedelta` instance :arg str report_level: INFO|WARN|ERROR :returns: the result of the email send operation :rtype: str :raises: :exc:`Exception` if an exception was thrown while sending the alert """ subscription = base_utils.get_subscription('Exchange Send Receive By Site') data = queries.dead_bodies( data_source='mail_collector.mailbotmessage', filter_exp='event__event_registered_on__gte', not_seen_after=base_utils.MomentOfTime.past( time_delta=report_interval), event__source_host__site__site=site).\ order_by('-mail_message_identifier', 'event__event_type_sort') try: ret = base_utils.borgs_are_hailing( data=data, subscription=subscription, logger=LOGGER, time_delta=report_interval, level=report_level, site=site) except Exception as error: raise error if ret: return ( 'emailed exchange send receive events report for site %s' % site) return ( 'could not email exchange send receive events report for site %s' % site)
def email_dead_sites_report(now=None, send_no_news=False, **dead_for): """ generate and email reports about remote sites where`Citrix` monitoring bots have not been seen within the time interval defined by arguments `now` and `dead_for` via email This task is almost identical to :func:`email_dead_borgs_alert` with the exception of the default value for the :class:`datetime.timedelta` object created when `dead_for` is not present. This value is picked from dynamic preference `Reporting period for dead nodes <../../../admin/dynamic_preferences/globalpreferencemodel/?q=node_forgotten_after>`__ This task used the subscription at `Dead Citrix client sites <../../../admin/ssl_cert_tracker/subscription/?q=Dead+Citrix+client+sites>`__ to render the emails being sent. """ now = base_utils.MomentOfTime.now(now) if not dead_for: time_delta = get_preference('citrusborgnode__node_forgotten_after') else: time_delta = base_utils.MomentOfTime.time_delta(**dead_for) data = get_dead_sites(now=now, time_delta=time_delta) if not data and send_no_news: return ( 'at least one monitoring bot on each site was active between' ' {:%a %b %d, %Y %H:%M %Z} and {:%a %b %d, %Y %H:%M %Z}'.format( timezone.localtime(value=now), timezone.localtime(now - time_delta))) try: return base_utils.borgs_are_hailing( data=data, subscription=base_utils.get_subscription( 'Dead Citrix client sites'), logger=LOGGER, time_delta=time_delta) except Exception as error: raise error
def email_ux_alarm(now, time_delta, send_no_news, ux_alert_threshold, site_host_args): """ raise user experience alert and send by email for each `site` and `host` in `site_host_args` :arg tuple site_host_args: `site` and `host_name` to which the alert is bound See the :func:`email_us_alarms` task for details about the other arguments. This alert used the subscription at `Citrix UX Alert <../../../admin/ssl_cert_tracker/subscription/?q=Citrix+UX+Alert>`__. """ now = base_utils.MomentOfTime.now(now) site, host_name = site_host_args data = raise_ux_alarm(now=now, time_delta=time_delta, ux_alert_threshold=ux_alert_threshold, site=site, host_name=host_name) if not data and send_no_news: return ( 'Citrix response times on {} bot in {} were better than {} between' ' {:%a %b %d, %Y %H:%M %Z} and {:%a %b %d, %Y %H:%M %Z}'.format( host_name, site, ux_alert_threshold, timezone.localtime(value=now), timezone.localtime(now - time_delta))) try: return base_utils.borgs_are_hailing( data=data, subscription=base_utils.get_subscription('Citrix UX Alert'), logger=LOGGER, time_delta=time_delta, ux_alert_threshold=ux_alert_threshold, site=site_host_args[0], host_name=site_host_args[1]) except Exception as error: raise error
def raise_failed_event_by_mail(event_pk): """ send an alert email for failed events :arg int event_pk: the primary key of the :class:`mail_collector.models.MailBotLogEvent` instance with the failed event :returns: the result of the email operation :rtype: str :raises: :exc:`Exception` if an error is thrown by the email send op """ data = models.MailBotLogEvent.objects.filter(pk=event_pk) subscription = base_utils.get_subscription('Exchange Client Error') # let's cache some data to avoid evaluating the queryset multiple times data_extract = data.values( 'uuid', 'event_type', 'source_host__site__site', 'source_host__host_name')[0] try: ret = base_utils.borgs_are_hailing( data=data, subscription=subscription, logger=LOGGER, level=get_preference('exchange__server_error'), event_type=data_extract.get('event_type'), site=data_extract.get('source_host__site__site'), bot=data_extract.get('source_host__host_name')) except Exception as error: raise error if ret: return 'raised email alert for event %s' % str( data_extract.get('uuid')) return 'could not raise email alert for event %s' % str( data_extract.get('uuid'))
def report_mail_between_domains(only_fails=False, subscription=None): """ task to run reports about mail between domains :arg str subscription: the email subscription. default: 'Mail Verification Report' :arg bool only_fails: only report the fails, default: ``False`` :returns: the result of the email send operation :rtype: str :raises: :exc:`Exception` if an exception was thrown while sending the alert """ if subscription is None: subscription = 'Mail Verification Report' subscription = base_utils.get_subscription(subscription) queryset = models.MailBetweenDomains.objects.filter( enabled=True, is_expired=False) if only_fails: queryset = queryset.filter(status__iexact='FAILED') try: ret = base_utils.borgs_are_hailing( data=queryset, subscription=subscription, logger=LOGGER) except Exception as error: raise error if ret: return 'emailed report for mail between domains verification' return 'could not email report for mail between domains verification'
def email_dead_borgs_alert(now=None, send_no_news=None, **dead_for): """ send out alerts about `Citrix` monitoring bots that have not been seen within the time interval defined by arguments `now` and `dead_for` via email :arg now: the initial moment By default (and in most useful cases for functions that return historical data), the initial moment should be the value of :meth:`datetime.datetime.now` (:meth:`django.utils.timezone.now` in `Django` applications). :Note: **We are not using this argument in the current implementation of the :ref:`Citrus Borg Application`.** The current code base expects that `now` is either a :class:`NoneType` object or a :class:`datetime.datetime` object. Unfortunately, :class:`datetime.datetime` objects cannot be serialized to `JSON <https://www.json.org/>`__ and the mechanism used to invoke this task is using `JSON` for passing arguments. .. todo:: Extend :meth:`p_soc_auto_base.utils.MomentOfTime.now` to accept data types are `JSON` serializable. It is possible to use this argument if the task is `called <https://docs.celeryproject.org/en/latest/userguide/calling.html#basics>`__ with `apply_async() <https://docs.celeryproject.org/en/latest/reference/celery.app.task.html#celery.app.task.Task.apply_async>`__ with the `serializer` argument set to 'pickle'. :arg dict dead_for: optional `Keyword Arguments <https://docs.python.org/3.6/tutorial/controlflow.html#keyword-arguments>`__ used for initializing a :class:`datetime.timedelta` object This is the equivalent of the `time_delta` argument used by :func:`citrus_borg.locutus.communication.get_dead_bots` and most of the other functions in that module. This argument must match the arguments required by the :class:`datetime.timedelta` constructor, e.g. .. ipython:: In [5]: from django.utils import timezone In [6]: time_delta=timezone.timedelta(hours=4, minutes=10) In [7]: time_delta Out[7]: datetime.timedelta(0, 15000) In [8]: print(time_delta) 4:10:00 In [9]: The reason for this approach is the same as above. :class:`datetime.timedelta` objects are not `JSON` serializable. Valid names for the `Keyword Arguments <https://docs.python.org/3.6/tutorial/controlflow.html#keyword-arguments>`__ defined by `dead_for` are `days`, `hours`, `minutes`, and `seconds`. All the values passed via these `keyword arguments` must be of type :class:`float` (or castable to :class:`float`, e.g. :class:`int`). By default the `dead_for` is not present. In this case, the function will pick the `time_delta` value from the dynamic preference `Bot not seen alert threshold <../../../admin/dynamic_preferences/globalpreferencemodel/?q=dead_bot_after>`__ :arg bool send_no_news: this argument determines whether emails will be sent even if there are no alerts Sending emails with "all is well" is atractive because it provides something similar to a heartbeat for the application. The problem with that is the number of emails not containing any relevant information that will clog inboxes and reduce user attention. By default, the value of this argument will be picked from dynamic preference `Do not send empty citrix alert emails <../../../admin/dynamic_preferences/globalpreferencemodel/?q=send_no_news>`__ This task used the subscription at `Dead Citrix monitoring bots <../../../admin/ssl_cert_tracker/subscription/?q=Dead+Citrix+monitoring+bots>`__ to render the emails being sent. """ now = base_utils.MomentOfTime.now(now) if not dead_for: time_delta = get_preference('citrusborgnode__dead_bot_after') else: time_delta = base_utils.MomentOfTime.time_delta(**dead_for) if send_no_news is None: send_no_news = get_preference('citrusborgcommon__send_no_news') if not isinstance(send_no_news, bool): raise TypeError( 'object {} type {} is not valid. must be boolean'.format( send_no_news, type(send_no_news))) data = get_dead_bots(now=now, time_delta=time_delta) if not data and send_no_news: return ( 'all monitoring bots were active between' ' {:%a %b %d, %Y %H:%M %Z} and {:%a %b %d, %Y %H:%M %Z}'.format( timezone.localtime(value=now), timezone.localtime(now - time_delta))) try: return base_utils.borgs_are_hailing( data=data, subscription=base_utils.get_subscription( 'Dead Citrix monitoring bots'), logger=LOGGER, time_delta=time_delta) except Exception as error: raise error
def email_failed_ux_report(now=None, send_no_news=False, ux_threshold_seconds=None, **dead_for): """ prepare and email a report with all the `Citrix` logon timings that do not satisfy the user response time threshold :arg now: see :func:`email_dead_borgs_alert` :arg bool send_no_news: see :func:`email_dead_borgs_alert` :arg datetime.timedelta ux_alert_threshold: the threshold for triggering a user experience alert By default, this will be retrieved from the dynamic preference `Maximum acceptable response time for citrix events <../../../admin/dynamic_preferences/globalpreferencemodel/?q=ux_alert_threshold>`__ :arg dict dead_for: see the `dead_for` argument of the :func:`email_dead_borgs_alert` task If this argument is not present, the reporting interval is picked from dynamic preference `User experience reporting period <../../../admin/dynamic_preferences/globalpreferencemodel/?q=ux_reporitng_period>`__ This report uses the subscription at `Citrix Failed UX Event Components Report <../../../admin/ssl_cert_tracker/subscription/?q=Citrix+Failed+UX+Event+Components+Report>`__ to render the emails being sent. """ if not dead_for: time_delta = get_preference('citrusborgux__ux_reporting_period') else: time_delta = base_utils.MomentOfTime.time_delta(**dead_for) if ux_threshold_seconds is None: ux_alert_threshold = get_preference('citrusborgux__ux_alert_threshold') else: ux_alert_threshold = base_utils.MomentOfTime.time_delta( time_delta=None, seconds=ux_threshold_seconds) now = base_utils.MomentOfTime.now(now) data = get_failed_ux_events(now=now, time_delta=time_delta, ux_alert_threshold=ux_alert_threshold) if not data and send_no_news: return ( 'there were no response time logon event components' ' longer than {:%S} seconds between' ' {:%a %b %d, %Y %H:%M %Z} and {:%a %b %d, %Y %H:%M %Z}'.format( ux_alert_threshold, timezone.localtime(value=now), timezone.localtime(now - time_delta))) try: return base_utils.borgs_are_hailing( data=data, subscription=base_utils.get_subscription( 'Citrix Failed UX Event Components Report'), logger=LOGGER, time_delta=time_delta, ux_alert_threshold=ux_alert_threshold) except Exception as error: raise error
def bring_out_your_dead( # pylint: disable=too-many-arguments data_source, filter_exp, subscription, url_annotate=False, level=None, filter_pref=None, **base_filters): """ generic task to raise email alerts about :ref:`Mail Collector Application` entities that have been in an abnormal state for a given duration measured going back from the current moment .. todo:: see `<https://trello.com/c/vav94p7e>`_ :arg str data_source: the reference to a :class:`django.db.models.Model` in the form of 'app_label.model_name' :arg str filter_exp: a django filter lhs expression (field_name__lookup); it is actually geared to deal with datetime fields :arg str subscription: the reference to the :class:`ssl_cert_tracker.models.Subscription` instance to be used for rendering the email alert :arg bool url_annotate: extend the queryset with the entity URL; default ``False`` :arg str level: INFO|WARN|ERROR to add to the subject line of the email alert; default ``None`` :arg `object` filter_pref: either a :class:`django.utils.timezone.timedelta` instance or a :class:`dict` suitable as argument for constructing a :class:`django.utils.timezone.timedelta` like {'days': ``float``, 'hours': ``float``, 'seconds': ``float``}; default ``None``. when ``None`` the value is picked up from the :class:`citrus_borg.dynamic_preferences_registry.ExchangeDefaultError` dynamic setting :arg \*\*base_filters: additional django lookup style arguments to be applied to the queryset :returns: the result of the email send operation :rtype: str :raises: :exc:`TypeError` if filter_pref cannot be cast to ``datetime.timedelta`` :exc:`Exception` if an exception was thrown while sending the alert example:: qs=dead_bodies('mail_collector.mailhost','excgh_last_seen__lte', not_seen_after={'minutes': 1}, enabled=True) """ if level is None: level = get_preference('exchange__default_level') subscription = base_utils.get_subscription(subscription) if filter_pref is None: filter_pref = get_preference('exchange__default_error') if not isinstance(filter_pref, dict): filter_pref = get_preference(filter_pref) if not isinstance(filter_pref, timezone.timedelta): raise TypeError( 'Invalid object type %s. Must be datetime.timedelta.' % type(filter_pref) ) not_seen_after = base_utils.MomentOfTime.past(time_delta=filter_pref) data = queries.dead_bodies( data_source, filter_exp, not_seen_after=not_seen_after, url_annotate=url_annotate, **base_filters) if not data and not get_preference('exchange__empty_alerts'): return 'no %s data found for %s' % (level, subscription.subscription) try: ret = base_utils.borgs_are_hailing( data=data, subscription=subscription, logger=LOGGER, time_delta=filter_pref, level=level) except Exception as error: raise error if ret: return 'emailed data for %s' % data.model._meta.verbose_name_plural return 'could not email data for %s' % data.model._meta.verbose_name_plural