0
0
Fork 0
mirror of https://github.com/alerta/alerta.git synced 2025-04-15 01:58:36 +00:00

Support per-status timeouts in alert history log

This commit is contained in:
Nick Satterly 2020-07-03 21:41:26 +02:00
parent 7a79bf1b48
commit 5109ef2283
12 changed files with 274 additions and 77 deletions
alerta
database
backends
mongodb
postgres
base.py
management
models
sql
tasks.py
utils
pylintrc
tests

View file

@ -566,6 +566,7 @@ class Backend(Database):
'origin': response['origin'],
'updateTime': response['history']['updateTime'],
'user': response['history'].get('user'),
'timeout': response['history'].get('timeout'),
'type': response['history'].get('type', 'unknown'),
'customer': response.get('customer')
}
@ -585,6 +586,7 @@ class Backend(Database):
'attributes': 1,
'origin': 1,
'user': 1,
'timeout': 1,
'type': 1,
'history': 1
}
@ -619,6 +621,7 @@ class Backend(Database):
'origin': response['origin'],
'updateTime': response['history']['updateTime'],
'user': response.get('user'),
'timeout': response.get('timeout'),
'type': response['history'].get('type', 'unknown'),
'customer': response.get('customer', None)
}
@ -1678,15 +1681,18 @@ class Backend(Database):
# get list of alerts to be newly expired
pipeline = [
{'$match': {'status': {'$nin': ['expired']}}},
{'$addFields': {
'expireTime': {'$add': ['$lastReceiveTime', {'$multiply': ['$timeout', 1000]}]}}
},
{'$match': {'status': {'$nin': ['expired', 'ack', 'shelved']}, 'expireTime': {'$lt': datetime.utcnow()}, 'timeout': {
'$ne': 0}}}
'computedTimeout': {'$multiply': [{'$ifNull': ['$timeout', current_app.config['ALERT_TIMEOUT']]}, 1000]}
}},
{'$addFields': {
'isExpired': {'$lt': [{'$add': ['$lastReceiveTime', '$computedTimeout']}, datetime.utcnow()]}
}},
{'$match': {'isExpired': True, 'computedTimeout': {'$ne': 0}}}
]
return self.get_db().alerts.aggregate(pipeline)
def get_timeout(self):
def get_unshelve(self):
# get list of alerts to be unshelved
pipeline = [
{'$match': {'status': 'shelved'}},
@ -1696,13 +1702,47 @@ class Backend(Database):
'history.status': 'shelved'
}},
{'$sort': {'history.updateTime': -1}},
{'$addFields': {
'expireTime': {'$add': ['$updateTime', {'$multiply': ['$timeout', 1000]}]}
{'$group': {
'_id': '$_id',
'resource': {'$first': '$resource'},
'event': {'$first': '$event'},
'environment': {'$first': '$environment'},
'severity': {'$first': '$severity'},
'correlate': {'$first': '$correlate'},
'status': {'$first': '$status'},
'service': {'$first': '$service'},
'group': {'$first': '$group'},
'value': {'$first': '$value'},
'text': {'$first': '$text'},
'tags': {'$first': '$tags'},
'attributes': {'$first': '$attributes'},
'origin': {'$first': '$origin'},
'type': {'$first': '$type'},
'createTime': {'$first': '$createTime'},
'timeout': {'$first': '$timeout'},
'rawData': {'$first': '$rawData'},
'customer': {'$first': '$customer'},
'duplicateCount': {'$first': '$duplicateCount'},
'repeat': {'$first': '$repeat'},
'previousSeverity': {'$first': '$previousSeverity'},
'trendIndication': {'$first': '$trendIndication'},
'receiveTime': {'$first': '$receiveTime'},
'lastReceiveId': {'$first': '$lastReceiveId'},
'lastReceiveTime': {'$first': '$lastReceiveTime'},
'updateTime': {'$first': '$updateTime'},
'history': {'$first': '$history'},
}},
{'$match': {'expireTime': {'$lt': datetime.utcnow()}, 'timeout': {'$ne': 0}}}
{'$addFields': {
'computedTimeout': {'$multiply': [{'$ifNull': ['$history.timeout', current_app.config['SHELVE_TIMEOUT']]}, 1000]}
}},
{'$addFields': {
'isExpired': {'$lt': [{'$add': ['$updateTime', '$computedTimeout']}, datetime.utcnow()]}
}},
{'$match': {'isExpired': True, 'computedTimeout': {'$ne': 0}}}
]
unshelve = self.get_db().alerts.aggregate(pipeline)
return self.get_db().alerts.aggregate(pipeline)
def get_unack(self):
# get list of alerts to be unack'ed
pipeline = [
{'$match': {'status': 'ack'}},
@ -1712,11 +1752,42 @@ class Backend(Database):
'history.status': 'ack'
}},
{'$sort': {'history.updateTime': -1}},
{'$addFields': {
'expireTime': {'$add': ['$updateTime', {'$multiply': ['$timeout', 1000]}]}
{'$group': {
'_id': '$_id',
'resource': {'$first': '$resource'},
'event': {'$first': '$event'},
'environment': {'$first': '$environment'},
'severity': {'$first': '$severity'},
'correlate': {'$first': '$correlate'},
'status': {'$first': '$status'},
'service': {'$first': '$service'},
'group': {'$first': '$group'},
'value': {'$first': '$value'},
'text': {'$first': '$text'},
'tags': {'$first': '$tags'},
'attributes': {'$first': '$attributes'},
'origin': {'$first': '$origin'},
'type': {'$first': '$type'},
'createTime': {'$first': '$createTime'},
'timeout': {'$first': '$timeout'},
'rawData': {'$first': '$rawData'},
'customer': {'$first': '$customer'},
'duplicateCount': {'$first': '$duplicateCount'},
'repeat': {'$first': '$repeat'},
'previousSeverity': {'$first': '$previousSeverity'},
'trendIndication': {'$first': '$trendIndication'},
'receiveTime': {'$first': '$receiveTime'},
'lastReceiveId': {'$first': '$lastReceiveId'},
'lastReceiveTime': {'$first': '$lastReceiveTime'},
'updateTime': {'$first': '$updateTime'},
'history': {'$first': '$history'},
}},
{'$match': {'expireTime': {'$lt': datetime.utcnow()}, 'timeout': {'$ne': 0}}}
{'$addFields': {
'computedTimeout': {'$multiply': [{'$ifNull': ['$history.timeout', current_app.config['ACK_TIMEOUT']]}, 1000]}
}},
{'$addFields': {
'isExpired': {'$lt': [{'$add': ['$updateTime', '$computedTimeout']}, datetime.utcnow()]}
}},
{'$match': {'isExpired': True, 'computedTimeout': {'$ne': 0}}}
]
unack = self.get_db().alerts.aggregate(pipeline)
return list(unshelve) + list(unack)
return self.get_db().alerts.aggregate(pipeline)

View file

@ -34,7 +34,7 @@ class HistoryAdapter:
a.prepare(self.conn)
return a.getquoted().decode('utf-8')
return '({}, {}, {}, {}, {}, {}, {}, {}::timestamp, {})::history'.format(
return '({}, {}, {}, {}, {}, {}, {}, {}::timestamp, {}, {})::history'.format(
quoted(self.history.id),
quoted(self.history.event),
quoted(self.history.severity),
@ -43,7 +43,8 @@ class HistoryAdapter:
quoted(self.history.text),
quoted(self.history.change_type),
quoted(self.history.update_time),
quoted(self.history.user)
quoted(self.history.user),
quoted(self.history.timeout)
)
def __str__(self):
@ -51,8 +52,9 @@ class HistoryAdapter:
Record = namedtuple('Record', [
'id', 'resource', 'event', 'environment', 'severity', 'status', 'service', 'group',
'value', 'text', 'tags', 'attributes', 'origin', 'update_time', 'user', 'type', 'customer'
'id', 'resource', 'event', 'environment', 'severity', 'status', 'service',
'group', 'value', 'text', 'tags', 'attributes', 'origin', 'update_time',
'user', 'timeout', 'type', 'customer'
])
@ -427,6 +429,7 @@ class Backend(Database):
origin=h.origin,
update_time=h.update_time,
user=getattr(h, 'user', None),
timeout=getattr(h, 'timeout', None),
type=h.type,
customer=h.customer
) for h in self._fetchall(select, vars(alert), limit=page_size, offset=(page - 1) * page_size)
@ -466,6 +469,7 @@ class Backend(Database):
origin=h.origin,
update_time=h.update_time,
user=getattr(h, 'user', None),
timeout=getattr(h, 'timeout', None),
type=h.type,
customer=h.customer
) for h in self._fetchall(select, query.vars, limit=page_size, offset=(page - 1) * page_size)
@ -1319,6 +1323,7 @@ class Backend(Database):
def get_expired(self, expired_threshold, info_threshold):
# delete 'closed' or 'expired' alerts older than "expired_threshold" hours
# and 'informational' alerts older than "info_threshold" hours
if expired_threshold:
delete = """
DELETE FROM alerts
@ -1339,46 +1344,39 @@ class Backend(Database):
select = """
SELECT *
FROM alerts
WHERE status NOT IN ('expired','ack','shelved') AND COALESCE(timeout, {timeout})!=0
WHERE status NOT IN ('expired') AND COALESCE(timeout, {timeout})!=0
AND (last_receive_time + INTERVAL '1 second' * timeout) < NOW() at time zone 'utc'
""".format(timeout=current_app.config['ALERT_TIMEOUT'])
return self._fetchall(select, {})
def get_timeout(self):
def get_unshelve(self):
# get list of alerts to be unshelved
select = """
WITH shelved AS (
SELECT DISTINCT ON (a.id) a.*
FROM alerts a, UNNEST(history) h
WHERE a.status='shelved'
AND h.type='shelve'
AND h.status='shelved'
ORDER BY a.id, h.update_time DESC
)
SELECT *
FROM shelved
WHERE COALESCE(timeout, {timeout})!=0 AND (update_time + INTERVAL '1 second' * timeout) < NOW() at time zone 'utc'
AND COALESCE(h.timeout, {timeout})!=0
AND (a.update_time + INTERVAL '1 second' * h.timeout) < NOW() at time zone 'utc'
ORDER BY a.id, a.update_time DESC
""".format(timeout=current_app.config['SHELVE_TIMEOUT'])
unshelve = self._fetchall(select, {})
return self._fetchall(select, {})
def get_unack(self):
# get list of alerts to be unack'ed
select = """
WITH acked AS (
SELECT DISTINCT ON (a.id) a.*
FROM alerts a, UNNEST(history) h
WHERE a.status='ack'
AND h.type='ack'
AND h.status='ack'
ORDER BY a.id, h.update_time DESC
)
SELECT *
FROM acked
WHERE COALESCE(timeout, {timeout})!=0 AND (update_time + INTERVAL '1 second' * timeout) < NOW() at time zone 'utc'
AND COALESCE(h.timeout, {timeout})!=0
AND (a.update_time + INTERVAL '1 second' * h.timeout) < NOW() at time zone 'utc'
ORDER BY a.id, a.update_time DESC
""".format(timeout=current_app.config['ACK_TIMEOUT'])
unack = self._fetchall(select, {})
return unshelve + unack
return self._fetchall(select, {})
# SQL HELPERS

View file

@ -406,7 +406,10 @@ class Database(Base):
def get_expired(self, expired_threshold, info_threshold):
raise NotImplementedError
def get_timeout(self):
def get_unshelve(self):
raise NotImplementedError
def get_unack(self):
raise NotImplementedError

View file

@ -148,12 +148,12 @@ def housekeeping():
expired_threshold = request.args.get('expired', default=current_app.config['DEFAULT_EXPIRED_DELETE_HRS'], type=int)
info_threshold = request.args.get('info', default=current_app.config['DEFAULT_INFO_DELETE_HRS'], type=int)
has_expired, has_timedout = Alert.housekeeping(expired_threshold, info_threshold)
has_expired, shelve_timeout, ack_timeout = Alert.housekeeping(expired_threshold, info_threshold)
errors = []
for alert in has_expired:
try:
alert, _, text, timeout = process_action(alert, action='expired', text='', timeout=current_app.config['ALERT_TIMEOUT'])
alert, _, text, timeout = process_action(alert, action='expired', text='', timeout=None)
alert = alert.from_expired(text, timeout)
except RejectException as e:
write_audit_trail.send(current_app._get_current_object(), event='alert-expire-rejected', message=alert.text,
@ -167,9 +167,10 @@ def housekeeping():
write_audit_trail.send(current_app._get_current_object(), event='alert-expired', message=text, user=g.login,
customers=g.customers, scopes=g.scopes, resource_id=alert.id, type='alert', request=request)
for alert in has_timedout:
for alert in shelve_timeout + ack_timeout:
print('>>>>>>>>> {}'.format(alert))
try:
alert, _, text, timeout = process_action(alert, action='timeout', text='', timeout=current_app.config['ALERT_TIMEOUT'])
alert, _, text, timeout = process_action(alert, action='timeout', text='', timeout=None)
alert = alert.from_timeout(text, timeout)
except RejectException as e:
write_audit_trail.send(current_app._get_current_object(), event='alert-timeout-rejected', message=alert.text,
@ -189,8 +190,9 @@ def housekeeping():
return jsonify(
status='ok',
expired=[a.id for a in has_expired],
timedout=[a.id for a in has_timedout],
count=len(has_expired) + len(has_timedout)
unshelve=[a.id for a in shelve_timeout],
unack=[a.id for a in ack_timeout],
count=len(has_expired) + len(shelve_timeout) + len(ack_timeout)
)

View file

@ -187,7 +187,10 @@ class StateMachine(AlarmModel):
return next_state('EXP-0', current_severity, EXPIRED)
if action == ACTION_TIMEOUT:
return next_state('OPEN-0', current_severity, OPEN)
if previous_status == ACK:
return next_state('ACK-0', current_severity, ACK)
else:
return next_state('OPEN-0', current_severity, OPEN)
if state == OPEN:
if action == ACTION_OPEN:

View file

@ -256,7 +256,7 @@ class Alert:
def _get_hist_info(self, action=None):
h_loop = self.get_alert_history(alert=self)
if len(h_loop) == 1:
return h_loop[0].status, h_loop[0].value, None
return h_loop[0].status, h_loop[0].value, None, None
if action == ChangeType.unack:
find = ChangeType.ack
elif action == ChangeType.unshelve:
@ -265,14 +265,14 @@ class Alert:
find = None
for h, h_next in zip(h_loop, h_loop[1:]):
if not find or h.change_type == find:
return h_loop[0].status, h_loop[0].value, h_next.status
return None, None, None
return h_loop[0].status, h_loop[0].value, h_next.status, h_next.timeout
return None, None, None, None
# de-duplicate an alert
def deduplicate(self, duplicate_of) -> 'Alert':
now = datetime.utcnow()
status, previous_value, previous_status = self._get_hist_info()
status, previous_value, previous_status, _ = self._get_hist_info()
_, new_status = alarm_model.transition(
alert=self,
@ -299,6 +299,7 @@ class Alert:
change_type=ChangeType.status,
update_time=self.create_time,
user=g.login,
timeout=self.timeout,
) # type: Optional[History]
elif current_app.config['HISTORY_ON_VALUE_CHANGE'] and self.value != previous_value:
@ -311,7 +312,8 @@ class Alert:
text=self.text,
change_type=ChangeType.value,
update_time=self.create_time,
user=g.login
user=g.login,
timeout=self.timeout,
)
else:
history = None
@ -326,7 +328,7 @@ class Alert:
self.previous_severity = db.get_severity(self)
self.trend_indication = alarm_model.trend(self.previous_severity, self.severity)
status, _, previous_status = self._get_hist_info()
status, _, previous_status, _ = self._get_hist_info()
_, new_status = alarm_model.transition(
alert=self,
@ -356,7 +358,8 @@ class Alert:
text=text,
change_type=ChangeType.severity,
update_time=self.create_time,
user=g.login
user=g.login,
timeout=self.timeout
)]
self.status = new_status
@ -390,7 +393,8 @@ class Alert:
text=self.text,
change_type=ChangeType.new,
update_time=self.create_time,
user=g.login
user=g.login,
timeout=self.timeout
)]
return Alert.from_db(db.create_alert(self))
@ -426,7 +430,8 @@ class Alert:
text=text,
change_type=ChangeType.status,
update_time=now,
user=g.login
user=g.login,
timeout=self.timeout
)
return db.set_status(self.id, status, timeout, update_time=now, history=history)
@ -540,10 +545,11 @@ class Alert:
return [Note.from_db(note) for note in notes]
@staticmethod
def housekeeping(expired_threshold: int = 2, info_threshold: int = 12) -> Tuple[List['Alert'], List['Alert']]:
def housekeeping(expired_threshold: int = 2, info_threshold: int = 12) -> Tuple[List['Alert'], List['Alert'], List['Alert']]:
return (
[Alert.from_db(alert) for alert in db.get_expired(expired_threshold, info_threshold)],
[Alert.from_db(alert) for alert in db.get_timeout()]
[Alert.from_db(alert) for alert in db.get_unshelve()],
[Alert.from_db(alert) for alert in db.get_unack()]
)
def from_status(self, status: str, text: str = '', timeout: int = None) -> 'Alert':
@ -559,7 +565,8 @@ class Alert:
text=text,
change_type=ChangeType.status,
update_time=now,
user=g.login
user=g.login,
timeout=self.timeout
)]
return Alert.from_db(db.set_alert(
id=self.id,
@ -576,9 +583,17 @@ class Alert:
def from_action(self, action: str, text: str = '', timeout: int = None) -> 'Alert':
now = datetime.utcnow()
self.timeout = timeout or current_app.config['ALERT_TIMEOUT']
status, _, previous_status, previous_timeout = self._get_hist_info(action)
status, _, previous_status = self._get_hist_info(action)
if action in ['unack', 'unshelve', 'timeout']:
timeout = timeout or previous_timeout
if action in ['ack', 'unack']:
timeout = timeout or current_app.config['ACK_TIMEOUT']
elif action in ['shelve', 'unshelve']:
timeout = timeout or current_app.config['SHELVE_TIMEOUT']
else:
timeout = timeout or self.timeout or current_app.config['ALERT_TIMEOUT']
new_severity, new_status = alarm_model.transition(
alert=self,
@ -604,7 +619,8 @@ class Alert:
text=text,
change_type=change_type,
update_time=now,
user=g.login
user=g.login,
timeout=timeout
)]
return Alert.from_db(db.set_alert(
@ -613,7 +629,7 @@ class Alert:
status=new_status,
tags=self.tags,
attributes=self.attributes,
timeout=timeout,
timeout=self.timeout,
previous_severity=self.severity if new_severity != self.severity else self.previous_severity,
update_time=now,
history=history)

View file

@ -15,6 +15,7 @@ class History:
self.change_type = kwargs.get('change_type', kwargs.get('type', None)) or ''
self.update_time = kwargs.get('update_time', None) or datetime.utcnow()
self.user = kwargs.get('user', None)
self.timeout = kwargs.get('timeout', None)
@property
def serialize(self):
@ -28,7 +29,8 @@ class History:
'text': self.text,
'type': self.change_type,
'updateTime': self.update_time,
'user': self.user
'user': self.user,
'timeout': self.timeout
}
def __repr__(self):
@ -46,7 +48,8 @@ class History:
text=doc.get('text', None),
change_type=doc.get('type', None),
update_time=doc.get('updateTime', None),
user=doc.get('user', None)
user=doc.get('user', None),
timeout=doc.get('timeout', None)
)
@classmethod
@ -60,7 +63,8 @@ class History:
text=rec.text,
change_type=rec.type,
update_time=rec.update_time,
user=getattr(rec, 'user', None)
user=getattr(rec, 'user', None),
timeout=getattr(rec, 'timeout', None)
)
@classmethod
@ -90,6 +94,7 @@ class RichHistory:
self.origin = kwargs.get('origin', None)
self.update_time = kwargs.get('update_time', None)
self.user = kwargs.get('user', None)
self.timeout = kwargs.get('timeout', None)
self.change_type = kwargs.get('change_type', kwargs.get('type', None))
self.customer = kwargs.get('customer', None)
@ -109,6 +114,7 @@ class RichHistory:
'origin': self.origin,
'updateTime': self.update_time,
'user': self.user,
'timeout': self.timeout,
'type': self.change_type,
'customer': self.customer
}
@ -125,8 +131,10 @@ class RichHistory:
return data
def __repr__(self):
return 'RichHistory(id={!r}, environment={!r}, resource={!r}, event={!r}, severity={!r}, status={!r}, type={!r}, customer={!r})'.format(
self.id, self.environment, self.resource, self.event, self.severity, self.status, self.change_type, self.customer)
return 'RichHistory(id={!r}, environment={!r}, resource={!r}, event={!r}, severity={!r},' \
' status={!r}, timeout={!r}, type={!r}, customer={!r})'.format(
self.id, self.environment, self.resource, self.event, self.severity,
self.status, self.timeout, self.change_type, self.customer)
@classmethod
def from_document(cls, doc):
@ -146,6 +154,7 @@ class RichHistory:
origin=doc.get('origin', None),
update_time=doc.get('updateTime', None),
user=doc.get('user', None),
timeout=doc.get('timeout', None),
change_type=doc.get('type', None),
customer=doc.get('customer', None)
)
@ -168,6 +177,7 @@ class RichHistory:
origin=rec.origin,
update_time=rec.update_time,
user=getattr(rec, 'user', None),
timeout=getattr(rec, 'timeout', None),
change_type=rec.type,
customer=rec.customer
)

View file

@ -11,7 +11,8 @@ BEGIN
text text,
type text,
update_time timestamp without time zone,
"user" text
"user" text,
timeout integer
);
ELSE
BEGIN
@ -19,6 +20,11 @@ BEGIN
EXCEPTION
WHEN duplicate_column THEN RAISE NOTICE 'column "user" already exists in history type.';
END;
BEGIN
ALTER TYPE history ADD ATTRIBUTE timeout integer CASCADE;
EXCEPTION
WHEN duplicate_column THEN RAISE NOTICE 'column "timeout" already exists in history type.';
END;
END IF;
END$$;

View file

@ -1,4 +1,4 @@
from typing import List
from typing import List, Optional
from alerta.app import create_celery_app
from alerta.exceptions import InvalidAction, RejectException
@ -9,7 +9,7 @@ celery = create_celery_app()
@celery.task
def action_alerts(alerts: List[str], action: str, text: str, timeout: int) -> None:
def action_alerts(alerts: List[str], action: str, text: str, timeout: Optional[int]) -> None:
updated = []
errors = []
for alert_id in alerts:

View file

@ -86,7 +86,7 @@ def process_alert(alert: Alert) -> Alert:
return alert
def process_action(alert: Alert, action: str, text: str, timeout: int) -> Tuple[Alert, str, str, int]:
def process_action(alert: Alert, action: str, text: str, timeout: int = None) -> Tuple[Alert, str, str, Optional[int]]:
wanted_plugins, wanted_config = plugins.routing(alert)
updated = None

View file

@ -174,7 +174,7 @@ zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent
generated-members=REQUEST,acl_users,aq_parent,ldap
[DESIGN]

View file

@ -13,7 +13,9 @@ class ManagementTestCase(unittest.TestCase):
test_config = {
'DEBUG': True,
'TESTING': True,
'AUTH_REQUIRED': False
'AUTH_REQUIRED': False,
# 'ACK_TIMEOUT': 2,
# 'SHELVE_TIMEOUT': 3
}
self.app = create_app(test_config)
self.client = self.app.test_client()
@ -57,6 +59,17 @@ class ManagementTestCase(unittest.TestCase):
'tags': ['foo']
}
self.acked_and_shelved_alert = {
'event': 'node_warn',
'resource': random_resource(),
'environment': 'Production',
'service': ['Network', 'Shared'],
'severity': 'warning',
'correlate': ['node_down', 'node_marginal', 'node_up'],
'tags': ['foo'],
'timeout': 240
}
self.ok_alert = {
'event': 'node_up',
'resource': random_resource(),
@ -105,14 +118,16 @@ class ManagementTestCase(unittest.TestCase):
shelved_id = data['id']
response = self.client.put('/alert/' + shelved_id + '/action',
data=json.dumps({'action': 'shelve', 'timeout': 2}), headers=self.headers)
data=json.dumps({'action': 'shelve', 'timeout': 3}), headers=self.headers)
self.assertEqual(response.status_code, 200)
response = self.client.get('/alert/' + shelved_id)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf-8'))
self.assertEqual(data['alert']['id'], shelved_id)
self.assertEqual(data['alert']['timeout'], 2)
self.assertEqual(data['alert']['timeout'], 20)
self.assertEqual(data['alert']['history'][0]['timeout'], 20)
self.assertEqual(data['alert']['history'][1]['timeout'], 3)
# create an alert and ack it
response = self.client.post('/alert', data=json.dumps(self.acked_alert), headers=self.headers)
@ -129,18 +144,91 @@ class ManagementTestCase(unittest.TestCase):
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf-8'))
self.assertEqual(data['alert']['id'], acked_id)
self.assertEqual(data['alert']['timeout'], 2)
self.assertEqual(data['alert']['timeout'], 86400)
self.assertEqual(data['alert']['history'][0]['status'], 'open')
self.assertEqual(data['alert']['history'][0]['timeout'], 86400)
self.assertEqual(data['alert']['history'][1]['status'], 'ack')
self.assertEqual(data['alert']['history'][1]['timeout'], 2)
# create an alert that should be unaffected
response = self.client.post('/alert', data=json.dumps(self.ok_alert), headers=self.headers)
self.assertEqual(response.status_code, 201)
data = json.loads(response.data.decode('utf-8'))
# create an alert and ack it then shelve it
response = self.client.post('/alert', data=json.dumps(self.acked_and_shelved_alert), headers=self.headers)
self.assertEqual(response.status_code, 201)
data = json.loads(response.data.decode('utf-8'))
acked_and_shelved_id = data['id']
response = self.client.put('/alert/' + acked_and_shelved_id + '/action',
data=json.dumps({'action': 'ack', 'timeout': 4}), headers=self.headers)
self.assertEqual(response.status_code, 200)
response = self.client.put('/alert/' + acked_and_shelved_id + '/action',
data=json.dumps({'action': 'shelve', 'timeout': 3}), headers=self.headers)
self.assertEqual(response.status_code, 200)
response = self.client.get('/alert/' + acked_and_shelved_id)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf-8'))
self.assertEqual(data['alert']['id'], acked_and_shelved_id)
self.assertEqual(data['alert']['timeout'], 240)
self.assertEqual(data['alert']['history'][0]['status'], 'open')
self.assertEqual(data['alert']['history'][0]['timeout'], 240)
self.assertEqual(data['alert']['history'][1]['status'], 'ack')
self.assertEqual(data['alert']['history'][1]['timeout'], 4)
self.assertEqual(data['alert']['history'][2]['status'], 'shelved')
self.assertEqual(data['alert']['history'][2]['timeout'], 3)
time.sleep(5)
response = self.client.get('/management/housekeeping', headers=self.headers)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf-8'))
self.assertEqual(data['count'], 3)
self.assertEqual(data['count'], 4)
self.assertListEqual(data['expired'], [expired_id])
self.assertListEqual(data['timedout'], [shelved_id, acked_id])
self.assertListEqual(sorted(data['unshelve']), sorted([shelved_id, acked_and_shelved_id]))
self.assertListEqual(sorted(data['unack']), sorted([acked_id]))
response = self.client.get('/alert/' + shelved_id)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf-8'))
self.assertEqual(data['alert']['id'], shelved_id)
self.assertEqual(data['alert']['status'], 'open')
self.assertEqual(data['alert']['timeout'], 20)
self.assertEqual(data['alert']['history'][0]['status'], 'open') # previous status
self.assertEqual(data['alert']['history'][0]['timeout'], 20) # previous timeout
self.assertEqual(data['alert']['history'][1]['status'], 'shelved') # status
self.assertEqual(data['alert']['history'][1]['timeout'], 3)
self.assertEqual(data['alert']['history'][2]['status'], 'open')
self.assertEqual(data['alert']['history'][2]['timeout'], 20, data['alert'])
response = self.client.get('/alert/' + acked_id)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf-8'))
self.assertEqual(data['alert']['id'], acked_id)
self.assertEqual(data['alert']['status'], 'open')
self.assertEqual(data['alert']['timeout'], 86400)
self.assertEqual(data['alert']['history'][0]['status'], 'open')
self.assertEqual(data['alert']['history'][0]['timeout'], 86400)
self.assertEqual(data['alert']['history'][1]['status'], 'ack')
self.assertEqual(data['alert']['history'][1]['timeout'], 2)
self.assertEqual(data['alert']['history'][2]['status'], 'open')
self.assertEqual(data['alert']['history'][2]['timeout'], 86400)
response = self.client.get('/alert/' + acked_and_shelved_id)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data.decode('utf-8'))
self.assertEqual(data['alert']['id'], acked_and_shelved_id)
self.assertEqual(data['alert']['status'], 'ack')
self.assertEqual(data['alert']['timeout'], 240)
self.assertEqual(data['alert']['history'][0]['status'], 'open')
self.assertEqual(data['alert']['history'][0]['timeout'], 240)
self.assertEqual(data['alert']['history'][1]['status'], 'ack')
self.assertEqual(data['alert']['history'][1]['timeout'], 4)
self.assertEqual(data['alert']['history'][2]['status'], 'shelved')
self.assertEqual(data['alert']['history'][2]['timeout'], 3)
self.assertEqual(data['alert']['history'][3]['status'], 'ack')
self.assertEqual(data['alert']['history'][3]['timeout'], 4)