Seed code
[nonrtric/rapp/healthcheck.git] / src / main.py
1 #  ============LICENSE_START===============================================
2 #  Copyright (C) 2020 Nordix Foundation. All rights reserved.
3 #  ========================================================================
4 #  Licensed under the Apache License, Version 2.0 (the "License");
5 #  you may not use this file except in compliance with the License.
6 #  You may obtain a copy of the License at
7 #
8 #       http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #  Unless required by applicable law or agreed to in writing, software
11 #  distributed under the License is distributed on an "AS IS" BASIS,
12 #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 #  See the License for the specific language governing permissions and
14 #  limitations under the License.
15 #  ============LICENSE_END=================================================
16 #
17
18 import argparse
19 from datetime import datetime
20 from jinja2 import Template
21 from flask import Flask, request
22 import os.path
23 from os import path
24 from pygments.util import xrange
25 from requests import ConnectionError
26 import requests
27 import sys
28 import threading
29 import time
30
31 SERVICE_NAME = 'HealthCheck'
32 BASE_URL = 'http://localhost:8081'
33 RIC_CHUNK_SIZE = 10
34 TIME_BETWEEN_CHECKS = 60
35
36 type_to_use = ''
37 policy_body = ''
38
39 app = Flask(__name__)
40
41 # Server info
42 HOST_IP = "::"
43 HOST_PORT = 9990
44 APP_URL = "/stats"
45
46 stat_page_template = """
47 <!DOCTYPE html>
48 <html>
49     <head>
50         <meta http-equiv=\"refresh\" content=\"{{refreshTime}}\">
51         <title>Non-RealTime RIC Health Check</title>
52     </head>
53     <body>
54         <h3>General</h3>
55         <font face=\"monospace\">
56             Policy type ID:...............................{{policyTypeId}}<br>
57             Policy body path:.............................{{policyBodyPath}}<br>
58             Time of last check:...........................{{time}}<br>
59             Duration of check:............................{{duration}}<br>
60             Number of checks:.............................{{noOfChecks}}<br>
61         </font>
62         <h3>Near-RT RICs</h3>
63         <font face=\"monospace\">
64             Number of unavailable Near-RT RICS:...........{{noOfUnavailableRics}}<br>
65             Number of Near-RT RICS not supporting type....{{noOfNotSupportingRics}}<br>
66             Number of Near-RT RICS supporting type:.......{{noOfSupportingRics}}<br>
67         </font>
68         <h3>Policies</h3>
69         <font face=\"monospace\">
70             Number of created policies:...................{{noOfCreatedPolicies}}<br>
71             Number of read policies:......................{{noOfReadPolicies}}<br>
72             Number of updated policies:...................{{noOfUpdatedPolicies}}<br>
73             Number of deleted policies:...................{{noOfDeletedPolicies}}<br>
74         </font>
75     </body>
76 </html>
77 """
78 type_to_use = "2"
79 policy_body_path = 'pihw_template.json'
80
81 duration = 0
82 no_of_checks = 0
83 no_of_unavailable_rics = 0
84 no_of_rics_not_supporting_type = 0
85 no_of_rics_supporting_type = 0
86 no_of_created_policies = 0
87 no_of_read_policies = 0
88 no_of_updated_policies = 0
89 no_of_deleted_policies = 0
90
91 class Ric:
92
93     def __init__(self, name, supported_types, state):
94         self.name = name
95         self.supports_type_to_use = self.policy_type_supported(supported_types)
96         self.state = state
97         self.no_of_created_policies = 0
98         self.no_of_read_policies = 0
99         self.no_of_updated_policies = 0
100         self.no_of_deleted_policies = 0
101
102     def update_supported_types(self, supported_types):
103         self.supports_type_to_use = self.policy_type_supported(supported_types)
104
105     def policy_type_supported(self, supported_policy_types):
106         for supported_type in supported_policy_types:
107             if type_to_use == supported_type:
108                 return True
109
110         return False
111
112
113 class PolicyCheckThread (threading.Thread):
114
115     def __init__(self, thread_id, ric):
116         threading.Thread.__init__(self)
117         self.thread_id = thread_id
118         self.ric = ric
119
120     def run(self):
121         verboseprint(f'Checking ric: {self.ric.name}')
122         if put_policy(self.thread_id, self.ric.name):
123             verboseprint(f'Created policy: {self.thread_id} in ric: {self.ric.name}')
124             self.ric.no_of_created_policies += 1
125             time.sleep(0.5)
126             if get_policy(self.thread_id):
127                 verboseprint(f'Read policy: {self.thread_id} from ric: {self.ric.name}')
128                 self.ric.no_of_read_policies += 1
129             if put_policy(self.thread_id, self.ric.name, update_value=1):
130                 verboseprint(f'Updated policy: {self.thread_id} in ric: {self.ric.name}')
131                 self.ric.no_of_updated_policies += 1
132             if delete_policy(self.thread_id):
133                 verboseprint(f'Deleted policy: {self.thread_id} from ric: {self.ric.name}')
134                 self.ric.no_of_deleted_policies += 1
135
136
137 class MonitorServer (threading.Thread):
138     def __init__(self):
139         threading.Thread.__init__(self)
140
141     def run(self):
142         verboseprint('Staring monitor server')
143         app.run(port=HOST_PORT, host=HOST_IP)
144
145
146 @app.route(APP_URL,
147     methods=['GET'])
148 def produceStatsPage():
149     t = Template(stat_page_template)
150     page = t.render(refreshTime=TIME_BETWEEN_CHECKS, policyTypeId=type_to_use, policyBodyPath=policy_body_path,
151     time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), duration=duration, noOfChecks=no_of_checks,
152     noOfUnavailableRics=no_of_unavailable_rics, noOfNotSupportingRics=no_of_rics_not_supporting_type,
153     noOfSupportingRics=no_of_rics_supporting_type, noOfCreatedPolicies=no_of_created_policies,
154     noOfReadPolicies=no_of_read_policies, noOfUpdatedPolicies=no_of_updated_policies,
155     noOfDeletedPolicies=no_of_deleted_policies)
156     return page,200
157
158 def get_rics_from_agent():
159     resp = requests.get(BASE_URL + '/rics')
160     if not resp.ok:
161         verboseprint(f'Unable to get Rics {resp.status_code}')
162         return {}
163     return resp.json()
164
165
166 def create_ric_dict(rics_as_json):
167     rics = {}
168     for ric_info in rics_as_json:
169         rics[ric_info["ricName"]] = (Ric(ric_info["ricName"], ric_info["policyTypes"], ric_info['state']))
170         verboseprint(f'Adding ric: {rics[ric_info["ricName"]]}')
171
172     return rics
173
174
175 def update_rics():
176     added_rics = {}
177     for ric_info in get_rics_from_agent():
178         if ric_info["ricName"] in rics:
179             rics[ric_info["ricName"]].update_supported_types(ric_info["policyTypes"])
180             rics[ric_info["ricName"]].state = ric_info['state']
181         else:
182             added_rics[ric_info["ricName"]] = (Ric(ric_info["ricName"], ric_info["policyTypes"]))
183             verboseprint(f'Adding ric: {rics[ric_info["ricName"]]}')
184
185     rics.update(added_rics)
186
187
188 def put_policy(thread_id, ric_name, update_value=0):
189     policy_id = f'thread_{thread_id}'
190     complete_url = f'{BASE_URL}/policy?type={type_to_use}&id={policy_id}&ric={ric_name}&service={SERVICE_NAME}'
191     headers = {'content-type': 'application/json'}
192     resp = requests.put(complete_url, policy_body.replace('XXX', str(thread_id + update_value)), headers=headers, verify=False)
193
194     if not resp.ok:
195         verboseprint(f'Unable to create policy {resp}')
196         return False
197     else:
198         return True
199
200
201 def get_policy(thread_id):
202     policy_id = f'thread_{thread_id}'
203     complete_url = f'{BASE_URL}/policy?id={policy_id}'
204     resp = requests.get(complete_url)
205
206     if not resp.ok:
207         verboseprint(f'Unable to get policy {resp}')
208         return False
209     else:
210         return True
211
212
213 def delete_policy(thread_id):
214     policy_id = f'thread_{thread_id}'
215     complete_url = f'{BASE_URL}/policy?id={policy_id}'
216     resp = requests.delete(complete_url)
217
218     if not resp.ok:
219         verboseprint(f'Unable to delete policy for policy ID {policy_id}')
220         return False
221
222     return True
223
224
225 def statistics():
226     global duration
227     global no_of_checks
228     global no_of_unavailable_rics
229     global no_of_rics_not_supporting_type
230     global no_of_rics_supporting_type
231     global no_of_created_policies
232     global no_of_read_policies
233     global no_of_updated_policies
234     global no_of_deleted_policies
235
236     # Clear ric data between checks as it may have changed since last check.
237     no_of_unavailable_rics = 0
238     no_of_rics_not_supporting_type = 0
239     no_of_rics_supporting_type = 0
240
241     for ric in rics.values():
242         if not (ric.state == 'AVAILABLE' or ric.state == 'CONSISTENCY_CHECK'):
243             no_of_unavailable_rics += 1
244         elif ric.supports_type_to_use:
245             no_of_rics_supporting_type += 1
246             no_of_created_policies += ric.no_of_created_policies
247             no_of_read_policies += ric.no_of_read_policies
248             no_of_updated_policies += ric.no_of_updated_policies
249             no_of_deleted_policies += ric.no_of_deleted_policies
250         else:
251             no_of_rics_not_supporting_type += 1
252
253     print(f'*********** Statistics {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} *******************')
254     print(f'Duration of check:                          {duration.total_seconds()} seconds')
255     print(f'Number of checks:                           {no_of_checks}')
256     print(f'Number of unavailable Near-RT RICS:         {no_of_unavailable_rics}')
257     print(f'Number of Near-RT RICS not supporting type: {no_of_rics_not_supporting_type}')
258     print(f'Number of Near-RT RICS supporting type:     {no_of_rics_supporting_type}')
259     print(f'Number of created policies:                 {no_of_created_policies}')
260     print(f'Number of read policies:                    {no_of_read_policies}')
261     print(f'Number of updated policies:                 {no_of_updated_policies}')
262     print(f'Number of deleted policies:                 {no_of_deleted_policies}')
263     print('**************************************************************')
264
265
266 def run_check_threads(rics):
267     thread_id = 1
268     threads = []
269     for ric in rics.values():
270         if ric.supports_type_to_use and (ric.state == 'AVAILABLE' or ric.state == 'CONSISTENCY_CHECK'): #or ric.name == 'ric_not_working':
271             policy_checker = PolicyCheckThread(thread_id, ric)
272             policy_checker.start()
273             thread_id += 1
274             threads.append(policy_checker)
275
276     for checker in threads:
277         checker.join()
278
279
280 def split_rics_equally(chunks):
281     # prep with empty dicts
282     return_list = [dict() for _ in xrange(chunks)]
283     if len(rics) < RIC_CHUNK_SIZE:
284         return [rics]
285
286     idx = 0
287     for k,v in rics.items():
288         return_list[idx][k] = v
289         if idx < chunks-1:  # indexes start at 0
290             idx += 1
291         else:
292             idx = 0
293     return return_list
294
295
296 def get_no_of_chunks(size_of_chunks, size_to_chunk):
297     (q, _) = divmod(size_to_chunk, size_of_chunks)
298     return q
299
300
301 if __name__ == '__main__':
302     parser = argparse.ArgumentParser(prog='PROG')
303     parser.add_argument('--policyTypeId', help='The ID of the policy type to use')
304     parser.add_argument('--policyBodyPath', help='The path to the JSON body of the policy to create')
305     parser.add_argument('-v', '--verbose', action='store_true', help='Turn on verbose printing')
306     parser.add_argument('--version', action='version', version='%(prog)s 1.0')
307     args = vars(parser.parse_args())
308
309     if args['verbose']:
310         def verboseprint(*args, **kwargs):
311             print(*args, **kwargs)
312     else:
313         verboseprint = lambda *a, **k: None # do-nothing function
314
315     if args["policyTypeId"]:
316         type_to_use = args["policyTypeId"]
317
318     if args["policyBodyPath"]:
319         policy_body_path = args["policyBodyPath"]
320         if not os.path.exists(policy_body_path):
321             print(f'Policy body {policy_body_path} does not exist.')
322             sys.exit(1)
323
324     verboseprint(f'Using policy type {type_to_use}')
325     verboseprint(f'Using policy file {policy_body_path}')
326
327     with open(policy_body_path) as json_file:
328         policy_body = json_file.read()
329         verboseprint(f'Policy body: {policy_body}')
330
331     try:
332         rics_from_agent = get_rics_from_agent()
333     except ConnectionError:
334         print(f'A1PMS is not answering on {BASE_URL}, cannot start!')
335         sys.exit(1)
336
337     rics = create_ric_dict(rics_from_agent)
338
339     monitor_server = MonitorServer()
340     monitor_server.start()
341
342     while True:
343         start_time = datetime.now()
344         chunked_rics = split_rics_equally(get_no_of_chunks(RIC_CHUNK_SIZE, rics.__len__()))
345         for ric_chunk in chunked_rics:
346             run_check_threads(ric_chunk)
347
348         no_of_checks += 1
349         finish_time = datetime.now()
350         duration = finish_time - start_time
351         statistics()
352         sleep_time = TIME_BETWEEN_CHECKS - duration.total_seconds()
353         verboseprint(f'Sleeping {sleep_time} seconds')
354         time.sleep(sleep_time)
355         update_rics()
356
357     verboseprint('Exiting main')