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