Code Coverage

Cobertura Coverage Report > osm_policy_module.alarming >

service.py

Trend

File Coverage summary

NameClassesLinesConditionals
service.py
100%
1/1
53%
89/169
100%
0/0

Coverage Breakdown by Class

NameLinesConditionals
service.py
53%
89/169
N/A

Source

osm_policy_module/alarming/service.py
1 # -*- coding: utf-8 -*-
2 # pylint: disable=no-member
3
4 # Copyright 2018 Whitestack, LLC
5 # *************************************************************
6
7 # This file is part of OSM Monitoring module
8 # All Rights Reserved to Whitestack, LLC
9
10 # Licensed under the Apache License, Version 2.0 (the "License"); you may
11 # not use this file except in compliance with the License. You may obtain
12 # a copy of the License at
13
14 #         http://www.apache.org/licenses/LICENSE-2.0
15
16 # Unless required by applicable law or agreed to in writing, software
17 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
18 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
19 # License for the specific language governing permissions and limitations
20 # under the License.
21
22 # For those usages not covered by the Apache License, Version 2.0 please
23 # contact: bdiaz@whitestack.com or glavado@whitestack.com
24 ##
25 1 import json
26 1 import logging
27 1 import operator
28 1 import functools
29
30 1 import requests
31 1 from requests.exceptions import ConnectionError, RequestException
32
33 1 from osm_policy_module.common.common_db_client import CommonDbClient
34 1 from osm_policy_module.common.lcm_client import LcmClient
35 1 from osm_policy_module.common.mon_client import MonClient
36 1 from osm_policy_module.core import database
37 1 from osm_policy_module.core.config import Config
38 1 from osm_policy_module.core.database import (
39     VnfAlarm,
40     VnfAlarmRepository,
41     AlarmActionRepository,
42 )
43 1 from osm_policy_module.core.exceptions import VdurNotFound
44
45 1 log = logging.getLogger(__name__)
46
47
48 1 class AlarmingService:
49 1     def __init__(self, config: Config):
50 1         self.conf = config
51 1         self.db_client = CommonDbClient(config)
52 1         self.mon_client = MonClient(config)
53 1         self.lcm_client = LcmClient(config)
54
55 1     async def configure_vnf_alarms(self, nsr_id: str, vnf_member_index=None):
56 1         log.info("Configuring vnf alarms for network service %s", nsr_id)
57 1         alarms_created = []
58 1         database.db.connect()
59 1         try:
60 1             with database.db.atomic():
61 1                 if vnf_member_index is None:
62 1                     vnfrs = self.db_client.get_vnfrs(nsr_id)
63                 else:
64 0                     vnfrs = []
65 0                     vnfr = self.db_client.get_vnfr(nsr_id, vnf_member_index)
66 0                     vnfrs.append(vnfr)
67                 # vnfrs = self.db_client.get_vnfrs(nsr_id)
68 1                 for vnfr in vnfrs:
69 1                     log.debug("Processing vnfr: %s", vnfr)
70 1                     vnfd = self.db_client.get_vnfd(vnfr["vnfd-id"])
71 1                     for vdur in vnfr["vdur"]:
72 1                         vdu = next(
73                             filter(
74                                 lambda vdu: vdu["id"] == vdur["vdu-id-ref"], vnfd["vdu"]
75                             )
76                         )
77 1                         if "alarm" in vdu:
78 1                             alarm_descriptors = vdu["alarm"]
79 1                             for alarm_descriptor in alarm_descriptors:
80 1                                 try:
81 1                                     VnfAlarmRepository.get(
82                                         VnfAlarm.alarm_id
83                                         == alarm_descriptor["alarm-id"],
84                                         VnfAlarm.vnf_member_index
85                                         == vnfr["member-vnf-index-ref"],
86                                         VnfAlarm.vdu_name == vdur["name"],
87                                         VnfAlarm.nsr_id == nsr_id,
88                                     )
89 0                                     log.debug(
90                                         "vdu %s already has an alarm configured with same id %s",
91                                         vdur["name"],
92                                         alarm_descriptor["alarm-id"],
93                                     )
94 0                                     continue
95 1                                 except VnfAlarm.DoesNotExist:
96 1                                     pass
97 1                                 vnf_monitoring_param = next(
98                                     filter(
99                                         lambda param: param["id"]
100                                         == alarm_descriptor["vnf-monitoring-param-ref"],
101                                         vdu.get("monitoring-parameter", []),
102                                     ),
103                                     {},
104                                 )
105 1                                 metric_name = self._get_metric_name(
106                                     vnf_monitoring_param
107                                 )
108 1                                 alarm_action = dict()
109 1                                 for action_type in ["ok", "insufficient-data", "alarm"]:
110 1                                     if (
111                                         "actions" in alarm_descriptor
112                                         and action_type in alarm_descriptor["actions"]
113                                     ):
114 1                                         for url in alarm_descriptor["actions"][
115                                             action_type
116                                         ]:
117 1                                             if "webhook" in alarm_action:
118 1                                                 alarm_action["webhook"].append(
119                                                     url["url"]
120                                                 )
121                                             else:
122 1                                                 alarm_action["webhook"] = [url["url"]]
123 1                                 alarm_uuid = await self.mon_client.create_alarm(
124                                     metric_name=metric_name,
125                                     ns_id=nsr_id,
126                                     vdu_name=vdur["name"],
127                                     vnf_member_index=vnfr["member-vnf-index-ref"],
128                                     threshold=alarm_descriptor["value"],
129                                     operation=alarm_descriptor["operation"],
130                                     action=str(alarm_action),
131                                     vnfr=vnfr,
132                                     vnfd=vnfd,
133                                 )
134 1                                 alarm = VnfAlarmRepository.create(
135                                     alarm_id=alarm_descriptor["alarm-id"],
136                                     alarm_uuid=alarm_uuid,
137                                     nsr_id=nsr_id,
138                                     vnf_member_index=vnfr["member-vnf-index-ref"],
139                                     vdu_name=vdur["name"],
140                                     last_action="insufficient-data",
141                                     id_suffix=0,
142                                     ok_ack=False,
143                                     alarm_ack=False,
144                                 )
145 1                                 for action_type in ["ok", "insufficient-data", "alarm"]:
146 1                                     if (
147                                         "actions" in alarm_descriptor
148                                         and action_type in alarm_descriptor["actions"]
149                                     ):
150 1                                         for url in alarm_descriptor["actions"][
151                                             action_type
152                                         ]:
153 1                                             AlarmActionRepository.create(
154                                                 type=action_type,
155                                                 url=url["url"],
156                                                 alarm=alarm,
157                                             )
158 1                                 alarms_created.append(alarm)
159
160 0         except Exception as e:
161 0             log.exception("Error configuring VNF alarms:")
162 0             if len(alarms_created) > 0:
163 0                 log.debug("Cleaning alarm resources in MON")
164 0                 for alarm in alarms_created:
165 0                     try:
166 0                         await self.mon_client.delete_alarm(
167                             alarm.nsr_id,
168                             alarm.vnf_member_index,
169                             alarm.vdu_name,
170                             alarm.alarm_uuid,
171                         )
172 0                     except ValueError:
173 0                         log.exception(
174                             "Error deleting alarm in MON %s", alarm.alarm_uuid
175                         )
176 0             raise e
177         finally:
178 1             database.db.close()
179
180 1     async def delete_orphaned_alarms(self, nsr_id):
181         # TODO: Review as it seems this code is never called
182 0         log.info("Deleting orphaned vnf alarms for network service %s", nsr_id)
183 0         database.db.connect()
184 0         try:
185 0             with database.db.atomic():
186 0                 for alarm in VnfAlarmRepository.list(VnfAlarm.nsr_id == nsr_id):
187 0                     try:
188 0                         self.db_client.get_vdur(
189                             nsr_id, alarm.vnf_member_index, alarm.vdu_name
190                         )
191 0                     except VdurNotFound:
192 0                         log.debug("Deleting orphaned alarm %s", alarm.alarm_uuid)
193 0                         try:
194 0                             await self.mon_client.delete_alarm(
195                                 alarm.nsr_id,
196                                 alarm.vnf_member_index,
197                                 alarm.vdu_name,
198                                 alarm.alarm_uuid,
199                             )
200 0                         except ValueError:
201 0                             log.exception(
202                                 "Error deleting alarm in MON %s", alarm.alarm_uuid
203                             )
204 0                         alarm.delete_instance()
205 0         except Exception as e:
206 0             log.exception("Error deleting orphaned alarms:")
207 0             raise e
208         finally:
209 0             database.db.close()
210
211 1     async def delete_vnf_alarms(self, nsr_id, vnf_member_index=None):
212 0         log.info("Deleting vnf alarms for network service %s", nsr_id)
213 0         database.db.connect()
214 0         try:
215 0             with database.db.atomic():
216 0                 if vnf_member_index is None:
217 0                     alarm_conditions = VnfAlarm.nsr_id == nsr_id
218                 else:
219 0                     query_list = [
220                         VnfAlarm.nsr_id == nsr_id,
221                         VnfAlarm.vnf_member_index == vnf_member_index,
222                     ]
223 0                     alarm_conditions = functools.reduce(operator.and_, query_list)
224 0                 for alarm in VnfAlarmRepository.list(alarm_conditions):
225 0                     log.debug("Deleting vnf alarm %s", alarm.alarm_uuid)
226 0                     try:
227 0                         await self.mon_client.delete_alarm(
228                             alarm.nsr_id,
229                             alarm.vnf_member_index,
230                             alarm.vdu_name,
231                             alarm.alarm_uuid,
232                         )
233 0                     except ValueError:
234 0                         log.exception(
235                             "Error deleting alarm in MON %s", alarm.alarm_uuid
236                         )
237 0                     alarm.delete_instance()
238
239 0         except Exception as e:
240 0             log.exception("Error deleting vnf alarms:")
241 0             raise e
242         finally:
243 0             database.db.close()
244
245 1     async def handle_alarm(self, alarm_uuid: str, status: str, payload: dict):
246 1         alert_timeout = int(self.conf.get("alert", "timeout"))
247 1         database.db.connect()
248 1         try:
249 1             with database.db.atomic():
250 1                 alarm = VnfAlarmRepository.get(VnfAlarm.alarm_uuid == alarm_uuid)
251 1                 log.debug(
252                     "Handling vnf alarm %s with status %s", alarm.alarm_id, status
253                 )
254 1                 for action in alarm.actions:
255                     """
256                     Compares the current status with the last_action status.
257                     If both the status are 'alarm', it avoid sending repetitive alarm notification.
258                     If both the status are 'ok', it avoid sending repetitive ok notification.
259                     """
260 1                     if action.type == status:
261 1                         if bool(self.conf.get("alert", "enhanced_alarms")):
262 1                             if (
263                                 status != "ok"
264                                 or (status == "ok" and alarm.ok_ack is False)
265                             ) and (
266                                 status != "alarm"
267                                 or (status == "alarm" and alarm.alarm_ack is False)
268                             ):
269 1                                 log.info(
270                                     "Executing request to url %s for vnf alarm %s with status %s",
271                                     action.url,
272                                     alarm.alarm_id,
273                                     status,
274                                 )
275 1                                 try:
276 1                                     if (
277                                         status == "alarm"
278                                         and alarm.last_action == "ok"
279                                         or (
280                                             status == "alarm"
281                                             and alarm.last_action == "insufficient-data"
282                                         )
283                                     ):
284 1                                         alarm.id_suffix += 1
285 1                                         alarm.ok_ack = False
286 1                                     if status == "ok" and alarm.last_action == "alarm":
287 1                                         alarm.alarm_ack = False
288 1                                     alarm.last_action = status
289 1                                     alarm.save()
290 0                                 except Exception as e:
291 0                                     log.exception(e)
292
293 1                                 payload["notify_details"][
294                                     "alarm_number"
295                                 ] = alarm.id_suffix
296 1                                 headers = {"content-type": "application/json"}
297 1                                 try:
298 1                                     resp = requests.post(
299                                         url=action.url,
300                                         data=json.dumps(payload),
301                                         headers=headers,
302                                         timeout=alert_timeout,
303                                     )
304 1                                     log.info("Response %s", resp)
305 1                                     if resp.status_code == 200:
306 0                                         if status == "ok":
307 0                                             alarm.ok_ack = True
308 0                                             alarm.save()
309 0                                         if status == "alarm":
310 0                                             alarm.alarm_ack = True
311 0                                             alarm.save()
312 0                                         if status == "insufficient-data":
313 0                                             alarm.alarm_ack = False
314 0                                             alarm.ok_ack = False
315 0                                             alarm.save()
316 0                                 except ConnectionError:
317 0                                     log.exception(
318                                         "Error connecting to url %s", action.url
319                                     )
320 0                                 except RequestException as e:
321 0                                     log.info(
322                                         "Error: RequestException while connecting to url %s",
323                                         action.url,
324                                     )
325 0                                     log.debug("RequestException %s", e)
326
327                         else:
328 0                             log.info(
329                                 "Executing request to url %s for vnf alarm %s with status %s",
330                                 action.url,
331                                 alarm.alarm_id,
332                                 status,
333                             )
334 0                             try:
335 0                                 requests.post(
336                                     url=action.url,
337                                     json=json.dumps(payload),
338                                     timeout=alert_timeout,
339                                 )
340 0                             except ConnectionError:
341 0                                 log.exception("Error connecting to url %s", action.url)
342 0                             except RequestException as e:
343 0                                 log.info(
344                                     "Error: RequestException while connecting to url %s",
345                                     action.url,
346                                 )
347 0                                 log.debug("RequestException %s", e)
348
349 0         except VnfAlarm.DoesNotExist:
350 0             log.debug(
351                 "There is no alarming action configured for alarm %s.", alarm_uuid
352             )
353         finally:
354 1             database.db.close()
355
356 1     def _get_metric_name(self, vnf_monitoring_param: dict):
357 1         if "performance-metric" in vnf_monitoring_param:
358 1             return vnf_monitoring_param["performance-metric"]
359 0         raise ValueError(
360             "No metric name found for vnf_monitoring_param %s"
361             % vnf_monitoring_param["id"]
362         )