Fix playbook execution for VNF
[ccsdk/distribution.git] / ansible-server / src / main / ansible-server / RestServer.py
1 '''
2 /*-
3 * ============LICENSE_START=======================================================
4 * ONAP : APPC
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
15 *
16 *      http://www.apache.org/licenses/LICENSE-2.0
17 *
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.
23 *
24 * ============LICENSE_END=========================================================
25 */
26 '''
27
28 import time, datetime, json, os, sys, subprocess
29 import uuid
30 import shutil
31 import glob
32 import crypt
33
34 import requests
35
36 import cherrypy
37 from cherrypy.lib.httputil import parse_query_string
38
39 from multiprocessing import Process, Manager
40
41 from AnsibleModule import ansibleSysCall
42 from BuildHostFile import buildHostsSysCall
43 from BuildPlaybookParams import buildInventorySysCall, getPlaybookFile
44
45 from os import listdir
46 from os.path import isfile, join
47
48 TestRecord = Manager().dict()
49 ActiveProcess = {}
50
51
52 def validate_password(realm, username, password):
53     comp = crypt.crypt(password, salt)
54     if username in userpassdict and userpassdict[username] == comp:
55         return True
56     return False
57
58
59 def sys_call(cmd):
60     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
61     output = p.stdout.readlines()
62     retval = p.wait()
63     if len(output) > 0:
64         for i in range(len(output)):
65             output[i] = output[i].strip()
66     return retval, output
67
68
69 def callback(Id, Result, Output, Log, returncode):
70     cherrypy.log("***> in RestServer.callback")
71
72     if Id in TestRecord:
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']
77         if returncode == 137:
78             Result['StatusCode'] = 500
79             Result['StatusMessage'] = "TERMINATED"
80         else:
81             Result['StatusCode'] = 200
82             Result['StatusMessage'] = "FINISHED"
83
84         # Need to update the whole data structure for key=Id otherwise Manager is not updated
85         TestRecord[Id] = {
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'],
98             'Result': Result,
99             'Log': Log,
100             'Output': Output,
101             'Path': TestRecord[Id]['Path'],
102             'Mandatory': TestRecord[Id]['Path']
103         }
104
105         if TestRecord[Id]['CallBack'] is not None:
106
107             # Posting results to callback server
108             data = {
109                 "StatusCode": 200,
110                 "StatusMessage": "FINISHED",
111                 "PlaybookName": TestRecord[Id]["PlaybookName"],
112                 "Version": TestRecord[Id]["Version"],
113                 "Duration": TestRecord[Id]["Duration"],
114                 "Results": TestRecord[Id]['Result']['Results']
115             }
116
117             cherrypy.log("CALLBACK: TestRecord[Id]['Output']['Output']:", str(TestRecord[Id]['Output']['Output']))
118             cherrypy.log("CALLBACK: Results:", str(data["Results"]))
119
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]
124
125             cherrypy.log("     Posting to", TestRecord[Id]['CallBack'])
126
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)
131
132
133 def RunAnsible_Playbook(callback, Id, Inventory, Playbook, NodeList, TestRecord, Path, ArchiveFlag, pnf_flag=False):
134     cherrypy.log("***> in RestServer.RunAnsible_Playbook")
135
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'],
142                                              Path)
143
144     cherrypy.log("Return code:" + str(returncode))
145     cherrypy.log("Return value:" + str(retval))
146
147     Log = ''.join(log)
148     if pnf_flag:
149         Output = {'Output': {}}
150     else:
151         Output = {}
152
153     onlyfiles = [f for f in listdir(Path)
154                  if isfile(join(Path, f))]
155
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")
162             if pnf_flag:
163                 key = file.split("_")[0]
164                 Output['Output'][key] = f.read()
165             else:
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
171             f.close()
172
173     if Output == {}:
174         Output = {'Output': {}}
175
176     Result = {'Results': {}}
177     if 'could not be found' in Log:
178         Result['Results'] = {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
179
180     if returncode == 137:
181         Result['Results'] = {"StatusCode": 500, "StatusMessage": "TERMINATED"}
182     elif TestRecord[Id]['NodeList'] == []:
183         host_index = None
184         if 'TargetNode' in TestRecord[Id]['EnvParameters']:
185             targetlist = TestRecord[Id]['EnvParameters']['TargetNode'].split(' ')
186         else:
187             targetlist = ["localhost"]
188
189         for key in retval:
190             for i in range(len(targetlist)):
191                 if key in targetlist[i]:
192                     host_index = i
193
194             if int(retval[key][0]) > 0 and int(retval[key][2]) == 0 and int(retval[key][3]) == 0:
195                 if host_index:
196                     Result['Results'][targetlist[host_index]] = \
197                         {"GroupName": 'na', "StatusCode": 200, "StatusMessage": "SUCCESS"}
198                 else:
199                     Result['Results'][key] = {"GroupName": 'na', "StatusCode": 200, "StatusMessage": "SUCCESS"}
200             elif int(retval[key][2]) > 0:
201                 if host_index:
202                     Result['Results'][targetlist[host_index]] = \
203                         {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "NOT REACHABLE"}
204                 else:
205                     Result['Results'][key] = \
206                         {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "NOT REACHABLE"}
207             elif int(retval[key][3]) > 0:
208                 if host_index:
209                     Result['Results'][targetlist[host_index]] = \
210                         {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "FAILURE"}
211                 else:
212                     Result['Results'][key] = \
213                         {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "FAILURE"}
214     else:
215         for key in retval:
216             if len(TestRecord[Id]['HostNameList']) > 0:
217                 host_index = []
218                 for i in range(len(TestRecord[Id]['HostNameList'])):
219                     if key in TestRecord[Id]['HostNameList'][i]:
220                         host_index.append(i)
221
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]]] = \
226                             {
227                                 "GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
228                                 "StatusCode": 200, "StatusMessage": "SUCCESS"
229                             }
230
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]]
234                     else:
235                         Result['Results'][key] = {"GroupName": key, "StatusCode": 200, "StatusMessage": "SUCCESS"}
236
237                 elif int(retval[key][2]) > 0:
238                     if len(host_index) > 0:
239                         Result['Results'][TestRecord[Id]['HostNameList'][host_index[0]]] = \
240                             {
241                                 "GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
242                                 "StatusCode": 400, "StatusMessage": "NOT REACHABLE"
243                             }
244
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]]
248                     else:
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]]] = \
254                             {
255                                 "GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
256                                 "StatusCode": 400, "StatusMessage": "FAILURE"
257                             }
258
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]]
262                     else:
263                         Result['Results'][key] = \
264                             {"GroupName": key, "StatusCode": 200, "StatusMessage": "FAILURE"}
265             else:
266                 host_index = None
267                 for i in range(len(TestRecord[Id]['NodeList'])):
268                     if key in TestRecord[Id]['NodeList'][i]:
269                         host_index = i
270
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"}
281
282     callback(Id, Result, Output, Log, returncode)
283
284
285 def store_local_vars(playbook_path, Id):
286     if not os.path.exists(playbook_path + "/vars"):
287         os.mkdir(playbook_path + "/vars")
288
289     if not os.path.isfile(playbook_path + "/vars/defaults.yml"):
290         os.mknod(playbook_path + "/vars/defaults.yml")
291
292     # ##################################################
293     # PAP
294     # write local parameters passed into defaults.yml
295     # PAP
296     local_parms = TestRecord[Id]['LocalParameters']
297     cherrypy.log("LocalParameters: " + str(local_parms))
298
299     f = open(playbook_path + "/vars/defaults.yml", "a")
300     for key, value in local_parms.items():
301         f.write(key + "=" + value + "\n")
302     f.close()
303     # ##################################################
304
305     for key in TestRecord[Id]['LocalParameters']:
306         host_index = []
307         for i in range(len(TestRecord[Id]['HostNameList'])):
308             if key in TestRecord[Id]['HostNameList'][i]:
309                 host_index.append(i)
310         if len(host_index) == 0:
311             for i in range(len(TestRecord[Id]['HostGroupList'])):
312                 if key in TestRecord[Id]['HostGroupList'][i]:
313                     host_index.append(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")
319                 f.close()
320
321
322 def process_pnf_playbook(input_json, Id, EnvParameters, time_now):
323     cherrypy.log("Processing playbook for PNF...")
324
325     PlaybookName = input_json['PlaybookName']
326     version = input_json.get('Version', None)
327
328     if AUTH:
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)
333
334     str_uuid = str(uuid.uuid4())
335
336     HomeDir = os.path.dirname(os.path.realpath("~/"))
337
338     PlaybookType = PlaybookName.split(".")[0].split('_')[-1]
339     PlaybookDir = HomeDir + '/' + ANSIBLE_TEMP + "/" + PlaybookName.split(".")[0] + "_" + str_uuid
340     AnsibleInv = PlaybookType + "_" + "inventory"
341     ArchiveFlag = False
342
343     cherrypy.log("Request Decode: PlaybookType     " + PlaybookType)
344     cherrypy.log("Request Decode: PlaybookDir      " + PlaybookDir)
345     cherrypy.log("Request Decode: AnsibleInv       " + AnsibleInv)
346
347     NodeList = input_json.get('NodeList', [])
348     cherrypy.log("Request Decode: NodeList: " + str(NodeList))
349
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)
354
355     os.mkdir(PlaybookDir)
356
357     # Process inventory file for target
358     hostgrouplist = []
359     hostnamelist = []
360
361     buildInventorySysCall(ANSIBLE_PATH, ANSIBLE_INV, NodeList, PlaybookDir, AnsibleInv, hostgrouplist, hostnamelist)
362
363     version_target = getPlaybookFile(ANSIBLE_PATH, PlaybookName, PlaybookType, PlaybookDir)
364     if not version_target:
365         return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
366
367     if version is None:
368         version = version_target
369
370     if 'Timeout' in input_json:
371         timeout = int(input_json['Timeout'])
372         cherrypy.log("Timeout from API: " + str(timeout))
373     else:
374         timeout = timeout_seconds
375         cherrypy.log("Timeout not passed from API using default: " + str(timeout))
376
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)
381
382     # if AnsibleServer is not set to 'na' don't send AnsibleServer in PENDING response.
383     TestRecord[Id] = {
384         'PlaybookName': PlaybookName,
385         'Version': version,
386         'NodeList': NodeList,
387         'HostGroupList': hostgrouplist,
388         'HostNameList': hostnamelist,
389         'Time': time_now,
390         'Duration': timeout,
391         'Timeout': timeout,
392         'EnvParameters': EnvParam,
393         'LocalParameters': LocalParam,
394         'FileParameters': FileParam,
395         'CallBack': callback_flag,
396         'Result': {
397             "StatusCode": 100,
398             "StatusMessage": 'PENDING',
399             "ExpectedDuration": str(timeout) + "sec"
400         },
401         'Log': '',
402         'Output': {},
403         'Path': PlaybookDir,
404         'Mandatory': None
405     }
406     if AnsibleServer != 'na':
407         TestRecord[Id]['Result']["AnsibleServer"] = str(AnsibleServer),
408
409     cherrypy.log("Test_Record: " + str(TestRecord[Id]))
410
411     # Write files
412     if TestRecord[Id]['FileParameters']:
413         for key in TestRecord[Id]['FileParameters']:
414             filename = key
415             filecontent = TestRecord[Id]['FileParameters'][key]
416             f = open(PlaybookDir + "/" + filename, "w")
417             f.write(filecontent)
418             f.close()
419
420     playbook_path = PlaybookDir
421
422     # Store local vars
423     store_local_vars(playbook_path, Id)
424
425     # write some info out to files before running
426     if AUTH:
427         f = open(playbook_path + "/User.txt", "a")
428         f.write(cherrypy.request.login)
429         f.close()
430
431     f = open(playbook_path + "/PlaybookName.txt", "a")
432     f.write(PlaybookName)
433     f.close()
434
435     f = open(playbook_path + "/JsonRequest.txt", "w")
436     f.write(json.dumps(input_json, indent=4, sort_keys=True))
437     f.close()
438
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))
444     p.start()
445     ActiveProcess[Id] = p
446     return TestRecord[Id]['Result']
447
448
449 def process_vnf_playbook(input_json, Id, EnvParameters, time_now):
450     cherrypy.log("Processing playbook for VNF...")
451
452     PlaybookName = input_json['PlaybookName']
453     VNF_instance = EnvParameters.get('vnf_instance')
454     version = input_json.get('Version', None)
455
456     # GetInventoryNames
457     HaveNodeList = False
458     HaveInventoryNames = False
459     inventory_names = None
460     if 'InventoryNames' in input_json:
461         inventory_names = input_json['InventoryNames']
462         HaveInventoryNames = True
463
464     AnsiblePlaybookFail = True
465
466     str_uuid = str(uuid.uuid4())
467
468     # VnfType = PlaybookName.split("/")[0]
469
470     if AUTH:
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))
475
476     # Verify VNF_instance was passed in EnvParameters
477     if VNF_instance is not None:
478         cherrypy.log("Request Decode: VnfInstance      " + VNF_instance)
479     else:
480         cherrypy.log("StatusCode: 107, StatusMessage: VNF_instance NOT PROVIDED")
481         return {"StatusCode": 107, "StatusMessage": "VNF_instance NOT PROVIDED"}
482
483     if inventory_names is not None:
484         cherrypy.log("Request Decode: Inventory Names  " + inventory_names)
485     else:
486         cherrypy.log("Request Decode: Inventory Names  " + "Not provided")
487
488     cherrypy.log("Request Decode: PlaybookName     " + PlaybookName)
489
490     PlayBookFunction = PlaybookName.rsplit("/", 2)[1]
491     PlayBookFile = PlayBookFunction + "/site.yml"
492
493     cherrypy.log("Request Decode: PlaybookFunction " + PlayBookFunction)
494     cherrypy.log("Request Decode: PlaybookFile     " + PlayBookFile)
495
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)
500
501     PlaybookDir = ANSIBLE_TEMP + "/" + VNF_instance + "_" + str_uuid + "_" + str(Id)
502     cherrypy.log("Request Decode: PlaybookDir      " + PlaybookDir)
503
504     # AnsibleInv is the directory where the host file to be run exists
505     # AnsibleInv = ANSIBLE_PATH + "/" + VnfType + "/latest/ansible/inventory/" + VNF_instance
506     ArchiveFlag = False
507
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)
512
513     if not os.path.exists(CopyDir):
514         cherrypy.log("Playbook Not Found")
515         return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
516
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  {} \;"
522     sys_call(cmd)
523     cherrypy.log(cmd)
524
525     cherrypy.log("PlaybookDir:    " + PlaybookDir)
526     # cherrypy.log("AnsibleInv:     " + AnsibleInv)
527
528     # Process inventory file for target
529     hostgrouplist = []
530     hostnamelist = []
531
532     NodeList = input_json.get('NodeList', [])
533
534     cherrypy.log("NodeList: " + str(NodeList))
535
536     # if NodeList empty
537     if not NodeList:
538         cherrypy.log("*** NodeList - Empty ***")
539     else:
540         HaveNodeList = True
541
542     # ##############################################################################
543     # #### Host file processing                          ###########################
544     # #### 1. Use file delivered with playbook           ###########################
545     # #### 2. If HostNames + NodeList generate and use   ###########################
546     # ##############################################################################
547
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)
552         try:
553             os.mkdir(path)
554         except OSError:
555             cherrypy.log("Creation of the directory %s failed" % path)
556         else:
557             cherrypy.log("Successfully created the directory %s " % path)
558
559
560     # location of host file - Default
561     HostFile = "inventory/" + VNF_instance + "hosts"
562     HostFilePath = PlaybookDir + "/" + HostFile
563     cherrypy.log("HostFile: " + HostFilePath)
564
565     # buildInventorySysCall(ANSIBLE_PATH, ANSIBLE_INV, NodeList, PlaybookDir, HostFile, hostgrouplist, hostnamelist)
566
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)
571         if ret < 0:
572             cherrypy.log("Returning Error: Not running Playbook")
573             return {"StatusCode": 105,
574                     "StatusMessage": "NodeList: Missing vnfc-type field"}
575
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)
579
580     if 'Timeout' in input_json:
581         timeout = int(input_json['Timeout'])
582         cherrypy.log("Timeout from API: " + str(timeout))
583     else:
584         timeout = timeout_seconds
585         cherrypy.log("Timeout not passed from API using default: " + str(timeout))
586
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)
591
592     # if AnsibleServer is not set to 'na' don't send AnsibleServer in PENDING response.
593     TestRecord[Id] = {
594         'PlaybookName': PlaybookName,
595         'Version': version,
596         'NodeList': NodeList,
597         'HostGroupList': hostgrouplist,
598         'HostNameList': hostnamelist,
599         'Time': time_now,
600         'Duration': timeout,
601         'Timeout': timeout,
602         'EnvParameters': EnvParam,
603         'LocalParameters': LocalParam,
604         'FileParameters': FileParam,
605         'CallBack': callback_flag,
606         'Result': {
607             "StatusCode": 100,
608             "StatusMessage": 'PENDING',
609             "ExpectedDuration": str(timeout) + "sec"
610         },
611         'Log': '',
612         'Output': {},
613         'Path': PlaybookDir,
614         'Mandatory': None
615     }
616     if AnsibleServer != 'na':
617         TestRecord[Id]['Result']["AnsibleServer"] = str(AnsibleServer),
618
619     cherrypy.log("Test_Record: " + str(TestRecord[Id]))
620
621     # Write files
622     if TestRecord[Id]['FileParameters']:
623         for key in TestRecord[Id]['FileParameters']:
624             filename = key
625             filecontent = TestRecord[Id]['FileParameters'][key]
626             f = open(PlaybookDir + "/" + filename, "w")
627             f.write(filecontent)
628             f.close()
629
630     # Process playbook
631     if os.path.exists(ANSIBLE_PATH + '/' + PlaybookName):
632         AnsiblePlaybookFail = False
633
634     if AnsiblePlaybookFail:
635         # if os.path.exists(PlaybookDir):
636         # shutil.rmtree (PlaybookDir)
637         cherrypy.log("AnsiblePlaybookFail")
638         del TestRecord[Id]
639         return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
640     else:
641         # Test EnvParameters
642         playbook_path = PlaybookDir
643
644         # Store local vars
645         store_local_vars(playbook_path, Id)
646
647         # write some info out to files before running
648         if AUTH:
649             f = open(playbook_path + "/User.txt", "a")
650             f.write(cherrypy.request.login)
651             f.close()
652
653         f = open(playbook_path + "/PlaybookName.txt", "a")
654         f.write(PlaybookName)
655         f.close()
656
657         f = open(playbook_path + "/PlaybookExDir.txt", "a")
658         f.write(PlaybookDir + "/" + PlayBookFunction)
659         f.close()
660
661         f = open(playbook_path + "/JsonRequest.txt", "w")
662         f.write(json.dumps(input_json, indent=4, sort_keys=True))
663         f.close()
664
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"}
669
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))
675         p.start()
676         ActiveProcess[Id] = p
677         return TestRecord[Id]['Result']
678
679
680 def handle_post_method(input_json, time_now):
681     cherrypy.log("Payload: " + str(input_json))
682
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"}
690
691             # if required it should be passed as an argument
692             EnvParameters = input_json.get('EnvParameters', {})
693
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 ])
698
699             pnf_flag = EnvParameters.get("pnf-flag", "")
700             if pnf_flag == "true":
701                 return process_pnf_playbook(input_json, Id, EnvParameters, time_now)
702             else:
703                 return process_vnf_playbook(input_json, Id, EnvParameters, time_now)
704         else:
705             cherrypy.log("TEST ID ALREADY DEFINED")
706             return {"StatusCode": 101, "StatusMessage": "TEST ID ALREADY DEFINED"}
707     else:
708         return {"StatusCode": 500, "StatusMessage": "JSON OBJECT MUST INCLUDE: ID, PLAYBOOKNAME, EnvParameters"}
709
710
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"}
715
716     if AUTH:
717         cherrypy.log("Request USER:             " + cherrypy.request.login)
718     cherrypy.log("Payload: " + str(input_data) + " Type " + input_data['Type'])
719
720     if 'LogRest' in input_data['Type']:
721         sys.stdout.close()
722         sys.stdout = open("/var/log/RestServer.log", "w")
723
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))
731         else:
732             cherrypy.log(" No Records to dump")
733
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"}
739
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)}
750                         # else:
751                         #    return {"StatusCode": 100, "StatusMessage": 'PENDING'}
752                         time.sleep(3)
753
754                     # cherrypy.log( "*** Request released " + input_data['Id'])
755
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']))
760
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']
764                 else:
765                     out_obj = {
766                         "StatusCode": 200,
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']
773                     }
774                 if not TestRecord[input_data['Id']]['Output']['Output'] == {}:
775                     cherrypy.log("TestRecord has Output:" + str(TestRecord[input_data['Id']]['Output']['Output']))
776                     # PAP
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]
781
782                 cherrypy.log("***** GET RETURNING RESULTS Back ****")
783                 cherrypy.log(str(out_obj))
784                 return 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'])
793
794                     # while TestRecord[input_data['Id']]['Output'] == {} \
795                     #         or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
796                     while ActiveProcess[input_data['Id']].is_alive():
797                         time.sleep(3)
798
799                     cherrypy.log("*** Request released " + input_data['Id'])
800
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'])
809                     for dir in run_dir:
810                         rdir = dir
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()
815                         rdir = playbookexdir
816                         f.close()
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()
823                         f.close()
824                         return output_log
825                 else:
826                     cherrypy.log("Globglob failed:")
827                     return
828
829             else:
830                 # GetLog
831                 if TestRecord[input_data['Id']]['Log'] == '' and \
832                         getresults_block:
833
834                     cherrypy.log("*** Request blocked " + input_data['Id'])
835
836                     while TestRecord[input_data['Id']]['Log'] == '' \
837                             or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
838                         time.sleep(5)
839
840                     cherrypy.log("*** Request released " + input_data['Id'])
841
842                 cherrypy.log("Log:" + str(TestRecord[input_data['Id']]['Log']))
843                 return {"Log": TestRecord[input_data['Id']]['Log']}
844         else:
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'])
850                 for dir in run_dir:
851                     rdir = dir
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()
856                     rdir = playbookexdir
857                     f.close()
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()
865                         f.close()
866                         return output_log
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()
872                         f.close()
873                         return output_log
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()
879                         f.close()
880                     else:
881                         playbooknametxt = "NA"
882
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")
886                         out_results = "NA:"
887
888                         f = open(rdir + '/log.file', 'r')
889                         line = f.readline()
890                         while line:
891                             if "fatal" in line:
892                                 out_results = out_results + line
893                             elif "RECAP" in line:
894                                 out_results = out_results + line
895                                 recap_line = f.readline()
896                                 while recap_line:
897                                     out_results = out_results + recap_line
898                                     recap_line = f.readline()
899                             line = f.readline()
900                         f.close()
901                         out_obj = {
902                             "StatusCode": 200,
903                             "StatusMessage": "FINISHED",
904                             "PlaybookName": playbooknametxt,
905                             "Version": "Version",
906                             "Duration": 200,
907                             "Results": out_results
908                         }
909                         return out_obj
910                 else:
911                     return {"StatusCode": 500, "StatusMessage": "PLAYBOOK FAILED "}
912
913             return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
914     else:
915         return {"StatusCode": 500, "StatusMessage": "MALFORMED REQUEST"}
916
917
918 def handle_delete_method(input_data):
919     cherrypy.log("***> in RestServer.DELETE")
920     cherrypy.log("Payload: " + str(input_data))
921
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"}
929         else:
930             return {"StatusCode": 200, "StatusMessage": "PENDING"}
931     else:
932         return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
933
934
935 class TestManager(object):
936     @cherrypy.expose
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
943
944         time_now = datetime.datetime.utcnow()
945
946         # Erase old test results (2x timeout)
947         # Do cleanup too of ActiveProcess list and old Records - PAP
948         if TestRecord:
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'])
964                     del TestRecord[key]
965
966         cherrypy.log("RestServer.Dispatch: " + cherrypy.request.method)
967
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
973             time.sleep(1)
974
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)
980
981
982 if __name__ == '__main__':
983
984     # Read configuration
985
986     config_file_path = "RestServer_config"
987
988     if not os.path.exists(config_file_path):
989         cherrypy.log('[INFO] The config file does not exist')
990         sys.exit(0)
991
992     ip = 'na'
993     AnsibleServer = 'na'
994     port = 'na'
995     tls = False
996     AUTH = False
997     pub = 'na'
998     priv = 'na'
999     intermediate = 'na'
1000     timeout_seconds = 'na'
1001     ANSIBLE_PATH = 'na'
1002     ANSIBLE_TEMP = 'na'
1003     host = 'na'
1004     users = 'na'
1005     getresults_block = False
1006     from_files = False
1007
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'
1037                     sys.exit(0)
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()
1048     config_file.close()
1049
1050     # Initialization
1051
1052     global_conf = {
1053         'global': {
1054             'log.screen': True,
1055             'response.timeout': 5400,
1056             'server.socket_host': ip,
1057             'server.socket_port': int(port),
1058             'server.protocol_version': 'HTTP/1.1'
1059         }
1060     }
1061
1062     if tls:
1063         # Use pythons built-in SSL
1064         cherrypy.server.ssl_module = 'builtin'
1065
1066         # Point to certificate files
1067
1068         if not os.path.exists(pub):
1069             cherrypy.log('[INFO] The public certificate does not exist')
1070             sys.exit(0)
1071
1072         if not os.path.exists(priv):
1073             cherrypy.log('[INFO] The private key does not exist')
1074             sys.exit(0)
1075
1076         if not os.path.exists(intermediate):
1077             cherrypy.log('[INFO] The intermediate certificate does not exist')
1078             sys.exit(0)
1079
1080         cherrypy.server.ssl_certificate = pub
1081         cherrypy.server.ssl_certificate_chain = intermediate
1082         cherrypy.server.ssl_private_key = priv
1083
1084     if AUTH:
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)
1088             sys.exit(0)
1089         userpassdict = {}
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
1096
1097         app_conf = {
1098             '/':
1099                 {'tools.auth_basic.on': True,
1100                  'tools.auth_basic.realm': 'earth',
1101                  'tools.auth_basic.checkpassword': validate_password
1102                  }
1103         }
1104
1105         application = cherrypy.tree.mount(TestManager(), '/', app_conf)
1106     else:
1107         application = cherrypy.tree.mount(TestManager(), '/')
1108
1109     cherrypy.config.update({
1110         'log.access_file': "/var/log/RestServer.access"
1111     })
1112     accessLogName = "/var/log/RestServer.access"
1113     applicationLogName = "/var/log/RestServer.log"
1114     cherrypy.config.update(global_conf)
1115
1116     log = application.log
1117     log.error_file = ""
1118     log.access_file = ""
1119     from logging import handlers
1120
1121     applicationLogFileHandler = handlers.RotatingFileHandler(applicationLogName, 'a', 1000000, 5000)
1122     accessLogFileHandler = handlers.RotatingFileHandler(accessLogName, 'a', 1000000, 5000)
1123     import logging
1124
1125     applicationLogFileHandler.setLevel(logging.DEBUG)
1126     log.error_log.addHandler(applicationLogFileHandler)
1127     log.access_log.addHandler(accessLogFileHandler)
1128
1129     # Start server
1130
1131     cherrypy.engine.start()
1132     cherrypy.engine.block()