Change alarm msg type to 110 to match RMR constant
[ric-plt/xapp-frame-py.git] / ricxappframe / alarm / alarm.py
1 # ==================================================================================
2 #       Copyright (c) 2020 AT&T Intellectual Property.
3 #       Copyright (c) 2020 Nokia
4 #
5 #   Licensed under the Apache License, Version 2.0 (the "License");
6 #   you may not use this file except in compliance with the License.
7 #   You may obtain a copy of the License at
8 #
9 #          http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #   Unless required by applicable law or agreed to in writing, software
12 #   distributed under the License is distributed on an "AS IS" BASIS,
13 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 #   See the License for the specific language governing permissions and
15 #   limitations under the License.
16 # ==================================================================================
17 """
18 Provides classes and methods to define, raise, reraise and clear alarms.
19 All actions are implemented by sending RMR messages to the Alarm Adapter
20 that comply with the JSON schema in file alarm-schema.json.
21 """
22
23 from ctypes import c_void_p
24 from enum import Enum, auto
25 import json
26 import time
27 from mdclogpy import Logger
28 from ricxappframe.rmr import rmr
29
30 ##############
31 # PRIVATE API
32 ##############
33
34 mdc_logger = Logger(name=__name__)
35 RETRIES = 4
36
37 ##############
38 # PUBLIC API
39 ##############
40
41 # constants
42 RIC_ALARM_UPDATE = 110
43
44 # Publish dict keys as constants for convenience of client code.
45 # Mixed lower/upper casing to comply with the Adapter JSON requirements.
46 KEY_ALARM = "alarm"
47 KEY_MANAGED_OBJECT_ID = "managedObjectId"
48 KEY_APPLICATION_ID = "applicationId"
49 KEY_SPECIFIC_PROBLEM = "specificProblem"
50 KEY_PERCEIVED_SEVERITY = "perceivedSeverity"
51 KEY_ADDITIONAL_INFO = "additionalInfo"
52 KEY_IDENTIFYING_INFO = "identifyingInfo"
53 KEY_ALARM_ACTION = "AlarmAction"
54 KEY_ALARM_TIME = "AlarmTime"
55
56
57 class AlarmAction(Enum):
58     """
59     Action to perform at the Alarm Adapter
60     """
61     RAISE = auto()
62     CLEAR = auto()
63     CLEARALL = auto()
64
65
66 class AlarmSeverity(Enum):
67     """
68     Severity of an alarm
69     """
70     UNSPECIFIED = auto()
71     CRITICAL = auto()
72     MAJOR = auto()
73     MINOR = auto()
74     WARNING = auto()
75     CLEARED = auto()
76     DEFAULT = auto()
77
78
79 class AlarmDetail(dict):
80     """
81     An alarm that can be raised or cleared.
82
83     Parameters
84     ----------
85     managed_object_id: str
86         The name of the managed object that is the cause of the fault (required)
87
88     application_id: str
89         The name of the process that raised the alarm (required)
90
91     specific_problem: int
92         The problem that is the cause of the alarm
93
94     perceived_severity: AlarmSeverity
95         The severity of the alarm, a value from the enum.
96
97     identifying_info: str
98         Identifying additional information, which is part of alarm identity
99
100     additional_info: str
101         Additional information given by the application (optional)
102     """
103     # pylint: disable=too-many-arguments
104     def __init__(self,
105                  managed_object_id: str,
106                  application_id: str,
107                  specific_problem: int,
108                  perceived_severity: AlarmSeverity,
109                  identifying_info: str,
110                  additional_info: str = ""):
111         """
112         Creates an object with the specified items.
113         """
114         dict.__init__(self)
115         self[KEY_MANAGED_OBJECT_ID] = managed_object_id
116         self[KEY_APPLICATION_ID] = application_id
117         self[KEY_SPECIFIC_PROBLEM] = specific_problem
118         self[KEY_PERCEIVED_SEVERITY] = perceived_severity.name
119         self[KEY_IDENTIFYING_INFO] = identifying_info
120         self[KEY_ADDITIONAL_INFO] = additional_info
121
122
123 class AlarmManager:
124     """
125     Provides an API for an Xapp to raise and clear alarms by sending messages
126     via RMR, which should route the messages to an Alarm Adapter.
127
128     Parameters
129     ----------
130     vctx: ctypes c_void_p
131         Pointer to RMR context obtained by initializing RMR.
132         The context is used to allocate space and send messages.
133         The RMR routing table must have a destination for message
134         type RIC_ALARM_UPDATE as defined in this module.
135
136     managed_object_id: str
137         The name of the managed object that raises alarms
138
139     application_id: str
140         The name of the process that raises alarms
141     """
142     def __init__(self,
143                  vctx: c_void_p,
144                  managed_object_id: str,
145                  application_id: str):
146         """
147         Creates an alarm manager.
148         """
149         self.vctx = vctx
150         self.managed_object_id = managed_object_id
151         self.application_id = application_id
152
153     def create_alarm(self,
154                      specific_problem: int,
155                      perceived_severity: AlarmSeverity,
156                      identifying_info: str,
157                      additional_info: str = ""):
158         """
159         Convenience method that creates an alarm instance, an AlarmDetail object,
160         using cached values for managed object ID and application ID.
161
162         Parameters
163         ----------
164         specific_problem: int
165             The problem that is the cause of the alarm
166
167         perceived_severity: AlarmSeverity
168             The severity of the alarm, a value from the enum.
169
170         identifying_info: str
171             Identifying additional information, which is part of alarm identity
172
173         additional_info: str
174             Additional information given by the application (optional)
175
176         Returns
177         -------
178         AlarmDetail
179         """
180         return AlarmDetail(managed_object_id=self.managed_object_id,
181                            application_id=self.application_id,
182                            specific_problem=specific_problem, perceived_severity=perceived_severity,
183                            identifying_info=identifying_info, additional_info=additional_info)
184
185     @staticmethod
186     def _create_alarm_message(alarm: AlarmDetail, action: AlarmAction):
187         """
188         Creates a dict with the specified alarm detail plus action and time.
189         Uses the current system time in milliseconds since the Epoch.
190
191         Parameters
192         ----------
193         detail: AlarmDetail
194             The alarm details.
195
196         action: AlarmAction
197             The action to perform at the Alarm Adapter on this alarm.
198         """
199         return {
200             **alarm,
201             KEY_ALARM_ACTION: action.name,
202             KEY_ALARM_TIME: int(round(time.time() * 1000))
203         }
204
205     def _rmr_send_alarm(self, msg: dict):
206         """
207         Serializes the dict and sends the result via RMR using a predefined message type.
208
209         Parameters
210         ----------
211         msg: dict
212             Dictionary with alarm message to encode and send
213
214         Returns
215         -------
216         bool
217             True if the send succeeded (possibly with retries), False otherwise
218         """
219         payload = json.dumps(msg).encode()
220         mdc_logger.debug("_rmr_send_alarm: payload is {}".format(payload))
221         sbuf = rmr.rmr_alloc_msg(vctx=self.vctx, size=len(payload), payload=payload,
222                                  mtype=RIC_ALARM_UPDATE, gen_transaction_id=True)
223
224         for _ in range(0, RETRIES):
225             sbuf = rmr.rmr_send_msg(self.vctx, sbuf)
226             post_send_summary = rmr.message_summary(sbuf)
227             mdc_logger.debug("_rmr_send_alarm: try {0} result is {1}".format(_, post_send_summary[rmr.RMR_MS_MSG_STATE]))
228             # stop trying if RMR does not indicate retry
229             if post_send_summary[rmr.RMR_MS_MSG_STATE] != rmr.RMR_ERR_RETRY:
230                 break
231
232         rmr.rmr_free_msg(sbuf)
233         if post_send_summary[rmr.RMR_MS_MSG_STATE] != rmr.RMR_OK:
234             mdc_logger.warning("_rmr_send_alarm: failed after {} retries".format(RETRIES))
235             return False
236
237         return True
238
239     def raise_alarm(self, detail: AlarmDetail):
240         """
241         Builds and sends a message to the AlarmAdapter to raise an alarm
242         with the specified detail.
243
244         Parameters
245         ----------
246         detail: AlarmDetail
247             Alarm to raise
248
249         Returns
250         -------
251         bool
252             True if the send succeeded (possibly with retries), False otherwise
253         """
254         msg = self._create_alarm_message(detail, AlarmAction.RAISE)
255         return self._rmr_send_alarm(msg)
256
257     def clear_alarm(self, detail: AlarmDetail):
258         """
259         Builds and sends a message to the AlarmAdapter to clear the alarm
260         with the specified detail.
261
262         Parameters
263         ----------
264         detail: AlarmDetail
265             Alarm to clear
266
267         Returns
268         -------
269         bool
270             True if the send succeeded (possibly with retries), False otherwise
271         """
272         msg = self._create_alarm_message(detail, AlarmAction.CLEAR)
273         return self._rmr_send_alarm(msg)
274
275     def reraise_alarm(self, detail: AlarmDetail):
276         """
277         Builds and sends a message to the AlarmAdapter to clear the alarm with the
278         the specified detail, then builds and sends a message to raise the alarm again.
279
280         Parameters
281         ----------
282         detail: AlarmDetail
283             Alarm to clear and raise again.
284
285         Returns
286         -------
287         bool
288             True if the send succeeded (possibly with retries), False otherwise
289         """
290         success = self.clear_alarm(detail)
291         if success:
292             success = self.raise_alarm(detail)
293         return success
294
295     def clear_all_alarms(self):
296         """
297         Builds and sends a message to the AlarmAdapter to clear all alarms.
298
299         Returns
300         -------
301         bool
302             True if the send succeeded (possibly with retries), False otherwise
303         """
304         detail = self.create_alarm(0, AlarmSeverity.DEFAULT, "", "")
305         msg = self._create_alarm_message(detail, AlarmAction.CLEARALL)
306         return self._rmr_send_alarm(msg)