Add functions test cases for ECS
[nonrtric.git] / test / prodstub / app / prodstub.py
1
2 #  ============LICENSE_START===============================================
3 #  Copyright (C) 2020 Nordix Foundation. All rights reserved.
4 #  ========================================================================
5 #  Licensed under the Apache License, Version 2.0 (the "License");
6 #  you may not use this file except in compliance with the License.
7 #  You may obtain a copy of the License at
8 #
9 #       http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #  Unless required by applicable law or agreed to in writing, software
12 #  distributed under the License is distributed on an "AS IS" BASIS,
13 #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 #  See the License for the specific language governing permissions and
15 #  limitations under the License.
16 #  ============LICENSE_END=================================================
17 #
18
19 from flask import Flask
20 from flask import request
21
22 import json
23 from jsonschema import validate
24
25 app = Flask(__name__)
26
27 # # list of callback messages
28 # msg_callbacks={}
29
30 # Server info
31 HOST_IP = "::"
32 HOST_PORT = 2222
33
34 # # Metrics vars
35 # cntr_msg_callbacks=0
36 # cntr_msg_fetched=0
37
38 # Request and response constants
39 CALLBACK_CREATE_URL="/callbacks/create/<string:producer_id>"
40 CALLBACK_DELETE_URL="/callbacks/delete/<string:producer_id>"
41 CALLBACK_SUPERVISION_URL="/callbacks/supervision/<string:producer_id>"
42
43 ARM_CREATE_RESPONSE="/arm/create/<string:producer_id>/<string:job_id>"
44 ARM_DELETE_RESPONSE="/arm/delete/<string:producer_id>/<string:job_id>"
45 ARM_SUPERVISION_RESPONSE="/arm/supervision/<string:producer_id>"
46 ARM_TYPE="/arm/type/<string:producer_id>/<string:type_id>"
47
48 COUNTER_SUPERVISION="/counter/supervision/<string:producer_id>"
49 COUNTER_CREATE="/counter/create/<string:producer_id>/<string:job_id>"
50 COUNTER_DELETE="/counter/delete/<string:producer_id>/<string:job_id>"
51
52 JOB_DATA="/jobdata/<string:producer_id>/<string:job_id>"
53
54 STATUS="/status"
55
56 #Constsants
57 APPL_JSON='application/json'
58 UNKNOWN_QUERY_PARAMETERS="Unknown query parameter(s)"
59 RETURNING_CONFIGURED_RESP="returning configured response code"
60 JOBID_NO_MATCH="job id in stored json does not match request"
61 PRODUCER_OR_JOB_NOT_FOUND="producer or job not found"
62 PRODUCER_NOT_FOUND="producer not found"
63 TYPE_NOT_FOUND="type not found"
64 TYPE_IN_USE="type is in use in a job"
65 JSON_CORRUPT="json in request is corrupt or missing"
66
67 #Producer and job db, including armed responses
68 db={}
69 # producer
70 #  armed response for supervision
71 #  armed types
72 #  supervision counter
73 #  job
74 #    job json
75 #    armed response for create
76 #    armed response for delete
77 #    create counter
78 #    delete counter
79
80 # Helper function to populate a callback dict with the basic structure
81 # if job_id is None then only the producer level is setup and the producer dict is returned
82 # if job_id is not None, the job level is setup and the job dict is returned (producer must exist)
83 def setup_callback_dict(producer_id, job_id):
84
85     producer_dict=None
86     if (producer_id in db.keys()):
87         producer_dict=db[producer_id]
88     else:
89         if (job_id is not None):
90             return None
91         producer_dict={}
92         db[producer_id]=producer_dict
93
94         producer_dict['supervision_response']=200
95         producer_dict['supervision_counter']=0
96         producer_dict['types']=[]
97
98     if (job_id is None):
99         return producer_dict
100
101     job_dict=None
102     if (job_id in producer_dict.keys()):
103         job_dict=producer_dict[job_id]
104     else:
105         job_dict={}
106         producer_dict[job_id]=job_dict
107         job_dict['create_response']=201
108         job_dict['delete_response']=404
109         job_dict['json']=None
110         job_dict['create_counter']=0
111         job_dict['delete_counter']=0
112     return job_dict
113
114
115 # Helper function to get an entry from the callback db
116 # if job_id is None then only the producer dict is returned (or None if producer is not found)
117 # if job_id is not None, the job is returned (or None if producer/job is not found)
118 def get_callback_dict(producer_id, job_id):
119
120     producer_dict=None
121     if (producer_id in db.keys()):
122         producer_dict=db[producer_id]
123
124     if (job_id is None):
125         return producer_dict
126
127     job_dict=None
128     if (job_id in producer_dict.keys()):
129         job_dict=producer_dict[job_id]
130
131     return job_dict
132
133 # Helper function find if a key/valye exist in the dictionay tree
134 # True if found
135 def recursive_search(s_dict, s_key, s_id):
136     for pkey in s_dict:
137         if (pkey == s_key) and (s_dict[pkey] == s_id):
138             return True
139         if (isinstance(s_dict[pkey], dict)):
140             recursive_search(s_dict[pkey], s_key, s_id)
141
142     return False
143
144 # I'm alive function
145 # response: always 200
146 @app.route('/',
147     methods=['GET'])
148 def index():
149     return 'OK', 200
150
151 # Arm the create callback with a response code
152 # Omitting the query parameter switch to response back to the standard 200/201 response
153 # URI and parameters (PUT): /arm/create/<producer_id>/<job-id>[?response=<resonsecode>]
154 # Setting
155 # response: 200 (400 if incorrect query params)
156 @app.route(ARM_CREATE_RESPONSE,
157      methods=['PUT'])
158 def arm_create(producer_id, job_id):
159
160     arm_response=request.args.get('response')
161
162     if (arm_response is None):
163         if (len(request.args) != 0):
164             return UNKNOWN_QUERY_PARAMETERS,400
165     else:
166         if (len(request.args) != 1):
167             return UNKNOWN_QUERY_PARAMETERS,400
168
169     print("Arm create received for producer: "+str(producer_id)+" and job: "+str(job_id)+" and response: "+str(arm_response))
170
171     job_dict=setup_callback_dict(producer_id, job_id)
172
173     if (arm_response is None):    #Reset the response depending if a job exists or not
174         if (job_dict['json'] is None):
175             job_dict['create_response']=201
176         else:
177             job_dict['create_response']=200
178     else:
179         job_dict['create_response']=arm_response
180
181     return "",200
182
183 # Arm the delete callback with a response code
184 # Omitting the query parameter switch to response back to the standard 204 response
185 # URI and parameters (PUT): /arm/delete/<producer_id>/<job-id>[?response=<resonsecode>]
186 # response: 200 (400 if incorrect query params)
187 @app.route(ARM_DELETE_RESPONSE,
188      methods=['PUT'])
189 def arm_delete(producer_id, job_id):
190
191     arm_response=request.args.get('response')
192
193     if (arm_response is None):
194         if (len(request.args) != 0):
195             return UNKNOWN_QUERY_PARAMETERS,400
196     else:
197         if (len(request.args) != 1):
198             return UNKNOWN_QUERY_PARAMETERS,400
199
200     print("Arm delete received for producer: "+str(producer_id)+" and job: "+str(job_id)+" and response: "+str(arm_response))
201
202     arm_response=request.args.get('response')
203
204     job_dict=setup_callback_dict(producer_id, job_id)
205
206     if (arm_response is None): #Reset the response depening if a job exists or not
207         if (job_dict['json'] is None):
208             job_dict['delete_response']=404
209         else:
210             job_dict['delete_response']=204
211     else:
212         job_dict['delete_response']=arm_response
213
214     return "",200
215
216 # Arm the supervision callback with a response code
217 # Omitting the query parameter switch to response back to the standard 200 response
218 # URI and parameters (PUT): /arm/supervision/<producer_id>[?response=<resonsecode>]
219 # response: 200 (400 if incorrect query params)
220 @app.route(ARM_SUPERVISION_RESPONSE,
221      methods=['PUT'])
222 def arm_supervision(producer_id):
223
224     arm_response=request.args.get('response')
225
226     if (arm_response is None):
227         if (len(request.args) != 0):
228             return UNKNOWN_QUERY_PARAMETERS,400
229     else:
230         if (len(request.args) != 1):
231             return UNKNOWN_QUERY_PARAMETERS,400
232
233     print("Arm supervision received for producer: "+str(producer_id)+" and response: "+str(arm_response))
234
235     producer_dict=setup_callback_dict(producer_id, None)
236     if (arm_response is None):
237         producer_dict['supervision_response']=200
238     else:
239         producer_dict['supervision_response']=arm_response
240
241     return "",200
242
243 # Arm a producer with a type
244 # URI and parameters (PUT): /arm/type/<string:producer_id>/<string:type-id>
245 # response: 200 (404)
246 @app.route(ARM_TYPE,
247     methods=['PUT'])
248 def arm_type(producer_id, type_id):
249
250     print("Arm type received for producer: "+str(producer_id)+" and type: "+str(type_id))
251
252     producer_dict=get_callback_dict(producer_id, None)
253
254     if (producer_dict is None):
255         return PRODUCER_NOT_FOUND,404
256
257     type_list=producer_dict['types']
258     if (type_id not in type_list):
259         type_list.append(type_id)
260
261     return "",200
262
263 # Disarm a producer with a type
264 # URI and parameters (DELETE): /arm/type/<string:producer_id>/<string:type-id>
265 # response: 200 (404)
266 @app.route(ARM_TYPE,
267     methods=['DELETE'])
268 def disarm_type(producer_id, type_id):
269
270     print("Disarm type received for producer: "+str(producer_id)+" and type: "+str(type_id))
271
272     producer_dict=get_callback_dict(producer_id, None)
273
274     if (producer_dict is None):
275         return PRODUCER_NOT_FOUND,404
276
277     if (recursive_search(producer_dict, "ei_job_type",type_id) is True):
278         return "TYPE_IN_USE",400
279
280     type_list=producer_dict['types']
281     type_list.remove(type_id)
282
283     return "",200
284
285 # Callback for create job
286 # URI and parameters (POST): /callbacks/create/<producer_id>
287 # response 201 at create, 200 at update or other configured response code
288 @app.route(CALLBACK_CREATE_URL,
289      methods=['POST'])
290 def callback_create(producer_id):
291
292     req_json_dict=None
293     try:
294         req_json_dict = json.loads(request.data)
295         with open('job-schema.json') as f:
296             schema = json.load(f)
297             validate(instance=req_json_dict, schema=schema)
298     except Exception:
299         return JSON_CORRUPT,400
300
301     producer_dict=get_callback_dict(producer_id, None)
302     if (producer_dict is None):
303         return PRODUCER_OR_JOB_NOT_FOUND,400
304     type_list=producer_dict['types']
305     type_id=req_json_dict['ei_type_identity']
306     if (type_id not in type_list):
307         return TYPE_NOT_FOUND
308
309     job_id=req_json_dict['ei_job_identity']
310     job_dict=get_callback_dict(producer_id, job_id)
311     if (job_dict is None):
312         return PRODUCER_OR_JOB_NOT_FOUND,400
313     return_code=0
314     return_msg=""
315     if (req_json_dict['ei_job_identity'] == job_id):
316         print("Create callback received for producer: "+str(producer_id)+" and job: "+str(job_id))
317         return_code=job_dict['create_response']
318         if ((job_dict['create_response'] == 200) or (job_dict['create_response'] == 201)):
319             job_dict['json']=req_json_dict
320             if (job_dict['create_response'] == 201): #Set up next response code if create was ok
321                 job_dict['create_response'] = 200
322             if (job_dict['delete_response'] == 404):
323                 job_dict['delete_response'] = 204
324         else:
325             return_msg=RETURNING_CONFIGURED_RESP
326
327         job_dict['create_counter']=job_dict['create_counter']+1
328     else:
329         return JOBID_NO_MATCH, 400
330
331     return return_msg,return_code
332
333 # Callback for delete job
334 # URI and parameters (POST): /callbacks/delete/<producer_id>
335 # response: 204 at delete or other configured response code
336 @app.route(CALLBACK_DELETE_URL,
337      methods=['POST'])
338 def callback_delete(producer_id):
339
340     req_json_dict=None
341     try:
342         req_json_dict = json.loads(request.data)
343         with open('job-schema.json') as f:
344             schema = json.load(f)
345             validate(instance=req_json_dict, schema=schema)
346     except Exception:
347         return JSON_CORRUPT,400
348
349     job_id=req_json_dict['ei_job_identity']
350     job_dict=get_callback_dict(producer_id, job_id)
351     if (job_dict is None):
352         return PRODUCER_OR_JOB_NOT_FOUND,400
353     return_code=0
354     return_msg=""
355     if (req_json_dict['ei_job_identity'] == job_id):
356         print("Delete callback received for producer: "+str(producer_id)+" and job: "+str(job_id))
357         return_code=job_dict['delete_response']
358         if (job_dict['delete_response'] == 204):
359             job_dict['json']=None
360             job_dict['delete_response']=404
361             if (job_dict['create_response'] == 200):
362                 job_dict['create_response'] = 201 # reset create response if delete was ok
363         else:
364             return_msg=RETURNING_CONFIGURED_RESP
365
366         job_dict['delete_counter']=job_dict['delete_counter']+1
367     else:
368         return JOBID_NO_MATCH, 400
369
370     return return_msg, return_code
371
372 # Callback for supervision of producer
373 # URI and parameters (GET): /callbacks/supervision/<producer_id>
374 # response: 200 or other configured response code
375 @app.route(CALLBACK_SUPERVISION_URL,
376      methods=['GET'])
377 def callback_supervision(producer_id):
378
379     print("Supervision callback received for producer: "+str(producer_id))
380
381     producer_dict=get_callback_dict(producer_id, None)
382     if (producer_dict is None):
383         return PRODUCER_NOT_FOUND,400
384     return_code=producer_dict['supervision_response']
385     return_msg=""
386     if (return_code != 200):
387         return_msg="returning configured response code"
388
389     producer_dict['supervision_counter']=producer_dict['supervision_counter']+1
390
391     return return_msg,producer_dict['supervision_response']
392
393 # Callback for supervision of producer
394 # URI and parameters (GET): "/jobdata/<string:producer_id>/<string:job_id>"
395 # response: 200 or 204
396 @app.route(JOB_DATA,
397      methods=['GET'])
398 def get_jobdata(producer_id, job_id):
399
400     print("Get job data received for producer: "+str(producer_id)+" and job: "+str(job_id))
401
402     job_dict=setup_callback_dict(producer_id, job_id)
403     if (job_dict['json'] is None):
404         return "",204
405     else:
406         return json.dumps(job_dict['json']), 200
407
408
409 # Counter for create calls for a job
410 # URI and parameters (GET): "/counter/create/<string:producer_id>/<string:job_id>"
411 # response: 200 and counter value
412 @app.route(COUNTER_CREATE,
413      methods=['GET'])
414 def counter_create(producer_id, job_id):
415     job_dict=get_callback_dict(producer_id, job_id)
416     if (job_dict is None):
417         return -1,200
418     return str(job_dict['create_counter']),200
419
420 # Counter for delete calls for a job
421 # URI and parameters (GET): "/counter/delete/<string:producer_id>/<string:job_id>"
422 # response: 200 and counter value
423 @app.route(COUNTER_DELETE,
424      methods=['GET'])
425 def counter_delete(producer_id, job_id):
426     job_dict=get_callback_dict(producer_id, job_id)
427     if (job_dict is None):
428         return -1,200
429     return str(job_dict['delete_counter']),200
430
431 # Counter for supervision calls for a producer
432 # URI and parameters (GET): "/counter/supervision/<string:producer_id>"
433 # response: 200 and counter value
434 @app.route(COUNTER_SUPERVISION,
435      methods=['GET'])
436 def counter_supervision(producer_id):
437     producer_dict=get_callback_dict(producer_id, None)
438     if (producer_dict is None):
439         return -1,200
440     return str(producer_dict['supervision_counter']),200
441
442 # Get status info
443 # URI and parameters (GET): "/status"
444 # -
445 @app.route(STATUS,
446     methods=['GET'])
447 def status():
448     global db
449     return json.dumps(db),200
450
451
452 # Reset db
453 @app.route('/reset',
454     methods=['GET', 'POST', 'PUT'])
455 def reset():
456     global db
457     db={}
458     return "",200
459
460 ### Main function ###
461
462 if __name__ == "__main__":
463     app.run(port=HOST_PORT, host=HOST_IP)