Merge "Remove DMaaP Adapter"
[nonrtric.git] / test / usecases / healthcheck / 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 test_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
80 policy_body_path = os.path.join(test_dir, 'auto-test','testdata','OSC','pihw_template.json')
81
82 duration = 0
83 no_of_checks = 0
84 no_of_unavailable_rics = 0
85 no_of_rics_not_supporting_type = 0
86 no_of_rics_supporting_type = 0
87 no_of_created_policies = 0
88 no_of_read_policies = 0
89 no_of_updated_policies = 0
90 no_of_deleted_policies = 0
91
92 class Ric:
93
94     def __init__(self, name, supported_types, state):
95         self.name = name
96         self.supports_type_to_use = self.policy_type_supported(supported_types)
97         self.state = state
98         self.no_of_created_policies = 0
99         self.no_of_read_policies = 0
100         self.no_of_updated_policies = 0
101         self.no_of_deleted_policies = 0
102
103     def update_supported_types(self, supported_types):
104         self.supports_type_to_use = self.policy_type_supported(supported_types)
105
106     def policy_type_supported(self, supported_policy_types):
107         for supported_type in supported_policy_types:
108             if type_to_use == supported_type:
109                 return True
110
111         return False
112
113
114 class PolicyCheckThread (threading.Thread):
115
116     def __init__(self, thread_id, ric):
117         threading.Thread.__init__(self)
118         self.thread_id = thread_id
119         self.ric = ric
120
121     def run(self):
122         verboseprint(f'Checking ric: {self.ric.name}')
123         if put_policy(self.thread_id, self.ric.name):
124             verboseprint(f'Created policy: {self.thread_id} in ric: {self.ric.name}')
125             self.ric.no_of_created_policies += 1
126             time.sleep(0.5)
127             if get_policy(self.thread_id):
128                 verboseprint(f'Read policy: {self.thread_id} from ric: {self.ric.name}')
129                 self.ric.no_of_read_policies += 1
130             if put_policy(self.thread_id, self.ric.name, update_value=1):
131                 verboseprint(f'Updated policy: {self.thread_id} in ric: {self.ric.name}')
132                 self.ric.no_of_updated_policies += 1
133             if delete_policy(self.thread_id):
134                 verboseprint(f'Deleted policy: {self.thread_id} from ric: {self.ric.name}')
135                 self.ric.no_of_deleted_policies += 1
136
137
138 class MonitorServer (threading.Thread):
139     def __init__(self):
140         threading.Thread.__init__(self)
141
142     def run(self):
143         verboseprint('Staring monitor server')
144         app.run(port=HOST_PORT, host=HOST_IP)
145
146
147 @app.route(APP_URL,
148     methods=['GET'])
149 def produceStatsPage():
150     t = Template(stat_page_template)
151     page = t.render(refreshTime=TIME_BETWEEN_CHECKS, policyTypeId=type_to_use, policyBodyPath=policy_body_path,
152     time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), duration=duration, noOfChecks=no_of_checks,
153     noOfUnavailableRics=no_of_unavailable_rics, noOfNotSupportingRics=no_of_rics_not_supporting_type,
154     noOfSupportingRics=no_of_rics_supporting_type, noOfCreatedPolicies=no_of_created_policies,
155     noOfReadPolicies=no_of_read_policies, noOfUpdatedPolicies=no_of_updated_policies,
156     noOfDeletedPolicies=no_of_deleted_policies)
157     return page,200
158
159 def get_rics_from_agent():
160     resp = requests.get(BASE_URL + '/rics')
161     if not resp.ok:
162         verboseprint(f'Unable to get Rics {resp.status_code}')
163         return {}
164     return resp.json()
165
166
167 def create_ric_dict(rics_as_json):
168     rics = {}
169     for ric_info in rics_as_json:
170         rics[ric_info["ricName"]] = (Ric(ric_info["ricName"], ric_info["policyTypes"], ric_info['state']))
171         verboseprint(f'Adding ric: {rics[ric_info["ricName"]]}')
172
173     return rics
174
175
176 def update_rics():
177     added_rics = {}
178     for ric_info in get_rics_from_agent():
179         if ric_info["ricName"] in rics:
180             rics[ric_info["ricName"]].update_supported_types(ric_info["policyTypes"])
181             rics[ric_info["ricName"]].state = ric_info['state']
182         else:
183             added_rics[ric_info["ricName"]] = (Ric(ric_info["ricName"], ric_info["policyTypes"]))
184             verboseprint(f'Adding ric: {rics[ric_info["ricName"]]}')
185
186     rics.update(added_rics)
187
188
189 def put_policy(thread_id, ric_name, update_value=0):
190     policy_id = f'thread_{thread_id}'
191     complete_url = f'{BASE_URL}/policy?type={type_to_use}&id={policy_id}&ric={ric_name}&service={SERVICE_NAME}'
192     headers = {'content-type': 'application/json'}
193     resp = requests.put(complete_url, policy_body.replace('XXX', str(thread_id + update_value)), headers=headers, verify=False)
194
195     if not resp.ok:
196         verboseprint(f'Unable to create policy {resp}')
197         return False
198     else:
199         return True
200
201
202 def get_policy(thread_id):
203     policy_id = f'thread_{thread_id}'
204     complete_url = f'{BASE_URL}/policy?id={policy_id}'
205     resp = requests.get(complete_url)
206
207     if not resp.ok:
208         verboseprint(f'Unable to get policy {resp}')
209         return False
210     else:
211         return True
212
213
214 def delete_policy(thread_id):
215     policy_id = f'thread_{thread_id}'
216     complete_url = f'{BASE_URL}/policy?id={policy_id}'
217     resp = requests.delete(complete_url)
218
219     if not resp.ok:
220         verboseprint(f'Unable to delete policy for policy ID {policy_id}')
221         return False
222
223     return True
224
225
226 def statistics():
227     global duration
228     global no_of_checks
229     global no_of_unavailable_rics
230     global no_of_rics_not_supporting_type
231     global no_of_rics_supporting_type
232     global no_of_created_policies
233     global no_of_read_policies
234     global no_of_updated_policies
235     global no_of_deleted_policies
236
237     # Clear ric data between checks as it may have changed since last check.
238     no_of_unavailable_rics = 0
239     no_of_rics_not_supporting_type = 0
240     no_of_rics_supporting_type = 0
241
242     for ric in rics.values():
243         if not (ric.state == 'AVAILABLE' or ric.state == 'CONSISTENCY_CHECK'):
244             no_of_unavailable_rics += 1
245         elif ric.supports_type_to_use:
246             no_of_rics_supporting_type += 1
247             no_of_created_policies += ric.no_of_created_policies
248             no_of_read_policies += ric.no_of_read_policies
249             no_of_updated_policies += ric.no_of_updated_policies
250             no_of_deleted_policies += ric.no_of_deleted_policies
251         else:
252             no_of_rics_not_supporting_type += 1
253
254     print(f'*********** Statistics {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} *******************')
255     print(f'Duration of check:                          {duration.total_seconds()} seconds')
256     print(f'Number of checks:                           {no_of_checks}')
257     print(f'Number of unavailable Near-RT RICS:         {no_of_unavailable_rics}')
258     print(f'Number of Near-RT RICS not supporting type: {no_of_rics_not_supporting_type}')
259     print(f'Number of Near-RT RICS supporting type:     {no_of_rics_supporting_type}')
260     print(f'Number of created policies:                 {no_of_created_policies}')
261     print(f'Number of read policies:                    {no_of_read_policies}')
262     print(f'Number of updated policies:                 {no_of_updated_policies}')
263     print(f'Number of deleted policies:                 {no_of_deleted_policies}')
264     print('**************************************************************')
265
266
267 def run_check_threads(rics):
268     thread_id = 1
269     threads = []
270     for ric in rics.values():
271         if ric.supports_type_to_use and (ric.state == 'AVAILABLE' or ric.state == 'CONSISTENCY_CHECK'): #or ric.name == 'ric_not_working':
272             policy_checker = PolicyCheckThread(thread_id, ric)
273             policy_checker.start()
274             thread_id += 1
275             threads.append(policy_checker)
276
277     for checker in threads:
278         checker.join()
279
280
281 def split_rics_equally(chunks):
282     # prep with empty dicts
283     return_list = [dict() for _ in xrange(chunks)]
284     if len(rics) < RIC_CHUNK_SIZE:
285         return [rics]
286
287     idx = 0
288     for k,v in rics.items():
289         return_list[idx][k] = v
290         if idx < chunks-1:  # indexes start at 0
291             idx += 1
292         else:
293             idx = 0
294     return return_list
295
296
297 def get_no_of_chunks(size_of_chunks, size_to_chunk):
298     (q, _) = divmod(size_to_chunk, size_of_chunks)
299     return q
300
301
302 if __name__ == '__main__':
303     parser = argparse.ArgumentParser(prog='PROG')
304     parser.add_argument('--policyTypeId', help='The ID of the policy type to use')
305     parser.add_argument('--policyBodyPath', help='The path to the JSON body of the policy to create')
306     parser.add_argument('-v', '--verbose', action='store_true', help='Turn on verbose printing')
307     parser.add_argument('--version', action='version', version='%(prog)s 1.0')
308     args = vars(parser.parse_args())
309
310     if args['verbose']:
311         def verboseprint(*args, **kwargs):
312             print(*args, **kwargs)
313     else:
314         verboseprint = lambda *a, **k: None # do-nothing function
315
316     if args["policyTypeId"]:
317         type_to_use = args["policyTypeId"]
318
319     if args["policyBodyPath"]:
320         policy_body_path = args["policyBodyPath"]
321         if not os.path.exists(policy_body_path):
322             print(f'Policy body {policy_body_path} does not exist.')
323             sys.exit(1)
324
325     verboseprint(f'Using policy type {type_to_use}')
326     verboseprint(f'Using policy file {policy_body_path}')
327
328     with open(policy_body_path) as json_file:
329         policy_body = json_file.read()
330         verboseprint(f'Policy body: {policy_body}')
331
332     try:
333         rics_from_agent = get_rics_from_agent()
334     except ConnectionError:
335         print(f'A1PMS is not answering on {BASE_URL}, cannot start!')
336         sys.exit(1)
337
338     rics = create_ric_dict(rics_from_agent)
339
340     monitor_server = MonitorServer()
341     monitor_server.start()
342
343     while True:
344         start_time = datetime.now()
345         chunked_rics = split_rics_equally(get_no_of_chunks(RIC_CHUNK_SIZE, rics.__len__()))
346         for ric_chunk in chunked_rics:
347             run_check_threads(ric_chunk)
348
349         no_of_checks += 1
350         finish_time = datetime.now()
351         duration = finish_time - start_time
352         statistics()
353         sleep_time = TIME_BETWEEN_CHECKS - duration.total_seconds()
354         verboseprint(f'Sleeping {sleep_time} seconds')
355         time.sleep(sleep_time)
356         update_rics()
357
358     verboseprint('Exiting main')