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:
parent
7a79bf1b48
commit
5109ef2283
12 changed files with 274 additions and 77 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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$$;
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
2
pylintrc
2
pylintrc
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue