3 * ============LICENSE_START=======================================================
5 * ================================================================================
6 * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
7 * ================================================================================
8 * Copyright (C) 2017 Amdocs
9 * ================================================================================
10 * Copyright (C) 2019 Orange
11 * ================================================================================
12 * Licensed under the Apache License, Version 2.0 (the "License");
13 * you may not use this file except in compliance with the License.
14 * You may obtain a copy of the License at
16 * http://www.apache.org/licenses/LICENSE-2.0
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 * ============LICENSE_END=========================================================
28 import time, datetime, json, os, sys, subprocess
37 from cherrypy.lib.httputil import parse_query_string
39 from multiprocessing import Process, Manager
41 from AnsibleModule import ansibleSysCall
42 from BuildHostFile import buildHostsSysCall
43 from BuildPlaybookParams import buildInventorySysCall, getPlaybookFile
45 from os import listdir
46 from os.path import isfile, join
48 TestRecord = Manager().dict()
52 def validate_password(realm, username, password):
53 comp = crypt.crypt(password, salt)
54 if username in userpassdict and userpassdict[username] == comp:
60 p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
61 output = p.stdout.readlines()
64 for i in range(len(output)):
65 output[i] = output[i].strip()
69 def callback(Id, Result, Output, Log, returncode):
70 cherrypy.log("***> in RestServer.callback")
73 time_now = datetime.datetime.utcnow()
74 delta_time = (time_now - TestRecord[Id]['Time']).total_seconds()
75 Result['PlaybookName'] = TestRecord[Id]['PlaybookName']
76 Result['Version'] = TestRecord[Id]['Version']
78 Result['StatusCode'] = 500
79 Result['StatusMessage'] = "TERMINATED"
81 Result['StatusCode'] = 200
82 Result['StatusMessage'] = "FINISHED"
84 # Need to update the whole data structure for key=Id otherwise Manager is not updated
86 'PlaybookName': TestRecord[Id]['PlaybookName'],
87 'Version': TestRecord[Id]['Version'],
88 'NodeList': TestRecord[Id]['NodeList'],
89 'HostGroupList': TestRecord[Id]['HostGroupList'],
90 'HostNameList': TestRecord[Id]['HostNameList'],
91 'Time': TestRecord[Id]['Time'],
92 'Timeout': TestRecord[Id]['Timeout'],
93 'Duration': str(delta_time),
94 'EnvParameters': TestRecord[Id]['EnvParameters'],
95 'LocalParameters': TestRecord[Id]['LocalParameters'],
96 'FileParameters': TestRecord[Id]['FileParameters'],
97 'CallBack': TestRecord[Id]['CallBack'],
101 'Path': TestRecord[Id]['Path'],
102 'Mandatory': TestRecord[Id]['Path']
105 if TestRecord[Id]['CallBack'] is not None:
107 # Posting results to callback server
110 "StatusMessage": "FINISHED",
111 "PlaybookName": TestRecord[Id]["PlaybookName"],
112 "Version": TestRecord[Id]["Version"],
113 "Duration": TestRecord[Id]["Duration"],
114 "Results": TestRecord[Id]['Result']['Results']
117 cherrypy.log("CALLBACK: TestRecord[Id]['Output']['Output']:", str(TestRecord[Id]['Output']['Output']))
118 cherrypy.log("CALLBACK: Results:", str(data["Results"]))
120 if not TestRecord[Id]['Output']['Output'] == {}:
121 for key in data["Results"]:
122 if key in TestRecord[Id]['Output']['Output']:
123 data["Results"][key]["Output"] = TestRecord[Id]['Output']['Output'][key]
125 cherrypy.log(" Posting to", TestRecord[Id]['CallBack'])
127 s = requests.Session()
128 r = s.post(TestRecord[Id]['CallBack'], data=json.dumps(data),
129 headers={'content-type': 'application/json'})
130 cherrypy.log(" Response", r.status_code, r.text)
133 def RunAnsible_Playbook(callback, Id, Inventory, Playbook, NodeList, TestRecord, Path, ArchiveFlag, pnf_flag=False):
134 cherrypy.log("***> in RestServer.RunAnsible_Playbook")
136 # Run test in playbook for given target
137 retval, log, returncode = ansibleSysCall(Inventory, Playbook, NodeList,
138 TestRecord[Id]['Mandatory'],
139 TestRecord[Id]['EnvParameters'],
140 TestRecord[Id]['LocalParameters'],
141 TestRecord[Id]['Timeout'],
144 cherrypy.log("Return code:" + str(returncode))
145 cherrypy.log("Return value:" + str(retval))
149 Output = {'Output': {}}
153 onlyfiles = [f for f in listdir(Path)
154 if isfile(join(Path, f))]
156 cherrypy.log("Checking for results.txt files: ")
157 for file in onlyfiles:
158 if "results.txt" in file:
159 # if file.endswith("results.txt"):
160 cherrypy.log("results file: " + file)
161 f = open(Path + "/" + file, "r")
163 key = file.split("_")[0]
164 Output['Output'][key] = f.read()
166 resultsData = f.read() # Not to pass vnf instance name
167 OutputP = json.loads(resultsData)
168 Output['Output'] = OutputP
169 cherrypy.log("Output = " + str(Output['Output']))
170 # Output['Output'][key] = f.read() # To pass vnf instance name
174 Output = {'Output': {}}
176 Result = {'Results': {}}
177 if 'could not be found' in Log:
178 Result['Results'] = {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
180 if returncode == 137:
181 Result['Results'] = {"StatusCode": 500, "StatusMessage": "TERMINATED"}
182 elif TestRecord[Id]['NodeList'] == []:
184 if 'TargetNode' in TestRecord[Id]['EnvParameters']:
185 targetlist = TestRecord[Id]['EnvParameters']['TargetNode'].split(' ')
187 targetlist = ["localhost"]
190 for i in range(len(targetlist)):
191 if key in targetlist[i]:
194 if int(retval[key][0]) > 0 and int(retval[key][2]) == 0 and int(retval[key][3]) == 0:
196 Result['Results'][targetlist[host_index]] = \
197 {"GroupName": 'na', "StatusCode": 200, "StatusMessage": "SUCCESS"}
199 Result['Results'][key] = {"GroupName": 'na', "StatusCode": 200, "StatusMessage": "SUCCESS"}
200 elif int(retval[key][2]) > 0:
202 Result['Results'][targetlist[host_index]] = \
203 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "NOT REACHABLE"}
205 Result['Results'][key] = \
206 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "NOT REACHABLE"}
207 elif int(retval[key][3]) > 0:
209 Result['Results'][targetlist[host_index]] = \
210 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "FAILURE"}
212 Result['Results'][key] = \
213 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "FAILURE"}
216 if len(TestRecord[Id]['HostNameList']) > 0:
218 for i in range(len(TestRecord[Id]['HostNameList'])):
219 if key in TestRecord[Id]['HostNameList'][i]:
222 if int(retval[key][0]) > 0 and int(retval[key][2]) == 0 and \
223 int(retval[key][3]) == 0:
224 if len(host_index) > 0:
225 Result['Results'][TestRecord[Id]['HostNameList'][host_index[0]]] = \
227 "GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
228 "StatusCode": 200, "StatusMessage": "SUCCESS"
231 for i in range(1, len(host_index)):
232 Result['Results'][TestRecord[Id]['HostNameList'][host_index[i]]]["GroupName"] += \
233 "," + TestRecord[Id]['HostGroupList'][host_index[i]]
235 Result['Results'][key] = {"GroupName": key, "StatusCode": 200, "StatusMessage": "SUCCESS"}
237 elif int(retval[key][2]) > 0:
238 if len(host_index) > 0:
239 Result['Results'][TestRecord[Id]['HostNameList'][host_index[0]]] = \
241 "GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
242 "StatusCode": 400, "StatusMessage": "NOT REACHABLE"
245 for i in range(1, len(host_index)):
246 Result['Results'][TestRecord[Id]['HostNameList'][host_index[i]]]["GroupName"] += \
247 "," + TestRecord[Id]['HostGroupList'][host_index[i]]
249 Result['Results'][key] = \
250 {"GroupName": key, "StatusCode": 200, "StatusMessage": "NOT REACHABLE"}
251 elif int(retval[key][3]) > 0:
252 if len(host_index) > 0:
253 Result['Results'][TestRecord[Id]['HostNameList'][host_index[0]]] = \
255 "GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
256 "StatusCode": 400, "StatusMessage": "FAILURE"
259 for i in range(1, len(host_index)):
260 Result['Results'][TestRecord[Id]['HostNameList'][host_index[i]]]["GroupName"] += \
261 "," + TestRecord[Id]['HostGroupList'][host_index[i]]
263 Result['Results'][key] = \
264 {"GroupName": key, "StatusCode": 200, "StatusMessage": "FAILURE"}
267 for i in range(len(TestRecord[Id]['NodeList'])):
268 if key in TestRecord[Id]['NodeList'][i]:
271 if int(retval[key][0]) > 0 and int(retval[key][2]) == 0 and \
272 int(retval[key][3]) == 0:
273 Result['Results'][TestRecord[Id]['NodeList'][host_index]] = \
274 {"GroupName": 'na', "StatusCode": 200, "StatusMessage": "SUCCESS"}
275 elif int(retval[key][2]) > 0:
276 Result['Results'][TestRecord[Id]['NodeList'][host_index]] = \
277 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "NOT REACHABLE"}
278 elif int(retval[key][3]) > 0:
279 Result['Results'][TestRecord[Id]['NodeList'][host_index]] = \
280 {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "FAILURE"}
282 callback(Id, Result, Output, Log, returncode)
285 def store_local_vars(playbook_path, Id):
286 if not os.path.exists(playbook_path + "/vars"):
287 os.mkdir(playbook_path + "/vars")
289 if not os.path.isfile(playbook_path + "/vars/defaults.yml"):
290 os.mknod(playbook_path + "/vars/defaults.yml")
292 # ##################################################
294 # write local parameters passed into defaults.yml
296 local_parms = TestRecord[Id]['LocalParameters']
297 cherrypy.log("LocalParameters: " + str(local_parms))
299 f = open(playbook_path + "/vars/defaults.yml", "a")
300 for key, value in local_parms.items():
301 f.write(key + "=" + value + "\n")
303 # ##################################################
305 for key in TestRecord[Id]['LocalParameters']:
307 for i in range(len(TestRecord[Id]['HostNameList'])):
308 if key in TestRecord[Id]['HostNameList'][i]:
310 if len(host_index) == 0:
311 for i in range(len(TestRecord[Id]['HostGroupList'])):
312 if key in TestRecord[Id]['HostGroupList'][i]:
314 if len(host_index) > 0:
315 for i in range(len(host_index)):
316 f = open(playbook_path + "/vars/" + TestRecord[Id]['HostNameList'][host_index[i]] + ".yml", "a")
317 for param in TestRecord[Id]['LocalParameters'][key]:
318 f.write(param + ": " + str(TestRecord[Id]['LocalParameters'][key][param]) + "\n")
322 def process_pnf_playbook(input_json, Id, EnvParameters, time_now):
323 cherrypy.log("Processing playbook for PNF...")
325 PlaybookName = input_json['PlaybookName']
326 version = input_json.get('Version', None)
329 cherrypy.log("Request USER : " + cherrypy.request.login)
330 cherrypy.log("Request Decode: ID " + Id)
331 cherrypy.log("Request Decode: EnvParameters " + json.dumps(EnvParameters))
332 cherrypy.log("Request Decode: PlaybookName " + PlaybookName)
334 str_uuid = str(uuid.uuid4())
336 HomeDir = os.path.dirname(os.path.realpath("~/"))
338 PlaybookType = PlaybookName.split(".")[0].split('_')[-1]
339 PlaybookDir = HomeDir + '/' + ANSIBLE_TEMP + "/" + PlaybookName.split(".")[0] + "_" + str_uuid
340 AnsibleInv = PlaybookType + "_" + "inventory"
343 cherrypy.log("Request Decode: PlaybookType " + PlaybookType)
344 cherrypy.log("Request Decode: PlaybookDir " + PlaybookDir)
345 cherrypy.log("Request Decode: AnsibleInv " + AnsibleInv)
347 NodeList = input_json.get('NodeList', [])
348 cherrypy.log("Request Decode: NodeList: " + str(NodeList))
350 # Create base run directory if it doesn't exist
351 if not os.path.exists(ANSIBLE_TEMP):
352 cherrypy.log("Creating Base Run Directory: " + ANSIBLE_TEMP)
353 os.makedirs(ANSIBLE_TEMP)
355 os.mkdir(PlaybookDir)
357 # Process inventory file for target
361 buildInventorySysCall(ANSIBLE_PATH, ANSIBLE_INV, NodeList, PlaybookDir, AnsibleInv, hostgrouplist, hostnamelist)
363 version_target = getPlaybookFile(ANSIBLE_PATH, PlaybookName, PlaybookType, PlaybookDir)
364 if not version_target:
365 return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
368 version = version_target
370 if 'Timeout' in input_json:
371 timeout = int(input_json['Timeout'])
372 cherrypy.log("Timeout from API: " + str(timeout))
374 timeout = timeout_seconds
375 cherrypy.log("Timeout not passed from API using default: " + str(timeout))
377 EnvParam = input_json.get('EnvParameters', {})
378 LocalParam = input_json.get('LocalParameters', {})
379 FileParam = input_json.get('FileParameters', {})
380 callback_flag = input_json.get('CallBack', None)
382 # if AnsibleServer is not set to 'na' don't send AnsibleServer in PENDING response.
384 'PlaybookName': PlaybookName,
386 'NodeList': NodeList,
387 'HostGroupList': hostgrouplist,
388 'HostNameList': hostnamelist,
392 'EnvParameters': EnvParam,
393 'LocalParameters': LocalParam,
394 'FileParameters': FileParam,
395 'CallBack': callback_flag,
398 "StatusMessage": 'PENDING',
399 "ExpectedDuration": str(timeout) + "sec"
406 if AnsibleServer != 'na':
407 TestRecord[Id]['Result']["AnsibleServer"] = str(AnsibleServer),
409 cherrypy.log("Test_Record: " + str(TestRecord[Id]))
412 if TestRecord[Id]['FileParameters']:
413 for key in TestRecord[Id]['FileParameters']:
415 filecontent = TestRecord[Id]['FileParameters'][key]
416 f = open(PlaybookDir + "/" + filename, "w")
420 playbook_path = PlaybookDir
423 store_local_vars(playbook_path, Id)
425 # write some info out to files before running
427 f = open(playbook_path + "/User.txt", "a")
428 f.write(cherrypy.request.login)
431 f = open(playbook_path + "/PlaybookName.txt", "a")
432 f.write(PlaybookName)
435 f = open(playbook_path + "/JsonRequest.txt", "w")
436 f.write(json.dumps(input_json, indent=4, sort_keys=True))
439 # Cannot use thread because ansible module uses signals which are only supported in main thread.
440 # So use multiprocess with shared object
441 p = Process(target=RunAnsible_Playbook,
442 args=(callback, Id, PlaybookDir + '/' + AnsibleInv, PlaybookDir + '/' + PlaybookType + '.yml',
443 NodeList, TestRecord, PlaybookDir, ArchiveFlag, True))
445 ActiveProcess[Id] = p
446 return TestRecord[Id]['Result']
449 def process_vnf_playbook(input_json, Id, EnvParameters, time_now):
450 cherrypy.log("Processing playbook for VNF...")
452 PlaybookName = input_json['PlaybookName']
453 VNF_instance = EnvParameters.get('vnf_instance')
454 version = input_json.get('Version', None)
458 HaveInventoryNames = False
459 inventory_names = None
460 if 'InventoryNames' in input_json:
461 inventory_names = input_json['InventoryNames']
462 HaveInventoryNames = True
464 AnsiblePlaybookFail = True
466 str_uuid = str(uuid.uuid4())
468 # VnfType = PlaybookName.split("/")[0]
471 cherrypy.log("Request USER : " + cherrypy.request.login)
472 cherrypy.log("Request Decode: ID " + Id)
473 # cherrypy.log("Request Decode: VnfType " + VnfType)
474 cherrypy.log("Request Decode: EnvParameters " + json.dumps(EnvParameters))
476 # Verify VNF_instance was passed in EnvParameters
477 if VNF_instance is not None:
478 cherrypy.log("Request Decode: VnfInstance " + VNF_instance)
480 cherrypy.log("StatusCode: 107, StatusMessage: VNF_instance NOT PROVIDED")
481 return {"StatusCode": 107, "StatusMessage": "VNF_instance NOT PROVIDED"}
483 if inventory_names is not None:
484 cherrypy.log("Request Decode: Inventory Names " + inventory_names)
486 cherrypy.log("Request Decode: Inventory Names " + "Not provided")
488 cherrypy.log("Request Decode: PlaybookName " + PlaybookName)
490 PlayBookFunction = PlaybookName.rsplit("/", 2)[1]
491 PlayBookFile = PlayBookFunction + "/site.yml"
493 cherrypy.log("Request Decode: PlaybookFunction " + PlayBookFunction)
494 cherrypy.log("Request Decode: PlaybookFile " + PlayBookFile)
496 BaseDir = ANSIBLE_PATH + "/" + PlaybookName.rsplit("/", 1)[0]
497 CopyDir = ANSIBLE_PATH + "/" + PlaybookName.rsplit("/", 2)[0]
498 cherrypy.log("Request Decode: Basedir " + BaseDir)
499 cherrypy.log("Request Decode: Copydir " + CopyDir)
501 PlaybookDir = ANSIBLE_TEMP + "/" + VNF_instance + "_" + str_uuid + "_" + str(Id)
502 cherrypy.log("Request Decode: PlaybookDir " + PlaybookDir)
504 # AnsibleInv is the directory where the host file to be run exists
505 # AnsibleInv = ANSIBLE_PATH + "/" + VnfType + "/latest/ansible/inventory/" + VNF_instance
508 # Create base run directory if it doesn't exist
509 if not os.path.exists(ANSIBLE_TEMP):
510 cherrypy.log("Creating Base Run Directory: " + ANSIBLE_TEMP)
511 os.makedirs(ANSIBLE_TEMP)
513 if not os.path.exists(CopyDir):
514 cherrypy.log("Playbook Not Found")
515 return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
517 # copy static playbook dir to run dir
518 cherrypy.log("Copying from " + CopyDir + " to " + PlaybookDir)
519 shutil.copytree(CopyDir, PlaybookDir)
520 # cmd="/usr/bin/find " + PlaybookDir + " -exec /usr/bin/touch {} \;"
521 cmd = "/usr/bin/find " + PlaybookDir + " -exec chmod +rx {} \;"
525 cherrypy.log("PlaybookDir: " + PlaybookDir)
526 # cherrypy.log("AnsibleInv: " + AnsibleInv)
528 # Process inventory file for target
532 NodeList = input_json.get('NodeList', [])
534 cherrypy.log("NodeList: " + str(NodeList))
538 cherrypy.log("*** NodeList - Empty ***")
542 # ##############################################################################
543 # #### Host file processing ###########################
544 # #### 1. Use file delivered with playbook ###########################
545 # #### 2. If HostNames + NodeList generate and use ###########################
546 # ##############################################################################
548 # Verify inventory directory exists
549 path = PlaybookDir + "/inventory/"
550 if not os.path.isdir(path):
551 cherrypy.log("Inventory directory %s does not exist - create it" % path)
555 cherrypy.log("Creation of the directory %s failed" % path)
557 cherrypy.log("Successfully created the directory %s " % path)
560 # location of host file - Default
561 HostFile = "inventory/" + VNF_instance + "hosts"
562 HostFilePath = PlaybookDir + "/" + HostFile
563 cherrypy.log("HostFile: " + HostFilePath)
565 # buildInventorySysCall(ANSIBLE_PATH, ANSIBLE_INV, NodeList, PlaybookDir, HostFile, hostgrouplist, hostnamelist)
567 # if NodeList and InventoryNames need to build host file
568 if HaveInventoryNames and HaveNodeList:
569 cherrypy.log("Build host file from NodeList")
570 ret = buildHostsSysCall(input_json, PlaybookDir, inventory_names)
572 cherrypy.log("Returning Error: Not running Playbook")
573 return {"StatusCode": 105,
574 "StatusMessage": "NodeList: Missing vnfc-type field"}
576 # Having been built now copy new file to correct file
577 shutil.copy(PlaybookDir + "/host_file.txt", HostFilePath)
578 cherrypy.log("Copying Generated host file to: " + HostFilePath)
580 if 'Timeout' in input_json:
581 timeout = int(input_json['Timeout'])
582 cherrypy.log("Timeout from API: " + str(timeout))
584 timeout = timeout_seconds
585 cherrypy.log("Timeout not passed from API using default: " + str(timeout))
587 EnvParam = input_json.get('EnvParameters', {})
588 LocalParam = input_json.get('LocalParameters', {})
589 FileParam = input_json.get('FileParameters', {})
590 callback_flag = input_json.get('CallBack', None)
592 # if AnsibleServer is not set to 'na' don't send AnsibleServer in PENDING response.
594 'PlaybookName': PlaybookName,
596 'NodeList': NodeList,
597 'HostGroupList': hostgrouplist,
598 'HostNameList': hostnamelist,
602 'EnvParameters': EnvParam,
603 'LocalParameters': LocalParam,
604 'FileParameters': FileParam,
605 'CallBack': callback_flag,
608 "StatusMessage": 'PENDING',
609 "ExpectedDuration": str(timeout) + "sec"
616 if AnsibleServer != 'na':
617 TestRecord[Id]['Result']["AnsibleServer"] = str(AnsibleServer),
619 cherrypy.log("Test_Record: " + str(TestRecord[Id]))
622 if TestRecord[Id]['FileParameters']:
623 for key in TestRecord[Id]['FileParameters']:
625 filecontent = TestRecord[Id]['FileParameters'][key]
626 f = open(PlaybookDir + "/" + filename, "w")
631 if os.path.exists(ANSIBLE_PATH + '/' + PlaybookName):
632 AnsiblePlaybookFail = False
634 if AnsiblePlaybookFail:
635 # if os.path.exists(PlaybookDir):
636 # shutil.rmtree (PlaybookDir)
637 cherrypy.log("AnsiblePlaybookFail")
639 return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
642 playbook_path = PlaybookDir
645 store_local_vars(playbook_path, Id)
647 # write some info out to files before running
649 f = open(playbook_path + "/User.txt", "a")
650 f.write(cherrypy.request.login)
653 f = open(playbook_path + "/PlaybookName.txt", "a")
654 f.write(PlaybookName)
657 f = open(playbook_path + "/PlaybookExDir.txt", "a")
658 f.write(PlaybookDir + "/" + PlayBookFunction)
661 f = open(playbook_path + "/JsonRequest.txt", "w")
662 f.write(json.dumps(input_json, indent=4, sort_keys=True))
665 # Check that HostFile exists
666 if not os.path.isfile(HostFilePath):
667 cherrypy.log("Inventory file Not Found: " + HostFilePath)
668 return {"StatusCode": 101, "StatusMessage": "PLAYBOOK INVENTORY FILE NOT FOUND"}
670 # Cannot use thread because ansible module uses signals which are only supported in main thread.
671 # So use multiprocess with shared object
672 p = Process(target=RunAnsible_Playbook,
673 args=(callback, Id, "../" + HostFile, "site.yml",
674 NodeList, TestRecord, PlaybookDir + "/" + PlayBookFunction, ArchiveFlag))
676 ActiveProcess[Id] = p
677 return TestRecord[Id]['Result']
680 def handle_post_method(input_json, time_now):
681 cherrypy.log("Payload: " + str(input_json))
683 if 'Id' in input_json and 'PlaybookName' in input_json and 'EnvParameters' in input_json:
684 if input_json['Id'] not in TestRecord:
685 # check if Id exists in previous run directory, if so return error
686 Id = input_json['Id']
687 if glob.glob(ANSIBLE_TEMP + '/*_' + input_json['Id']):
688 cherrypy.log("Old directory found for ID: " + Id)
689 return {"StatusCode": 101, "StatusMessage": "TEST ID FILE ALREADY DEFINED"}
691 # if required it should be passed as an argument
692 EnvParameters = input_json.get('EnvParameters', {})
694 # The lines below are to test multiple EnvParameters being passed
695 # for i in EnvParameters:
696 # cherrypy.log("EnvParameter object: " + i)
697 # cherrypy.log(" EnvParameter Value: " + EnvParameters[ i ])
699 pnf_flag = EnvParameters.get("pnf-flag", "")
700 if pnf_flag == "true":
701 return process_pnf_playbook(input_json, Id, EnvParameters, time_now)
703 return process_vnf_playbook(input_json, Id, EnvParameters, time_now)
705 cherrypy.log("TEST ID ALREADY DEFINED")
706 return {"StatusCode": 101, "StatusMessage": "TEST ID ALREADY DEFINED"}
708 return {"StatusCode": 500, "StatusMessage": "JSON OBJECT MUST INCLUDE: ID, PLAYBOOKNAME, EnvParameters"}
711 def handle_get_method(input_data):
712 # Verify we have a Type passed in GET request
713 if 'Type' not in input_data:
714 return {"StatusCode": 500, "StatusMessage": "RESULTS TYPE UNDEFINED"}
717 cherrypy.log("Request USER: " + cherrypy.request.login)
718 cherrypy.log("Payload: " + str(input_data) + " Type " + input_data['Type'])
720 if 'LogRest' in input_data['Type']:
722 sys.stdout = open("/var/log/RestServer.log", "w")
724 # Just a debug to dump any records
725 if 'GetStatus' in input_data['Type']:
726 cherrypy.log("******** Dump Records **********")
727 if TestRecord.items():
728 for id, record in TestRecord.items():
729 cherrypy.log(" Id: " + id)
730 cherrypy.log("Record: " + str(record))
732 cherrypy.log(" No Records to dump")
734 if 'Id' in input_data and 'Type' in input_data:
735 if not ('GetResult' in input_data['Type'] or 'GetOutputLog' in input_data['Type'] or
736 'GetTheOutput' in input_data['Type'] or 'GetOutput' in input_data['Type'] or
737 'GetLog' in input_data['Type']):
738 return {"StatusCode": 500, "StatusMessage": "RESULTS TYPE UNDEFINED"}
740 if input_data['Id'] in TestRecord:
741 if 'GetResult' in input_data['Type']:
742 cherrypy.log(" ** GetResult for: " + str(input_data['Id']))
743 if 'StatusMessage' in TestRecord[input_data['Id']]['Result'] and getresults_block:
744 # check if playbook is still running
745 while ActiveProcess[input_data['Id']].is_alive():
746 cherrypy.log("*** Playbook running returning PENDING for " + str(input_data['Id']))
747 # If still running return PENDING response
748 # if AnsibleServer != 'na':
749 # return {"StatusCode": 100, "StatusMessage": 'PENDING', "AnsibleServer": str(AnsibleServer)}
751 # return {"StatusCode": 100, "StatusMessage": 'PENDING'}
754 # cherrypy.log( "*** Request released " + input_data['Id'])
756 cherrypy.log(str(TestRecord[input_data['Id']]['Result']))
757 cherrypy.log("Output: " + str(TestRecord[input_data['Id']]['Output']))
758 cherrypy.log("StatusCode: " + str(TestRecord[input_data['Id']]['Result']['StatusCode']))
759 cherrypy.log("StatusMessage: " + str(TestRecord[input_data['Id']]['Result']['StatusMessage']))
761 # out_obj gets returned to GET request
762 if TestRecord[input_data['Id']]['Result']['StatusCode'] == 500:
763 out_obj = TestRecord[input_data['Id']]['Result']['Results']
767 "StatusMessage": "FINISHED",
768 "PlaybookName": TestRecord[input_data['Id']]["PlaybookName"],
769 "Version": TestRecord[input_data['Id']]["Version"],
770 "Duration": TestRecord[input_data['Id']]["Duration"],
771 "Output": TestRecord[input_data['Id']]["Output"]["Output"],
772 "Results": TestRecord[input_data['Id']]['Result']['Results']
774 if not TestRecord[input_data['Id']]['Output']['Output'] == {}:
775 cherrypy.log("TestRecord has Output:" + str(TestRecord[input_data['Id']]['Output']['Output']))
777 for key in out_obj["Results"]:
778 cherrypy.log("Output key: " + str(key))
779 if key in TestRecord[input_data['Id']]['Output']['Output']:
780 out_obj["Results"][key]["Output"] = TestRecord[input_data['Id']]['Output']['Output'][key]
782 cherrypy.log("***** GET RETURNING RESULTS Back ****")
783 cherrypy.log(str(out_obj))
785 elif 'GetStatus' in input_data['Type']:
786 cherrypy.log(" Dump Records")
787 for id, record in TestRecord.items():
788 cherrypy.log(" id: " + id)
789 cherrypy.log(" Record:" + str(record))
790 elif 'GetTheOutput' in input_data['Type'] or 'GetOutput' in input_data['Type']:
791 if TestRecord[input_data['Id']]['Output'] == {} and getresults_block:
792 cherrypy.log("*** Request blocked " + input_data['Id'])
794 # while TestRecord[input_data['Id']]['Output'] == {} \
795 # or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
796 while ActiveProcess[input_data['Id']].is_alive():
799 cherrypy.log("*** Request released " + input_data['Id'])
801 cherrypy.log("Output: " + str(TestRecord[input_data['Id']]['Output']))
802 return {"Output": TestRecord[input_data['Id']]['Output']['Output']}
803 elif 'GetOutputLog' in input_data['Type']:
804 cherrypy.log("GetOutputLog: processing.")
805 if glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id']):
806 id = input_data['Id']
807 cherrypy.log("Old directory found for ID: " + id)
808 run_dir = glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id'])
811 if os.path.exists(rdir + "/PlaybookExDir.txt"):
812 cherrypy.log("Found PlaybookExDir.txt file")
813 f = open(rdir + '/PlaybookExDir.txt', 'r')
814 playbookexdir = f.readline()
817 cherrypy.log("Id: " + id)
818 cherrypy.log("RunDir: " + rdir)
819 if os.path.exists(rdir + "/output.log"):
820 cherrypy.log("Found output.log file")
821 f = open(rdir + '/output.log', 'r')
822 output_log = f.readline()
826 cherrypy.log("Globglob failed:")
831 if TestRecord[input_data['Id']]['Log'] == '' and \
834 cherrypy.log("*** Request blocked " + input_data['Id'])
836 while TestRecord[input_data['Id']]['Log'] == '' \
837 or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
840 cherrypy.log("*** Request released " + input_data['Id'])
842 cherrypy.log("Log:" + str(TestRecord[input_data['Id']]['Log']))
843 return {"Log": TestRecord[input_data['Id']]['Log']}
845 # Not in memory check for a file
846 if glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id']):
847 id = input_data['Id']
848 cherrypy.log("Old directory found for ID: " + id)
849 run_dir = glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id'])
852 if os.path.exists(rdir + "/PlaybookExDir.txt"):
853 cherrypy.log("Found PlaybookExDir.txt file")
854 f = open(rdir + '/PlaybookExDir.txt', 'r')
855 playbookexdir = f.readline()
858 cherrypy.log("Id: " + id)
859 cherrypy.log("RunDir: " + rdir)
860 if 'GetLog' in input_data['Type']:
861 if os.path.exists(rdir + "/output.log"):
862 cherrypy.log("Found output.log file")
863 f = open(rdir + '/output.log', 'r')
864 output_log = f.readline()
867 elif 'GetOutputLog' in input_data['Type']:
868 if os.path.exists(rdir + "/output.log"):
869 cherrypy.log("Found output.log file")
870 f = open(rdir + '/output.log', 'r')
871 output_log = f.readline()
874 elif 'GetResult' in input_data['Type']:
875 if os.path.exists(rdir + "/PlaybookName.txt"):
876 cherrypy.log("Found PlaybookName.txt file")
877 f = open(rdir + '/PlaybookName.txt', 'r')
878 playbooknametxt = f.readline()
881 playbooknametxt = "NA"
883 # Add code to get other items not just output.log from files
884 if os.path.exists(rdir + "/log.file"):
885 cherrypy.log("Found log.file")
888 f = open(rdir + '/log.file', 'r')
892 out_results = out_results + line
893 elif "RECAP" in line:
894 out_results = out_results + line
895 recap_line = f.readline()
897 out_results = out_results + recap_line
898 recap_line = f.readline()
903 "StatusMessage": "FINISHED",
904 "PlaybookName": playbooknametxt,
905 "Version": "Version",
907 "Results": out_results
911 return {"StatusCode": 500, "StatusMessage": "PLAYBOOK FAILED "}
913 return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
915 return {"StatusCode": 500, "StatusMessage": "MALFORMED REQUEST"}
918 def handle_delete_method(input_data):
919 cherrypy.log("***> in RestServer.DELETE")
920 cherrypy.log("Payload: " + str(input_data))
922 if input_data['Id'] in TestRecord:
923 if 'PENDING' not in TestRecord[input_data['Id']]['Result']:
924 cherrypy.log(" Path: " + str(TestRecord[input_data['Id']]['Path']))
925 TestRecord.pop(input_data['Id'])
926 if input_data['Id'] in ActiveProcess:
927 ActiveProcess.pop(input_data['Id'])
928 return {"StatusCode": 200, "StatusMessage": "PLAYBOOK EXECUTION RECORDS DELETED"}
930 return {"StatusCode": 200, "StatusMessage": "PENDING"}
932 return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
935 class TestManager(object):
937 @cherrypy.tools.json_out()
938 @cherrypy.tools.json_in()
939 @cherrypy.tools.allow(methods=['POST', 'GET', 'DELETE'])
940 def Dispatch(self, **kwargs):
941 # Let cherrypy error handler deal with malformed requests
942 # No need for explicit error handler, we use default ones
944 time_now = datetime.datetime.utcnow()
946 # Erase old test results (2x timeout)
947 # Do cleanup too of ActiveProcess list and old Records - PAP
949 for key in TestRecord.copy():
950 cherrypy.log("LOOKING AT ALL TestRecords: " + str(key))
951 if key in ActiveProcess:
952 if not ActiveProcess[key].is_alive(): # Just to cleanup defunct processes
953 cherrypy.log("Not ActiveProcess for ID: " + str(key))
954 delta_time = (time_now - TestRecord[key]['Time']).seconds
955 if delta_time > 2 * TestRecord[key]['Timeout']:
956 cherrypy.log("DELETED HISTORY for ID: " + str(key))
957 if key in ActiveProcess:
958 if not ActiveProcess[key].is_alive():
959 ActiveProcess.pop(key)
960 cherrypy.log("DELETED ActiveProcess for ID: " + str(key))
961 # if os.path.exists(TestRecord[key]['Path']):
962 # don't remove run dirrectory
963 # shutil.rmtree (TestRecord[key]['Path'])
966 cherrypy.log("RestServer.Dispatch: " + cherrypy.request.method)
968 if 'POST' in cherrypy.request.method:
969 input_json = cherrypy.request.json
970 return handle_post_method(input_json, time_now)
971 elif 'GET' in cherrypy.request.method:
972 # Lets pause for a second just in case the request was just kicked off
975 input_data = parse_query_string(cherrypy.request.query_string)
976 return handle_get_method(input_data)
977 elif 'DELETE' in cherrypy.request.method:
978 input_data = parse_query_string(cherrypy.request.query_string)
979 return handle_delete_method(input_data)
982 if __name__ == '__main__':
986 config_file_path = "RestServer_config"
988 if not os.path.exists(config_file_path):
989 cherrypy.log('[INFO] The config file does not exist')
1000 timeout_seconds = 'na'
1005 getresults_block = False
1008 config_file = open(config_file_path, 'r')
1009 for config_line in config_file.readlines():
1010 if '#' not in config_line:
1011 if 'ip:' in config_line:
1012 ip = config_line.split(':')[1].strip()
1013 elif 'AnsibleServer:' in config_line:
1014 AnsibleServer = config_line.split(':')[1].strip()
1015 elif 'port:' in config_line:
1016 port = config_line.split(':')[1].strip()
1017 elif 'ksalt:' in config_line:
1018 salt = config_line.split(':')[1].strip()
1019 elif 'tls:' in config_line:
1020 tls = 'YES' in config_line.split(':')[1].strip().upper()
1021 elif 'auth:' in config_line:
1022 AUTH = 'YES' in config_line.split(':')[1].strip().upper()
1023 if tls and 'priv:' in config_line:
1024 priv = config_line.split(':')[1].strip()
1025 if tls and 'pub:' in config_line:
1026 pub = config_line.split(':')[1].strip()
1027 if tls and 'inter_cert:' in config_line:
1028 intermediate = config_line.split(':')[1].strip()
1029 if 'timeout_seconds' in config_line:
1030 timeout_seconds = int(config_line.split(':')[1].strip())
1031 if 'ansible_path' in config_line:
1032 ANSIBLE_PATH = config_line.split(':')[1].strip()
1033 if 'ansible_inv' in config_line:
1034 ANSIBLE_INV = config_line.split(':')[1].strip()
1035 if not os.path.exists(ANSIBLE_PATH + "/" + ANSIBLE_INV):
1036 print '[INFO] The ansible_inv file does not exist'
1038 if 'ansible_temp' in config_line:
1039 ANSIBLE_TEMP = config_line.split(':')[1].strip()
1040 if 'host' in config_line:
1041 host = config_line.split(':')[1].strip()
1042 if 'users' in config_line:
1043 users = config_line.split(':')[1].strip()
1044 if 'getresults_block' in config_line:
1045 getresults_block = 'YES' in config_line.split(':')[1].strip().upper()
1046 if 'from_files' in config_line:
1047 from_files = 'YES' in config_line.split(':')[1].strip().upper()
1055 'response.timeout': 5400,
1056 'server.socket_host': ip,
1057 'server.socket_port': int(port),
1058 'server.protocol_version': 'HTTP/1.1'
1063 # Use pythons built-in SSL
1064 cherrypy.server.ssl_module = 'builtin'
1066 # Point to certificate files
1068 if not os.path.exists(pub):
1069 cherrypy.log('[INFO] The public certificate does not exist')
1072 if not os.path.exists(priv):
1073 cherrypy.log('[INFO] The private key does not exist')
1076 if not os.path.exists(intermediate):
1077 cherrypy.log('[INFO] The intermediate certificate does not exist')
1080 cherrypy.server.ssl_certificate = pub
1081 cherrypy.server.ssl_certificate_chain = intermediate
1082 cherrypy.server.ssl_private_key = priv
1085 # Read in and build user dictionary
1086 if not os.path.exists(users):
1087 cherrypy.log('[INFO] The users file does not exist: ' + users)
1090 user_file = open(users, 'r')
1091 for config_line in user_file.readlines():
1092 if '#' not in config_line:
1093 uid = config_line.split(':')[0].strip()
1094 pw = config_line.split(':')[1].strip()
1095 userpassdict[uid] = pw
1099 {'tools.auth_basic.on': True,
1100 'tools.auth_basic.realm': 'earth',
1101 'tools.auth_basic.checkpassword': validate_password
1105 application = cherrypy.tree.mount(TestManager(), '/', app_conf)
1107 application = cherrypy.tree.mount(TestManager(), '/')
1109 cherrypy.config.update({
1110 'log.access_file': "/var/log/RestServer.access"
1112 accessLogName = "/var/log/RestServer.access"
1113 applicationLogName = "/var/log/RestServer.log"
1114 cherrypy.config.update(global_conf)
1116 log = application.log
1118 log.access_file = ""
1119 from logging import handlers
1121 applicationLogFileHandler = handlers.RotatingFileHandler(applicationLogName, 'a', 1000000, 5000)
1122 accessLogFileHandler = handlers.RotatingFileHandler(accessLogName, 'a', 1000000, 5000)
1125 applicationLogFileHandler.setLevel(logging.DEBUG)
1126 log.error_log.addHandler(applicationLogFileHandler)
1127 log.access_log.addHandler(accessLogFileHandler)
1131 cherrypy.engine.start()
1132 cherrypy.engine.block()