A simple cluster health alerting module.
"""
-from mgr_module import MgrModule, HandleCommandResult
+from mgr_module import CLIReadCommand, HandleCommandResult, MgrModule, Option
from threading import Event
-import errno
+from typing import Any, Optional, Dict, List, TYPE_CHECKING, Union
import json
import smtplib
-class Alerts(MgrModule):
- COMMANDS = [
- {
- "cmd": "alerts send",
- "desc": "(re)send alerts immediately",
- "perm": "r"
- },
- ]
+class Alerts(MgrModule):
MODULE_OPTIONS = [
- {
- 'name': 'interval',
- 'type': 'secs',
- 'default': 60,
- 'desc': 'How frequently to reexamine health status',
- 'runtime': True,
- },
+ Option(
+ name='interval',
+ type='secs',
+ default=60,
+ desc='How frequently to reexamine health status',
+ runtime=True),
# smtp
- {
- 'name': 'smtp_host',
- 'default': '',
- 'desc': 'SMTP server',
- 'runtime': True,
- },
- {
- 'name': 'smtp_destination',
- 'default': '',
- 'desc': 'Email address to send alerts to',
- 'runtime': True,
- },
- {
- 'name': 'smtp_port',
- 'type': 'int',
- 'default': 465,
- 'desc': 'SMTP port',
- 'runtime': True,
- },
- {
- 'name': 'smtp_ssl',
- 'type': 'bool',
- 'default': True,
- 'desc': 'Use SSL to connect to SMTP server',
- 'runtime': True,
- },
- {
- 'name': 'smtp_user',
- 'default': '',
- 'desc': 'User to authenticate as',
- 'runtime': True,
- },
- {
- 'name': 'smtp_password',
- 'default': '',
- 'desc': 'Password to authenticate with',
- 'runtime': True,
- },
- {
- 'name': 'smtp_sender',
- 'default': '',
- 'desc': 'SMTP envelope sender',
- 'runtime': True,
- },
- {
- 'name': 'smtp_from_name',
- 'default': 'Ceph',
- 'desc': 'Email From: name',
- 'runtime': True,
- },
+ Option(
+ name='smtp_host',
+ default='',
+ desc='SMTP server',
+ runtime=True),
+ Option(
+ name='smtp_destination',
+ default='',
+ desc='Email address to send alerts to',
+ runtime=True),
+ Option(
+ name='smtp_port',
+ type='int',
+ default=465,
+ desc='SMTP port',
+ runtime=True),
+ Option(
+ name='smtp_ssl',
+ type='bool',
+ default=True,
+ desc='Use SSL to connect to SMTP server',
+ runtime=True),
+ Option(
+ name='smtp_user',
+ default='',
+ desc='User to authenticate as',
+ runtime=True),
+ Option(
+ name='smtp_password',
+ default='',
+ desc='Password to authenticate with',
+ runtime=True),
+ Option(
+ name='smtp_sender',
+ default='',
+ desc='SMTP envelope sender',
+ runtime=True),
+ Option(
+ name='smtp_from_name',
+ default='Ceph',
+ desc='Email From: name',
+ runtime=True)
]
# These are "native" Ceph options that this module cares about.
- NATIVE_OPTIONS = [
+ NATIVE_OPTIONS: List[str] = [
]
- def __init__(self, *args, **kwargs):
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
super(Alerts, self).__init__(*args, **kwargs)
# set up some members to enable the serve() method and shutdown()
self.log.info("Init")
-
- def config_notify(self):
+ if TYPE_CHECKING:
+ self.interval = 60
+ self.smtp_host = ''
+ self.smtp_destination = ''
+ self.smtp_port = 0
+ self.smtp_ssl = True
+ self.smtp_user = ''
+ self.smtp_password = ''
+ self.smtp_sender = ''
+ self.smtp_from_name = ''
+
+ def config_notify(self) -> None:
"""
This method is called whenever one of our config options is changed.
"""
self.get_ceph_option(opt))
self.log.debug(' native option %s = %s', opt, getattr(self, opt))
- def handle_command(self, inbuf, cmd):
- ret = 0
- out = ''
- err = ''
- if cmd['prefix'] == 'alerts send':
- status = json.loads(self.get('health')['json'])
- self._send_alert(status, {})
- return HandleCommandResult(
- retval=ret, # exit code
- stdout=out, # stdout
- stderr=err)
+ @CLIReadCommand('alerts send')
+ def send(self) -> HandleCommandResult:
+ """
+ (re)send alerts immediately
+ """
+ status = json.loads(self.get('health')['json'])
+ self._send_alert(status, {})
+ return HandleCommandResult()
- def _diff(self, last, new):
- d = {}
+ def _diff(self, last: Dict[str, Any], new: Dict[str, Any]) -> Dict[str, Any]:
+ d: Dict[str, Any] = {}
for code, alert in new.get('checks', {}).items():
self.log.debug('new code %s alert %s' % (code, alert))
if code not in last.get('checks', {}):
if 'new' not in d:
d['new'] = {}
d['new'][code] = alert
- elif alert['summary'].get('count', 0) > \
- last['checks'][code]['summary'].get('count', 0):
+ elif (alert['summary'].get('count', 0)
+ > last['checks'][code]['summary'].get('count', 0)):
if 'updated' not in d:
d['updated'] = {}
d['updated'][code] = alert
d['cleared'][code] = alert
return d
- def _send_alert(self, status, diff):
+ def _send_alert(self, status: Dict[str, Any], diff: Dict[str, Any]) -> None:
checks = {}
if self.smtp_host:
r = self._send_alert_smtp(status, diff)
self.log.warning('Alert is not sent because smtp_host is not configured')
self.set_health_checks(checks)
- def serve(self):
+ def serve(self) -> None:
"""
This method is called by the mgr when the module starts and can be
used for any background activity.
"""
self.log.info("Starting")
- last_status = {}
+ last_status: Dict[str, Any] = {}
while self.run:
# Do some useful background work here.
new_status = json.loads(self.get('health')['json'])
self._send_alert(new_status, diff)
last_status = new_status
- self.log.debug('Sleeping for %d seconds', self.interval)
- ret = self.event.wait(self.interval)
+ self.log.debug('Sleeping for %s seconds', self.interval)
+ self.event.wait(self.interval or 60)
self.event.clear()
- def shutdown(self):
+ def shutdown(self) -> None:
"""
This method is called by the mgr when the module needs to shut
down (i.e., when the serve() function needs to exit).
self.event.set()
# SMTP
- def _smtp_format_alert(self, code, alert):
+ def _smtp_format_alert(self, code: str, alert: Dict[str, Any]) -> str:
r = '[{sev}] {code}: {summary}\n'.format(
code=code,
sev=alert['severity'].split('_')[1],
message=detail['message'])
return r
- def _send_alert_smtp(self, status, diff):
+ def _send_alert_smtp(self,
+ status: Dict[str, Any],
+ diff: Dict[str, Any]) -> Optional[Dict[str, Any]]:
# message
self.log.debug('_send_alert_smtp')
message = ('From: {from_name} <{sender}>\n'
message += ('\n\n=== Full health status ===\n')
for code, alert in status['checks'].items():
message += self._smtp_format_alert(code, alert)
-
+
self.log.debug('message: %s' % message)
# send
try:
if self.smtp_ssl:
- server = smtplib.SMTP_SSL(self.smtp_host, self.smtp_port)
+ server: Union[smtplib.SMTP_SSL, smtplib.SMTP] = \
+ smtplib.SMTP_SSL(self.smtp_host, self.smtp_port)
else:
server = smtplib.SMTP(self.smtp_host, self.smtp_port)
if self.smtp_password:
'severity': 'warning',
'summary': 'unable to send alert email',
'count': 1,
- 'detail': [ str(e) ]
+ 'detail': [str(e)]
}
}
self.log.debug('Sent email to %s' % self.smtp_destination)