Lott, Christopher (cl778h) | 81084bc | 2020-06-01 20:53:12 -0400 | [diff] [blame^] | 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) |