f24eae0aabdda7354ab3a4a798beb81918b189e5
[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 & 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                         time.sleep(5)
791
792                     cherrypy.log("*** Request released " + input_data['Id'])
793
794                 cherrypy.log("Output: " + str(TestRecord[input_data['Id']]['Output']))
795                 return {"Output": TestRecord[input_data['Id']]['Output']['Output']}
796             elif 'GetOutputLog' in input_data['Type']:
797                 cherrypy.log("GetOutputLog: processing.")
798                 if glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id']):
799                     id = input_data['Id']
800                     cherrypy.log("Old directory found for ID: " + id)
801                     run_dir = glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id'])
802                     for dir in run_dir:
803                         rdir = dir
804                     if os.path.exists(rdir + "/PlaybookExDir.txt"):
805                         cherrypy.log("Found PlaybookExDir.txt file")
806                         f = open(rdir + '/PlaybookExDir.txt', 'r')
807                         playbookexdir = f.readline()
808                         rdir = playbookexdir
809                         f.close()
810                     cherrypy.log("Id:     " + id)
811                     cherrypy.log("RunDir: " + rdir)
812                     if os.path.exists(rdir + "/output.log"):
813                         cherrypy.log("Found output.log file")
814                         f = open(rdir + '/output.log', 'r')
815                         output_log = f.readline()
816                         f.close()
817                         return output_log
818                 else:
819                     cherrypy.log("Globglob failed:")
820                     return
821
822             else:
823                 # GetLog
824                 if TestRecord[input_data['Id']]['Log'] == '' and \
825                         getresults_block:
826
827                     cherrypy.log("*** Request blocked " + input_data['Id'])
828
829                     while TestRecord[input_data['Id']]['Log'] == '' \
830                             or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
831                         time.sleep(5)
832
833                     cherrypy.log("*** Request released " + input_data['Id'])
834
835                 cherrypy.log("Log:" + str(TestRecord[input_data['Id']]['Log']))
836                 return {"Log": TestRecord[input_data['Id']]['Log']}
837         else:
838             # Not in memory check for a file
839             if glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id']):
840                 id = input_data['Id']
841                 cherrypy.log("Old directory found for ID: " + id)
842                 run_dir = glob.glob(ANSIBLE_TEMP + '/*_' + input_data['Id'])
843                 for dir in run_dir:
844                     rdir = dir
845                 if os.path.exists(rdir + "/PlaybookExDir.txt"):
846                     cherrypy.log("Found PlaybookExDir.txt file")
847                     f = open(rdir + '/PlaybookExDir.txt', 'r')
848                     playbookexdir = f.readline()
849                     rdir = playbookexdir
850                     f.close()
851                 cherrypy.log("Id:     " + id)
852                 cherrypy.log("RunDir: " + rdir)
853                 if 'GetLog' in input_data['Type']:
854                     if os.path.exists(rdir + "/output.log"):
855                         cherrypy.log("Found output.log file")
856                         f = open(rdir + '/output.log', 'r')
857                         output_log = f.readline()
858                         f.close()
859                         return output_log
860                 elif 'GetOutputLog' in input_data['Type']:
861                     if os.path.exists(rdir + "/output.log"):
862                         cherrypy.log("Found output.log file")
863                         f = open(rdir + '/output.log', 'r')
864                         output_log = f.readline()
865                         f.close()
866                         return output_log
867                 elif 'GetResult' in input_data['Type']:
868                     if os.path.exists(rdir + "/PlaybookName.txt"):
869                         cherrypy.log("Found PlaybookName.txt file")
870                         f = open(rdir + '/PlaybookName.txt', 'r')
871                         playbooknametxt = f.readline()
872                         f.close()
873                     else:
874                         playbooknametxt = "NA"
875
876                     # Add code to get other items not just output.log from files
877                     if os.path.exists(rdir + "/log.file"):
878                         cherrypy.log("Found log.file")
879                         out_results = "NA:"
880
881                         f = open(rdir + '/log.file', 'r')
882                         line = f.readline()
883                         while line:
884                             if "fatal" in line:
885                                 out_results = out_results + line
886                             elif "RECAP" in line:
887                                 out_results = out_results + line
888                                 recap_line = f.readline()
889                                 while recap_line:
890                                     out_results = out_results + recap_line
891                                     recap_line = f.readline()
892                             line = f.readline()
893                         f.close()
894                         out_obj = {
895                             "StatusCode": 200,
896                             "StatusMessage": "FINISHED",
897                             "PlaybookName": playbooknametxt,
898                             "Version": "Version",
899                             "Duration": 200,
900                             "Results": out_results
901                         }
902                         return out_obj
903                 else:
904                     return {"StatusCode": 500, "StatusMessage": "PLAYBOOK FAILED "}
905
906             return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
907     else:
908         return {"StatusCode": 500, "StatusMessage": "MALFORMED REQUEST"}
909
910
911 def handle_delete_method(input_data):
912     cherrypy.log("***> in RestServer.DELETE")
913     cherrypy.log("Payload: " + str(input_data))
914
915     if input_data['Id'] in TestRecord:
916         if 'PENDING' not in TestRecord[input_data['Id']]['Result']:
917             cherrypy.log(" Path: " + str(TestRecord[input_data['Id']]['Path']))
918             TestRecord.pop(input_data['Id'])
919             if input_data['Id'] in ActiveProcess:
920                 ActiveProcess.pop(input_data['Id'])
921             return {"StatusCode": 200, "StatusMessage": "PLAYBOOK EXECUTION RECORDS DELETED"}
922         else:
923             return {"StatusCode": 200, "StatusMessage": "PENDING"}
924     else:
925         return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
926
927
928 class TestManager(object):
929     @cherrypy.expose
930     @cherrypy.tools.json_out()
931     @cherrypy.tools.json_in()
932     @cherrypy.tools.allow(methods=['POST', 'GET', 'DELETE'])
933     def Dispatch(self, **kwargs):
934         # Let cherrypy error handler deal with malformed requests
935         # No need for explicit error handler, we use default ones
936
937         time_now = datetime.datetime.utcnow()
938
939         # Erase old test results (2x timeout)
940         # Do cleanup too of ActiveProcess list and old Records - PAP
941         if TestRecord:
942             for key in TestRecord.copy():
943                 cherrypy.log("LOOKING AT ALL TestRecords: " + str(key))
944                 if key in ActiveProcess:
945                     if not ActiveProcess[key].is_alive():  # Just to cleanup defunct processes
946                         cherrypy.log("Not ActiveProcess for ID: " + str(key))
947                 delta_time = (time_now - TestRecord[key]['Time']).seconds
948                 if delta_time > 2 * TestRecord[key]['Timeout']:
949                     cherrypy.log("DELETED HISTORY for ID: " + str(key))
950                     if key in ActiveProcess:
951                         if not ActiveProcess[key].is_alive():
952                             ActiveProcess.pop(key)
953                             cherrypy.log("DELETED ActiveProcess for ID: " + str(key))
954                     # if os.path.exists(TestRecord[key]['Path']):
955                     # don't remove run dirrectory
956                     # shutil.rmtree (TestRecord[key]['Path'])
957                     del TestRecord[key]
958
959         cherrypy.log("RestServer.Dispatch: " + cherrypy.request.method)
960
961         if 'POST' in cherrypy.request.method:
962             input_json = cherrypy.request.json
963             return handle_post_method(input_json, time_now)
964         elif 'GET' in cherrypy.request.method:
965             # Lets pause for a second just in case the request was just kicked off
966             time.sleep(1)
967
968             input_data = parse_query_string(cherrypy.request.query_string)
969             return handle_get_method(input_data)
970         elif 'DELETE' in cherrypy.request.method:
971             input_data = parse_query_string(cherrypy.request.query_string)
972             return handle_delete_method(input_data)
973
974
975 if __name__ == '__main__':
976
977     # Read configuration
978
979     config_file_path = "RestServer_config"
980
981     if not os.path.exists(config_file_path):
982         cherrypy.log('[INFO] The config file does not exist')
983         sys.exit(0)
984
985     ip = 'na'
986     AnsibleServer = 'na'
987     port = 'na'
988     tls = False
989     AUTH = False
990     pub = 'na'
991     priv = 'na'
992     intermediate = 'na'
993     timeout_seconds = 'na'
994     ANSIBLE_PATH = 'na'
995     ANSIBLE_TEMP = 'na'
996     host = 'na'
997     users = 'na'
998     getresults_block = False
999     from_files = False
1000
1001     config_file = open(config_file_path, 'r')
1002     for config_line in config_file.readlines():
1003         if '#' not in config_line:
1004             if 'ip:' in config_line:
1005                 ip = config_line.split(':')[1].strip()
1006             elif 'AnsibleServer:' in config_line:
1007                 AnsibleServer = config_line.split(':')[1].strip()
1008             elif 'port:' in config_line:
1009                 port = config_line.split(':')[1].strip()
1010             elif 'ksalt:' in config_line:
1011                 salt = config_line.split(':')[1].strip()
1012             elif 'tls:' in config_line:
1013                 tls = 'YES' in config_line.split(':')[1].strip().upper()
1014             elif 'auth:' in config_line:
1015                 AUTH = 'YES' in config_line.split(':')[1].strip().upper()
1016             if tls and 'priv:' in config_line:
1017                 priv = config_line.split(':')[1].strip()
1018             if tls and 'pub:' in config_line:
1019                 pub = config_line.split(':')[1].strip()
1020             if tls and 'inter_cert:' in config_line:
1021                 intermediate = config_line.split(':')[1].strip()
1022             if 'timeout_seconds' in config_line:
1023                 timeout_seconds = int(config_line.split(':')[1].strip())
1024             if 'ansible_path' in config_line:
1025                 ANSIBLE_PATH = config_line.split(':')[1].strip()
1026             if 'ansible_inv' in config_line:
1027                 ANSIBLE_INV = config_line.split(':')[1].strip()
1028                 if not os.path.exists(ANSIBLE_PATH + "/" + ANSIBLE_INV):
1029                     print '[INFO] The ansible_inv file does not exist'
1030                     sys.exit(0)
1031             if 'ansible_temp' in config_line:
1032                 ANSIBLE_TEMP = config_line.split(':')[1].strip()
1033             if 'host' in config_line:
1034                 host = config_line.split(':')[1].strip()
1035             if 'users' in config_line:
1036                 users = config_line.split(':')[1].strip()
1037             if 'getresults_block' in config_line:
1038                 getresults_block = 'YES' in config_line.split(':')[1].strip().upper()
1039             if 'from_files' in config_line:
1040                 from_files = 'YES' in config_line.split(':')[1].strip().upper()
1041     config_file.close()
1042
1043     # Initialization
1044
1045     global_conf = {
1046         'global': {
1047             'log.screen': True,
1048             'response.timeout': 5400,
1049             'server.socket_host': ip,
1050             'server.socket_port': int(port),
1051             'server.protocol_version': 'HTTP/1.1'
1052         }
1053     }
1054
1055     if tls:
1056         # Use pythons built-in SSL
1057         cherrypy.server.ssl_module = 'builtin'
1058
1059         # Point to certificate files
1060
1061         if not os.path.exists(pub):
1062             cherrypy.log('[INFO] The public certificate does not exist')
1063             sys.exit(0)
1064
1065         if not os.path.exists(priv):
1066             cherrypy.log('[INFO] The private key does not exist')
1067             sys.exit(0)
1068
1069         if not os.path.exists(intermediate):
1070             cherrypy.log('[INFO] The intermediate certificate does not exist')
1071             sys.exit(0)
1072
1073         cherrypy.server.ssl_certificate = pub
1074         cherrypy.server.ssl_certificate_chain = intermediate
1075         cherrypy.server.ssl_private_key = priv
1076
1077     if AUTH:
1078         # Read in and build user dictionary
1079         if not os.path.exists(users):
1080             cherrypy.log('[INFO] The users file does not exist: ' + users)
1081             sys.exit(0)
1082         userpassdict = {}
1083         user_file = open(users, 'r')
1084         for config_line in user_file.readlines():
1085             if '#' not in config_line:
1086                 uid = config_line.split(':')[0].strip()
1087                 pw = config_line.split(':')[1].strip()
1088                 userpassdict[uid] = pw
1089
1090         app_conf = {
1091             '/':
1092                 {'tools.auth_basic.on': True,
1093                  'tools.auth_basic.realm': 'earth',
1094                  'tools.auth_basic.checkpassword': validate_password
1095                  }
1096         }
1097
1098         application = cherrypy.tree.mount(TestManager(), '/', app_conf)
1099     else:
1100         application = cherrypy.tree.mount(TestManager(), '/')
1101
1102     cherrypy.config.update({
1103         'log.access_file': "/var/log/RestServer.access"
1104     })
1105     accessLogName = "/var/log/RestServer.access"
1106     applicationLogName = "/var/log/RestServer.log"
1107     cherrypy.config.update(global_conf)
1108
1109     log = application.log
1110     log.error_file = ""
1111     log.access_file = ""
1112     from logging import handlers
1113
1114     applicationLogFileHandler = handlers.RotatingFileHandler(applicationLogName, 'a', 1000000, 5000)
1115     accessLogFileHandler = handlers.RotatingFileHandler(accessLogName, 'a', 1000000, 5000)
1116     import logging
1117
1118     applicationLogFileHandler.setLevel(logging.DEBUG)
1119     log.error_log.addHandler(applicationLogFileHandler)
1120     log.access_log.addHandler(accessLogFileHandler)
1121
1122     # Start server
1123
1124     cherrypy.engine.start()
1125     cherrypy.engine.block()