From fc88a986ba2bcb97a6a3c6f51e1fa48d78a7c507 Mon Sep 17 00:00:00 2001 From: Dmitry Volodin Date: Mon, 18 Apr 2022 13:07:24 +0200 Subject: [PATCH 1/2] #1804 Clearing alarms from web interface sends message to correlator --- docs/en/docs/dev/reference/streams/dispose.md | 10 +++++ services/correlator/models/clearidreq.py | 18 +++++++++ services/correlator/models/disposereq.py | 5 ++- services/correlator/service.py | 37 ++++++++++++++++++- services/web/apps/fm/alarm/views.py | 14 ++++++- 5 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 services/correlator/models/clearidreq.py diff --git a/docs/en/docs/dev/reference/streams/dispose.md b/docs/en/docs/dev/reference/streams/dispose.md index 8f83e32243..c51b87e487 100644 --- a/docs/en/docs/dev/reference/streams/dispose.md +++ b/docs/en/docs/dev/reference/streams/dispose.md @@ -65,6 +65,16 @@ in the `$op` field. Unknown message types and malformed messages are discarded. | `reference` | String | Alarm reference. Should be the same as in previous [raise](#raise-message) message. | | `timestamp` | String | Optional timestamp in ISO 8601 format | +### clearid message + +`clearid` message represents a direct alarm clear request, issued by an external mechanism. + +| Field | Type | Description | +| ----------- | ------ | ------------------------------------- | +| `$op` | String | Equals to `clear` | +| `id` | String | Alarm id | +| `timestamp` | String | Optional timestamp in ISO 8601 format | + ### ensure_group message `ensure_group` message creates and synchronizes group with given set of alarms. diff --git a/services/correlator/models/clearidreq.py b/services/correlator/models/clearidreq.py new file mode 100644 index 0000000000..7190759b37 --- /dev/null +++ b/services/correlator/models/clearidreq.py @@ -0,0 +1,18 @@ +# --------------------------------------------------------------------- +# ClearId Request +# --------------------------------------------------------------------- +# Copyright (C) 2007-2022 The NOC Project +# See LICENSE for details +# --------------------------------------------------------------------- + +# Python modules +from typing import Optional, Literal + +# Third-party modules +from pydantic import BaseModel, Field + + +class ClearIdRequest(BaseModel): + op: Literal["clearid"] = Field(None, alias="$op") + id: str + timestamp: Optional[str] diff --git a/services/correlator/models/disposereq.py b/services/correlator/models/disposereq.py index ffef9ab574..94f999ce97 100644 --- a/services/correlator/models/disposereq.py +++ b/services/correlator/models/disposereq.py @@ -1,7 +1,7 @@ # --------------------------------------------------------------------- # Dispose Request # --------------------------------------------------------------------- -# Copyright (C) 2007-2021 The NOC Project +# Copyright (C) 2007-2022 The NOC Project # See LICENSE for details # --------------------------------------------------------------------- @@ -10,8 +10,9 @@ from typing import Union # NOC modules from .clearreq import ClearRequest +from .clearidreq import ClearIdRequest from .eventreq import EventRequest from .raisereq import RaiseRequest from .ensuregroupreq import EnsureGroupRequest -DisposeRequest = Union[ClearRequest, EventRequest, RaiseRequest, EnsureGroupRequest] +DisposeRequest = Union[ClearRequest, ClearIdRequest, EventRequest, RaiseRequest, EnsureGroupRequest] diff --git a/services/correlator/service.py b/services/correlator/service.py index 4c591c6637..72567d96e6 100755 --- a/services/correlator/service.py +++ b/services/correlator/service.py @@ -2,7 +2,7 @@ # --------------------------------------------------------------------- # noc-correlator daemon # --------------------------------------------------------------------- -# Copyright (C) 2007-2021 The NOC Project +# Copyright (C) 2007-2022 The NOC Project # See LICENSE for details # --------------------------------------------------------------------- @@ -38,6 +38,7 @@ from noc.services.correlator.trigger import Trigger from noc.services.correlator.models.disposereq import DisposeRequest from noc.services.correlator.models.eventreq import EventRequest from noc.services.correlator.models.clearreq import ClearRequest +from noc.services.correlator.models.clearidreq import ClearIdRequest from noc.services.correlator.models.raisereq import RaiseRequest from noc.services.correlator.models.ensuregroupreq import EnsureGroupRequest from noc.fm.models.eventclass import EventClass @@ -784,6 +785,14 @@ class CorrelatorService(TornadoService): ts = parse_date(req.timestamp) if req.timestamp else datetime.datetime.now() await self.clear_by_reference(req.reference, ts) + async def on_msg_clearid(self, req: ClearIdRequest) -> None: + """ + Process `clearid` message. + """ + # Fetch timestamp + ts = parse_date(req.timestamp) if req.timestamp else datetime.datetime.now() + await self.clear_by_id(req.id, ts) + async def on_msg_ensure_group(self, req: EnsureGroupRequest) -> None: """ Process `ensure_group` message. @@ -879,6 +888,32 @@ class CorrelatorService(TornadoService): for h_ref in set(open_alarms) - seen_refs: await self.clear_by_reference(h_ref, ts=now) + async def clear_by_id( + self, id: Union[str, bytes], ts: Optional[datetime.datetime] = None + ) -> None: + """ + Clear alarm by reference + """ + ts = ts or datetime.datetime.now() + # Get alarm + alarm = ActiveAlarm.objects.filter(pk=id).first() + if not alarm: + self.logger.info("Alarm '%s' is not found. Skipping", id) + return + # Clear alarm + self.logger.info( + "[%s|%s] Clear alarm %s(%s) by id", + alarm.managed_object.name, + alarm.managed_object.address, + alarm.alarm_class.name, + alarm.id, + ) + alarm.last_update = max(alarm.last_update, ts) + groups = alarm.groups + alarm.clear_alarm("Cleared by id") + metrics["alarm_clear"] += 1 + await self.clear_groups(groups, ts=ts) + async def clear_by_reference( self, reference: Union[str, bytes], ts: Optional[datetime.datetime] = None ) -> None: diff --git a/services/web/apps/fm/alarm/views.py b/services/web/apps/fm/alarm/views.py index 787023e1f4..16c8c14e67 100644 --- a/services/web/apps/fm/alarm/views.py +++ b/services/web/apps/fm/alarm/views.py @@ -11,6 +11,7 @@ import inspect import datetime import operator from typing import Tuple +import asyncio # Third-party modules import bson @@ -683,8 +684,17 @@ class AlarmApplication(ExtApplication): if not alarm.alarm_class.user_clearable: return {"status": False, "error": "Deny clear alarm by user"} if alarm.status == "A": - alarm.clear_alarm( - "Cleared by %s: %s" % (request.user, msg), source=request.user.username + # Report message + alarm.log_message(f"Clear request from {request.user}: {msg}") + # Send clear signal to the correlator + fm_pool = alarm.managed_object.get_effective_fm_pool().name + stream = f"dispose.{fm_pool}" + num_partitions = asyncio.run(self.service.get_stream_partitions(stream)) + partition = int(alarm.managed_object.id) % num_partitions + self.service.publish( + orjson.dumps({"$op": "clearid", "id": str(alarm.id)}), + stream=stream, + partition=partition, ) return True -- GitLab From f514e9a7de89bb7d51314b6aaac921997b32693e Mon Sep 17 00:00:00 2001 From: Dmitry Volodin Date: Tue, 19 Apr 2022 08:10:10 +0200 Subject: [PATCH 2/2] pass message and source to alarm closing request --- docs/en/docs/dev/reference/streams/dispose.md | 2 ++ services/correlator/models/clearidreq.py | 2 ++ services/correlator/service.py | 13 +++++++++---- services/web/apps/fm/alarm/views.py | 11 ++++++++--- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/en/docs/dev/reference/streams/dispose.md b/docs/en/docs/dev/reference/streams/dispose.md index c51b87e487..d9264a8aca 100644 --- a/docs/en/docs/dev/reference/streams/dispose.md +++ b/docs/en/docs/dev/reference/streams/dispose.md @@ -74,6 +74,8 @@ in the `$op` field. Unknown message types and malformed messages are discarded. | `$op` | String | Equals to `clear` | | `id` | String | Alarm id | | `timestamp` | String | Optional timestamp in ISO 8601 format | +| `message` | String | Optional closing message | +| `source` | String | Optional closing source/user | ### ensure_group message `ensure_group` message creates and synchronizes group with given set of alarms. diff --git a/services/correlator/models/clearidreq.py b/services/correlator/models/clearidreq.py index 7190759b37..e5ccc7d36e 100644 --- a/services/correlator/models/clearidreq.py +++ b/services/correlator/models/clearidreq.py @@ -16,3 +16,5 @@ class ClearIdRequest(BaseModel): op: Literal["clearid"] = Field(None, alias="$op") id: str timestamp: Optional[str] + message: Optional[str] + source: Optional[str] diff --git a/services/correlator/service.py b/services/correlator/service.py index 72567d96e6..28ae952e53 100755 --- a/services/correlator/service.py +++ b/services/correlator/service.py @@ -791,7 +791,7 @@ class CorrelatorService(TornadoService): """ # Fetch timestamp ts = parse_date(req.timestamp) if req.timestamp else datetime.datetime.now() - await self.clear_by_id(req.id, ts) + await self.clear_by_id(req.id, ts=ts, message=req.message, source=req.source) async def on_msg_ensure_group(self, req: EnsureGroupRequest) -> None: """ @@ -889,7 +889,11 @@ class CorrelatorService(TornadoService): await self.clear_by_reference(h_ref, ts=now) async def clear_by_id( - self, id: Union[str, bytes], ts: Optional[datetime.datetime] = None + self, + id: Union[str, bytes], + ts: Optional[datetime.datetime] = None, + message: Optional[str] = None, + source: Optional[str] = None, ) -> None: """ Clear alarm by reference @@ -902,15 +906,16 @@ class CorrelatorService(TornadoService): return # Clear alarm self.logger.info( - "[%s|%s] Clear alarm %s(%s) by id", + "[%s|%s] Clear alarm %s(%s) by id: %s", alarm.managed_object.name, alarm.managed_object.address, alarm.alarm_class.name, alarm.id, + message, ) alarm.last_update = max(alarm.last_update, ts) groups = alarm.groups - alarm.clear_alarm("Cleared by id") + alarm.clear_alarm(message or "Cleared by id", source=source) metrics["alarm_clear"] += 1 await self.clear_groups(groups, ts=ts) diff --git a/services/web/apps/fm/alarm/views.py b/services/web/apps/fm/alarm/views.py index 16c8c14e67..68ccef9386 100644 --- a/services/web/apps/fm/alarm/views.py +++ b/services/web/apps/fm/alarm/views.py @@ -684,15 +684,20 @@ class AlarmApplication(ExtApplication): if not alarm.alarm_class.user_clearable: return {"status": False, "error": "Deny clear alarm by user"} if alarm.status == "A": - # Report message - alarm.log_message(f"Clear request from {request.user}: {msg}") # Send clear signal to the correlator fm_pool = alarm.managed_object.get_effective_fm_pool().name stream = f"dispose.{fm_pool}" num_partitions = asyncio.run(self.service.get_stream_partitions(stream)) partition = int(alarm.managed_object.id) % num_partitions self.service.publish( - orjson.dumps({"$op": "clearid", "id": str(alarm.id)}), + orjson.dumps( + { + "$op": "clearid", + "id": str(alarm.id), + "message": msg, + "source": request.user.username, + } + ), stream=stream, partition=partition, ) -- GitLab