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