Commit 2d1ef701 authored by Tobias Gawron-Deutsch's avatar Tobias Gawron-Deutsch
Browse files

Merge branch 'develop' into 'master'

Develop

See merge request !12
parents 1c3fba71 1aba068b
version="0.4.1"
\ No newline at end of file
version="0.4.2"
\ No newline at end of file
......@@ -51,6 +51,7 @@ class CommandFactory:
add_command("onboardingstats", self.do_onboardingstats)
add_command("notificationstats", self.do_notificationstats)
add_command("archive", self.do_archive)
add_command("send_notification", self.do_send_notification)
add_command("ad", self.do_ad) # agentdetails full
add_command("am", self.do_am) # agentmessages
add_command("al", self.do_al) # agentmessages
......@@ -72,12 +73,23 @@ class CommandFactory:
print("Sending {} request to all monitored services\n".format(name))
elif len(args) == 2 and str(args[0]).lower() == "gid":
gid = self._check_gid(args[1])
print("Send {} regest to monitoring service with gid == {}\n".format(name, gid))
print("Send {} request to monitoring service with gid == {}\n".format(name, gid))
else:
raise ValueError("Wrong arguments: {}. expected '{} [all|gid gid]'.\n".format(args, name))
return gid
def do_send_notification(self, arg):
"""send_notification [digest|minimal] - send the digest/minimal notification email"""
output = ""
if arg == "digest":
output = self._notification.send_digest()
elif arg == "minimal":
output = self._notification.send_minimal()
else:
output = "parameter must be 'digest' or 'minimal'. got: '{}'".format(arg)
pelops.ui.tools.more(output)
def do_exit(self, arg):
"""exit - stops the monitoring service: EXIT"""
return True
......
import datetime
import time
import pprint
from hippodamia.enums import ViewDetails
from hippodamia.enums import StatType
from hippodamia.email.anotifier import ANotifier
class AInfo(ANotifier):
_repeat = None
_at = None
_start_day = None
_send_at_startup = None
_agentshadows = None
_heartbeat = None
_hierarchical_view = None
_onboarding = None
_archive = None
_mqtt_client = None
def __init__(self, config, send_mail, logger, mqtt_client, agentshadows, heartbeat, hierarchical_view,
onboarding, archive):
ANotifier.__init__(self, config, send_mail, logger)
self._repeat = self._config["repeat"]
try:
temp = self._config["at"].split(":")
self._at = datetime.time(hour=int(temp[0]), minute=int(temp[1]))
except KeyError:
self._at = datetime.datetime.now().time()
self._logger.debug("__init__ - setting 'at' to {}".format(self._at))
try:
weekday = self._config["start-day"]
weekdays = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]
self._start_day = weekdays.index(weekday)
except KeyError:
self._start_day = datetime.datetime.now().weekday()
self._logger.debug("__init__ - setting 'start-day' to {}".format(self._start_day))
try:
self._send_at_startup = self._config["send-at-startup"]
except:
self._send_at_startup = False
if self._send_at_startup and "start-day" not in self._config and "at" not in self._config:
self._send_at_startup = False
self._logger.debug("__init__ - forcing 'send-at-startup' to {} - 'start.day' and 'at' are not set -> a "
"digest email is sent immediately after startup anyways.".format(self._send_at_startup))
else:
self._logger.debug("__init__ - setting 'send-at-startup' to {}".format(self._send_at_startup))
self._mqtt_client = mqtt_client
self._agentshadows = agentshadows
self._heartbeat = heartbeat
self._hierarchical_view = hierarchical_view
self._onboarding = onboarding
self._archive = archive
def _calc_first_timestamp(self):
diff = self._start_day - datetime.datetime.now().weekday()
if diff < 0:
diff += 7
days = datetime.timedelta(days=diff)
target_day = datetime.datetime.now().date() + days
target_time = self._at
target = datetime.datetime.combine(target_day, target_time)
first = time.mktime(target.timetuple())
self._logger.info("_calc_first_timestamp: at {} seconds (now {} s)".format(first, time.time()))
return first
def _calc_interval(self):
if self._repeat == "HOURLY":
interval = 3600
elif self._repeat == "DAILY":
interval = 3600 * 24
elif self._repeat == "WEEKLY":
interval = 3600 * 24 * 7
else:
err_message = "_calc_interval - unknown repeat '{}'".format(self._repeat)
self._logger.error(err_message)
raise RuntimeError(err_message)
self._logger.info("_calc_interval: {} seconds".format(interval))
return interval
def send_message(self):
body = self._render_body()
self._send_message(body)
def _render_body(self):
raise NotImplementedError()
def _start(self):
self._sched.repeatabs(self._calc_first_timestamp(), self._calc_interval(), 1, self.send_message)
if self._send_at_startup:
self._logger.info("sending info at startup")
self.send_message()
def _stop(self):
pass
def _render_subject(self):
health = self._hierarchical_view.to_dict()
subject = "{} <{}>".format(self._subject, health["health"])
return subject
def _get_stats(self):
return {}
def _message_footer(self):
message = "<br/>"
message += "<div style='text-align:right'>generated at {}</div>".format(datetime.datetime.now())
try:
ne = self._sched.scheduler.queue[0]
ne_time = ne.time
dt = datetime.datetime.fromtimestamp(ne_time)
next_event = dt.strftime("%Y-%m-%d %H:%M:%S.%f")
except IndexError:
next_event = "n/a"
message += "<div style='text-align:right'>Next scheduled for {}</div>".format(next_event)
return message
\ No newline at end of file
......@@ -42,9 +42,12 @@ class ANotifier:
message = "<html><body><div>{}</div><div>{}</div><div>{}</div><div>{}</div></body></html>"\
.format(self._pre_text, body, self._post_text, self._message_footer())
self._logger.debug("_send_message - message: {}".format(message.encode("utf-8")))
self._send_email(self._subject, message, self._to)
self._send_email(self._render_subject(), message, self._to)
self._counter += 1
def _render_subject(self):
raise NotImplementedError()
def _message_footer(self):
raise NotImplementedError()
......
import datetime
import time
import pprint
from hippodamia.enums import ViewDetails
from hippodamia.enums import StatType
from hippodamia.email.anotifier import ANotifier
from hippodamia.email.ainfo import AInfo
class Digest(ANotifier):
_repeat = None
_at = None
_start_day = None
_send_at_startup = None
_agentshadows = None
_heartbeat = None
_hierarchical_view = None
_onboarding = None
_archive = None
_mqtt_client = None
def __init__(self, config, send_mail, logger, mqtt_client, agentshadows, heartbeat, hierarchical_view,
onboarding, archive):
ANotifier.__init__(self, config, send_mail, logger)
self._repeat = self._config["repeat"]
try:
temp = self._config["at"].split(":")
self._at = datetime.time(hour=int(temp[0]), minute=int(temp[1]))
except KeyError:
self._at = datetime.datetime.now().time()
self._logger.debug("__init__ - setting 'at' to {}".format(self._at))
try:
weekday = self._config["start-day"]
weekdays = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]
self._start_day = weekdays.index(weekday)
except KeyError:
self._start_day = datetime.datetime.now().weekday()
self._logger.debug("__init__ - setting 'start-day' to {}".format(self._start_day))
try:
self._send_at_startup = self._config["send-at-startup"]
except:
self._send_at_startup = False
if self._send_at_startup and "start-day" not in self._config and "at" not in self._config:
self._send_at_startup = False
self._logger.debug("__init__ - forcing 'send-at-startup' to {} - 'start.day' and 'at' are not set -> a "
"digest email is sent immediately after startup anyways.".format(self._send_at_startup))
else:
self._logger.debug("__init__ - setting 'send-at-startup' to {}".format(self._send_at_startup))
self._mqtt_client = mqtt_client
self._agentshadows = agentshadows
self._heartbeat = heartbeat
self._hierarchical_view = hierarchical_view
self._onboarding = onboarding
self._archive = archive
def _calc_first_timestamp(self):
diff = self._start_day - datetime.datetime.now().weekday()
if diff < 0:
diff += 7
days = datetime.timedelta(days=diff)
target_day = datetime.datetime.now().date() + days
target_time = self._at
target = datetime.datetime.combine(target_day, target_time)
first = time.mktime(target.timetuple())
self._logger.info("_calc_first_timestamp: at {} seconds (now {} s)".format(first, time.time()))
return first
def _calc_interval(self):
if self._repeat == "HOURLY":
interval = 3600
elif self._repeat == "DAILY":
interval = 3600 * 24
elif self._repeat == "WEEKLY":
interval = 3600 * 24 * 7
else:
err_message = "_calc_interval - unknown repeat '{}'".format(self._repeat)
self._logger.error(err_message)
raise RuntimeError(err_message)
self._logger.info("_calc_interval: {} seconds".format(interval))
return interval
def _digest_send_message(self):
body = self._render_body()
self._send_message(body)
def _start(self):
self._sched.repeatabs(self._calc_first_timestamp(), self._calc_interval(), 1, self._digest_send_message)
if self._send_at_startup:
self._logger.info("sending digest at startup")
self._digest_send_message()
def _stop(self):
pass
class Digest(AInfo):
def _render_body(self):
message = "<h1>Hierarchical View</h1>"
message += "<pre>{}</pre>".format(self._hierarchical_view.pformat(details=ViewDetails.FULL))
message = "<h1>Hierarchical View</h1>\n"
message += "<pre>{}</pre>\n".format(self._hierarchical_view.pformat(details=ViewDetails.FULL))
message += "<h1>Micro-Service Overview</h1>"
message += "<h1>Micro-Service Overview</h1>\n"
keys = list(self._agentshadows.keys())
keys.sort()
for k in keys:
microservice = self._agentshadows[k]
message += "<h2>[{}] {}</h2>".format(microservice.properties.gid, microservice.properties.name)
message += "<pre>{}</pre>".format(microservice.get_string_stats(StatType.GENERAL))
message += "<h2>[{}] {}</h2>\n".format(microservice.properties.gid, microservice.properties.name)
message += "<pre>{}</pre>\n".format(microservice.get_string_stats(StatType.GENERAL))
message += "<h1>Micro-Service Archive</h1>"
message += "<h1>Micro-Service Archive</h1>\n"
if len(self._archive) == 0:
message += "<div>Archive is empty.</div>"
message += "<div>Archive is empty.</div>\n"
else:
message = "<ul>"
message = "<ul>\n"
for microservice in self._archive:
message += " <li>[{}] {}</li>".format(microservice.properties.gid, microservice.properties.name)
message += "</ul>"
message += " <li>[{}] {}</li>\n".format(microservice.properties.gid, microservice.properties.name)
message += "</ul>\n"
message += "<h1>Heartbeat</h1>"
message += "<pre>{}</pre>".format(self._heartbeat.get_string_stats())
message += "<h1>Heartbeat</h1>\n"
message += "<pre>{}</pre>\n".format(self._heartbeat.get_string_stats())
message += "<h1>Onboarding</h1>"
message += "<pre>{}</pre>".format(self._onboarding.get_string_stats())
message += "<h1>Onboarding</h1>\n"
message += "<pre>{}</pre>\n".format(self._onboarding.get_string_stats())
message += "<h1>MQTT</h1>"
message += "<div>mqtt client connection active: <div>{}</div>".format(self._mqtt_client.is_connected.is_set())
message += "<h2>active subscriptions</h2>"
message += "<ul>"
message += "<h1>MQTT</h1>\n"
message += "<div>mqtt client connection active: <div>{}</div>\n".format(self._mqtt_client.is_connected.is_set())
message += "<h2>active subscriptions</h2>\n"
message += "<ul>\n"
for sub, func in self._mqtt_client.subscribed_topics().items():
message += " - {}: [{}]\n".format(sub, func)
message += "</ul>"
message += "<h2>send/receive statistics</h2>"
message += "<pre>{}</pre>".format(pprint.pformat(self._mqtt_client.stats.get_stats(), indent=2))
return message
message += "</ul>\n"
message += "<h2>send/receive statistics</h2>\n"
message += "<pre>{}</pre>\n".format(pprint.pformat(self._mqtt_client.stats.get_stats(), indent=2))
def _message_footer(self):
message = "<br/>"
message += "<div style='text-align:right'>Digest generated at {}</div>".format(datetime.datetime.now())
try:
ne = self._sched.scheduler.queue[0]
ne_time = ne.time
dt = datetime.datetime.fromtimestamp(ne_time)
next_event = dt.strftime("%Y-%m-%d %H:%M:%S.%f")
except IndexError:
next_event = "n/a"
message += "<div style='text-align:right'>Next digest scheduled for {}</div>".format(next_event)
return message
def _get_stats(self):
return {}
import pprint
from hippodamia.email.sendemail import SendEmail
from hippodamia.email.digest import Digest
from hippodamia.email.minimal import Minimal
from hippodamia.email.spontaneous import Spontaneous
from pelops.logging.mylogger import get_child
......@@ -12,6 +13,7 @@ class EmailNotification:
_spontaneous = None
_digest = None
_minimal = None
def __init__(self, config, logger, mqtt_client, agentshadows, heartbeat, hierarchical_view, onboarding, archive,
agentfactory):
......@@ -27,22 +29,46 @@ class EmailNotification:
except KeyError:
self._logger.info("Digest is deactivated")
try:
self._minimal = Minimal(self._config["minimal"], self._send_email.send_message, self._logger, mqtt_client,
agentshadows, heartbeat, hierarchical_view, onboarding, archive)
except KeyError:
self._logger.info("Minimal is deactivated")
try:
self._spontaneous = Spontaneous(self._config["spontaneous"], self._send_email.send_message, self._logger,
agentshadows, agentfactory)
except KeyError:
self._logger.info("Spontaneous is deactivated")
if self._digest is None and self._spontaneous is None:
self._logger.error("Digest and Spontaneous are deactived - at least one must be set.")
raise RuntimeError("Digest and Spontaneous are deactived - at least one must be set.")
if self._digest is None and self._spontaneous is None and self._minimal is None:
self._logger.error("Digest, Minimal, and Spontaneous are deactived - at least one must be set.")
raise RuntimeError("Digest, Minimal, and Spontaneous are deactived - at least one must be set.")
self._logger.info("__init__ - completed")
def send_digest(self):
self._logger.info("send_digest manually triggered.")
if self._digest is not None:
self._digest.send_message()
return "digest email notification sent."
else:
return "digest email notification not activated."
def send_minimal(self):
self._logger.info("send_minimal manually triggered.")
if self._minimal is not None:
self._minimal.send_message()
return "minimal email notification sent."
else:
return "minimal email notification not activated."
def start(self):
self._logger.info("start - begin")
if self._digest is not None:
self._digest.start()
if self._minimal is not None:
self._minimal.start()
if self._spontaneous is not None:
self._spontaneous.start()
self._logger.info("start - completed")
......@@ -51,6 +77,8 @@ class EmailNotification:
self._logger.info("stop - begin")
if self._digest is not None:
self._digest.stop()
if self._minimal is not None:
self._minimal.stop()
if self._spontaneous is not None:
self._spontaneous.stop()
self._logger.info("stop - completed")
......@@ -61,6 +89,8 @@ class EmailNotification:
}
if self._digest is not None:
stats["digest"] = self._digest.get_stats()
if self._minimal is not None:
stats["minimal"] = self._minimal.get_stats()
if self._spontaneous is not None:
stats["spontaneous"] = self._spontaneous.get_stats()
return stats
......
from hippodamia.email.ainfo import AInfo
class Minimal(AInfo):
def _render_body(self):
message = "<h1>List of Micro-Services</h1>\n"
message += "<ul>\n"
for gid, shadow in self._agentshadows.items():
message += "<li>{} {} {} {}</li>\n".format(shadow.properties.name, shadow.properties.health.name,
shadow.get_state_id().name, gid)
message += "</ul>\n"
return message
......@@ -114,6 +114,11 @@ class Spontaneous(ANotifier):
body = self._render_body(event_mapping)
self._send_message(body)
def _render_subject(self):
health = self._hierarchical_view.to_dict()
subject = "{} <{}>".format(self._subject, health["health"])
return subject
def _render_body(self, event_mapping):
text = "<h1>Affected Agent Shadows</h1>"
text += "<ul>"
......
......@@ -3,12 +3,13 @@ from hippodamia.agentshadow.states.state_ids import state_ids
def get_schema():
schema = {
"description": "Email notification service for hippodamia. At least 'digest' or 'spontaneous' must be "
"present to be operational.",
"description": "Email notification service for hippodamia. At least one of 'digest', 'minimal' or "
"'spontaneous' must be present to be operational.",
"type": "object",
"properties": {
"config": _config_schema(),
"digest": _digest_schema(),
"digest": _ainfo_schema("digest"),
"minimal": _ainfo_schema("minimal"),
"spontaneous": _spontaneous_schema(),
},
"required": ["config"],
......@@ -55,10 +56,10 @@ def _config_schema():
return schema
def _digest_schema():
def _ainfo_schema(info):
schema = {
"description": "Regularly send a digest notification with the most important data. At least 'at' or 'start-day'"
" must be given to be operational.",
"description": "Regularly send a {} notification with the most important data. At least 'at' or 'start-day'"
" must be given to be operational.".format(info),
"type": "object",
"properties": {
"subject": {
......@@ -86,25 +87,25 @@ def _digest_schema():
]
},
"repeat": {
"description": "'...' repeat send digest",
"description": "'...' repeat send {}".format(info),
"type": "string",
"enum": ["HOURLY", "DAILY", "WEEKLY"]
},
"at": {
"description": "send digest notification at this time. in case of repeat=='HOURLY', delay until this "
"time. if omited, use time at startup.",
"description": "send {} notification at this time. in case of repeat=='HOURLY', delay until this "
"time. if omited, use time at startup.".format(info),
"type": "string",
"pattern": "^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$",
"example": "12:34"
},
"start-day": {
"description": "similar to 'at'. send digest notification on this day. in case of repeat=='HOURLY' or"
" repeat=='DAILY', delay until this day. if omitted, use day at startup.",
"description": "similar to 'at'. send {} notification on this day. in case of repeat=='HOURLY' or"
" repeat=='DAILY', delay until this day. if omitted, use day at startup.".format(info),
"type": "string",
"enum": ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]
},
"send-at-startup": {
"description": "send a digest immediately after startup - optional (default: False)",
"description": "send a {} immediately after startup - optional (default: False)".format(info),
"type": "boolean"
}
},
......
......@@ -127,6 +127,15 @@ monitoringservice:
#at: "12:34" # HH:MM - optional
#start-day: MONDAY # [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY] - optional
send-at-startup: true # send a digest immediately after startup - optional
minimal: # regular state overview - optional
to: name123@receiver123.com
subject: Daily Info
pre-text: This is the pre-text
post-text: This is the post-text
repeat: HOURLY # HOURLY, DAILY, WEEKLY
#at: "12:34" # HH:MM - optional
#start-day: MONDAY # [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY] - optional
send-at-startup: true # send a digest immediately after startup - optional
spontaneous: # on event - optional
to: name123@receiver123.com
subject: Emergency Notification
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment