Update version number in container-tag for F Maintenance Release
[sim/a1-interface.git] / near-rt-ric-simulator / src / STD_2.0.0 / a1.py
1 #  ============LICENSE_START===============================================
2 #  Copyright (C) 2021 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 os
19 import copy
20 import datetime
21 import json
22 import logging
23 import collections
24 import time
25 import requests
26
27 from connexion import NoContent
28 from flask import Flask, escape, request, Response, make_response
29 from jsonschema import validate
30 from var_declaration import policy_instances, policy_types, policy_status, callbacks, forced_settings, policy_fingerprint, hosts_set
31 from utils import calcFingerprint
32 from maincommon import check_apipath, apipath, get_supported_interfaces_response, extract_host_name, is_duplicate_check
33
34 #Constsants
35 APPL_JSON='application/json'
36 APPL_PROB_JSON='application/problem+json'
37
38 EXT_SRV_URL=os.getenv('EXT_SRV_URL')
39 KAFKA_DISPATCHER_URL=os.getenv('KAFKA_DISPATCHER_URL')
40
41 # API Function: Get all policy type ids
42 def get_all_policy_types():
43
44   extract_host_name(hosts_set, request)
45
46   if ((r := check_modified_response()) is not None):
47     return r
48
49   res = list(policy_types.keys())
50   return (res, 200)
51
52 # API Function: Get a policy type
53 def get_policy_type(policyTypeId):
54
55   extract_host_name(hosts_set, request)
56
57   if ((r := check_modified_response()) is not None):
58     return r
59
60   policy_type_id=str(policyTypeId)
61
62   if (policy_type_id not in policy_types.keys()):
63     pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_type_id)
64     return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
65
66   return Response(json.dumps(policy_types[policy_type_id]), 200, mimetype=APPL_JSON)
67
68 # API Function: Get all policy ids
69 def get_all_policy_identities(policyTypeId):
70
71   extract_host_name(hosts_set, request)
72
73   if ((r := check_modified_response()) is not None):
74     return r
75
76   policy_type_id=str(policyTypeId)
77
78   if (policy_type_id not in policy_types.keys()):
79     pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_type_id)
80     return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
81
82   return (list(policy_instances[policy_type_id].keys()), 200)
83
84 # API Function: Create or update a policy
85 def put_policy(policyTypeId, policyId):
86
87   extract_host_name(hosts_set, request)
88
89   if ((r := check_modified_response()) is not None):
90     return r
91
92   policy_type_id=str(policyTypeId)
93   policy_id=str(policyId)
94
95   if (policy_type_id not in policy_types.keys()):
96     pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
97     return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
98
99   try:
100     data = request.data
101     data = json.loads(data)
102   except Exception:
103     pjson=create_problem_json(None, "The policy is corrupt or missing.", 400, None, policy_id)
104     return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
105
106   try:
107     validate(instance=data, schema=policy_types[policy_type_id]['policySchema'])
108   except Exception:
109     return (None, 400)
110
111   fp_previous=None
112   retcode=201
113   if policy_id in policy_instances[policy_type_id].keys():
114     retcode=200
115     if (is_duplicate_check()):
116       fp_previous=calcFingerprint(policy_instances[policy_type_id][policy_id], policy_type_id)
117     else:
118       fp_previous=policy_id
119   else:
120     if (policy_id in policy_fingerprint.values()):
121       pjson=create_problem_json(None, "The policy id already exist for other policy type.", 400, None, policy_id)
122       return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
123
124   if (is_duplicate_check()):
125     fp=calcFingerprint(data, policy_type_id)
126   else:
127     fp=policy_id
128
129   if ((fp in policy_fingerprint.keys()) and is_duplicate_check()):
130     p_id=policy_fingerprint[fp]
131     if (p_id != policy_id):
132       pjson=create_problem_json(None, "Duplicate, the policy json already exists.", 400, None, policy_id)
133       return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
134
135   #Callout hooks for kafka dispatcher
136   if (KAFKA_DISPATCHER_URL is not None):
137     resp = callout_kafka_dispatcher(policy_type_id, policy_id, data, retcode)
138     if (resp != 200):
139       pjson=create_error_response(resp)
140       return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
141
142   #Callout hooks for external server
143   #When it fails, break and return 419 HTTP status code
144   if (EXT_SRV_URL is not None):
145     resp = callout_external_server(policy_id, data, 'PUT')
146     if (resp != retcode):
147       pjson=create_error_response(resp)
148       return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
149
150   if (fp_previous is not None):
151     del policy_fingerprint[fp_previous]
152
153   policy_fingerprint[fp]=policy_id
154
155   noti=request.args.get('notificationDestination')
156   callbacks[policy_id]=noti
157
158   policy_instances[policy_type_id][policy_id]=data
159
160   if (policy_types[policy_type_id]['statusSchema'] is not None):
161     ps = {}
162     ps["enforceStatus"] = ""
163     ps["enforceReason"] = ""
164     policy_status[policy_id] = ps
165
166   if (retcode == 200):
167     return Response(json.dumps(data), 200, mimetype=APPL_JSON)
168   else:
169     headers={}
170     headers['Location']='/A1-P/v2/policytypes/' + policy_type_id + '/policies/' + policy_id
171     return Response(json.dumps(data), 201, headers=headers, mimetype=APPL_JSON)
172
173 # API Function: Get a policy
174 def get_policy(policyTypeId, policyId):
175
176   extract_host_name(hosts_set, request)
177
178   if ((r := check_modified_response()) is not None):
179     return r
180
181   policy_type_id=str(policyTypeId)
182   policy_id=str(policyId)
183
184   if (policy_type_id not in policy_types.keys()):
185     pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
186     return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
187
188   if (policy_id not in policy_instances[policy_type_id].keys()):
189     pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
190     return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
191
192   return Response(json.dumps(policy_instances[policy_type_id][policy_id]), 200, mimetype=APPL_JSON)
193
194 # API Function: Delete a policy
195 def delete_policy(policyTypeId, policyId):
196
197   extract_host_name(hosts_set, request)
198
199   if ((r := check_modified_response()) is not None):
200     return r
201
202   policy_type_id=str(policyTypeId)
203   policy_id=str(policyId)
204
205   if (policy_type_id not in policy_types.keys()):
206     pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
207     return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
208
209   if (policy_id not in policy_instances[policy_type_id].keys()):
210     pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
211     return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
212
213   #Callout hooks for kafka dispatcher
214   if (KAFKA_DISPATCHER_URL is not None):
215     resp = callout_kafka_dispatcher(policy_type_id, policy_id, None, 204)
216     if (resp != 200):
217       pjson=create_error_response(resp)
218       return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
219
220   #Callout hooks for external server
221   #When it fails, break and return 419 HTTP status code
222   if (EXT_SRV_URL is not None):
223     resp = callout_external_server(policy_id, None, 'DELETE')
224     if (resp != 204):
225       pjson=create_error_response(resp)
226       return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
227
228   if (is_duplicate_check()):
229     fp_previous=calcFingerprint(policy_instances[policy_type_id][policy_id], policy_type_id)
230   else:
231     fp_previous=policy_id
232
233   policy_fingerprint.pop(fp_previous)
234   policy_instances[policy_type_id].pop(policy_id)
235   policy_status.pop(policy_id)
236   callbacks.pop(policy_id)
237   return Response('', 204, mimetype=APPL_JSON)
238
239 # API Function: Get status for a policy
240 def get_policy_status(policyTypeId, policyId):
241
242   extract_host_name(hosts_set, request)
243
244   if ((r := check_modified_response()) is not None):
245     return r
246
247   policy_type_id=str(policyTypeId)
248   policy_id=str(policyId)
249
250   if (policy_type_id not in policy_types.keys()):
251     pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
252     return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
253
254   if (policy_id not in policy_instances[policy_type_id].keys()):
255     pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
256     return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
257
258   #Callout hooks for kafka dispatcher
259   if (KAFKA_DISPATCHER_URL is not None):
260     resp = callout_kafka_dispatcher(policy_type_id, policy_id, None, 202)
261     if (resp != 200):
262       pjson=create_error_response(resp)
263       return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
264
265   return Response(json.dumps(policy_status[policy_id]), status=200, mimetype=APPL_JSON)
266
267
268 # Helper: Callout kafka dispatcher server to notify it for policy operations
269 def callout_kafka_dispatcher(policy_type_id, policy_id, payload, retcode):
270
271   target_url = KAFKA_DISPATCHER_URL + "/policytypes/" + policy_type_id + "/kafkadispatcher/" + policy_id
272   try:
273     # create operation, publish with payload
274     if (retcode == 201):
275       resp=requests.put(target_url, json=payload, timeout=30, verify=False)
276       return resp.status_code
277     # update operation, publish with payload
278     elif (retcode == 200):
279       # add headers an update-flag
280       headers = {'updateoper' : 'yes'}
281       resp=requests.put(target_url, json=payload, headers=headers, timeout=30, verify=False)
282       return resp.status_code
283     # delete operation, publish without payload
284     elif (retcode == 204):
285       resp=requests.delete(target_url, timeout=30, verify=False)
286       return resp.status_code
287     # get policy status operation, publish without payload
288     elif (retcode == 202):
289       # update endpoint
290       target_url = target_url + "/status"
291       resp=requests.get(target_url, timeout=30, verify=False)
292       return resp.status_code
293   except Exception:
294     return 419
295
296
297 # Helper: Callout external server to notify it for policy operations
298 # Returns 200, 201 and 204 for the success callout hooks, for the others returns 419
299 def callout_external_server(policy_id, payload, operation):
300
301   target_url=EXT_SRV_URL + policy_id
302   try:
303     if (operation == 'PUT'):
304       #Suppress error when self-signed certificate is being used with verify flag
305       resp=requests.put(target_url, json=payload, timeout=10, verify=False)
306       return resp.status_code
307     elif (operation == 'DELETE'):
308       resp=requests.delete(target_url, timeout=10, verify=False)
309       return resp.status_code
310   except Exception:
311     #Return a generic unassigned HTTP status code as per iana, for all exceptions (419:Callout failed)
312     #https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
313     return 419
314
315 # Helper: Create a response object if forced http response code is set
316 def get_forced_response():
317
318   if (forced_settings['code'] is not None):
319     value=forced_settings['code']
320     pjson=create_error_response(int(value))
321     forced_settings['code']=None
322     return Response(json.dumps(pjson), pjson['status'], mimetype=APPL_PROB_JSON)
323   return None
324
325 # Helper: Delay if delayed response code is set
326 def do_delay():
327
328   if (forced_settings['delay'] is not None):
329     try:
330       val=int(forced_settings['delay'])
331       time.sleep(val)
332     except Exception:
333       return
334
335 # Helper: Check if response shall be delayed or a forced response shall be sent
336 def check_modified_response():
337
338   do_delay()
339   return get_forced_response()
340
341 # Helper: Create a problem json object
342 def create_problem_json(type_of, title, status, detail, instance):
343
344   error = {}
345   if type_of is not None:
346     error["type"] = type_of
347   if title is not None:
348     error["title"] = title
349   if status is not None:
350     error["status"] = status
351   if detail is not None:
352     error["detail"] = detail
353   if instance is not None:
354     error["instance"] = instance
355   return error
356
357 # Helper: Create a problem json based on a generic http response code
358 def create_error_response(code):
359
360     if (code == 400):
361       return(create_problem_json(None, "Bad request", 400, "Object in payload not properly formulated or not related to the method", None))
362     elif (code == 404):
363       return(create_problem_json(None, "Not found", 404, "No resource found at the URI", None))
364     elif (code == 405):
365       return(create_problem_json(None, "Method not allowed", 405, "Method not allowed for the URI", None))
366     elif (code == 409):
367       return(create_problem_json(None, "Conflict", 409, "Request could not be processed in the current state of the resource", None))
368     elif (code == 419):
369       return(create_problem_json(None, "Callout failed", 419, "Callout hooks could not be processed on the external server", None))
370     elif (code == 429):
371       return(create_problem_json(None, "Too many requests", 429, "Too many requests have been sent in a given amount of time", None))
372     elif (code == 507):
373       return(create_problem_json(None, "Insufficient storage", 507, "The method could not be performed on the resource because the provider is unable to store the representation needed to successfully complete the request", None))
374     elif (code == 503):
375       return(create_problem_json(None, "Service unavailable", 503, "The provider is currently unable to handle the request due to a temporary overload", None))
376     else:
377       return(create_problem_json(None, "Unknown", code, "Not implemented response code", None))