Add alarm API module ricxappframe.alarm.alarm
[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 = 13111
43 # RIC_ALARM_QUERY = 13112 # TBD
44
45 # Publish dict keys as constants for convenience of client code.
46 # Mixed lower/upper casing to comply with the Adapter JSON requirements.
47 KEY_ALARM = "alarm"
48 KEY_MANAGED_OBJECT_ID = "managedObjectId"
49 KEY_APPLICATION_ID = "applicationId"
50 KEY_SPECIFIC_PROBLEM = "specificProblem"
51 KEY_PERCEIVED_SEVERITY = "perceivedSeverity"
52 KEY_ADDITIONAL_INFO = "additionalInfo"
53 KEY_IDENTIFYING_INFO = "identifyingInfo"
54 KEY_ALARM_ACTION = "AlarmAction"
55 KEY_ALARM_TIME = "AlarmTime"
56
57
58 class AlarmAction(Enum):
59     """
60     Action to perform at the Alarm Adapter
61     """
62     RAISE = auto()
63     CLEAR = auto()
64     CLEARALL = auto()
65
66
67 class AlarmSeverity(Enum):
68     """
69     Severity of an alarm
70     """
71     UNSPECIFIED = auto()
72     CRITICAL = auto()
73     MAJOR = auto()
74     MINOR = auto()
75     WARNING = auto()
76     CLEARED = auto()
77     DEFAULT = auto()
78
79
80 class AlarmDetail(dict):
81     """
82     An alarm that can be raised or cleared.
83
84     Parameters
85     ----------
86     managed_object_id: str
87         The name of the managed object that is the cause of the fault (required)
88
89     application_id: str
90         The name of the process that raised the alarm (required)
91
92     specific_problem: int
93         The problem that is the cause of the alarm
94
95     perceived_severity: AlarmSeverity
96         The severity of the alarm, a value from the enum.
97
98     identifying_info: str
99         Identifying additional information, which is part of alarm identity
100
101     additional_info: str
102         Additional information given by the application (optional)
103     """
104     # pylint: disable=too-many-arguments
105     def __init__(self,
106                  managed_object_id: str,
107                  application_id: str,
108                  specific_problem: int,
109                  perceived_severity: AlarmSeverity,
110                  identifying_info: str,
111                  additional_info: str = ""):
112         """
113         Creates an object with the specified items.
114         """
115         dict.__init__(self)
116         self[KEY_MANAGED_OBJECT_ID] = managed_object_id
117         self[KEY_APPLICATION_ID] = application_id
118         self[KEY_SPECIFIC_PROBLEM] = specific_problem
119         self[KEY_PERCEIVED_SEVERITY] = perceived_severity.name
120         self[KEY_IDENTIFYING_INFO] = identifying_info
121         self[KEY_ADDITIONAL_INFO] = additional_info
122
123
124 class AlarmManager:
125     """
126     Provides an API for an Xapp to raise and clear alarms by sending messages
127     via RMR, which should route the messages to an Alarm Adapter.
128
129     Parameters
130     ----------
131     vctx: ctypes c_void_p
132         Pointer to RMR context obtained by initializing RMR.
133         The context is used to allocate space and send messages.
134         The RMR routing table must have a destination for message
135         type RIC_ALARM_UPDATE as defined in this module.
136
137     managed_object_id: str
138         The name of the managed object that raises alarms
139
140     application_id: str
141         The name of the process that raises alarms
142     """
143     def __init__(self,
144                  vctx: c_void_p,
145                  managed_object_id: str,
146                  application_id: str):
147         """
148         Creates an alarm manager.
149         """
150         self.vctx = vctx
151         self.managed_object_id = managed_object_id
152         self.application_id = application_id
153
154     def create_alarm(self,
155                      specific_problem: int,
156                      perceived_severity: AlarmSeverity,
157                      identifying_info: str,
158                      additional_info: str = ""):
159         """
160         Convenience method that creates an alarm instance, an AlarmDetail object,
161         using cached values for managed object ID and application ID.
162
163         Parameters
164         ----------
165         specific_problem: int
166             The problem that is the cause of the alarm
167
168         perceived_severity: AlarmSeverity
169             The severity of the alarm, a value from the enum.
170
171         identifying_info: str
172             Identifying additional information, which is part of alarm identity
173
174         additional_info: str
175             Additional information given by the application (optional)
176
177         Returns
178         -------
179         AlarmDetail
180         """
181         return AlarmDetail(managed_object_id=self.managed_object_id,
182                            application_id=self.application_id,
183                            specific_problem=specific_problem, perceived_severity=perceived_severity,
184                            identifying_info=identifying_info, additional_info=additional_info)
185
186     @staticmethod
187     def _create_alarm_message(alarm: AlarmDetail, action: AlarmAction):
188         """
189         Creates a dict with the specified alarm detail plus action and time.
190         Uses the current system time in milliseconds since the Epoch.
191
192         Parameters
193         ----------
194         detail: AlarmDetail
195             The alarm details.
196
197         action: AlarmAction
198             The action to perform at the Alarm Adapter on this alarm.
199         """
200         return {
201             **alarm,
202             KEY_ALARM_ACTION: action.name,
203             KEY_ALARM_TIME: int(round(time.time() * 1000))
204         }
205
206     def _rmr_send_alarm(self, msg: dict):
207         """
208         Serializes the dict and sends the result via RMR using a predefined message type.
209
210         Parameters
211         ----------
212         msg: dict
213             Dictionary with alarm message to encode and send
214
215         Returns
216         -------
217         bool
218             True if the send succeeded (possibly with retries), False otherwise
219         """
220         payload = json.dumps(msg).encode()
221         mdc_logger.debug("_rmr_send_alarm: payload is {}".format(payload))
222         sbuf = rmr.rmr_alloc_msg(vctx=self.vctx, size=len(payload), payload=payload,
223                                  mtype=RIC_ALARM_UPDATE, gen_transaction_id=True)
224
225         for _ in range(0, RETRIES):
226             sbuf = rmr.rmr_send_msg(self.vctx, sbuf)
227             post_send_summary = rmr.message_summary(sbuf)
228             mdc_logger.debug("_rmr_send_alarm: try {0} result is {1}".format(_, post_send_summary[rmr.RMR_MS_MSG_STATE]))
229             # stop trying if RMR does not indicate retry
230             if post_send_summary[rmr.RMR_MS_MSG_STATE] != rmr.RMR_ERR_RETRY:
231                 break
232
233         rmr.rmr_free_msg(sbuf)
234         if post_send_summary[rmr.RMR_MS_MSG_STATE] != rmr.RMR_OK:
235             mdc_logger.warning("_rmr_send_alarm: failed after {} retries".format(RETRIES))
236             return False
237
238         return True
239
240     def raise_alarm(self, detail: AlarmDetail):
241         """
242         Builds and sends a message to the AlarmAdapter to raise an alarm
243         with the specified detail.
244
245         Parameters
246         ----------
247         detail: AlarmDetail
248             Alarm to raise
249
250         Returns
251         -------
252         bool
253             True if the send succeeded (possibly with retries), False otherwise
254         """
255         msg = self._create_alarm_message(detail, AlarmAction.RAISE)
256         return self._rmr_send_alarm(msg)
257
258     def clear_alarm(self, detail: AlarmDetail):
259         """
260         Builds and sends a message to the AlarmAdapter to clear the alarm
261         with the specified detail.
262
263         Parameters
264         ----------
265         detail: AlarmDetail
266             Alarm to clear
267
268         Returns
269         -------
270         bool
271             True if the send succeeded (possibly with retries), False otherwise
272         """
273         msg = self._create_alarm_message(detail, AlarmAction.CLEAR)
274         return self._rmr_send_alarm(msg)
275
276     def reraise_alarm(self, detail: AlarmDetail):
277         """
278         Builds and sends a message to the AlarmAdapter to clear the alarm with the
279         the specified detail, then builds and sends a message to raise the alarm again.
280
281         Parameters
282         ----------
283         detail: AlarmDetail
284             Alarm to clear and raise again.
285
286         Returns
287         -------
288         bool
289             True if the send succeeded (possibly with retries), False otherwise
290         """
291         success = self.clear_alarm(detail)
292         if success:
293             success = self.raise_alarm(detail)
294         return success
295
296     def clear_all_alarms(self):
297         """
298         Builds and sends a message to the AlarmAdapter to clear all alarms.
299
300         Returns
301         -------
302         bool
303             True if the send succeeded (possibly with retries), False otherwise
304         """
305         detail = self.create_alarm(0, AlarmSeverity.DEFAULT, "", "")
306         msg = self._create_alarm_message(detail, AlarmAction.CLEARALL)
307         return self._rmr_send_alarm(msg)