RIC-642 related changes: REST subscription, rnib enhancements, symptomdata, rest...
[ric-plt/xapp-frame-py.git] / ricxappframe / xapp_symptomdata.py
1 #       Copyright (c) 2022 Nokia
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 # Symptomdata collection is triggered from the trblmgr ricplt pod. This subsystem provides for xapp interface to subscribe the
16 # symptomdata collection via lwsd pod. When the symptomdata collection is triggered then the xapp gets the callback to collect
17 # the symptomdata.
18 #
19 # If the dynamic registration is needed, then the xapp needs to use the Symptomdata.subscribe(...) method to indicate symptomdata
20 # collection. In case the xapp is set to trblmgr config file then the registration is not needed.
21 #
22 # If the xapp has the internal data for symptomdata collection REST call response, it can use the helper methods getFileList and collect
23 # to get the needed files or readymade zipped package for reponse.
24 #
25 import os
26 import re
27 import time
28 import requests
29 import json
30 from requests.exceptions import HTTPError
31 from zipfile import ZipFile
32 from threading import Timer
33 from datetime import datetime
34 from mdclogpy import Logger
35
36 logging = Logger(name=__name__)
37
38
39 class RepeatTimer(Timer):
40     # timer class for housekeeping and file rotating
41     def run(self):
42         while not self.finished.wait(self.interval):
43             self.function(*self.args, **self.kwargs)
44
45
46 class Symptomdata(object):
47     # service is the local POD service id, path the temporal storage, host should be the trblmgr service name
48     def __init__(self, service="", servicehost="", path="/tmp/", lwsduri=None, timeout=30):
49         """
50         init
51
52         Parameters
53         ----------
54         service: string
55             xapp service name
56         servicehost: string
57             xapp service host name
58         path:
59             temporal path where the symptomdata collection is stored
60         lwsduri:
61             lwsd uri for symptomdata dynamic registration
62         timeout:
63             timeout for subscription status polling
64         """
65         if not os.path.exists(path):
66             os.mkdir(path)
67         self.service = service
68         self.servicehost = servicehost
69         self.path = path
70         self.lwsduri = lwsduri
71         self.timeout = timeout
72         # runtime attrs
73         self.zipfilename = None
74         logging.info("Symptomdata init service:%s path:%s lwsduri:%s timeout:%d" % (self.service, self.path, self.lwsduri, self.timeout))
75         if self.lwsduri is not None:
76             # do the subscription, set to True so that first the query is triggered
77             self.lwsdok = True
78             self.subscribe(args=("",))
79             self.subscribetimer = RepeatTimer(self.timeout, self.subscribe, args=("",))
80             self.subscribetimer.start()
81
82     # make the symptomdata subscription query to lwsd - dynamic registration (needed if the static config in trblmgr does not have xapp service data)
83     def subscribe(self, args):
84         """
85         subscribe
86             internally used subscription function if the dynamic registration has been set
87         """
88         if self.lwsduri is not None:
89             try:
90                 proxies = {"http": "", "https": ""}    # disable proxy usage
91                 headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
92                 if self.lwsdok is False:
93                     jsondata = json.dumps({'url': 'http://' + self.servicehost +
94                                            ':8080/ric/v1/symptomdata', 'service': self.service, 'instance': '1'})
95                     response = requests.post(self.lwsduri,
96                                              data=jsondata,
97                                              headers=headers,
98                                              proxies=proxies)
99                     logging.info("Symptomdata subscription success")
100                     self.lwsdok = True
101                 elif self.lwsdok is True:
102                     self.lwsdok = False
103                     response = requests.get(self.lwsduri, headers=headers, proxies=proxies)
104                     for item in response.json():
105                         if item.get('service') == self.service:
106                             logging.info("Symptomdata subscription request success")
107                             self.lwsdok = True
108                     if self.lwsdok is False:
109                         logging.error("Symptomdata subscription missing")
110                 response.raise_for_status()
111             except HTTPError as http_err:
112                 logging.error("Symptomdata subscription failed - http error : %s" % (http_err))
113                 self.lwsdok = False
114             except Exception as err:
115                 logging.error("Symptomdata subscription failed - error : %s" % (err))
116                 self.lwsdok = False
117
118     def stop(self):
119         """
120         stop
121             stops the dynamic service registration/polling
122         """
123         if self.subscribetimer is not None:
124             self.subscribetimer.cancel()
125
126     def __del__(self):
127         if self.subscribetimer is not None:
128             self.subscribetimer.cancel()
129
130     def getFileList(self, regex, fromtime, totime):
131         """
132         getFileList
133             internal use only, get the matching files for collect method
134         """
135         fileList = []
136         path, wc = regex.rsplit('/', 1)
137         logging.info("Filtering path: %s using wildcard %s fromtime %d totime %d" % (path + '/', wc, fromtime, totime))
138         try:
139             for root, dirs, files in os.walk((path + '/')):
140                 for filename in files:
141                     if re.match(wc, filename):
142                         file_path = os.path.join(root, filename)
143                         filest = os.stat(file_path)
144                         if fromtime > 0:
145                             logging.info("Filtering file time %d fromtime %d totime %d" % (filest.st_ctime, fromtime, totime))
146                             if fromtime <= filest.st_ctime:
147                                 logging.info("Adding file time %d fromtime %d" % (filest.st_ctime, fromtime))
148                                 if totime > 0:
149                                     if totime >= filest.st_ctime:
150                                         fileList.append(file_path)
151                                 else:
152                                     fileList.append(file_path)
153                         elif totime > 0:
154                             if totime >= filest.st_ctime:
155                                 logging.info("Filtering file time %d fromtime %d totime %d" % (filest.st_ctime, fromtime, totime))
156                                 fileList.append(file_path)
157                         else:
158                             fileList.append(file_path)
159
160         except OSError as e:
161             logging.error("System error %d" % (e.errno))
162         return fileList
163
164     def collect(self, zipfiletmpl, fileregexlist, fromtime, totime):
165         """
166         collect
167             collects the symptomdata based on the file regular expression match and stored the symptomdata. Optionaly
168             caller can use fromtime and totime to choose only files matching the access time
169
170         Parameters
171         ----------
172         zipfiletmpl: string
173             template for zip file name using the strftime format - ex: ``"symptomdata"+'-%Y-%m-%d-%H-%M-%S.zip'``
174         fileregexlist: string array
175             array for file matching - ex: ``('examples/*.csv',)``
176         fromtime: integer
177             time value seconds
178         totime: integer
179             time value seconds
180         Returns
181         -------
182         string
183             zipfile name
184         """
185         zipfilename = self.path + datetime.fromtimestamp(int(time.time())).strftime(zipfiletmpl)
186         logging.info("Compressing files to symptomdata archive: %s" % (zipfilename))
187         zipdata = ZipFile(zipfilename, "w")
188         self.remove()
189         self.zipfilename = None
190         fileCnt = 0
191         for fileregex in fileregexlist:
192             logging.info("Compressing files using %s" % (fileregex))
193             fileList = self.getFileList(fileregex, fromtime, totime)
194             try:
195                 if len(fileList) > 0:
196                     for file_path in fileList:
197                         logging.info("Adding file %s to archive" % (file_path))
198                         zipdata.write(file_path, file_path)
199                         fileCnt += 1
200             except OSError as e:
201                 logging.error("System error %d" % (e.errno))
202         zipdata.close()
203         if fileCnt > 0:
204             self.zipfilename = zipfilename
205         return self.zipfilename
206
207     def read(self):
208         """
209         read
210             reads the stored symptomdata file content
211
212         Returns
213         -------
214         string
215             zipfile name
216         integer
217             data lenght
218         bytes
219             bytes of the file data
220         """
221         data = None
222         with open(self.zipfilename, 'rb') as file:
223             data = file.read()
224         return (self.zipfilename, len(data), data)
225
226     def remove(self):
227         if self.zipfilename is not None:
228             os.remove(self.zipfilename)