Fix install O2 on subcloud failed
[pti/o2.git] / o2ims / adapter / clients / fault_client.py
1 # Copyright (C) 2022-2024 Wind River Systems, Inc.
2 #
3 #  Licensed under the Apache License, Version 2.0 (the "License");
4 #  you may not use this file except in compliance with the License.
5 #  You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 #  Unless required by applicable law or agreed to in writing, software
10 #  distributed under the License is distributed on an "AS IS" BASIS,
11 #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 #  See the License for the specific language governing permissions and
13 #  limitations under the License.
14
15 # client talking to Stx standalone
16
17 from typing import List  # Optional,  Set
18 import uuid as uuid
19
20 from cgtsclient.client import get_client as get_stx_client
21 from cgtsclient.exc import EndpointException
22 from dcmanagerclient.api.client import client as get_dc_client
23 from fmclient.client import get_client as get_fm_client
24 from fmclient.common.exceptions import HTTPNotFound, HttpServerError
25
26 from o2app.adapter import unit_of_work
27 from o2common.config import config
28 from o2common.service.client.base_client import BaseClient
29 from o2ims.domain import alarm_obj as alarmModel
30
31 from o2common.helper import o2logging
32 logger = o2logging.get_logger(__name__)
33
34
35 CGTSCLIENT_ENDPOINT_ERROR_MSG = \
36     'Must provide Keystone credentials or user-defined endpoint and token'
37
38
39 class StxAlarmClient(BaseClient):
40     def __init__(self, uow: unit_of_work.AbstractUnitOfWork, driver=None):
41         super().__init__()
42         self.driver = driver if driver else StxFaultClientImp()
43         self.uow = uow
44
45     def _get(self, id) -> alarmModel.FaultGenericModel:
46         return self.driver.getAlarmInfo(id)
47
48     def _list(self, **filters) -> List[alarmModel.FaultGenericModel]:
49         newmodels = self.driver.getAlarmList(**filters)
50         uow = self.uow
51         exist_alarms = {}
52         with uow:
53             rs = uow.session.execute(
54                 '''
55                 SELECT "alarmEventRecordId"
56                 FROM "alarmEventRecord"
57                 WHERE "perceivedSeverity" != :perceived_severity_enum
58                 ''',
59                 dict(perceived_severity_enum=alarmModel.PerceivedSeverityEnum.
60                      CLEARED)
61             )
62             for row in rs:
63                 id = row[0]
64                 # logger.debug('Exist alarm: ' + id)
65                 exist_alarms[id] = False
66
67         ret = []
68         for m in newmodels:
69             try:
70                 if exist_alarms[m.id]:
71                     ret.append(m)
72                     exist_alarms[m.id] = True
73             except KeyError:
74                 logger.debug('alarm new: ' + m.id)
75                 ret.append(m)
76
77         for alarm in exist_alarms:
78             logger.debug('exist alarm: ' + alarm)
79             if exist_alarms[alarm]:
80                 # exist alarm is active
81                 continue
82             try:
83                 event = self._get(alarm)
84             except HTTPNotFound:
85                 logger.debug('alarm {} not in this resource pool {}'
86                              .format(alarm, self._pool_id))
87                 continue
88             except HttpServerError:
89                 # TODO(jon): This exception needs to be removed when the
90                 # INF-457 related FM client upgrade and issue fix occur.
91                 logger.debug('alarm {} query failed'.format(alarm))
92                 continue
93             ret.append(event)
94
95         return ret
96
97     def _set_stx_client(self):
98         self.driver.setFaultClient(self._pool_id)
99
100     def delete(self, id) -> alarmModel.FaultGenericModel:
101         return self.driver.deleteAlarm(id)
102
103
104 class StxEventClient(BaseClient):
105     def __init__(self, uow: unit_of_work.AbstractUnitOfWork, driver=None):
106         super().__init__()
107         self.driver = driver if driver else StxFaultClientImp()
108         self.uow = uow
109
110     def _get(self, id) -> alarmModel.FaultGenericModel:
111         return self.driver.getEventInfo(id)
112
113     def _list(self, **filters) -> List[alarmModel.FaultGenericModel]:
114         return self.driver.getEventList(**filters)
115
116     def _set_stx_client(self):
117         self.driver.setFaultClient(self._pool_id)
118
119     def suppression_list(self, alarm_id) -> List[alarmModel.FaultGenericModel]:
120         return self.driver.getSuppressionList(alarm_id)
121
122     def suppress(self, id) -> alarmModel.FaultGenericModel:
123         return self.driver.suppressEvent(id)
124
125
126 # internal driver which implement client call to Stx Fault Management instance
127 class StxFaultClientImp(object):
128     def __init__(self, fm_client=None, stx_client=None, dc_client=None):
129         super().__init__()
130         self.fmclient = fm_client if fm_client else self.getFmClient()
131         self.stxclient = stx_client if stx_client else self.getStxClient()
132         self.dcclient = dc_client if dc_client else self.getDcmanagerClient()
133
134     def getStxClient(self):
135         os_client_args = config.get_stx_access_info()
136         config_client = get_stx_client(**os_client_args)
137         return config_client
138
139     def getDcmanagerClient(self):
140         os_client_args = config.get_dc_access_info()
141         config_client = get_dc_client(**os_client_args)
142         return config_client
143
144     def getFmClient(self):
145         os_client_args = config.get_fm_access_info()
146         config_client = get_fm_client(1, **os_client_args)
147         return config_client
148
149     def getSubcloudList(self):
150         self.dcclient = self.getDcmanagerClient()
151         subs = self.dcclient.subcloud_manager.list_subclouds()
152         known_subs = [sub for sub in subs if sub.sync_status != 'unknown']
153         return known_subs
154
155     def getSubcloudFaultClient(self, subcloud_id):
156         subcloud = self.dcclient.subcloud_manager.\
157             subcloud_additional_details(subcloud_id)
158         logger.debug('subcloud name: %s, oam_floating_ip: %s' %
159                      (subcloud[0].name, subcloud[0].oam_floating_ip))
160         try:
161             sub_is_https = False
162             os_client_args = config.get_stx_access_info(
163                 region_name=subcloud[0].region_name,
164                 subcloud_hostname=subcloud[0].oam_floating_ip)
165             stx_client = get_stx_client(**os_client_args)
166         except EndpointException as e:
167             msg = e.format_message()
168             if CGTSCLIENT_ENDPOINT_ERROR_MSG in msg:
169                 sub_is_https = True
170                 os_client_args = config.get_stx_access_info(
171                     region_name=subcloud[0].region_name,
172                     sub_is_https=sub_is_https,
173                     subcloud_hostname=subcloud[0].oam_floating_ip)
174                 stx_client = get_stx_client(**os_client_args)
175             else:
176                 raise ValueError('Stx endpoint exception: %s' % msg)
177         except Exception:
178             raise ValueError('cgtsclient get subcloud client failed')
179
180         os_client_args = config.get_fm_access_info(
181             sub_is_https=sub_is_https,
182             subcloud_hostname=subcloud[0].oam_floating_ip)
183         fm_client = get_fm_client(1, **os_client_args)
184
185         return stx_client, fm_client
186
187     def setFaultClient(self, resource_pool_id):
188         systems = self.stxclient.isystem.list()
189         if resource_pool_id == systems[0].uuid:
190             logger.debug('Fault Client not change: %s' % resource_pool_id)
191             self.fmclient = self.getFmClient()
192             return
193
194         subclouds = self.getSubcloudList()
195         for subcloud in subclouds:
196             substxclient, subfaultclient = self.getSubcloudFaultClient(
197                 subcloud.subcloud_id)
198             systems = substxclient.isystem.list()
199             if resource_pool_id == systems[0].uuid:
200                 self.fmclient = subfaultclient
201
202     def getAlarmList(self, **filters) -> List[alarmModel.FaultGenericModel]:
203         alarms = self.fmclient.alarm.list(expand=True)
204         if len(alarms) == 0:
205             return []
206         [logger.debug(
207             'alarm:' + str(alarm.to_dict())) for alarm in alarms if alarm]
208         return [alarmModel.FaultGenericModel(
209             alarmModel.EventTypeEnum.ALARM, self._alarmconverter(alarm))
210             for alarm in alarms if alarm]
211
212     def getAlarmInfo(self, id) -> alarmModel.FaultGenericModel:
213         try:
214             alarm = self.fmclient.alarm.get(id)
215             logger.debug(
216                 'get alarm id: ' + id + ', result:' + str(alarm.to_dict()))
217         except HTTPNotFound:
218             event = self.fmclient.event_log.get(id)
219             return alarmModel.FaultGenericModel(
220                 alarmModel.EventTypeEnum.ALARM, self._eventconverter(event,
221                                                                      True))
222         return alarmModel.FaultGenericModel(
223             alarmModel.EventTypeEnum.ALARM, self._alarmconverter(alarm))
224
225     def deleteAlarm(self, id) -> alarmModel.FaultGenericModel:
226         alarm = self.fmclient.alarm.delete(id)
227         logger.debug('delete alarm id ' + id + ':' + str(alarm.to_dict()))
228         return alarmModel.FaultGenericModel(
229             alarmModel.EventTypeEnum.ALARM, self._alarmconverter(alarm))
230
231     def getEventList(self, **filters) -> List[alarmModel.FaultGenericModel]:
232         events = self.fmclient.event_log.list(alarms=True, expand=True)
233         [logger.debug(
234             'alarm:' + str(event.to_dict())) for event in events if event]
235         return [alarmModel.FaultGenericModel(
236             alarmModel.EventTypeEnum.EVENT, self._eventconverter(event))
237             for event in events if event]
238
239     def getEventInfo(self, id) -> alarmModel.FaultGenericModel:
240         event = self.fmclient.event_log.get(id)
241         logger.debug('get event id ' + id + ':' + str(event.to_dict()))
242         return alarmModel.FaultGenericModel(
243             alarmModel.EventTypeEnum.EVENT, self._eventconverter(event))
244
245     def suppressEvent(self, id) -> alarmModel.FaultGenericModel:
246         patch = [dict(path='/' + 'suppression_status', value='suppressed',
247                       op='replace')]
248         event = self.fmclient.event_suppression.update(id, patch)
249         logger.debug('suppressed event id ' + id + ':' + str(event.to_dict()))
250         return alarmModel.FaultGenericModel(
251             alarmModel.EventTypeEnum.EVENT, self._suppression_converter(event))
252
253     def getSuppressionList(self, alarm_id) -> alarmModel.FaultGenericModel:
254         suppression_list = []
255         query_as_array = []
256         events = self.fmclient.event_suppression.list(q=query_as_array)
257         for event in events:
258             if event.alarm_id == alarm_id:
259                 # logger.debug('suppression event:' + str(event.to_dict()))
260                 suppression_list.append(
261                     alarmModel.FaultGenericModel(
262                         alarmModel.EventTypeEnum.EVENT,
263                         self._suppression_converter(event)))
264         return suppression_list
265
266     @staticmethod
267     def _alarmconverter(alarm):
268         selected_keys = [
269             'alarm_id', 'alarm_state', 'entity_type_id', 'entity_instance_id',
270             'reason_text', 'alarm_type', 'probable_cause',
271             'proposed_repair_action', 'service_affecting', 'suppression',
272             'suppression_status', 'mgmt_affecting', 'degrade_affecting'
273         ]
274         content = alarm.to_dict()
275         filtered = dict(
276             filter(lambda item: item[0] in selected_keys, content.items()))
277         setattr(alarm, 'filtered', filtered)
278         # setattr(alarm, 'alarm_def_id', uuid.uuid3(
279         #         uuid.NAMESPACE_URL, alarm.alarm_id))
280         setattr(alarm, 'state', alarm.alarm_state)
281
282         setattr(alarm, 'alarm_def_id', str(uuid.uuid3(
283                 uuid.NAMESPACE_URL, alarm.alarm_id)))
284         setattr(alarm, 'probable_cause_id', str(uuid.uuid3(
285                 uuid.NAMESPACE_URL, alarm.probable_cause)))
286         return alarm
287
288     @staticmethod
289     def _eventconverter(event, clear=False):
290         selected_keys = [
291             'event_log_id', 'state', 'entity_type_id',
292             'entity_instance_id', 'reason_text', 'event_log_type',
293             'probable_cause', 'proposed_repair_action',
294             'service_affecting', 'suppression', 'suppression_status'
295         ]
296         content = event.to_dict()
297         filtered = dict(
298             filter(lambda item: item[0] in selected_keys, content.items()))
299         setattr(event, 'filtered', filtered)
300         setattr(event, 'alarm_id', event.event_log_id)
301         setattr(event, 'alarm_type', event.event_log_type)
302         if clear:
303             logger.debug('alarm is clear')
304             event.state = 'clear'
305         setattr(event, 'alarm_def_id', str(uuid.uuid3(
306                 uuid.NAMESPACE_URL, event.alarm_id)))
307         setattr(event, 'probable_cause_id', str(uuid.uuid3(
308                 uuid.NAMESPACE_URL, event.probable_cause)))
309         return event
310
311     @staticmethod
312     def _suppression_converter(event, clear=False):
313         selected_keys = [
314             'alarm_id', 'description', 'suppression_status',
315             'links'
316         ]
317         content = event.to_dict()
318         filtered = dict(
319             filter(lambda item: item[0] in selected_keys, content.items()))
320         setattr(event, 'filtered', filtered)
321         setattr(event, 'uuid', event.uuid)
322         setattr(event, 'alarm_id', event.alarm_id)
323         setattr(event, 'description', event.description)
324         setattr(event, 'suppression_status', event.suppression_status)
325         setattr(event, 'alarm_type', None)
326         setattr(event, 'alarm_def_id', None)
327         setattr(event, 'probable_cause_id', None)
328         setattr(event, 'state', None)
329         setattr(event, 'timestamp', None)
330         return event
331
332     @staticmethod
333     def _alarmeventhasher(event, state=''):
334         # The event model and the alarm model have different parameter name
335         # of the state. alarm model is alarm_state, event model is state.
336         status = event.alarm_state if state == '' else state
337         return str(hash((event.uuid, event.timestamp, status)))