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