3 * ============LICENSE_START=======================================================
5 * ================================================================================
6 * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
7 * ================================================================================
8 * Copyright (C) 2017 Amdocs
9 * =============================================================================
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
22 * ============LICENSE_END=========================================================
26 import time, datetime, json, os, sys, subprocess
35 from cherrypy.lib.httputil import parse_query_string
37 from multiprocessing import Process, Manager
39 from AnsibleModule import ansibleSysCall
40 from BuildHostFile import buildHostsSysCall
41 from BuildPlaybookParams import buildInventorySysCall, getPlaybookFile
43 from os import listdir
44 from os.path import isfile, join
46 TestRecord = Manager().dict()
50 def validate_password(realm, username, password):
51 comp = crypt.crypt(password, salt)
52 if username in userpassdict and userpassdict[username] == comp:
58 p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
59 output = p.stdout.readlines()
62 for i in range(len(output)):
63 output[i] = output[i].strip()
67 def callback(Id, Result, Output, Log, returncode):
68 cherrypy.log("***> in RestServer.callback")
71 time_now = datetime.datetime.utcnow()
72 delta_time = (time_now - TestRecord[Id]['Time']).total_seconds()
73 Result['PlaybookName'] = TestRecord[Id]['PlaybookName']
74 Result['Version'] = TestRecord[Id]['Version']
76 Result['StatusCode'] = 500
77 Result['StatusMessage'] = "TERMINATED"
79 Result['StatusCode'] = 200
80 Result['StatusMessage'] = "FINISHED"
82 # Need to update the whole data structure for key=Id otherwise Manager is not updated
84 'PlaybookName': TestRecord[Id]['PlaybookName'],
85 'Version': TestRecord[Id]['Version'],
86 'NodeList': TestRecord[Id]['NodeList'],
87 'HostGroupList': TestRecord[Id]['HostGroupList'],
88 'HostNameList': TestRecord[Id]['HostNameList'],
89 'Time': TestRecord[Id]['Time'],
90 'Timeout': TestRecord[Id]['Timeout'],
91 'Duration': str(delta_time),
92 'EnvParameters': TestRecord[Id]['EnvParameters'],
93 'LocalParameters': TestRecord[Id]['LocalParameters'],
94 'FileParameters': TestRecord[Id]['FileParameters'],
95 'CallBack': TestRecord[Id]['CallBack'],
99 'Path': TestRecord[Id]['Path'],
100 'Mandatory': TestRecord[Id]['Path']
103 if TestRecord[Id]['CallBack'] is not None:
105 # Posting results to callback server
108 "StatusMessage": "FINISHED",
109 "PlaybookName": TestRecord[Id]["PlaybookName"],
110 "Version": TestRecord[Id]["Version"],
111 "Duration": TestRecord[Id]["Duration"],
112 "Results": TestRecord[Id]['Result']['Results']
115 cherrypy.log("CALLBACK: TestRecord[Id]['Output']['Output']:", str(TestRecord[Id]['Output']['Output']))
116 cherrypy.log("CALLBACK: Results:", str(data["Results"]))
118 if not TestRecord[Id]['Output']['Output'] == {}:
119 for key in data["Results"]:
120 if key in TestRecord[Id]['Output']['Output']:
121 data["Results"][key]["Output"] = TestRecord[Id]['Output']['Output'][key]
123 cherrypy.log(" Posting to", TestRecord[Id]['CallBack'])
125 s = requests.Session()
126 r = s.post(TestRecord[Id]['CallBack'], data=json.dumps(data),
127 headers={'content-type': 'application/json'})
128 cherrypy.log(" Response", r.status_code, r.text)
131 def RunAnsible_Playbook(callback, Id, Inventory, Playbook, NodeList, TestRecord, Path, ArchiveFlag, pnf_flag=False):
132 cherrypy.log("***> in RestServer.RunAnsible_Playbook")
134 # Run test in playbook for given target
135 retval, log, returncode = ansibleSysCall(Inventory, Playbook, NodeList,
136 TestRecord[Id]['Mandatory'],
137 TestRecord[Id]['EnvParameters'],
138 TestRecord[Id]['LocalParameters'],
139 TestRecord[Id]['Timeout'],
142 cherrypy.log("Return code:" + str(returncode))
143 cherrypy.log("Return value:" + str(retval))
147 Output = {'Output': {}}
151 onlyfiles = [f for f in listdir(Path)
152 if isfile(join(Path, f))]
154 cherrypy.log("Checking for results.txt files: ")
155 for file in onlyfiles:
156 if "results.txt" in file:
157 # if file.endswith("results.txt"):
158 cherrypy.log("results file: " + file)
159 f = open(Path + "/" + file, "r")
161 key = file.split("_")[0]
162 Output['Output'][key] = f.read()
164 resultsData = f.read() # Not to pass vnf instance name
165 OutputP = json.loads(resultsData)
166 Output['Output'] = OutputP
167 cherrypy.log("Output = " + str(Output['Output']))
168 # Output['Output'][key] = f.read() # To pass vnf instance name
172 Output = {'Output': {}}
174 Result = {'Results': {}}
175 if 'could not be found' in Log:
176 Result['Results'] = {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
178 if returncode == 137:
179 Result['Results'] = {"StatusCode": 500, "StatusMessage": "TERMINATED"}
180 elif TestRecord[Id]['NodeList'] == []:
182 if 'TargetNode' in TestRecord[Id]['EnvParameters']:
183 targetlist = TestRecord[Id]['EnvParameters']['TargetNode'].split(' ')
185 targetlist = ["localhost"]
188 for i in range(len(targetlist)):
189 if key in targetlist[i]:
192 if int(retval[key][0]) > 0 and int(retval[key][2]) == 0 and int(retval[key][3]) == 0:
194 Result['Results'][targetlist[host_index]] = \
195 {"GroupName": 'na', "StatusCode": 200, "StatusMessage": "SUCCESS"}
197 Result['Results'][key] = {"GroupName": 'na', "StatusCode": 200, "StatusMessage": "SUCCESS"}
198 elif int(retval[key][2]) > 0:
200 Result['Results'][targetlist[host_index]] = \
201 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "NOT REACHABLE"}
203 Result['Results'][key] = \
204 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "NOT REACHABLE"}
205 elif int(retval[key][3]) > 0:
207 Result['Results'][targetlist[host_index]] = \
208 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "FAILURE"}
210 Result['Results'][key] = \
211 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "FAILURE"}
214 if len(TestRecord[Id]['HostNameList']) > 0:
216 for i in range(len(TestRecord[Id]['HostNameList'])):
217 if key in TestRecord[Id]['HostNameList'][i]:
220 if int(retval[key][0]) > 0 and int(retval[key][2]) == 0 and \
221 int(retval[key][3]) == 0:
222 if len(host_index) > 0:
223 Result['Results'][TestRecord[Id]['HostNameList'][host_index[0]]] = \
225 "GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
226 "StatusCode": 200, "StatusMessage": "SUCCESS"
229 for i in range(1, len(host_index)):
230 Result['Results'][TestRecord[Id]['HostNameList'][host_index[i]]]["GroupName"] += \
231 "," + TestRecord[Id]['HostGroupList'][host_index[i]]
233 Result['Results'][key] = {"GroupName": key, "StatusCode": 200, "StatusMessage": "SUCCESS"}
235 elif int(retval[key][2]) > 0:
236 if len(host_index) > 0:
237 Result['Results'][TestRecord[Id]['HostNameList'][host_index[0]]] = \
239 "GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
240 "StatusCode": 400, "StatusMessage": "NOT REACHABLE"
243 for i in range(1, len(host_index)):
244 Result['Results'][TestRecord[Id]['HostNameList'][host_index[i]]]["GroupName"] += \
245 "," + TestRecord[Id]['HostGroupList'][host_index[i]]
247 Result['Results'][key] = \
248 {"GroupName": key, "StatusCode": 200, "StatusMessage": "NOT REACHABLE"}
249 elif int(retval[key][3]) > 0:
250 if len(host_index) > 0:
251 Result['Results'][TestRecord[Id]['HostNameList'][host_index[0]]] = \
253 "GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
254 "StatusCode": 400, "StatusMessage": "FAILURE"
257 for i in range(1, len(host_index)):
258 Result['Results'][TestRecord[Id]['HostNameList'][host_index[i]]]["GroupName"] += \
259 "," + TestRecord[Id]['HostGroupList'][host_index[i]]
261 Result['Results'][key] = \
262 {"GroupName": key, "StatusCode": 200, "StatusMessage": "FAILURE"}
265 for i in range(len(TestRecord[Id]['NodeList'])):
266 if key in TestRecord[Id]['NodeList'][i]:
269 if int(retval[key][0]) > 0 and int(retval[key][2]) == 0 and \
270 int(retval[key][3]) == 0:
271 Result['Results'][TestRecord[Id]['NodeList'][host_index]] = \
272 {"GroupName": 'na', "StatusCode": 200, "StatusMessage": "SUCCESS"}
273 elif int(retval[key][2]) > 0:
274 Result['Results'][TestRecord[Id]['NodeList'][host_index]] = \
275 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "NOT REACHABLE"}
276 elif int(retval[key][3]) > 0:
277 Result['Results'][TestRecord[Id]['NodeList'][host_index]] = \
278 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "FAILURE"}
280 callback(Id, Result, Output, Log, returncode)
283 def store_local_vars(playbook_path, Id):
284 if not os.path.exists(playbook_path + "/vars"):
285 os.mkdir(playbook_path + "/vars")
287 if not os.path.isfile(playbook_path + "/vars/defaults.yml"):
288 os.mknod(playbook_path + "/vars/defaults.yml")
290 # ##################################################
292 # write local parameters passed into defaults.yml
294 local_parms = TestRecord[Id]['LocalParameters']
295 cherrypy.log("LocalParameters: " + str(local_parms))
297 f = open(playbook_path + "/vars/defaults.yml", "a")
298 for key, value in list(local_parms.items()):
299 f.write(key + "=" + value + "\n")
301 # ##################################################
303 for key in TestRecord[Id]['LocalParameters']:
305 for i in range(len(TestRecord[Id]['HostNameList'])):
306 if key in TestRecord[Id]['HostNameList'][i]:
308 if len(host_index) == 0:
309 for i in range(len(TestRecord[Id]['HostGroupList'])):
310 if key in TestRecord[Id]['HostGroupList'][i]:
312 if len(host_index) > 0:
313 for i in range(len(host_index)):
314 f = open(playbook_path + "/vars/" + TestRecord[Id]['HostNameList'][host_index[i]] + ".yml", "a")
315 for param in TestRecord[Id]['LocalParameters'][key]:
316 f.write(param + ": " + str(TestRecord[Id]['LocalParameters'][key][param]) + "\n")
320 def process_pnf_playbook(input_json, Id, EnvParameters, time_now):
321 cherrypy.log("Processing playbook for PNF...")
323 PlaybookName = input_json['PlaybookName']
324 version = input_json.get('Version', None)
327 cherrypy.log("Request USER : " + cherrypy.request.login)
328 cherrypy.log("Request Decode: ID " + Id)
329 cherrypy.log("Request Decode: EnvParameters " + json.dumps(EnvParameters))
330 cherrypy.log("Request Decode: PlaybookName " + PlaybookName)
332 for key in EnvParameters:
333 value = EnvParameters[key]
334 if isinstance(value, (list, dict)):
335 valueStr = json.dumps(value)
336 # Need to dump two times to keep the backslash and double quotes, add backslash and single quotes for spaces
337 EnvParameters[key] = "\\'" + json.dumps(valueStr)[1:-1] + "\\'"
339 str_uuid = str(uuid.uuid4())
341 HomeDir = os.path.dirname(os.path.realpath("~/"))
343 PlaybookType = PlaybookName.split(".")[0].split('_')[-1]
344 PlaybookDir = HomeDir + '/' + ANSIBLE_TEMP + "/" + PlaybookName.split(".")[0] + "_" + str_uuid
345 AnsibleInv = PlaybookType + "_" + "inventory"
348 cherrypy.log("Request Decode: PlaybookType " + PlaybookType)
349 cherrypy.log("Request Decode: PlaybookDir " + PlaybookDir)
350 cherrypy.log("Request Decode: AnsibleInv " + AnsibleInv)
352 NodeList = input_json.get('NodeList', [])
353 cherrypy.log("Request Decode: NodeList: " + str(NodeList))
355 # Create base run directory if it doesn't exist
356 if not os.path.exists(ANSIBLE_TEMP):
357 cherrypy.log("Creating Base Run Directory: " + ANSIBLE_TEMP)
358 os.makedirs(ANSIBLE_TEMP)
360 os.mkdir(PlaybookDir)
362 # Process inventory file for target
366 buildInventorySysCall(ANSIBLE_PATH, ANSIBLE_INV, NodeList, PlaybookDir, AnsibleInv, hostgrouplist, hostnamelist)
368 version_target = getPlaybookFile(ANSIBLE_PATH, PlaybookName, PlaybookType, PlaybookDir)
369 if not version_target:
370 return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
373 version = version_target
375 if 'Timeout' in input_json:
376 timeout = int(input_json['Timeout'])
377 cherrypy.log("Timeout from API: " + str(timeout))
379 timeout = timeout_seconds
380 cherrypy.log("Timeout not passed from API using default: " + str(timeout))
382 EnvParam = input_json.get('EnvParameters', {})
383 LocalParam = input_json.get('LocalParameters', {})
384 FileParam = input_json.get('FileParameters', {})
385 callback_flag = input_json.get('CallBack', None)
387 # if AnsibleServer is not set to 'na' don't send AnsibleServer in PENDING response.
389 'PlaybookName': PlaybookName,
391 'NodeList': NodeList,
392 'HostGroupList': hostgrouplist,
393 'HostNameList': hostnamelist,
397 'EnvParameters': EnvParam,
398 'LocalParameters': LocalParam,
399 'FileParameters': FileParam,
400 'CallBack': callback_flag,
403 "StatusMessage": 'PENDING',
404 "ExpectedDuration": str(timeout) + "sec"
411 if AnsibleServer != 'na':
412 TestRecord[Id]['Result']["AnsibleServer"] = str(AnsibleServer),
414 cherrypy.log("Test_Record: " + str(TestRecord[Id]))
417 if TestRecord[Id]['FileParameters']:
418 for key in TestRecord[Id]['FileParameters']:
420 filecontent = TestRecord[Id]['FileParameters'][key]
421 f = open(PlaybookDir + "/" + filename, "w")
425 playbook_path = PlaybookDir
428 store_local_vars(playbook_path, Id)
430 # write some info out to files before running
432 f = open(playbook_path + "/User.txt", "a")
433 f.write(cherrypy.request.login)
436 f = open(playbook_path + "/PlaybookName.txt", "a")
437 f.write(PlaybookName)
440 f = open(playbook_path + "/JsonRequest.txt", "w")
441 f.write(json.dumps(input_json, indent=4, sort_keys=True))
444 # Cannot use thread because ansible module uses signals which are only supported in main thread.
445 # So use multiprocess with shared object
446 p = Process(target=RunAnsible_Playbook,
447 args=(callback, Id, PlaybookDir + '/' + AnsibleInv, PlaybookDir + '/' + PlaybookType + '.yml',
448 NodeList, TestRecord, PlaybookDir, ArchiveFlag, True))
450 ActiveProcess[Id] = p
451 return TestRecord[Id]['Result']
454 def process_vnf_playbook(input_json, Id, EnvParameters, time_now):
455 cherrypy.log("Processing playbook for VNF...")
457 PlaybookName = input_json['PlaybookName']
458 VNF_instance = EnvParameters.get('vnf_instance')
459 version = input_json.get('Version', None)
463 HaveInventoryNames = False
464 inventory_names = None
465 if 'InventoryNames' in input_json:
466 inventory_names = input_json['InventoryNames']
467 HaveInventoryNames = True
469 AnsiblePlaybookFail = True
471 str_uuid = str(uuid.uuid4())
473 # VnfType = PlaybookName.split("/")[0]
476 cherrypy.log("Request USER : " + cherrypy.request.login)
477 cherrypy.log("Request Decode: ID " + Id)
478 # cherrypy.log("Request Decode: VnfType " + VnfType)
479 cherrypy.log("Request Decode: EnvParameters " + json.dumps(EnvParameters))
481 # Verify VNF_instance was passed in EnvParameters
482 if VNF_instance is not None:
483 cherrypy.log("Request Decode: VnfInstance " + VNF_instance)
485 cherrypy.log("StatusCode: 107, StatusMessage: VNF_instance NOT PROVIDED")
486 return {"StatusCode": 107, "StatusMessage": "VNF_instance NOT PROVIDED"}
488 if inventory_names is not None:
489 cherrypy.log("Request Decode: Inventory Names " + inventory_names)
491 cherrypy.log("Request Decode: Inventory Names " + "Not provided")
493 cherrypy.log("Request Decode: PlaybookName " + PlaybookName)
495 PlayBookFunction = PlaybookName.rsplit("/", 2)[1]
496 PlayBookFile = PlayBookFunction + "/site.yml"
498 cherrypy.log("Request Decode: PlaybookFunction " + PlayBookFunction)
499 cherrypy.log("Request Decode: PlaybookFile " + PlayBookFile)
501 BaseDir = ANSIBLE_PATH + "/" + PlaybookName.rsplit("/", 1)[0]
502 CopyDir = ANSIBLE_PATH + "/" + PlaybookName.rsplit("/", 2)[0]
503 cherrypy.log("Request Decode: Basedir " + BaseDir)
504 cherrypy.log("Request Decode: Copydir " + CopyDir)
506 PlaybookDir = ANSIBLE_TEMP + "/" + VNF_instance + "_" + str_uuid + "_" + str(Id)
507 cherrypy.log("Request Decode: PlaybookDir " + PlaybookDir)
509 # AnsibleInv is the directory where the host file to be run exists
510 # AnsibleInv = ANSIBLE_PATH + "/" + VnfType + "/latest/ansible/inventory/" + VNF_instance
513 # Create base run directory if it doesn't exist
514 if not os.path.exists(ANSIBLE_TEMP):
515 cherrypy.log("Creating Base Run Directory: " + ANSIBLE_TEMP)
516 os.makedirs(ANSIBLE_TEMP)
518 if not os.path.exists(CopyDir):
519 cherrypy.log("Playbook Not Found")
520 return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
522 # copy static playbook dir to run dir
523 cherrypy.log("Copying from " + CopyDir + " to " + PlaybookDir)
524 shutil.copytree(CopyDir, PlaybookDir)
525 # cmd="/usr/bin/find " + PlaybookDir + " -exec /usr/bin/touch {} \;"
526 cmd = "/usr/bin/find " + PlaybookDir + " -exec chmod +rx {} \;"
530 cherrypy.log("PlaybookDir: " + PlaybookDir)
531 # cherrypy.log("AnsibleInv: " + AnsibleInv)
533 # Process inventory file for target
537 NodeList = input_json.get('NodeList', [])
539 cherrypy.log("NodeList: " + str(NodeList))
543 cherrypy.log("*** NodeList - Empty ***")
547 # ##############################################################################
548 # #### Host file processing ###########################
549 # #### 1. Use file delivered with playbook ###########################
550 # #### 2. If HostNames + NodeList generate and use ###########################
551 # ##############################################################################
553 # Verify inventory directory exists
554 path = PlaybookDir + "/inventory/"
555 if not os.path.isdir(path):
556 cherrypy.log("Inventory directory %s does not exist - create it" % path)
560 cherrypy.log("Creation of the directory %s failed" % path)
562 cherrypy.log("Successfully created the directory %s " % path)
564 # location of host file - Default
565 HostFile = PlaybookDir + "/inventory/" + VNF_instance + "hosts"
566 cherrypy.log("HostFile: " + HostFile)
568 # if NodeList and InventoryNames need to build host file
569 if HaveInventoryNames and HaveNodeList:
570 cherrypy.log("Build host file from NodeList")
571 ret = buildHostsSysCall(input_json, PlaybookDir, inventory_names)
573 cherrypy.log("Returning Error: Not running Playbook")
574 return {"StatusCode": 105,
575 "StatusMessage": "NodeList: Missing vnfc-type field"}
577 # Having been built now copy new file to correct file
578 shutil.copy(PlaybookDir + "/host_file.txt", HostFile)
579 cherrypy.log("Copying Generated host file to: " + HostFile)
581 if 'Timeout' in input_json:
582 timeout = int(input_json['Timeout'])
583 cherrypy.log("Timeout from API: " + str(timeout))
585 timeout = timeout_seconds
586 cherrypy.log("Timeout not passed from API using default: " + str(timeout))
588 EnvParam = input_json.get('EnvParameters', {})
589 LocalParam = input_json.get('LocalParameters', {})
590 FileParam = input_json.get('FileParameters', {})
591 callback_flag = input_json.get('CallBack', None)
593 # if AnsibleServer is not set to 'na' don't send AnsibleServer in PENDING response.
595 'PlaybookName': PlaybookName,
597 'NodeList': NodeList,
598 'HostGroupList': hostgrouplist,
599 'HostNameList': hostnamelist,
603 'EnvParameters': EnvParam,
604 'LocalParameters': LocalParam,
605 'FileParameters': FileParam,
606 'CallBack': callback_flag,
609 "StatusMessage": 'PENDING',
610 "ExpectedDuration": str(timeout) + "sec"
617 if AnsibleServer != 'na':
618 TestRecord[Id]['Result']["AnsibleServer"] = str(AnsibleServer),
620 cherrypy.log("Test_Record: " + str(TestRecord[Id]))
623 if TestRecord[Id]['FileParameters']:
624 for key in TestRecord[Id]['FileParameters']:
626 filecontent = TestRecord[Id]['FileParameters'][key]
627 f = open(PlaybookDir + "/" + filename, "w")
632 if os.path.exists(ANSIBLE_PATH + '/' + PlaybookName):
633 AnsiblePlaybookFail = False
635 if AnsiblePlaybookFail:
636 # if os.path.exists(PlaybookDir):
637 # shutil.rmtree (PlaybookDir)
638 cherrypy.log("AnsiblePlaybookFail")
640 return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
643 playbook_path = PlaybookDir
646 store_local_vars(playbook_path, Id)
648 # write some info out to files before running
650 f = open(playbook_path + "/User.txt", "a")
651 f.write(cherrypy.request.login)
654 f = open(playbook_path + "/PlaybookName.txt", "a")
655 f.write(PlaybookName)
658 f = open(playbook_path + "/PlaybookExDir.txt", "a")
659 f.write(PlaybookDir + "/" + PlayBookFunction)
662 f = open(playbook_path + "/JsonRequest.txt", "w")
663 f.write(json.dumps(input_json, indent=4, sort_keys=True))
666 # Check that HostFile exists
667 if not os.path.isfile(HostFile):
668 cherrypy.log("Inventory file Not Found: " + HostFile)
669 return {"StatusCode": 101, "StatusMessage": "PLAYBOOK INVENTORY FILE NOT FOUND"}
671 # Cannot use thread because ansible module uses signals which are only supported in main thread.
672 # So use multiprocess with shared object
673 p = Process(target=RunAnsible_Playbook,
674 args=(callback, Id, HostFile, PlaybookDir + '/' + PlayBookFile,
675 NodeList, TestRecord, PlaybookDir + "/" + PlayBookFunction, ArchiveFlag))
677 ActiveProcess[Id] = p
678 return TestRecord[Id]['Result']
681 def handle_post_method(input_json, time_now):
682 cherrypy.log("Payload: " + str(input_json))
684 if 'Id' in input_json and 'PlaybookName' in input_json and 'EnvParameters' in input_json:
685 if input_json['Id'] not in TestRecord:
686 # check if Id exists in previous run directory, if so return error
687 Id = input_json['Id']
688 if glob.glob(ANSIBLE_TEMP + '/*_' + input_json['Id']):
689 cherrypy.log("Old directory found for ID: " + Id)
690 return {"StatusCode": 101, "StatusMessage": "TEST ID FILE ALREADY DEFINED"}
692 # if required it should be passed as an argument
693 EnvParameters = input_json.get('EnvParameters', {})
695 # The lines below are to test multiple EnvParameters being passed
696 # for i in EnvParameters:
697 # cherrypy.log("EnvParameter object: " + i)
698 # cherrypy.log(" EnvParameter Value: " + EnvParameters[ i ])
700 pnf_flag = EnvParameters.get("pnf-flag", "")
701 if pnf_flag == "true":
702 return process_pnf_playbook(input_json, Id, EnvParameters, time_now)
704 return process_vnf_playbook(input_json, Id, EnvParameters, time_now)
706 cherrypy.log("TEST ID ALREADY DEFINED")
707 return {"StatusCode": 101, "StatusMessage": "TEST ID ALREADY DEFINED"}
709 return {"StatusCode": 500, "StatusMessage": "JSON OBJECT MUST INCLUDE: ID, PLAYBOOKNAME, EnvParameters"}
712 def handle_get_method(input_data):
713 # Verify we have a Type passed in GET request
714 if 'Type' not in input_data:
715 return {"StatusCode": 500, "StatusMessage": "RESULTS TYPE UNDEFINED"}
718 cherrypy.log("Request USER: " + cherrypy.request.login)
719 cherrypy.log("Payload: " + str(input_data) + " Type " + input_data['Type'])
721 if 'LogRest' in input_data['Type']:
723 sys.stdout = open("/var/log/RestServer.log", "w")
725 # Just a debug to dump any records
726 if 'GetStatus' in input_data['Type']:
727 cherrypy.log("******** Dump Records **********")
728 if list(TestRecord.items()):
729 for id, record in list(TestRecord.items()):
730 cherrypy.log(" Id: " + id)
731 cherrypy.log("Record: " + str(record))
733 cherrypy.log(" No Records to dump")
735 if 'Id' in input_data and 'Type' in input_data:
736 if not ('GetResult' in input_data['Type'] or 'GetOutputLog' in input_data['Type'] or
737 'GetTheOutput' in input_data['Type'] or 'GetOutput' in input_data['Type'] or
738 'GetLog' in input_data['Type']):
739 return {"StatusCode": 500, "StatusMessage": "RESULTS TYPE UNDEFINED"}
741 if input_data['Id'] in TestRecord:
742 if 'GetResult' in input_data['Type']:
743 cherrypy.log(" ** GetResult for: " + str(input_data['Id']))
744 if 'StatusMessage' in TestRecord[input_data['Id']]['Result'] and getresults_block:
745 # check if playbook is still running
746 while ActiveProcess[input_data['Id']].is_alive():
747 cherrypy.log("*** Playbook running returning PENDING for " + str(input_data['Id']))
748 # If still running return PENDING response
749 # if AnsibleServer != 'na':
750 # return {"StatusCode": 100, "StatusMessage": 'PENDING', "AnsibleServer": str(AnsibleServer)}
752 # return {"StatusCode": 100, "StatusMessage": 'PENDING'}
755 # cherrypy.log( "*** Request released " + input_data['Id'])
757 cherrypy.log(str(TestRecord[input_data['Id']]['Result']))
758 cherrypy.log("Output: " + str(TestRecord[input_data['Id']]['Output']))
759 cherrypy.log("StatusCode: " + str(TestRecord[input_data['Id']]['Result']['StatusCode']))
760 cherrypy.log("StatusMessage: " + str(TestRecord[input_data['Id']]['Result']['StatusMessage']))
762 # out_obj gets returned to GET request
763 if TestRecord[input_data['Id']]['Result']['StatusCode'] == 500:
764 out_obj = TestRecord[input_data['Id']]['Result']['Results']
768 "StatusMessage": "FINISHED",
769 "PlaybookName": TestRecord[input_data['Id']]["PlaybookName"],
770 "Version": TestRecord[input_data['Id']]["Version"],
771 "Duration": TestRecord[input_data['Id']]["Duration"],
772 "Output": TestRecord[input_data['Id']]["Output"]["Output"],
773 "Results": TestRecord[input_data['Id']]['Result']['Results']
775 if not TestRecord[input_data['Id']]['Output']['Output'] == {}:
776 cherrypy.log("TestRecord has Output:" + str(TestRecord[input_data['Id']]['Output']['Output']))
778 for key in out_obj["Results"]:
779 cherrypy.log("Output key: " + str(key))
780 if key in TestRecord[input_data['Id']]['Output']['Output']:
781 out_obj["Results"][key]["Output"] = TestRecord[input_data['Id']]['Output']['Output'][key]
783 cherrypy.log("***** GET RETURNING RESULTS Back ****")
784 cherrypy.log(str(out_obj))
786 elif 'GetStatus' in input_data['Type']:
787 cherrypy.log(" Dump Records")
788 for id, record in list(TestRecord.items()):
789 cherrypy.log(" id: " + id)
790 cherrypy.log(" Record:" + str(record))
791 elif 'GetTheOutput' in input_data['Type'] or 'GetOutput' in input_data['Type']:
792 if TestRecord[input_data['Id']]['Output'] == {} and getresults_block:
793 cherrypy.log("*** Request blocked " + input_data['Id'])
795 # while TestRecord[input_data['Id']]['Output'] == {} \
796 # or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
797 while ActiveProcess[input_data['Id']].is_alive():
800 cherrypy.log("*** Request released " + input_data['Id'])
802 cherrypy.log("Output: " + str(TestRecord[input_data['Id']]['Output']))
803 return {"Output": TestRecord[input_data['Id']]['Output']['Output']}
804 elif 'GetOutputLog' in input_data['Type']:
805 cherrypy.log("GetOutputLog: processing.")
806 if glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id']):
807 id = input_data['Id']
808 cherrypy.log("Old directory found for ID: " + id)
809 run_dir = glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id'])
812 if os.path.exists(rdir + "/PlaybookExDir.txt"):
813 cherrypy.log("Found PlaybookExDir.txt file")
814 f = open(rdir + '/PlaybookExDir.txt', 'r')
815 playbookexdir = f.readline()
818 cherrypy.log("Id: " + id)
819 cherrypy.log("RunDir: " + rdir)
820 if os.path.exists(rdir + "/output.log"):
821 cherrypy.log("Found output.log file")
822 f = open(rdir + '/output.log', 'r')
823 output_log = f.readline()
827 cherrypy.log("Globglob failed:")
832 if TestRecord[input_data['Id']]['Log'] == '' and \
835 cherrypy.log("*** Request blocked " + input_data['Id'])
837 while TestRecord[input_data['Id']]['Log'] == '' \
838 or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
841 cherrypy.log("*** Request released " + input_data['Id'])
843 cherrypy.log("Log:" + str(TestRecord[input_data['Id']]['Log']))
844 return {"Log": TestRecord[input_data['Id']]['Log']}
846 # Not in memory check for a file
847 if glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id']):
848 id = input_data['Id']
849 cherrypy.log("Old directory found for ID: " + id)
850 run_dir = glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id'])
853 if os.path.exists(rdir + "/PlaybookExDir.txt"):
854 cherrypy.log("Found PlaybookExDir.txt file")
855 f = open(rdir + '/PlaybookExDir.txt', 'r')
856 playbookexdir = f.readline()
859 cherrypy.log("Id: " + id)
860 cherrypy.log("RunDir: " + rdir)
861 if 'GetLog' in input_data['Type']:
862 if os.path.exists(rdir + "/output.log"):
863 cherrypy.log("Found output.log file")
864 f = open(rdir + '/output.log', 'r')
865 output_log = f.readline()
868 elif 'GetOutputLog' in input_data['Type']:
869 if os.path.exists(rdir + "/output.log"):
870 cherrypy.log("Found output.log file")
871 f = open(rdir + '/output.log', 'r')
872 output_log = f.readline()
875 elif 'GetResult' in input_data['Type']:
876 if os.path.exists(rdir + "/PlaybookName.txt"):
877 cherrypy.log("Found PlaybookName.txt file")
878 f = open(rdir + '/PlaybookName.txt', 'r')
879 playbooknametxt = f.readline()
882 playbooknametxt = "NA"
884 # Add code to get other items not just output.log from files
885 if os.path.exists(rdir + "/log.file"):
886 cherrypy.log("Found log.file")
889 f = open(rdir + '/log.file', 'r')
893 out_results = out_results + line
894 elif "RECAP" in line:
895 out_results = out_results + line
896 recap_line = f.readline()
898 out_results = out_results + recap_line
899 recap_line = f.readline()
904 "StatusMessage": "FINISHED",
905 "PlaybookName": playbooknametxt,
906 "Version": "Version",
908 "Results": out_results
912 return {"StatusCode": 500, "StatusMessage": "PLAYBOOK FAILED "}
914 return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
916 return {"StatusCode": 500, "StatusMessage": "MALFORMED REQUEST"}
919 def handle_delete_method(input_data):
920 cherrypy.log("***> in RestServer.DELETE")
921 cherrypy.log("Payload: " + str(input_data))
923 if input_data['Id'] in TestRecord:
924 if 'PENDING' not in TestRecord[input_data['Id']]['Result']:
925 cherrypy.log(" Path: " + str(TestRecord[input_data['Id']]['Path']))
926 TestRecord.pop(input_data['Id'])
927 if input_data['Id'] in ActiveProcess:
928 ActiveProcess.pop(input_data['Id'])
929 return {"StatusCode": 200, "StatusMessage": "PLAYBOOK EXECUTION RECORDS DELETED"}
931 return {"StatusCode": 200, "StatusMessage": "PENDING"}
933 return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
936 class TestManager(object):
938 @cherrypy.tools.json_out()
939 @cherrypy.tools.json_in()
940 @cherrypy.tools.allow(methods=['POST', 'GET', 'DELETE'])
941 def Dispatch(self, **kwargs):
942 # Let cherrypy error handler deal with malformed requests
943 # No need for explicit error handler, we use default ones
945 time_now = datetime.datetime.utcnow()
947 # Erase old test results (2x timeout)
948 # Do cleanup too of ActiveProcess list and old Records - PAP
950 for key in TestRecord.copy():
951 cherrypy.log("LOOKING AT ALL TestRecords: " + str(key))
952 if key in ActiveProcess:
953 if not ActiveProcess[key].is_alive(): # Just to cleanup defunct processes
954 cherrypy.log("Not ActiveProcess for ID: " + str(key))
955 delta_time = (time_now - TestRecord[key]['Time']).seconds
956 if delta_time > 2 * TestRecord[key]['Timeout']:
957 cherrypy.log("DELETED HISTORY for ID: " + str(key))
958 if key in ActiveProcess:
959 if not ActiveProcess[key].is_alive():
960 ActiveProcess.pop(key)
961 cherrypy.log("DELETED ActiveProcess for ID: " + str(key))
962 # if os.path.exists(TestRecord[key]['Path']):
963 # don't remove run dirrectory
964 # shutil.rmtree (TestRecord[key]['Path'])
967 cherrypy.log("RestServer.Dispatch: " + cherrypy.request.method)
969 if 'POST' in cherrypy.request.method:
970 input_json = cherrypy.request.json
971 return handle_post_method(input_json, time_now)
972 elif 'GET' in cherrypy.request.method:
973 # Lets pause for a second just in case the request was just kicked off
976 input_data = parse_query_string(cherrypy.request.query_string)
977 return handle_get_method(input_data)
978 elif 'DELETE' in cherrypy.request.method:
979 input_data = parse_query_string(cherrypy.request.query_string)
980 return handle_delete_method(input_data)
983 if __name__ == '__main__':
987 config_file_path = "RestServer_config"
989 if not os.path.exists(config_file_path):
990 cherrypy.log('[INFO] The config file does not exist')
1001 timeout_seconds = 'na'
1006 getresults_block = False
1009 config_file = open(config_file_path, 'r')
1010 for config_line in config_file.readlines():
1011 if '#' not in config_line:
1012 if 'ip:' in config_line:
1013 ip = config_line.split(':')[1].strip()
1014 elif 'AnsibleServer:' in config_line:
1015 AnsibleServer = config_line.split(':')[1].strip()
1016 elif 'port:' in config_line:
1017 port = config_line.split(':')[1].strip()
1018 elif 'ksalt:' in config_line:
1019 salt = config_line.split(':')[1].strip()
1020 elif 'tls:' in config_line:
1021 tls = 'YES' in config_line.split(':')[1].strip().upper()
1022 elif 'auth:' in config_line:
1023 AUTH = 'YES' in config_line.split(':')[1].strip().upper()
1024 if tls and 'priv:' in config_line:
1025 priv = config_line.split(':')[1].strip()
1026 if tls and 'pub:' in config_line:
1027 pub = config_line.split(':')[1].strip()
1028 if tls and 'inter_cert:' in config_line:
1029 intermediate = config_line.split(':')[1].strip()
1030 if 'timeout_seconds' in config_line:
1031 timeout_seconds = int(config_line.split(':')[1].strip())
1032 if 'ansible_path' in config_line:
1033 ANSIBLE_PATH = config_line.split(':')[1].strip()
1034 if 'ansible_inv' in config_line:
1035 ANSIBLE_INV = config_line.split(':')[1].strip()
1036 if not os.path.exists(ANSIBLE_PATH + "/" + ANSIBLE_INV):
1037 print('[INFO] The ansible_inv file does not exist')
1039 if 'ansible_temp' in config_line:
1040 ANSIBLE_TEMP = config_line.split(':')[1].strip()
1041 if 'host' in config_line:
1042 host = config_line.split(':')[1].strip()
1043 if 'users' in config_line:
1044 users = config_line.split(':')[1].strip()
1045 if 'getresults_block' in config_line:
1046 getresults_block = 'YES' in config_line.split(':')[1].strip().upper()
1047 if 'from_files' in config_line:
1048 from_files = 'YES' in config_line.split(':')[1].strip().upper()
1056 'response.timeout': 5400,
1057 'server.socket_host': ip,
1058 'server.socket_port': int(port),
1059 'server.protocol_version': 'HTTP/1.1'
1064 # Use pythons built-in SSL
1065 cherrypy.server.ssl_module = 'builtin'
1067 # Point to certificate files
1069 if not os.path.exists(pub):
1070 cherrypy.log('[INFO] The public certificate does not exist')
1073 if not os.path.exists(priv):
1074 cherrypy.log('[INFO] The private key does not exist')
1077 if not os.path.exists(intermediate):
1078 cherrypy.log('[INFO] The intermediate certificate does not exist')
1081 cherrypy.server.ssl_certificate = pub
1082 cherrypy.server.ssl_certificate_chain = intermediate
1083 cherrypy.server.ssl_private_key = priv
1086 # Read in and build user dictionary
1087 if not os.path.exists(users):
1088 cherrypy.log('[INFO] The users file does not exist: ' + users)
1091 user_file = open(users, 'r')
1092 for config_line in user_file.readlines():
1093 if '#' not in config_line:
1094 uid = config_line.split(':')[0].strip()
1095 pw = config_line.split(':')[1].strip()
1096 userpassdict[uid] = pw
1100 {'tools.auth_basic.on': True,
1101 'tools.auth_basic.realm': 'earth',
1102 'tools.auth_basic.checkpassword': validate_password
1106 application = cherrypy.tree.mount(TestManager(), '/', app_conf)
1108 application = cherrypy.tree.mount(TestManager(), '/')
1110 cherrypy.config.update({
1111 'log.access_file': "/var/log/RestServer.access"
1113 accessLogName = "/var/log/RestServer.access"
1114 applicationLogName = "/var/log/RestServer.log"
1115 cherrypy.config.update(global_conf)
1117 log = application.log
1119 log.access_file = ""
1120 from logging import handlers
1122 applicationLogFileHandler = handlers.RotatingFileHandler(applicationLogName, 'a', 1000000, 5000)
1123 accessLogFileHandler = handlers.RotatingFileHandler(accessLogName, 'a', 1000000, 5000)
1126 applicationLogFileHandler.setLevel(logging.DEBUG)
1127 log.error_log.addHandler(applicationLogFileHandler)
1128 log.access_log.addHandler(accessLogFileHandler)
1132 cherrypy.engine.start()
1133 cherrypy.engine.block()