blob: c21a7ab3df4d9d7a1964255e9aa6465a51382d65 [file] [log] [blame]
BjornMagnussonXAf38e1e82020-10-11 23:05:02 +02001
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
19from flask import Flask
20from flask import request
21
22import json
23from jsonschema import validate
24
25app = Flask(__name__)
26
27# # list of callback messages
28# msg_callbacks={}
29
30# Server info
31HOST_IP = "::"
32HOST_PORT = 2222
33
34# # Metrics vars
35# cntr_msg_callbacks=0
36# cntr_msg_fetched=0
37
38# Request and response constants
39CALLBACK_CREATE_URL="/callbacks/create/<string:producer_id>"
40CALLBACK_DELETE_URL="/callbacks/delete/<string:producer_id>"
41CALLBACK_SUPERVISION_URL="/callbacks/supervision/<string:producer_id>"
42
43ARM_CREATE_RESPONSE="/arm/create/<string:producer_id>/<string:job_id>"
44ARM_DELETE_RESPONSE="/arm/delete/<string:producer_id>/<string:job_id>"
45ARM_SUPERVISION_RESPONSE="/arm/supervision/<string:producer_id>"
46ARM_TYPE="/arm/type/<string:producer_id>/<string:type_id>"
47
48COUNTER_SUPERVISION="/counter/supervision/<string:producer_id>"
49COUNTER_CREATE="/counter/create/<string:producer_id>/<string:job_id>"
50COUNTER_DELETE="/counter/delete/<string:producer_id>/<string:job_id>"
51
52JOB_DATA="/jobdata/<string:producer_id>/<string:job_id>"
53
54STATUS="/status"
55
56#Constsants
57APPL_JSON='application/json'
58UNKNOWN_QUERY_PARAMETERS="Unknown query parameter(s)"
59RETURNING_CONFIGURED_RESP="returning configured response code"
60JOBID_NO_MATCH="job id in stored json does not match request"
61PRODUCER_OR_JOB_NOT_FOUND="producer or job not found"
62PRODUCER_NOT_FOUND="producer not found"
63TYPE_NOT_FOUND="type not found"
64TYPE_IN_USE="type is in use in a job"
65JSON_CORRUPT="json in request is corrupt or missing"
66
67#Producer and job db, including armed responses
68db={}
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)
83def 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)
118def 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
135def 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'])
148def 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'])
158def 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'])
189def 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'])
222def 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'])
248def 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'])
268def 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'])
290def 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'])
338def 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'])
377def 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'])
398def 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'])
414def 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'])
425def 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'])
436def 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'])
447def status():
448 global db
449 return json.dumps(db),200
450
451
452# Reset db
453@app.route('/reset',
454 methods=['GET', 'POST', 'PUT'])
455def reset():
456 global db
457 db={}
458 return "",200
459
460### Main function ###
461
462if __name__ == "__main__":
463 app.run(port=HOST_PORT, host=HOST_IP)