2b4da3357bf86c98d7cc7b53c142dfdac3e8a87b
[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 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     str_uuid = str(uuid.uuid4())
333
334     HomeDir = os.path.dirname(os.path.realpath("~/"))
335
336     PlaybookType = PlaybookName.split(".")[0].split('_')[-1]
337     PlaybookDir = HomeDir + '/' + ANSIBLE_TEMP + "/" + PlaybookName.split(".")[0] + "_" + str_uuid
338     AnsibleInv = PlaybookType + "_" + "inventory"
339     ArchiveFlag = False
340
341     cherrypy.log("Request Decode: PlaybookType     " + PlaybookType)
342     cherrypy.log("Request Decode: PlaybookDir      " + PlaybookDir)
343     cherrypy.log("Request Decode: AnsibleInv       " + AnsibleInv)
344
345     NodeList = input_json.get('NodeList', [])
346     cherrypy.log("Request Decode: NodeList: " + str(NodeList))
347
348     # Create base run directory if it doesn't exist
349     if not os.path.exists(ANSIBLE_TEMP):
350         cherrypy.log("Creating Base Run Directory: " + ANSIBLE_TEMP)
351         os.makedirs(ANSIBLE_TEMP)
352
353     os.mkdir(PlaybookDir)
354
355     # Process inventory file for target
356     hostgrouplist = []
357     hostnamelist = []
358
359     buildInventorySysCall(ANSIBLE_PATH, ANSIBLE_INV, NodeList, PlaybookDir, AnsibleInv, hostgrouplist, hostnamelist)
360
361     version_target = getPlaybookFile(ANSIBLE_PATH, PlaybookName, PlaybookType, PlaybookDir)
362     if not version_target:
363         return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
364
365     if version is None:
366         version = version_target
367
368     if 'Timeout' in input_json:
369         timeout = int(input_json['Timeout'])
370         cherrypy.log("Timeout from API: " + str(timeout))
371     else:
372         timeout = timeout_seconds
373         cherrypy.log("Timeout not passed from API using default: " + str(timeout))
374
375     EnvParam = input_json.get('EnvParameters', {})
376     LocalParam = input_json.get('LocalParameters', {})
377     FileParam = input_json.get('FileParameters', {})
378     callback_flag = input_json.get('CallBack', None)
379
380     # if AnsibleServer is not set to 'na' don't send AnsibleServer in PENDING response.
381     TestRecord[Id] = {
382         'PlaybookName': PlaybookName,
383         'Version': version,
384         'NodeList': NodeList,
385         'HostGroupList': hostgrouplist,
386         'HostNameList': hostnamelist,
387         'Time': time_now,
388         'Duration': timeout,
389         'Timeout': timeout,
390         'EnvParameters': EnvParam,
391         'LocalParameters': LocalParam,
392         'FileParameters': FileParam,
393         'CallBack': callback_flag,
394         'Result': {
395             "StatusCode": 100,
396             "StatusMessage": 'PENDING',
397             "ExpectedDuration": str(timeout) + "sec"
398         },
399         'Log': '',
400         'Output': {},
401         'Path': PlaybookDir,
402         'Mandatory': None
403     }
404     if AnsibleServer != 'na':
405         TestRecord[Id]['Result']["AnsibleServer"] = str(AnsibleServer),
406
407     cherrypy.log("Test_Record: " + str(TestRecord[Id]))
408
409     # Write files
410     if TestRecord[Id]['FileParameters']:
411         for key in TestRecord[Id]['FileParameters']:
412             filename = key
413             filecontent = TestRecord[Id]['FileParameters'][key]
414             f = open(PlaybookDir + "/" + filename, "w")
415             f.write(filecontent)
416             f.close()
417
418     playbook_path = PlaybookDir
419
420     # Store local vars
421     store_local_vars(playbook_path, Id)
422
423     # write some info out to files before running
424     if AUTH:
425         f = open(playbook_path + "/User.txt", "a")
426         f.write(cherrypy.request.login)
427         f.close()
428
429     f = open(playbook_path + "/PlaybookName.txt", "a")
430     f.write(PlaybookName)
431     f.close()
432
433     f = open(playbook_path + "/JsonRequest.txt", "w")
434     f.write(json.dumps(input_json, indent=4, sort_keys=True))
435     f.close()
436
437     # Cannot use thread because ansible module uses signals which are only supported in main thread.
438     # So use multiprocess with shared object
439     p = Process(target=RunAnsible_Playbook,
440                 args=(callback, Id, PlaybookDir + '/' + AnsibleInv, PlaybookDir + '/' + PlaybookType + '.yml',
441                       NodeList, TestRecord, PlaybookDir, ArchiveFlag, True))
442     p.start()
443     ActiveProcess[Id] = p
444     return TestRecord[Id]['Result']
445
446
447 def process_vnf_playbook(input_json, Id, EnvParameters, time_now):
448     cherrypy.log("Processing playbook for VNF...")
449
450     PlaybookName = input_json['PlaybookName']
451     VNF_instance = EnvParameters.get('vnf_instance')
452     version = input_json.get('Version', None)
453
454     # GetInventoryNames
455     HaveNodeList = False
456     HaveInventoryNames = False
457     inventory_names = None
458     if 'InventoryNames' in input_json:
459         inventory_names = input_json['InventoryNames']
460         HaveInventoryNames = True
461
462     AnsiblePlaybookFail = True
463
464     str_uuid = str(uuid.uuid4())
465
466     # VnfType = PlaybookName.split("/")[0]
467
468     if AUTH:
469         cherrypy.log("Request USER  :                  " + cherrypy.request.login)
470     cherrypy.log("Request Decode: ID               " + Id)
471     # cherrypy.log("Request Decode: VnfType          " + VnfType)
472     cherrypy.log("Request Decode: EnvParameters    " + json.dumps(EnvParameters))
473
474     # Verify VNF_instance was passed in EnvParameters
475     if VNF_instance is not None:
476         cherrypy.log("Request Decode: VnfInstance      " + VNF_instance)
477     else:
478         cherrypy.log("StatusCode: 107, StatusMessage: VNF_instance NOT PROVIDED")
479         return {"StatusCode": 107, "StatusMessage": "VNF_instance NOT PROVIDED"}
480
481     if inventory_names is not None:
482         cherrypy.log("Request Decode: Inventory Names  " + inventory_names)
483     else:
484         cherrypy.log("Request Decode: Inventory Names  " + "Not provided")
485
486     cherrypy.log("Request Decode: PlaybookName     " + PlaybookName)
487
488     PlayBookFunction = PlaybookName.rsplit("/", 2)[1]
489     PlayBookFile = PlayBookFunction + "/site.yml"
490
491     cherrypy.log("Request Decode: PlaybookFunction " + PlayBookFunction)
492     cherrypy.log("Request Decode: PlaybookFile     " + PlayBookFile)
493
494     BaseDir = ANSIBLE_PATH + "/" + PlaybookName.rsplit("/", 1)[0]
495     CopyDir = ANSIBLE_PATH + "/" + PlaybookName.rsplit("/", 2)[0]
496     cherrypy.log("Request Decode: Basedir          " + BaseDir)
497     cherrypy.log("Request Decode: Copydir          " + CopyDir)
498
499     PlaybookDir = ANSIBLE_TEMP + "/" + VNF_instance + "_" + str_uuid + "_" + str(Id)
500     cherrypy.log("Request Decode: PlaybookDir      " + PlaybookDir)
501
502     # AnsibleInv is the directory where the host file to be run exists
503     # AnsibleInv = ANSIBLE_PATH + "/" + VnfType + "/latest/ansible/inventory/" + VNF_instance
504     ArchiveFlag = False
505
506     # Create base run directory if it doesn't exist
507     if not os.path.exists(ANSIBLE_TEMP):
508         cherrypy.log("Creating Base Run Directory: " + ANSIBLE_TEMP)
509         os.makedirs(ANSIBLE_TEMP)
510
511     if not os.path.exists(CopyDir):
512         cherrypy.log("Playbook Not Found")
513         return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
514
515     # copy static playbook dir to run dir
516     cherrypy.log("Copying from " + CopyDir + " to " + PlaybookDir)
517     shutil.copytree(CopyDir, PlaybookDir)
518     # cmd="/usr/bin/find " + PlaybookDir + " -exec /usr/bin/touch {} \;"
519     cmd = "/usr/bin/find " + PlaybookDir + " -exec chmod +rx  {} \;"
520     sys_call(cmd)
521     cherrypy.log(cmd)
522
523     cherrypy.log("PlaybookDir:    " + PlaybookDir)
524     # cherrypy.log("AnsibleInv:     " + AnsibleInv)
525
526     # Process inventory file for target
527     hostgrouplist = []
528     hostnamelist = []
529
530     NodeList = input_json.get('NodeList', [])
531
532     cherrypy.log("NodeList: " + str(NodeList))
533
534     # if NodeList empty
535     if not NodeList:
536         cherrypy.log("*** NodeList - Empty ***")
537     else:
538         HaveNodeList = True
539
540     # ##############################################################################
541     # #### Host file processing                          ###########################
542     # #### 1. Use file delivered with playbook           ###########################
543     # #### 2. If HostNames + NodeList generate and use   ###########################
544     # ##############################################################################
545
546     # Verify inventory directory exists
547     path = PlaybookDir + "/inventory/"
548     if not os.path.isdir(path):
549         cherrypy.log("Inventory directory %s does not exist - create it" % path)
550         try:
551             os.mkdir(path)
552         except OSError:
553             cherrypy.log("Creation of the directory %s failed" % path)
554         else:
555             cherrypy.log("Successfully created the directory %s " % path)
556
557     # location of host file - Default
558     HostFile = PlaybookDir + "/inventory/" + VNF_instance + "hosts"
559     cherrypy.log("HostFile: " + HostFile)
560
561     # if NodeList and InventoryNames need to build host file
562     if HaveInventoryNames and HaveNodeList:
563         cherrypy.log("Build host file from NodeList")
564         ret = buildHostsSysCall(input_json, PlaybookDir, inventory_names)
565         if ret < 0:
566             cherrypy.log("Returning Error: Not running Playbook")
567             return {"StatusCode": 105,
568                     "StatusMessage": "NodeList: Missing vnfc-type field"}
569
570         # Having been built now copy new file to correct file
571         shutil.copy(PlaybookDir + "/host_file.txt", HostFile)
572         cherrypy.log("Copying Generated host file to: " + HostFile)
573
574     if 'Timeout' in input_json:
575         timeout = int(input_json['Timeout'])
576         cherrypy.log("Timeout from API: " + str(timeout))
577     else:
578         timeout = timeout_seconds
579         cherrypy.log("Timeout not passed from API using default: " + str(timeout))
580
581     EnvParam = input_json.get('EnvParameters', {})
582     LocalParam = input_json.get('LocalParameters', {})
583     FileParam = input_json.get('FileParameters', {})
584     callback_flag = input_json.get('CallBack', None)
585
586     # if AnsibleServer is not set to 'na' don't send AnsibleServer in PENDING response.
587     TestRecord[Id] = {
588         'PlaybookName': PlaybookName,
589         'Version': version,
590         'NodeList': NodeList,
591         'HostGroupList': hostgrouplist,
592         'HostNameList': hostnamelist,
593         'Time': time_now,
594         'Duration': timeout,
595         'Timeout': timeout,
596         'EnvParameters': EnvParam,
597         'LocalParameters': LocalParam,
598         'FileParameters': FileParam,
599         'CallBack': callback_flag,
600         'Result': {
601             "StatusCode": 100,
602             "StatusMessage": 'PENDING',
603             "ExpectedDuration": str(timeout) + "sec"
604         },
605         'Log': '',
606         'Output': {},
607         'Path': PlaybookDir,
608         'Mandatory': None
609     }
610     if AnsibleServer != 'na':
611         TestRecord[Id]['Result']["AnsibleServer"] = str(AnsibleServer),
612
613     cherrypy.log("Test_Record: " + str(TestRecord[Id]))
614
615     # Write files
616     if TestRecord[Id]['FileParameters']:
617         for key in TestRecord[Id]['FileParameters']:
618             filename = key
619             filecontent = TestRecord[Id]['FileParameters'][key]
620             f = open(PlaybookDir + "/" + filename, "w")
621             f.write(filecontent)
622             f.close()
623
624     # Process playbook
625     if os.path.exists(ANSIBLE_PATH + '/' + PlaybookName):
626         AnsiblePlaybookFail = False
627
628     if AnsiblePlaybookFail:
629         # if os.path.exists(PlaybookDir):
630         # shutil.rmtree (PlaybookDir)
631         cherrypy.log("AnsiblePlaybookFail")
632         del TestRecord[Id]
633         return {"StatusCode": 101, "StatusMessage": "PLAYBOOK NOT FOUND"}
634     else:
635         # Test EnvParameters
636         playbook_path = PlaybookDir
637
638         # Store local vars
639         store_local_vars(playbook_path, Id)
640
641         # write some info out to files before running
642         if AUTH:
643             f = open(playbook_path + "/User.txt", "a")
644             f.write(cherrypy.request.login)
645             f.close()
646
647         f = open(playbook_path + "/PlaybookName.txt", "a")
648         f.write(PlaybookName)
649         f.close()
650
651         f = open(playbook_path + "/PlaybookExDir.txt", "a")
652         f.write(PlaybookDir + "/" + PlayBookFunction)
653         f.close()
654
655         f = open(playbook_path + "/JsonRequest.txt", "w")
656         f.write(json.dumps(input_json, indent=4, sort_keys=True))
657         f.close()
658
659         # Check that HostFile exists
660         if not os.path.isfile(HostFile):
661             cherrypy.log("Inventory file Not Found: " + HostFile)
662             return {"StatusCode": 101, "StatusMessage": "PLAYBOOK INVENTORY FILE NOT FOUND"}
663
664         # Cannot use thread because ansible module uses signals which are only supported in main thread.
665         # So use multiprocess with shared object
666         p = Process(target=RunAnsible_Playbook,
667                     args=(callback, Id, HostFile, PlaybookDir + '/' + PlayBookFile,
668                           NodeList, TestRecord, PlaybookDir + "/" + PlayBookFunction, ArchiveFlag))
669         p.start()
670         ActiveProcess[Id] = p
671         return TestRecord[Id]['Result']
672
673
674 def handle_post_method(input_json, time_now):
675     cherrypy.log("Payload: " + str(input_json))
676
677     if 'Id' in input_json and 'PlaybookName' in input_json and 'EnvParameters' in input_json:
678         if input_json['Id'] not in TestRecord:
679             # check if Id exists in previous run directory, if so return error
680             Id = input_json['Id']
681             if glob.glob(ANSIBLE_TEMP + '/*_' + input_json['Id']):
682                 cherrypy.log("Old directory found for ID: " + Id)
683                 return {"StatusCode": 101, "StatusMessage": "TEST ID FILE ALREADY DEFINED"}
684
685             # if required it should be passed as an argument
686             EnvParameters = input_json.get('EnvParameters', {})
687
688             # The lines below are to test multiple EnvParameters being passed
689             # for i in EnvParameters:
690             #   cherrypy.log("EnvParameter object: " + i)
691             #   cherrypy.log("  EnvParameter Value: " + EnvParameters[ i ])
692
693             pnf_flag = EnvParameters.get("pnf-flag", "")
694             if pnf_flag == "true":
695                 return process_pnf_playbook(input_json, Id, EnvParameters, time_now)
696             else:
697                 return process_vnf_playbook(input_json, Id, EnvParameters, time_now)
698         else:
699             cherrypy.log("TEST ID ALREADY DEFINED")
700             return {"StatusCode": 101, "StatusMessage": "TEST ID ALREADY DEFINED"}
701     else:
702         return {"StatusCode": 500, "StatusMessage": "JSON OBJECT MUST INCLUDE: ID, PLAYBOOKNAME, EnvParameters"}
703
704
705 def handle_get_method(input_data):
706     # Verify we have a Type passed in GET request
707     if 'Type' not in input_data:
708         return {"StatusCode": 500, "StatusMessage": "RESULTS TYPE UNDEFINED"}
709
710     if AUTH:
711         cherrypy.log("Request USER:             " + cherrypy.request.login)
712     cherrypy.log("Payload: " + str(input_data) + " Type " + input_data['Type'])
713
714     if 'LogRest' in input_data['Type']:
715         sys.stdout.close()
716         sys.stdout = open("/var/log/RestServer.log", "w")
717
718     # Just a debug to dump any records
719     if 'GetStatus' in input_data['Type']:
720         cherrypy.log("******** Dump Records **********")
721         if TestRecord.items():
722             for id, record in TestRecord.items():
723                 cherrypy.log("    Id: " + id)
724                 cherrypy.log("Record: " + str(record))
725         else:
726             cherrypy.log(" No Records to dump")
727
728     if 'Id' in input_data and 'Type' in input_data:
729         if not ('GetResult' in input_data['Type'] or 'GetOutputLog' in input_data['Type'] or
730                 'GetTheOutput' in input_data['Type'] or 'GetOutput' in input_data['Type'] or
731                 'GetLog' in input_data['Type']):
732             return {"StatusCode": 500, "StatusMessage": "RESULTS TYPE UNDEFINED"}
733
734         if input_data['Id'] in TestRecord:
735             if 'GetResult' in input_data['Type']:
736                 cherrypy.log(" ** GetResult for: " + str(input_data['Id']))
737                 if 'StatusMessage' in TestRecord[input_data['Id']]['Result'] and getresults_block:
738                     # check if playbook is still running
739                     while ActiveProcess[input_data['Id']].is_alive():
740                         cherrypy.log("*** Playbook running returning PENDING for " + str(input_data['Id']))
741                         # If still running return PENDING response
742                         # if AnsibleServer != 'na':
743                         #     return {"StatusCode": 100, "StatusMessage": 'PENDING', "AnsibleServer": str(AnsibleServer)}
744                         # else:
745                         #    return {"StatusCode": 100, "StatusMessage": 'PENDING'}
746                         time.sleep(3)
747
748                     # cherrypy.log( "*** Request released " + input_data['Id'])
749
750                 cherrypy.log(str(TestRecord[input_data['Id']]['Result']))
751                 cherrypy.log("Output: " + str(TestRecord[input_data['Id']]['Output']))
752                 cherrypy.log("StatusCode: " + str(TestRecord[input_data['Id']]['Result']['StatusCode']))
753                 cherrypy.log("StatusMessage: " + str(TestRecord[input_data['Id']]['Result']['StatusMessage']))
754
755                 # out_obj gets returned to GET request
756                 if TestRecord[input_data['Id']]['Result']['StatusCode'] == 500:
757                     out_obj = TestRecord[input_data['Id']]['Result']['Results']
758                 else:
759                     out_obj = {
760                         "StatusCode": 200,
761                         "StatusMessage": "FINISHED",
762                         "PlaybookName": TestRecord[input_data['Id']]["PlaybookName"],
763                         "Version": TestRecord[input_data['Id']]["Version"],
764                         "Duration": TestRecord[input_data['Id']]["Duration"],
765                         "Output": TestRecord[input_data['Id']]["Output"]["Output"],
766                         "Results": TestRecord[input_data['Id']]['Result']['Results']
767                     }
768                 if not TestRecord[input_data['Id']]['Output']['Output'] == {}:
769                     cherrypy.log("TestRecord has Output:" + str(TestRecord[input_data['Id']]['Output']['Output']))
770                     # PAP
771                     for key in out_obj["Results"]:
772                         cherrypy.log("Output key: " + str(key))
773                         if key in TestRecord[input_data['Id']]['Output']['Output']:
774                             out_obj["Results"][key]["Output"] = TestRecord[input_data['Id']]['Output']['Output'][key]
775
776                 cherrypy.log("***** GET RETURNING RESULTS Back ****")
777                 cherrypy.log(str(out_obj))
778                 return out_obj
779             elif 'GetStatus' in input_data['Type']:
780                 cherrypy.log(" Dump Records")
781                 for id, record in TestRecord.items():
782                     cherrypy.log(" id: " + id)
783                     cherrypy.log("   Record:" + str(record))
784             elif 'GetTheOutput' in input_data['Type'] or 'GetOutput' in input_data['Type']:
785                 if TestRecord[input_data['Id']]['Output'] == {} and getresults_block:
786                     cherrypy.log("*** Request blocked " + input_data['Id'])
787
788                     # while TestRecord[input_data['Id']]['Output'] == {} \
789                     #         or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
790                     while ActiveProcess[input_data['Id']].is_alive():
791                         time.sleep(3)
792
793                     cherrypy.log("*** Request released " + input_data['Id'])
794
795                 cherrypy.log("Output: " + str(TestRecord[input_data['Id']]['Output']))
796                 return {"Output": TestRecord[input_data['Id']]['Output']['Output']}
797             elif 'GetOutputLog' in input_data['Type']:
798                 cherrypy.log("GetOutputLog: processing.")
799                 if glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id']):
800                     id = input_data['Id']
801                     cherrypy.log("Old directory found for ID: " + id)
802                     run_dir = glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id'])
803                     for dir in run_dir:
804                         rdir = dir
805                     if os.path.exists(rdir + "/PlaybookExDir.txt"):
806                         cherrypy.log("Found PlaybookExDir.txt file")
807                         f = open(rdir + '/PlaybookExDir.txt', 'r')
808                         playbookexdir = f.readline()
809                         rdir = playbookexdir
810                         f.close()
811                     cherrypy.log("Id:     " + id)
812                     cherrypy.log("RunDir: " + rdir)
813                     if os.path.exists(rdir + "/output.log"):
814                         cherrypy.log("Found output.log file")
815                         f = open(rdir + '/output.log', 'r')
816                         output_log = f.readline()
817                         f.close()
818                         return output_log
819                 else:
820                     cherrypy.log("Globglob failed:")
821                     return
822
823             else:
824                 # GetLog
825                 if TestRecord[input_data['Id']]['Log'] == '' and \
826                         getresults_block:
827
828                     cherrypy.log("*** Request blocked " + input_data['Id'])
829
830                     while TestRecord[input_data['Id']]['Log'] == '' \
831                             or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
832                         time.sleep(5)
833
834                     cherrypy.log("*** Request released " + input_data['Id'])
835
836                 cherrypy.log("Log:" + str(TestRecord[input_data['Id']]['Log']))
837                 return {"Log": TestRecord[input_data['Id']]['Log']}
838         else:
839             # Not in memory check for a file
840             if glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id']):
841                 id = input_data['Id']
842                 cherrypy.log("Old directory found for ID: " + id)
843                 run_dir = glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id'])
844                 for dir in run_dir:
845                     rdir = dir
846                 if os.path.exists(rdir + "/PlaybookExDir.txt"):
847                     cherrypy.log("Found PlaybookExDir.txt file")
848                     f = open(rdir + '/PlaybookExDir.txt', 'r')
849                     playbookexdir = f.readline()
850                     rdir = playbookexdir
851                     f.close()
852                 cherrypy.log("Id:     " + id)
853                 cherrypy.log("RunDir: " + rdir)
854                 if 'GetLog' in input_data['Type']:
855                     if os.path.exists(rdir + "/output.log"):
856                         cherrypy.log("Found output.log file")
857                         f = open(rdir + '/output.log', 'r')
858                         output_log = f.readline()
859                         f.close()
860                         return output_log
861                 elif 'GetOutputLog' 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 'GetResult' in input_data['Type']:
869                     if os.path.exists(rdir + "/PlaybookName.txt"):
870                         cherrypy.log("Found PlaybookName.txt file")
871                         f = open(rdir + '/PlaybookName.txt', 'r')
872                         playbooknametxt = f.readline()
873                         f.close()
874                     else:
875                         playbooknametxt = "NA"
876
877                     # Add code to get other items not just output.log from files
878                     if os.path.exists(rdir + "/log.file"):
879                         cherrypy.log("Found log.file")
880                         out_results = "NA:"
881
882                         f = open(rdir + '/log.file', 'r')
883                         line = f.readline()
884                         while line:
885                             if "fatal" in line:
886                                 out_results = out_results + line
887                             elif "RECAP" in line:
888                                 out_results = out_results + line
889                                 recap_line = f.readline()
890                                 while recap_line:
891                                     out_results = out_results + recap_line
892                                     recap_line = f.readline()
893                             line = f.readline()
894                         f.close()
895                         out_obj = {
896                             "StatusCode": 200,
897                             "StatusMessage": "FINISHED",
898                             "PlaybookName": playbooknametxt,
899                             "Version": "Version",
900                             "Duration": 200,
901                             "Results": out_results
902                         }
903                         return out_obj
904                 else:
905                     return {"StatusCode": 500, "StatusMessage": "PLAYBOOK FAILED "}
906
907             return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
908     else:
909         return {"StatusCode": 500, "StatusMessage": "MALFORMED REQUEST"}
910
911
912 def handle_delete_method(input_data):
913     cherrypy.log("***> in RestServer.DELETE")
914     cherrypy.log("Payload: " + str(input_data))
915
916     if input_data['Id'] in TestRecord:
917         if 'PENDING' not in TestRecord[input_data['Id']]['Result']:
918             cherrypy.log(" Path: " + str(TestRecord[input_data['Id']]['Path']))
919             TestRecord.pop(input_data['Id'])
920             if input_data['Id'] in ActiveProcess:
921                 ActiveProcess.pop(input_data['Id'])
922             return {"StatusCode": 200, "StatusMessage": "PLAYBOOK EXECUTION RECORDS DELETED"}
923         else:
924             return {"StatusCode": 200, "StatusMessage": "PENDING"}
925     else:
926         return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
927
928
929 class TestManager(object):
930     @cherrypy.expose
931     @cherrypy.tools.json_out()
932     @cherrypy.tools.json_in()
933     @cherrypy.tools.allow(methods=['POST', 'GET', 'DELETE'])
934     def Dispatch(self, **kwargs):
935         # Let cherrypy error handler deal with malformed requests
936         # No need for explicit error handler, we use default ones
937
938         time_now = datetime.datetime.utcnow()
939
940         # Erase old test results (2x timeout)
941         # Do cleanup too of ActiveProcess list and old Records - PAP
942         if TestRecord:
943             for key in TestRecord.copy():
944                 cherrypy.log("LOOKING AT ALL TestRecords: " + str(key))
945                 if key in ActiveProcess:
946                     if not ActiveProcess[key].is_alive():  # Just to cleanup defunct processes
947                         cherrypy.log("Not ActiveProcess for ID: " + str(key))
948                 delta_time = (time_now - TestRecord[key]['Time']).seconds
949                 if delta_time > 2 * TestRecord[key]['Timeout']:
950                     cherrypy.log("DELETED HISTORY for ID: " + str(key))
951                     if key in ActiveProcess:
952                         if not ActiveProcess[key].is_alive():
953                             ActiveProcess.pop(key)
954                             cherrypy.log("DELETED ActiveProcess for ID: " + str(key))
955                     # if os.path.exists(TestRecord[key]['Path']):
956                     # don't remove run dirrectory
957                     # shutil.rmtree (TestRecord[key]['Path'])
958                     del TestRecord[key]
959
960         cherrypy.log("RestServer.Dispatch: " + cherrypy.request.method)
961
962         if 'POST' in cherrypy.request.method:
963             input_json = cherrypy.request.json
964             return handle_post_method(input_json, time_now)
965         elif 'GET' in cherrypy.request.method:
966             # Lets pause for a second just in case the request was just kicked off
967             time.sleep(1)
968
969             input_data = parse_query_string(cherrypy.request.query_string)
970             return handle_get_method(input_data)
971         elif 'DELETE' in cherrypy.request.method:
972             input_data = parse_query_string(cherrypy.request.query_string)
973             return handle_delete_method(input_data)
974
975
976 if __name__ == '__main__':
977
978     # Read configuration
979
980     config_file_path = "RestServer_config"
981
982     if not os.path.exists(config_file_path):
983         cherrypy.log('[INFO] The config file does not exist')
984         sys.exit(0)
985
986     ip = 'na'
987     AnsibleServer = 'na'
988     port = 'na'
989     tls = False
990     AUTH = False
991     pub = 'na'
992     priv = 'na'
993     intermediate = 'na'
994     timeout_seconds = 'na'
995     ANSIBLE_PATH = 'na'
996     ANSIBLE_TEMP = 'na'
997     host = 'na'
998     users = 'na'
999     getresults_block = False
1000     from_files = False
1001
1002     config_file = open(config_file_path, 'r')
1003     for config_line in config_file.readlines():
1004         if '#' not in config_line:
1005             if 'ip:' in config_line:
1006                 ip = config_line.split(':')[1].strip()
1007             elif 'AnsibleServer:' in config_line:
1008                 AnsibleServer = config_line.split(':')[1].strip()
1009             elif 'port:' in config_line:
1010                 port = config_line.split(':')[1].strip()
1011             elif 'ksalt:' in config_line:
1012                 salt = config_line.split(':')[1].strip()
1013             elif 'tls:' in config_line:
1014                 tls = 'YES' in config_line.split(':')[1].strip().upper()
1015             elif 'auth:' in config_line:
1016                 AUTH = 'YES' in config_line.split(':')[1].strip().upper()
1017             if tls and 'priv:' in config_line:
1018                 priv = config_line.split(':')[1].strip()
1019             if tls and 'pub:' in config_line:
1020                 pub = config_line.split(':')[1].strip()
1021             if tls and 'inter_cert:' in config_line:
1022                 intermediate = config_line.split(':')[1].strip()
1023             if 'timeout_seconds' in config_line:
1024                 timeout_seconds = int(config_line.split(':')[1].strip())
1025             if 'ansible_path' in config_line:
1026                 ANSIBLE_PATH = config_line.split(':')[1].strip()
1027             if 'ansible_inv' in config_line:
1028                 ANSIBLE_INV = config_line.split(':')[1].strip()
1029                 if not os.path.exists(ANSIBLE_PATH + "/" + ANSIBLE_INV):
1030                     print '[INFO] The ansible_inv file does not exist'
1031                     sys.exit(0)
1032             if 'ansible_temp' in config_line:
1033                 ANSIBLE_TEMP = config_line.split(':')[1].strip()
1034             if 'host' in config_line:
1035                 host = config_line.split(':')[1].strip()
1036             if 'users' in config_line:
1037                 users = config_line.split(':')[1].strip()
1038             if 'getresults_block' in config_line:
1039                 getresults_block = 'YES' in config_line.split(':')[1].strip().upper()
1040             if 'from_files' in config_line:
1041                 from_files = 'YES' in config_line.split(':')[1].strip().upper()
1042     config_file.close()
1043
1044     # Initialization
1045
1046     global_conf = {
1047         'global': {
1048             'log.screen': True,
1049             'response.timeout': 5400,
1050             'server.socket_host': ip,
1051             'server.socket_port': int(port),
1052             'server.protocol_version': 'HTTP/1.1'
1053         }
1054     }
1055
1056     if tls:
1057         # Use pythons built-in SSL
1058         cherrypy.server.ssl_module = 'builtin'
1059
1060         # Point to certificate files
1061
1062         if not os.path.exists(pub):
1063             cherrypy.log('[INFO] The public certificate does not exist')
1064             sys.exit(0)
1065
1066         if not os.path.exists(priv):
1067             cherrypy.log('[INFO] The private key does not exist')
1068             sys.exit(0)
1069
1070         if not os.path.exists(intermediate):
1071             cherrypy.log('[INFO] The intermediate certificate does not exist')
1072             sys.exit(0)
1073
1074         cherrypy.server.ssl_certificate = pub
1075         cherrypy.server.ssl_certificate_chain = intermediate
1076         cherrypy.server.ssl_private_key = priv
1077
1078     if AUTH:
1079         # Read in and build user dictionary
1080         if not os.path.exists(users):
1081             cherrypy.log('[INFO] The users file does not exist: ' + users)
1082             sys.exit(0)
1083         userpassdict = {}
1084         user_file = open(users, 'r')
1085         for config_line in user_file.readlines():
1086             if '#' not in config_line:
1087                 uid = config_line.split(':')[0].strip()
1088                 pw = config_line.split(':')[1].strip()
1089                 userpassdict[uid] = pw
1090
1091         app_conf = {
1092             '/':
1093                 {'tools.auth_basic.on': True,
1094                  'tools.auth_basic.realm': 'earth',
1095                  'tools.auth_basic.checkpassword': validate_password
1096                  }
1097         }
1098
1099         application = cherrypy.tree.mount(TestManager(), '/', app_conf)
1100     else:
1101         application = cherrypy.tree.mount(TestManager(), '/')
1102
1103     cherrypy.config.update({
1104         'log.access_file': "/var/log/RestServer.access"
1105     })
1106     accessLogName = "/var/log/RestServer.access"
1107     applicationLogName = "/var/log/RestServer.log"
1108     cherrypy.config.update(global_conf)
1109
1110     log = application.log
1111     log.error_file = ""
1112     log.access_file = ""
1113     from logging import handlers
1114
1115     applicationLogFileHandler = handlers.RotatingFileHandler(applicationLogName, 'a', 1000000, 5000)
1116     accessLogFileHandler = handlers.RotatingFileHandler(accessLogName, 'a', 1000000, 5000)
1117     import logging
1118
1119     applicationLogFileHandler.setLevel(logging.DEBUG)
1120     log.error_log.addHandler(applicationLogFileHandler)
1121     log.access_log.addHandler(accessLogFileHandler)
1122
1123     # Start server
1124
1125     cherrypy.engine.start()
1126     cherrypy.engine.block()