Sample Custom Alert Script
You can use this sample script to respond to an alert by sending emails based on the content of the alert.
The Sample Custom Alert Script provided below provides an example of how custom alert scripts can be implemented. This example uses a Python script, which includes multiple routing rules called Alert Templates. Once the script is called, it parses the alert JSON file using a path provided by the Alert Publisher. Based on the Alert Templates, it sends out alerts to different email addresses based on the content of the alert.
The script requires an SMTP server (defined by its host and port) and an email address to send out the emails. You need to set these parameters in the script.
There are multiple Alert Templates in the sample script as examples. Pick the ones you need and delete the rest, or create new templates.
Python should be always available on your hosts because hosts managed by Cloudera Manager require Python for the Cloudera Manager Agent to run. Because the Alert Publisher is not able to execute python scripts, a small Bash script is needed to execute the main Python script.
Bash Script
This Bash script is required to execute the Python script.
# © 2022 by Cloudera, Inc. All rights reserved.
# Scripts and sample code are licensed under the Apache License,
# Version 2.0
#!/bin/bash
python /bin/script_test/main.py $1
Python script
Setting up the Python script
To use the Python script, you will need to provide the values described below.
- SMTP
hostname:
host = "mailserver.mycompany.com"
- SMTP port:
port = 25
- Sender email
address:
sender_address = "noreply@mycompany.com"
- Values for the
AlertTemplate(email, attribute_key, attribute_value, remove_after)
method:- Email addresses:
AlertTemplate("admin@mycompany.com", "SEVERITY", "CRITICAL", False)
Specify two or more recipients using this format:AlertTemplate("admin@mycompany.com, admin2@mycompany.com", "SEVERITY", "CRITICAL", False)
- An
attribute_key
andattribute_value
containing the rules that your alert should match for the email to be sent:
To use Regular Expression matching for your alerts, useAlertTemplate("admin@mycompany.com", "SEVERITY", "CRITICAL", False),
“REGEX”
as theattribute_key
:AlertTemplate("admin@mycompany.com", "REGEX", '.*content": "This is a test alert that was generated.*', False)
A None
attribute_key
andattribute_value
pair indicates a default template, which should be applied to all alerts. You can use this when there's no matchingAlertTemplate
, but you still do not want to lose the Alert. remove_after
:Set this argument toTrue
if you want the alert to not be sent, even if further Alert Templates match the alert:AlertTemplate("admin@mycompany.com", "SEVERITY", "CRITICAL", True),
- Email addresses:
Sample Python script
You can copy this script and modify it for your deployment.
# -*- coding: utf-8 -*-
# © 2022 by Cloudera, Inc. All rights reserved.
# Scripts and sample code are licensed under the Apache License,
# Version 2.0
#!/usr/bin/env python2
from __future__ import print_function
import sys
import smtplib
import json
import re
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
class AlertReader(object):
def __init__(self, file_path):
self.alerts = []
self.file_path = file_path
def read_alert(self):
alert_file = open(self.file_path, "r")
data = json.load(alert_file)
for alert in data:
self.alerts.append(alert["body"]["alert"])
print("Done reading " + self.file_path)
def print_alerts(self):
print(self.alerts)
def alert_as_string(alert):
return json.dumps(alert)
class AlertTemplate(object):
def __init__(self, email, attribute_key, attribute_value, remove_after):
self.email = email
self.attribute_key = attribute_key
self.attribute_value = attribute_value
self.remove_after = remove_after
"""
A None attribute_key and attribute_value pair
indicates a default template, which should be
applied to all alerts. You can use this when
there's no matching AlertTemplate, but you still
don't want to lose the Alert.
Put these AlertTemplates to the end of your
AlertTemplate list to catch all remaining alerts.
But if only one of the attribute_key or attribute_value is None,
that is an invalid state.
"""
if (self.attribute_key is None) != (self.attribute_value is None):
raise ValueError("AlertTemplate is in invalid state, "
"one of attribute_key or attribute_value is None.")
def does_apply(self, alert):
if self.attribute_key == "REGEX":
alert_string = alert_as_string(alert)
search_result = re.search(self.attribute_value, alert_string)
return search_result is not None
if self.attribute_key == "CONTENT":
if self.attribute_value in alert["content"]:
return True
return False
if self.attribute_key is None and self.attribute_value is None:
return True
try:
if self.attribute_value in alert["attributes"][self.attribute_key]:
return True
except KeyError:
print("There's no match for attribute_key: " + self.attribute_key)
return False
def to_string(self):
return "AlertTemplate[" + \
str(self.attribute_key) + ": " + \
str(self.attribute_value) + ", " + \
str(self.email) + "]"
class EmailSender(object):
def __init__(self, smtp_host, smtp_port, sender):
self.server = smtplib.SMTP(smtp_host, smtp_port)
self.sender = sender
def send_email_to_recipients(self, alerts, alert_templates):
print("Processing alerts to send emails.")
for alert in alerts:
for alert_template in alert_templates:
if alert_template.does_apply(alert):
"""
With a template with no email address, you usually want to exclude
a type of alert from the processed alert list. Use None to email address
with remove_after = True if you want an alert to be removed from the
list of alerts sent.
These templates should be in the beginning of the template list.
"""
if alert_template.email is not None:
self.send_email(alert_template, alert)
print(create_message_text(alert_template, alert))
print("Sending alert based on " + alert_template.to_string())
if alert_template.remove_after:
break
print("All alerts are processed.")
def send_email(self, alert_template, alert):
message = MIMEMultipart("alternative")
message["Subject"] = "Cloudera Alert"
message["From"] = self.sender
message["To"] = alert_template.email
message.attach(MIMEText(create_message_text(alert_template, alert), "plain"))
self.server.sendmail(self.sender, alert_template.email.split(","), message.as_string())
def create_message_text(alert_template, alert):
out = alert_template.to_string()
out += " firing for: \n"
out += alert_as_string(alert)
return out
if __name__ == '__main__':
"""
SMTP server parameters:
- email where the emails sent from
- file name, where the alerts are coming from. This is provided by the script interface.
Alert templates:
- email address to send email to when the alert contains the specified key with the specified value.
- Set remove_after to True if you don't want the alert sent to other recipients,
even if the template is matching. Because of this, it is required that the recipients
list is in a specific order.
"""
host = "mailserver.mycompany.com" # SMTP server host name
port = 25 # SMTP Server port
sender_address = "noreply@"MyCompanyMailServer" # Email address to send emails from
# The file's name which contains the alerts is provided by the script framework
if len(sys.argv) < 2:
print("Please provide a file path for alerts. Usually this comes from the script framework.")
exit(2)
file_name = sys.argv[1]
alert_reader = AlertReader(file_name)
recipients = []
try:
recipients = [AlertTemplate(None, None, None, True), # This template matches every alert
# This template matches to all alerts which matches to the given regex pattern
AlertTemplate("admin@mycompany.com", "REGEX", '.*content": "This is a test alert that was generated.*', False),
# This template matches only for issues with critical severity
AlertTemplate("admin@mycompany.com", "SEVERITY", "CRITICAL", False),
# This template matches only for Kafka related issues
AlertTemplate("kafka_admin@mycompany.com", "SERVICE_TYPE", "KAFKA", True),
# This template matches only for Kafka Broker related issues
AlertTemplate("user1@gmail.com", "ROLE_TYPE", "KAFKA_BROKER", True),
# This template matches only for a specific host's alerts
AlertTemplate("admin2@mycompany.com", "HOSTS", "myhost", True),
# This template matches for a specific cluster's alerts
AlertTemplate("admin3@mycompany.com", "CLUSTER", "Cluster 1", True),
# This template matches for a specific health test message
AlertTemplate("admin@mycompany.com", "CONTENT", "This is a test alert that was generated by user request from "
"Cloudera Manager", True)]
except ValueError as error:
print(error)
exit(2)
alert_reader.read_alert()
alert_reader.print_alerts()
email_sender = EmailSender(host, port, sender_address)
email_sender.send_email_to_recipients(alert_reader.alerts, recipients)