implement alert periodic notifications for agent, task, and check. implement sms/email functionality for autotasks

This commit is contained in:
sadnub 2021-02-06 10:37:40 -05:00
parent 5dbfb64822
commit 79409af168
7 changed files with 292 additions and 52 deletions

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1.4 on 2021-02-06 15:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agents', '0027_agent_overdue_dashboard_alert'),
]
operations = [
migrations.AddField(
model_name='agentoutage',
name='outage_email_sent_time',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='agentoutage',
name='outage_sms_sent_time',
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -11,6 +11,7 @@ from collections import Counter
from typing import List
from typing import Union
from loguru import logger
import datetime as dt
from packaging import version as pyver
from distutils.version import LooseVersion
from nats.aio.client import Client as NATS
@ -680,16 +681,20 @@ class Agent(BaseAuditModel):
# called when agent is offline
else:
# return if outage has already been created
if self.agentoutages.exists() and self.agentoutages.last().is_active:
return
# outage hasn't been created yet so create it
if (
not self.agentoutages.exists()
and not self.agentoutages.last().is_active
):
outage = AgentOutage(agent=self)
outage.save()
outage = AgentOutage(agent=self)
outage.save()
# add a null check history to allow gaps in graph
for check in self.agentchecks.all():
check.add_check_history(None)
# add a null check history to allow gaps in graph
for check in self.agentchecks.all():
check.add_check_history(None)
else:
outage = self.agentoutages.last()
# create dashboard alert if enabled
if (
@ -705,14 +710,20 @@ class Agent(BaseAuditModel):
and alert_template.agent_always_email
or self.overdue_email_alert
):
agent_outage_email_task.delay(pk=outage.pk)
agent_outage_email_task.delay(
pk=outage.pk,
alert_interval=alert_template.agent_periodic_alert_days,
)
if (
alert_template
and alert_template.agent_always_text
or self.overdue_text_alert
):
agent_outage_sms_task.delay(pk=outage.pk)
agent_outage_sms_task.delay(
pk=outage.pk,
alert_interval=alert_template.agent_periodic_alert_days,
)
class AgentOutage(models.Model):
@ -727,6 +738,8 @@ class AgentOutage(models.Model):
recovery_time = models.DateTimeField(null=True, blank=True)
outage_email_sent = models.BooleanField(default=False)
outage_sms_sent = models.BooleanField(default=False)
outage_email_sent_time = models.DateTimeField(null=True, blank=True)
outage_sms_sent_time = models.DateTimeField(null=True, blank=True)
recovery_email_sent = models.BooleanField(default=False)
recovery_sms_sent = models.BooleanField(default=False)

View File

@ -3,8 +3,10 @@ from loguru import logger
from time import sleep
import random
from packaging import version as pyver
from typing import List
from typing import List, Union
import datetime as dt
from django.utils import timezone as djangotime
from django.conf import settings
from scripts.models import Script
@ -106,40 +108,74 @@ def auto_self_agent_update_task() -> None:
@app.task
def agent_outage_email_task(pk) -> None:
sleep(random.randint(1, 15))
def agent_outage_email_task(pk: int, alert_interval: Union[float, None]) -> str:
outage = AgentOutage.objects.get(pk=pk)
outage.send_outage_email()
outage.outage_email_sent = True
outage.save(update_fields=["outage_email_sent"])
if not outage.outage_email_sent:
sleep(random.randint(1, 15))
outage.send_outage_email()
outage.outage_email_sent = True
outage.outage_email_sent_time = djangotime.now()
outage.save(update_fields=["outage_email_sent"])
else:
if alert_interval:
# send an email only if the last email sent is older than alert interval
delta = djangotime.now() - dt.timedelta(days=alert_interval)
if outage.outage_email_sent_time < delta:
sleep(random.randint(1, 10))
outage.send_outage_email()
outage.outage_email_sent_time = djangotime.now()
outage.save(update_fields=["outage_email_sent_time"])
return "ok"
@app.task
def agent_recovery_email_task(pk) -> None:
def agent_recovery_email_task(pk: int) -> str:
sleep(random.randint(1, 15))
outage = AgentOutage.objects.get(pk=pk)
outage.send_recovery_email()
outage.recovery_email_sent = True
outage.save(update_fields=["recovery_email_sent"])
outage.outage_email_sent_time = djangotime.now()
outage.save(update_fields=["recovery_email_sent", "outage_email_sent_time"])
return "ok"
@app.task
def agent_outage_sms_task(pk) -> None:
sleep(random.randint(1, 3))
def agent_outage_sms_task(pk: int, alert_interval: Union[float, None]) -> str:
outage = AgentOutage.objects.get(pk=pk)
outage.send_outage_sms()
outage.outage_sms_sent = True
outage.save(update_fields=["outage_sms_sent"])
if not outage.outage_sms_sent:
sleep(random.randint(1, 15))
outage.send_outage_sms()
outage.outage_sms_sent = True
outage.outage_sms_sent_time = djangotime.now()
outage.save(update_fields=["outage_sms_sent", "outage_sms_sent_time"])
else:
if alert_interval:
# send an sms only if the last sms sent is older than alert interval
delta = djangotime.now() - dt.timedelta(days=alert_interval)
if outage.outage_sms_sent_time < delta:
sleep(random.randint(1, 10))
outage.send_outage_sms()
outage.outage_sms_sent_time = djangotime.now()
outage.save(update_fields=["outage_sms_sent_time"])
return "ok"
@app.task
def agent_recovery_sms_task(pk) -> None:
def agent_recovery_sms_task(pk: int) -> str:
sleep(random.randint(1, 3))
outage = AgentOutage.objects.get(pk=pk)
outage.send_recovery_sms()
outage.recovery_sms_sent = True
outage.save(update_fields=["recovery_sms_sent"])
return "ok"
@app.task
def agent_outages_task() -> None:

View File

@ -225,6 +225,12 @@ class AutomatedTask(BaseAuditModel):
def handle_alert(self) -> None:
from alerts.models import Alert, AlertTemplate
from autotasks.tasks import (
handle_task_email_alert,
handle_task_sms_alert,
handle_resolved_task_sms_alert,
handle_resolved_task_email_alert,
)
self.status = "failing" if self.retcode != 0 else "passing"
@ -247,11 +253,9 @@ class AutomatedTask(BaseAuditModel):
not self.resolved_email_sent
and alert_template.task_email_on_resolved
):
# TODO: send email on resolved
pass
handle_resolved_task_email_alert.delay(self.pk)
if not self.resolved_text_sent and alert_template.task_text_on_resolved:
# TODO: send text on resolved
pass
handle_resolved_task_sms_alert.delay(self.pk)
else:
# create alert in dashboard if enabled
if (
@ -263,10 +267,72 @@ class AutomatedTask(BaseAuditModel):
# send email if enabled
if self.email_alert or alert_template and alert_template.check_always_email:
handle_task_email_alert_task.delay(self.pk)
handle_task_email_alert.delay(
self.pk, alert_template.task_periodic_alert_days
)
# send text if enabled
if self.text_alert or alert_template and alert_template.check_always_text:
handle_task_sms_alert_task.delay(self.pk)
handle_task_sms_alert.delay(
self.pk, alert_template.task_periodic_alert_days
)
self.save()
def send_email(self):
from core.models import CoreSettings
CORE = CoreSettings.objects.first()
if self.agent:
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Failed"
else:
subject = f"{self} Failed"
body = (
subject
+ f" - Return code: {self.retcode}\nStdout:{self.stdout}\nStderr: {self.stderr}"
)
CORE.send_mail(subject, body)
def send_sms(self):
from core.models import CoreSettings
CORE = CoreSettings.objects.first()
if self.agent:
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Failed"
else:
subject = f"{self} Failed"
body = (
subject
+ f" - Return code: {self.retcode}\nStdout:{self.stdout}\nStderr: {self.stderr}"
)
CORE.send_sms(body)
def send_resolved_email(self):
from core.models import CoreSettings
CORE = CoreSettings.objects.first()
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Resolved"
body = (
subject
+ f" - Return code: {self.retcode}\nStdout:{self.stdout}\nStderr: {self.stderr}"
)
CORE.send_mail(subject, body)
def send_resolved_text(self):
from core.models import CoreSettings
CORE = CoreSettings.objects.first()
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Resolved"
body = (
subject
+ f" - Return code: {self.retcode}\nStdout:{self.stdout}\nStderr: {self.stderr}"
)
CORE.send_sms(body)

View File

@ -6,6 +6,9 @@ from django.conf import settings
import pytz
from django.utils import timezone as djangotime
from packaging import version as pyver
from typing import Union
import random
from time import sleep
from .models import AutomatedTask
from logs.models import PendingAction
@ -243,3 +246,89 @@ def remove_orphaned_win_tasks(agentpk):
logger.info(f"Removed orphaned task {task} from {agent.hostname}")
logger.info(f"Orphaned task cleanup finished on {agent.hostname}")
@app.task
def handle_task_email_alert(pk: int, alert_interval: Union[float, None]) -> str:
from .models import AutomatedTask
check = AutomatedTask.objects.get(pk=pk)
if not check.agent.maintenance_mode:
# first time sending email
if not check.email_sent:
sleep(random.randint(1, 10))
check.send_email()
check.email_sent = djangotime.now()
check.save(update_fields=["email_sent"])
else:
if alert_interval:
# send an email only if the last email sent is older than alert interval
delta = djangotime.now() - dt.timedelta(days=alert_interval)
if check.email_sent < delta:
sleep(random.randint(1, 10))
check.send_email()
check.email_sent = djangotime.now()
check.save(update_fields=["email_sent"])
return "ok"
@app.task
def handle_task_sms_alert(pk: int, alert_interval: Union[float, None]) -> str:
from .models import AutomatedTask
task = AutomatedTask.objects.get(pk=pk)
if not task.agent.maintenance_mode:
# first time sending text
if not task.text_sent:
sleep(random.randint(1, 3))
task.send_sms()
task.text_sent = djangotime.now()
task.save(update_fields=["text_sent"])
else:
if alert_interval:
# send a text only if the last text sent is older than alert interval
delta = djangotime.now() - dt.timedelta(days=alert_interval)
if task.text_sent < delta:
sleep(random.randint(1, 3))
task.send_sms()
task.text_sent = djangotime.now()
task.save(update_fields=["text_sent"])
return "ok"
@app.task
def handle_resolved_task_sms_alert(pk: int) -> str:
from .models import AutomatedTask
task = AutomatedTask.objects.get(pk=pk)
if not task.agent.maintenance_mode:
# first time sending text
if not task.resolved_text_sent:
sleep(random.randint(1, 3))
task.send_resolved_sms()
task.resolved_text_sent = djangotime.now()
task.save(update_fields=["resolved_text_sent"])
return "ok"
@app.task
def handle_resolved_task_email_alert(pk: int) -> str:
from .models import AutomatedTask
task = AutomatedTask.objects.get(pk=pk)
if not task.agent.maintenance_mode:
# first time sending email
if not task.resolved_email_sent:
sleep(random.randint(1, 10))
task.send_resolved_email()
task.resolved_email_sent = djangotime.now()
task.save(update_fields=["resolved_email_sent"])
return "ok"

View File

@ -307,7 +307,9 @@ class Check(BaseAuditModel):
and self.email_alert
or alert_template.check_always_email
):
handle_check_email_alert_task.delay(self.pk)
handle_check_email_alert_task.delay(
self.pk, alert_template.check_periodic_alert_days
)
# send text if enabled
if (
@ -315,7 +317,9 @@ class Check(BaseAuditModel):
and self.text_alert
or alert_template.check_always_text
):
handle_check_sms_alert_task.delay(self.pk)
handle_check_sms_alert_task.delay(
self.pk, alert_template.check_periodic_alert_days
)
if alert_template.actions:
# TODO: run scripts on agent
@ -803,10 +807,16 @@ class Check(BaseAuditModel):
CORE.send_sms(body)
def send_resolved_email(self):
pass
CORE = CoreSettings.objects.first()
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Resolved"
body = f"{self} is now back to normal"
CORE.send_mail(subject, body)
def send_resolved_text(self):
pass
CORE = CoreSettings.objects.first()
subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Resolved"
CORE.send_sms(subject)
class CheckHistory(models.Model):

View File

@ -1,13 +1,14 @@
import datetime as dt
import random
from time import sleep
from typing import Union
from tacticalrmm.celery import app
from django.utils import timezone as djangotime
@app.task
def handle_check_email_alert_task(pk):
def handle_check_email_alert_task(pk, alert_interval: Union[float, None]) -> str:
from .models import Check
check = Check.objects.get(pk=pk)
@ -20,19 +21,20 @@ def handle_check_email_alert_task(pk):
check.email_sent = djangotime.now()
check.save(update_fields=["email_sent"])
else:
# send an email only if the last email sent is older than 24 hours
delta = djangotime.now() - dt.timedelta(hours=24)
if check.email_sent < delta:
sleep(random.randint(1, 10))
check.send_email()
check.email_sent = djangotime.now()
check.save(update_fields=["email_sent"])
if alert_interval:
# send an email only if the last email sent is older than alert interval
delta = djangotime.now() - dt.timedelta(days=alert_interval)
if check.email_sent < delta:
sleep(random.randint(1, 10))
check.send_email()
check.email_sent = djangotime.now()
check.save(update_fields=["email_sent"])
return "ok"
@app.task
def handle_check_sms_alert_task(pk):
def handle_check_sms_alert_task(pk, alert_interval: Union[float, None]) -> str:
from .models import Check
check = Check.objects.get(pk=pk)
@ -45,19 +47,20 @@ def handle_check_sms_alert_task(pk):
check.text_sent = djangotime.now()
check.save(update_fields=["text_sent"])
else:
# send a text only if the last text sent is older than 24 hours
delta = djangotime.now() - dt.timedelta(hours=24)
if check.text_sent < delta:
sleep(random.randint(1, 3))
check.send_sms()
check.text_sent = djangotime.now()
check.save(update_fields=["text_sent"])
if alert_interval:
# send a text only if the last text sent is older than 24 hours
delta = djangotime.now() - dt.timedelta(days=alert_interval)
if check.text_sent < delta:
sleep(random.randint(1, 3))
check.send_sms()
check.text_sent = djangotime.now()
check.save(update_fields=["text_sent"])
return "ok"
@app.task
def handle_resolved_check_sms_alert_task(pk):
def handle_resolved_check_sms_alert_task(pk: int) -> str:
from .models import Check
check = Check.objects.get(pk=pk)
@ -74,7 +77,7 @@ def handle_resolved_check_sms_alert_task(pk):
@app.task
def handle_resolved_check_email_alert_task(pk):
def handle_resolved_check_email_alert_task(pk: int) -> str:
from .models import Check
check = Check.objects.get(pk=pk)