Skip to content

Commit 9825f0f

Browse files
authored
Fix heartbeat alert severity logic (#224)
1 parent 395b0a0 commit 9825f0f

5 files changed

Lines changed: 62 additions & 41 deletions

File tree

alertaclient/commands/cmd_heartbeat.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ def cli(obj, origin, environment, severity, service, group, tags, timeout, custo
3030
if any(t.startswith('environment') or t.startswith('group') for t in tags):
3131
click.secho('WARNING: Do not use tags for "environment" or "group". See help.', err=True)
3232

33+
if severity in ['normal', 'ok', 'cleared']:
34+
raise click.UsageError('Must be a non-normal severity.')
35+
36+
if severity not in obj['alarm_model']['severity'].keys():
37+
raise click.UsageError('Must be a valid severity.')
38+
3339
attributes = dict()
3440
if environment:
3541
attributes['environment'] = environment

alertaclient/commands/cmd_heartbeats.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
@click.command('heartbeats', short_help='List heartbeats')
1111
@click.option('--alert', is_flag=True, help='Alert on stale or slow heartbeats')
12-
@click.option('--severity', '-s', metavar='SEVERITY', default='major', help='Severity for stale heartbeat alerts')
12+
@click.option('--severity', '-s', metavar='SEVERITY', default='major', help='Severity for heartbeat alerts')
1313
@click.option('--timeout', metavar='SECONDS', type=int, help='Seconds before stale heartbeat alerts will be expired')
1414
@click.option('--purge', is_flag=True, help='Delete all stale heartbeats')
1515
@click.pass_obj
@@ -22,6 +22,12 @@ def cli(obj, alert, severity, timeout, purge):
2222
except KeyError:
2323
default_normal_severity = 'normal'
2424

25+
if severity in ['normal', 'ok', 'cleared']:
26+
raise click.UsageError('Must be a non-normal severity.')
27+
28+
if severity not in obj['alarm_model']['severity'].keys():
29+
raise click.UsageError('Must be a valid severity.')
30+
2531
if obj['output'] == 'json':
2632
r = client.http.get('/heartbeats')
2733
heartbeats = [Heartbeat.parse(hb) for hb in r['heartbeats']]
@@ -46,20 +52,20 @@ def cli(obj, alert, severity, timeout, purge):
4652
with click.progressbar(heartbeats, label='Alerting {} heartbeats'.format(len(heartbeats))) as bar:
4753
for b in bar:
4854

49-
environment = b.attributes.pop('environment', 'Production')
50-
service = b.attributes.pop('service', ['Alerta'])
51-
group = b.attributes.pop('group', 'System')
55+
want_environment = b.attributes.pop('environment', 'Production')
56+
want_severity = b.attributes.pop('severity', severity)
57+
want_service = b.attributes.pop('service', ['Alerta'])
58+
want_group = b.attributes.pop('group', 'System')
5259

5360
if b.status == 'expired': # aka. "stale"
54-
severity = b.attributes.pop('severity', severity)
5561
client.send_alert(
5662
resource=b.origin,
5763
event='HeartbeatFail',
58-
environment=environment,
59-
severity=severity,
64+
environment=want_environment,
65+
severity=want_severity,
6066
correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'],
61-
service=service,
62-
group=group,
67+
service=want_service,
68+
group=want_group,
6369
value='{}'.format(b.since),
6470
text='Heartbeat not received in {} seconds'.format(b.timeout),
6571
tags=b.tags,
@@ -70,15 +76,14 @@ def cli(obj, alert, severity, timeout, purge):
7076
customer=b.customer
7177
)
7278
elif b.status == 'slow':
73-
severity = b.attributes.pop('severity', severity)
7479
client.send_alert(
7580
resource=b.origin,
7681
event='HeartbeatSlow',
77-
environment=environment,
78-
severity=severity,
82+
environment=want_environment,
83+
severity=want_severity,
7984
correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'],
80-
service=service,
81-
group=group,
85+
service=want_service,
86+
group=want_group,
8287
value='{}ms'.format(b.latency),
8388
text='Heartbeat took more than {}ms to be processed'.format(b.max_latency),
8489
tags=b.tags,
@@ -89,15 +94,14 @@ def cli(obj, alert, severity, timeout, purge):
8994
customer=b.customer
9095
)
9196
else:
92-
severity = b.attributes.pop('severity', default_normal_severity)
9397
client.send_alert(
9498
resource=b.origin,
9599
event='HeartbeatOK',
96-
environment=environment,
97-
severity=severity,
100+
environment=want_environment,
101+
severity=default_normal_severity,
98102
correlate=['HeartbeatFail', 'HeartbeatSlow', 'HeartbeatOK'],
99-
service=service,
100-
group=group,
103+
service=want_service,
104+
group=want_group,
101105
value='',
102106
text='Heartbeat OK',
103107
tags=b.tags,

alertaclient/config.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@
2525

2626
class Config:
2727

28-
def __init__(self, config_file):
28+
def __init__(self, config_file, config_override=None):
2929
self.options = default_config
3030
self.parser = configparser.RawConfigParser(defaults=self.options)
3131

3232
self.options['config_file'] = config_file or os.environ.get('ALERTA_CONF_FILE') or self.options['config_file']
3333
self.parser.read(os.path.expanduser(self.options['config_file']))
3434

35+
self.options.update(config_override or {})
36+
3537
def get_config_for_profle(self, profile=None):
3638
want_profile = profile or os.environ.get('ALERTA_DEFAULT_PROFILE') or self.parser.defaults().get('profile')
3739

@@ -63,7 +65,4 @@ def get_remote_config(self, endpoint=None):
6365
except json.decoder.JSONDecodeError:
6466
raise ClientException('Failed to get config from {}: Reason: not a JSON object'.format(config_url))
6567

66-
if not self.options['client_id']:
67-
del self.options['client_id']
68-
6968
self.options = {**remote_config, **self.options}

tests/unit/test_commands.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,29 @@ class CommandsTestCase(unittest.TestCase):
1616
def setUp(self):
1717
self.client = Client()
1818

19-
config = Config(config_file=None)
19+
alarm_model = {
20+
'name': 'Alerta 8.0.1',
21+
'severity': {
22+
'security': 0,
23+
'critical': 1,
24+
'major': 2,
25+
'minor': 3,
26+
'warning': 4,
27+
'indeterminate': 5,
28+
'informational': 6,
29+
'normal': 7,
30+
'ok': 7,
31+
'cleared': 7,
32+
'debug': 8,
33+
'trace': 9,
34+
'unknown': 10
35+
},
36+
'defaults': {
37+
'normal_severity': 'normal'
38+
}
39+
}
40+
41+
config = Config(config_file=None, config_override={'alarm_model': alarm_model})
2042
self.obj = config.options
2143
self.obj['client'] = self.client
2244

@@ -25,11 +47,6 @@ def setUp(self):
2547
@requests_mock.mock()
2648
def test_heartbeat_cmd(self, m):
2749

28-
config_response = """
29-
{}
30-
"""
31-
m.get('/config', text=config_response)
32-
3350
heartbeat_response = """
3451
{
3552
"heartbeat": {
@@ -67,18 +84,13 @@ def test_heartbeat_cmd(self, m):
6784
@requests_mock.mock()
6885
def test_heartbeats_cmd(self, m):
6986

70-
config_response = """
71-
{}
72-
"""
73-
m.get('/config', text=config_response)
74-
7587
heartbeats_response = """
7688
{
7789
"heartbeats": [
7890
{
7991
"attributes": {
8092
"environment": "Infrastructure",
81-
"severity": "Major",
93+
"severity": "major",
8294
"service": ["Internal"],
8395
"group": "Heartbeats",
8496
"region": "EU"
@@ -123,7 +135,7 @@ def test_heartbeats_cmd(self, m):
123135
"event": "HeartbeatSlow",
124136
"href": "http://api.local.alerta.io:8080/alert/6cfbc30f-c2d6-4edf-b672-841070995206",
125137
"id": "6cfbc30f-c2d6-4edf-b672-841070995206",
126-
"severity": "High",
138+
"severity": "warning",
127139
"status": "open",
128140
"text": "new alert",
129141
"type": "new",
@@ -137,15 +149,15 @@ def test_heartbeats_cmd(self, m):
137149
"lastReceiveId": "6cfbc30f-c2d6-4edf-b672-841070995206",
138150
"lastReceiveTime": "2020-03-10T21:55:07.916Z",
139151
"origin": "alerta/macbook.lan",
140-
"previousSeverity": "Not classified",
152+
"previousSeverity": "indeterminate",
141153
"rawData": null,
142154
"receiveTime": "2020-03-10T21:55:07.916Z",
143155
"repeat": false,
144156
"resource": "monitoring-01",
145157
"service": [
146158
"Internal"
147159
],
148-
"severity": "Major",
160+
"severity": "warning",
149161
"status": "open",
150162
"tags": [],
151163
"text": "Heartbeat took more than 2ms to be processed",
@@ -169,7 +181,7 @@ def test_heartbeats_cmd(self, m):
169181
history = m.request_history
170182
data = history[1].json()
171183
self.assertEqual(data['environment'], 'Infrastructure')
172-
self.assertEqual(data['severity'], 'Major')
184+
self.assertEqual(data['severity'], 'major')
173185
self.assertEqual(data['service'], ['Internal'])
174186
self.assertEqual(data['group'], 'Heartbeats')
175187
self.assertEqual(data['attributes'], {'region': 'EU'})

tests/unit/test_remoteconfig.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def setUp(self):
1717
{
1818
"actions": [],
1919
"alarm_model": {
20-
"name": "Alerta 6.7.5"
20+
"name": "Alerta 8.0.1"
2121
},
2222
"audio": {
2323
"new": null
@@ -90,7 +90,7 @@ def test_config_success(self, m):
9090
"""Tests successful remote config fetch"""
9191
m.get('/api/config', text=self.remote_json_config, status_code=200)
9292
self.config.get_remote_config('http://localhost:8080/api')
93-
self.assertEqual(self.config.options['alarm_model']['name'], 'Alerta 6.7.5')
93+
self.assertEqual(self.config.options['alarm_model']['name'], 'Alerta 8.0.1')
9494

9595
@requests_mock.mock()
9696
def test_config_timeout(self, m):

0 commit comments

Comments
 (0)