65f7398677e53e96781542ae79ea80b7016d65ad
[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, re
27 import uuid
28 import tarfile
29 import shutil
30 import glob
31 import crypt
32
33 import requests
34
35 import cherrypy
36 from cherrypy.lib.httputil import parse_query_string
37 from cherrypy.lib import auth_basic
38
39 from multiprocessing import Process, Manager
40
41 from AnsibleModule import ansibleSysCall
42 from BuildHostFile import buildHostsSysCall
43
44 from os import listdir
45 from os.path import isfile, join
46
47 TestRecord = Manager().dict()
48 ActiveProcess = {}
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 def sys_call (cmd):
57     p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
58     output = p.stdout.readlines()
59     retval = p.wait()
60     if len (output) > 0:
61         for i in range(len(output)):
62             output[i] = output[i].strip()
63     return retval, output
64
65 def callback (Id, Result, Output, Log, returncode):
66
67     print "***> in RestServer.callback"
68
69     if Id in TestRecord:
70         time_now = datetime.datetime.utcnow()
71         delta_time = (time_now - TestRecord[Id]['Time']).total_seconds()
72         Result['PlaybookName'] = TestRecord[Id]['PlaybookName']
73         Result['Version'] = TestRecord[Id]['Version']
74         if returncode == 137:
75             Result['StatusCode'] = 500
76             Result['StatusMessage'] = "TERMINATED"
77         else:
78             Result['StatusCode'] = 200
79             Result['StatusMessage'] = "FINISHED"
80
81         # Need to update the whole data structure for key=Id otherwise Manager is not updated
82         TestRecord[Id] = {'PlaybookName': TestRecord[Id]['PlaybookName'],
83                           'Version': TestRecord[Id]['Version'],
84                           'NodeList': TestRecord[Id]['NodeList'],
85                           'HostGroupList': TestRecord[Id]['HostGroupList'],
86                           'HostNameList': TestRecord[Id]['HostNameList'],
87                           'Time': TestRecord[Id]['Time'],
88                           'Timeout': TestRecord[Id]['Timeout'],
89                           'Duration': str(delta_time),
90                           'EnvParameters': TestRecord[Id]['EnvParameters'],
91                           'LocalParameters': TestRecord[Id]['LocalParameters'],
92                           'FileParameters': TestRecord[Id]['FileParameters'],
93                           'CallBack': TestRecord[Id]['CallBack'],
94                           'Result': Result,
95                           'Log': Log,
96                           'Output': Output,
97                           'Path': TestRecord[Id]['Path'],
98                           'Mandatory': TestRecord[Id]['Path']}
99
100         if not TestRecord[Id]['CallBack'] == None:
101
102             # Posting results to callback server
103
104             data = {"StatusCode": 200,
105                     "StatusMessage": "FINISHED",
106                     "PlaybookName": TestRecord[Id]["PlaybookName"],
107                     "Version": TestRecord[Id]["Version"],
108                     "Duration": TestRecord[Id]["Duration"],
109                     "Results": TestRecord[Id]['Result']['Results']}
110
111             cherrypy.log("CALLBACK: TestRecord[Id]['Output']['Output']:", str(TestRecord[Id]['Output']['Output']))
112             cherrypy.log("CALLBACK: Results:", str(data["Results"]))
113
114             if not TestRecord[Id]['Output']['Output'] == {}:
115                 for key in data["Results"]:
116                     if key in TestRecord[Id]['Output']['Output']:
117                         data["Results"][key]["Output"] = TestRecord[Id]['Output']['Output'][key]
118
119             print "     Posting to", TestRecord[Id]['CallBack']
120
121             s = requests.Session()
122             r = s.post(TestRecord[Id]['CallBack'], data = json.dumps(data),
123                        headers = {'content-type': 'application/json'})
124             print  "     Response", r.status_code, r.text
125
126 def RunAnsible_Playbook (callback, Id, Inventory, Playbook, NodeList, TestRecord,
127                          Path, ArchiveFlag):
128
129     print "***> in RestServer.RunAnsible_Playbook"
130
131     # Run test in playbook for given target
132     Result = ''
133
134     retval, log, returncode = ansibleSysCall (Inventory, Playbook, NodeList,
135                                               TestRecord[Id]['Mandatory'],
136                                               TestRecord[Id]['EnvParameters'],
137                                               TestRecord[Id]['LocalParameters'],
138                                               TestRecord[Id]['Timeout'],
139                                               Path)
140
141
142     cherrypy.log("Return code:" + str(returncode))
143     cherrypy.log("Return val:" +  str(retval))
144
145     Log = ''.join(log)
146     #Output = {'Output': {}}
147     Output = {}
148
149     onlyfiles = [f for f in listdir(Path)
150                  if isfile(join(Path, f))]
151
152     cherrypy.log("Checking for results.txt files: ")
153     for file in onlyfiles:
154         if "results.txt" in file:
155 #       if file.endswith("results.txt"):
156             cherrypy.log("results file: " + file)
157             f = open(Path + "/" + file, "r")
158             resultsData =  f.read()  # Not to pass vnf instance name
159             OutputP = json.loads(resultsData)
160             Output['Output'] =  OutputP
161             cherrypy.log("Output = " + str(Output['Output']))
162             #Output['Output'][key] = f.read() # To pass vnf instance name
163             f.close()
164
165     if Output == {}:
166       Output = {'Output': {}}
167
168     Result = {'Results': {}}
169     if 'could not be found' in Log:
170         Result['Results'] = {"StatusCode": 101,
171                              "StatusMessage": "PLAYBOOK NOT FOUND"}
172     if returncode == 137:
173         Result['Results'] = {"StatusCode": 500,
174                              "StatusMessage": "TERMINATED"}
175
176     elif TestRecord[Id]['NodeList'] == []:
177
178         host_index = None
179
180         if 'TargetNode' in TestRecord[Id]['EnvParameters']:
181             targetlist = TestRecord[Id]['EnvParameters']['TargetNode'].split(' ')
182         else:
183             targetlist = ["localhost"]
184
185         for key in retval:
186             for i in range (len(targetlist)):
187                 if key in targetlist[i]:
188                     host_index = i
189
190             if int(retval[key][0]) > 0 and int(retval[key][2]) == 0 and \
191                    int(retval[key][3]) == 0:
192
193                 if host_index:
194                     Result['Results'][targetlist[host_index]] = \
195                              {"GroupName": 'na', "StatusCode": 200, \
196                               "StatusMessage": "SUCCESS"}
197                 else:
198                     Result['Results'][key] = \
199                              {"GroupName": 'na', "StatusCode": 200, \
200                               "StatusMessage": "SUCCESS"}
201             elif int(retval[key][2]) > 0:
202                 if host_index:
203                     Result['Results'][targetlist[host_index]] = \
204                        {"GroupName": 'na', "StatusCode": 400, \
205                         "StatusMessage": "NOT REACHABLE"}
206                 else:
207                     Result['Results'][key] = \
208                        {"GroupName": 'na', "StatusCode": 400, \
209                         "StatusMessage": "NOT REACHABLE"}
210             elif int(retval[key][3]) > 0:
211                 if host_index:
212                     Result['Results'][targetlist[host_index]] = \
213                        {"GroupName": 'na', "StatusCode": 400, \
214                         "StatusMessage": "FAILURE"}
215                 else:
216                     Result['Results'][key] = \
217                        {"GroupName": 'na', "StatusCode": 400, \
218                         "StatusMessage": "FAILURE"}
219     else:
220
221         for key in retval:
222
223             if len(TestRecord[Id]['HostNameList']) > 0:
224
225                 host_index = []
226                 for i in range (len(TestRecord[Id]['HostNameList'])):
227                     if key in TestRecord[Id]['HostNameList'][i]:
228                         host_index.append(i)
229
230                 if int(retval[key][0]) > 0 and int(retval[key][2]) == 0 and \
231                        int(retval[key][3]) == 0:
232
233                     if len(host_index) > 0:
234                         Result['Results'][TestRecord[Id]['HostNameList'][host_index[0]]] = \
235                           {"GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
236                            "StatusCode": 200, "StatusMessage": "SUCCESS"}
237
238                         for i in range (1, len(host_index)):
239                             Result['Results'][TestRecord[Id]['HostNameList'][host_index[i]]]["GroupName"]+=\
240                              "," + TestRecord[Id]['HostGroupList'][host_index[i]]
241                     else:
242                        Result['Results'][key] = \
243                           {"GroupName": key,
244                            "StatusCode": 200, "StatusMessage": "SUCCESS"}
245
246                 elif int(retval[key][2]) > 0:
247
248                     if len(host_index) > 0:
249                         Result['Results'][TestRecord[Id]['HostNameList'][host_index[0]]] = \
250                           {"GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
251                            "StatusCode": 400, "StatusMessage": "NOT REACHABLE"}
252
253                         for i in range (1, len(host_index)):
254                             Result['Results'][TestRecord[Id]['HostNameList'][host_index[i]]]["GroupName"]+=\
255                              "," + TestRecord[Id]['HostGroupList'][host_index[i]]
256                     else:
257                        Result['Results'][key] = \
258                           {"GroupName": key,
259                            "StatusCode": 200, "StatusMessage": "NOT REACHABLE"}
260
261                 elif int(retval[key][3]) > 0:
262
263                     if len(host_index) > 0:
264                         Result['Results'][TestRecord[Id]['HostNameList'][host_index[0]]] = \
265                           {"GroupName": TestRecord[Id]['HostGroupList'][host_index[0]],
266                            "StatusCode": 400, "StatusMessage": "FAILURE"}
267
268                         for i in range (1, len(host_index)):
269                             Result['Results'][TestRecord[Id]['HostNameList'][host_index[i]]]["GroupName"]+=\
270                              "," + TestRecord[Id]['HostGroupList'][host_index[i]]
271                     else:
272                        Result['Results'][key] = \
273                           {"GroupName": key,
274                            "StatusCode": 200, "StatusMessage": "FAILURE"}
275             else:
276                 host_index = None
277                 for i in range (len(TestRecord[Id]['NodeList'])):
278                     if key in TestRecord[Id]['NodeList'][i]:
279                         host_index = i
280
281                 if int(retval[key][0]) > 0 and int(retval[key][2]) == 0 and \
282                        int(retval[key][3]) == 0:
283                     Result['Results'][TestRecord[Id]['NodeList'][host_index]] = \
284                              {"GroupName": 'na', "StatusCode": 200, \
285                              "StatusMessage": "SUCCESS"}
286                 elif int(retval[key][2]) > 0:
287                     Result['Results'][TestRecord[Id]['NodeList'][host_index]] = \
288                        {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "NOT REACHABLE"}
289                 elif int(retval[key][3]) > 0:
290                     Result['Results'][TestRecord[Id]['NodeList'][host_index]] = \
291                        {"GroupName": 'na', "StatusCode": 400, "StatusMessage": "FAILURE"}
292
293     callback (Id, Result, Output, Log, returncode)
294
295 class TestManager (object):
296
297     @cherrypy.expose
298     @cherrypy.tools.json_out()
299     @cherrypy.tools.json_in()
300     @cherrypy.tools.allow(methods=['POST', 'GET', 'DELETE'])
301
302     def Dispatch(self, **kwargs):
303
304         # Let cherrypy error handler deal with malformed requests
305         # No need for explicit error handler, we use default ones
306
307         time_now = datetime.datetime.utcnow()
308
309         # Erase old test results (2x timeout)
310         # Do cleanup too of ActiveProcess list and old Records - PAP
311         if TestRecord:
312             for key in TestRecord.copy():
313                 cherrypy.log( "LOOKING AT ALL TestRecords: " + str(key))
314                 if key in ActiveProcess:
315                    if not ActiveProcess[key].is_alive(): # Just to cleanup defunct processes
316                       cherrypy.log( "Not ActiveProcess for ID: " + str(key))
317                 delta_time = (time_now - TestRecord[key]['Time']).seconds
318                 if delta_time > 2*TestRecord[key]['Timeout']:
319                     cherrypy.log( "DELETED HISTORY for ID: " + str(key))
320                     if key in ActiveProcess:
321                       if not ActiveProcess[key].is_alive():
322                           ActiveProcess.pop (key)
323                           cherrypy.log( "DELETED ActiveProcess for ID: " + str(key))
324                     #if os.path.exists(TestRecord[key]['Path']):
325                         # don't remove run dirrectory
326                         #shutil.rmtree (TestRecord[key]['Path'])
327                     del TestRecord[key]
328
329         cherrypy.log("RestServer.Dispatch: " + cherrypy.request.method)
330
331
332         if 'POST' in cherrypy.request.method:
333
334             input_json = cherrypy.request.json
335             cherrypy.log("Payload: " + str(input_json))
336
337             if 'Id' in input_json and 'PlaybookName' in input_json and 'EnvParameters' in input_json:
338
339                 if True:
340
341                     if not input_json['Id'] in TestRecord:
342                         # check if Id exists in previous run dirctory
343                         # if so retun error
344                         s_cmd = 'ls ' + ansible_temp + '/*_' + input_json['Id']
345                         #if subprocess.check_output([s_cmd, ]):
346                         Id = input_json['Id']
347                         if glob.glob(  ansible_temp + '/*_' + input_json['Id']):
348                             cherrypy.log("Old directory found for ID: " + Id)
349                             return {"StatusCode": 101, "StatusMessage": "TEST ID FILE ALREADY DEFINED"}
350
351                         PlaybookName = input_json['PlaybookName']
352                         # if required it should be passed as an argument
353                         EnvParameters = input_json['EnvParameters']
354
355                         # The lines below are to test multiple EnvParameters being passed
356                         #for i in EnvParameters:
357                         #  cherrypy.log("EnvParameter object: " + i)
358                         #  cherrypy.log("  EnvParameter Value: " + EnvParameters[ i ])
359
360                         # Now get things out of EnvParameters
361                         VNF_instance   = None
362                         VNF_instance = EnvParameters.get('vnf_instance')
363
364                         # Get Version if present
365                         version = None
366                         if 'Version' in input_json:
367                             version = input_json['Version']
368
369                         # GetInventoryNames
370                         HaveNodeList         = False
371                         HaveInventoryNames   = False
372                         inventory_names = None
373                         if 'InventoryNames' in input_json:
374                            inventory_names = input_json['InventoryNames']
375                            HaveInventoryNames   = True
376
377                         #AnsibleInvFail = True
378                         AnsiblePlaybookFail = True
379
380                         LocalNodeList = None
381
382                         str_uuid = str (uuid.uuid4())
383
384
385                         VnfType= PlaybookName.split("/")[0]
386                         if auth:
387                            cherrypy.log( "Request USER  :                  " + cherrypy.request.login)
388                         cherrypy.log( "Request Decode: ID               " + Id)
389                         cherrypy.log( "Request Decode: VnfType          " + VnfType)
390                         cherrypy.log( "Request Decode: EnvParameters    " + json.dumps(EnvParameters))
391
392                         # Verify VNF_instance was passed in EnvParameters
393                         if VNF_instance != None:
394                           cherrypy.log( "Request Decode: VnfInstance      " + VNF_instance)
395                         else:
396                           cherrypy.log( "StatusCode: 107, StatusMessage: VNF_instance NOT PROVIDED" )
397                           return {"StatusCode": 107,
398                                     "StatusMessage": "VNF_instance NOT PROVIDED"}
399
400                         if inventory_names != None:
401                           cherrypy.log( "Request Decode: Inventory Names  " + inventory_names)
402                         else:
403                           cherrypy.log( "Request Decode: Inventory Names  " + "Not provided")
404
405                         cherrypy.log( "Request Decode: PlaybookName     " + PlaybookName)
406                         PlayBookFunction = PlaybookName.rsplit("/",2)[1]
407                         PlayBookFile = PlayBookFunction + "/site.yml"
408                         cherrypy.log( "Request Decode: PlaybookFunction " + PlayBookFunction)
409                         cherrypy.log( "Request Decode: Playbook file    " + PlayBookFile)
410
411                         BaseDir = ansible_path + "/" + PlaybookName.rsplit("/",1)[0]
412                         CopyDir = ansible_path + "/" + PlaybookName.rsplit("/",2)[0]
413                         cherrypy.log( "Request Decode: Basedir          " + BaseDir)
414                         cherrypy.log( "Request Decode: Copydir          " + CopyDir)
415
416
417                         PlaybookDir = ansible_temp + "/" + \
418                                       VNF_instance + "_" + str_uuid + "_" + str(Id)
419
420                         # AnsibleInv is the directory where the host file to be run exsists
421                         AnsibleInv = ansible_path + "/" + VnfType + "/latest/ansible/inventory/" + VNF_instance
422                         ArchiveFlag = False
423
424                         # Create base run directory if it doesn't exist
425                         if not os.path.exists(ansible_temp):
426                             cherrypy.log( "Creating Base Run Directory: "  + ansible_temp)
427                             os.makedirs(ansible_temp)
428
429                         if not os.path.exists( CopyDir ):
430                             cherrypy.log("Playbook Not Found")
431                             return {"StatusCode": 101,
432                                     "StatusMessage": "PLAYBOOK NOT FOUND"}
433
434                         # copy static playbook dir to run dir
435                         cherrypy.log("Copying from " + CopyDir + " to " + PlaybookDir)
436                         shutil.copytree(CopyDir, PlaybookDir)
437                         cmd="/usr/bin/find " + PlaybookDir + " -exec /usr/bin/touch {} \;"
438                         cmd="/usr/bin/find " + PlaybookDir + " -exec chmod +rx  {} \;"
439                         sys_call(cmd)
440                         cherrypy.log(cmd)
441
442                         cherrypy.log( "PlaybookDir:    " + PlaybookDir)
443                         cherrypy.log( "AnsibleInv:     " + AnsibleInv)
444
445                         # Process inventory file for target
446
447                         hostgrouplist = []
448                         hostnamelist = []
449
450                         NodeList = []
451                         if 'NodeList' in input_json:
452                             NodeList = input_json['NodeList']
453
454                         cherrypy.log("NodeList: " + str(NodeList));
455
456                         # if NodeList empty
457                         if NodeList == []:
458                                 cherrypy.log( "*** NodeList - Empty ***")
459
460                         else:
461                                 HaveNodeList = True
462
463                         ###############################################################################
464                         ##### Host file processing                          ###########################
465                         ##### 1. Use file delivered with playbook           ###########################
466                         ##### 2. If HostNames + NodeList generate and use   ###########################
467                         ###############################################################################
468
469                         #Verify inventory directory exists
470                         path = PlaybookDir + "/inventory/"
471                         if not os.path.isdir(path):
472                             cherrypy.log ("Inventory directory %s does not exist - create it" % path)
473                             try:
474                                 os.mkdir(path)
475                             except OSError:
476                                 cherrypy.log ("Creation of the directory %s failed" % path)
477                             else:
478                                 cherrypy.log ("Successfully created the directory %s " % path)
479
480                         #location of host file - Default
481                         HostFile = PlaybookDir + "/inventory/" + VNF_instance + "hosts"
482                         cherrypy.log("HostFile: " +  HostFile)
483
484                         # if NodeList and InventoryNames need to build host file
485                         if HaveInventoryNames & HaveNodeList:
486                            cherrypy.log("Build host file from NodeList")
487                            ret = buildHostsSysCall (input_json, PlaybookDir, inventory_names)
488                            if (ret < 0):
489                               cherrypy.log("Returning Error: Not running Playbook")
490                               return {"StatusCode": 105,
491                                     "StatusMessage": "NodeList: Missing vnfc-type field"}
492
493                            # Having been built now copy new file to correct file
494                            shutil.copy(PlaybookDir + "/host_file.txt", HostFile)
495                            cherrypy.log("Copying Generated host file to: " + HostFile)
496
497                         timeout = timeout_seconds
498                         if 'Timeout' in input_json:
499                             timeout = int (input_json['Timeout'])
500                             cherrypy.log("Timeout from API: " + str(timeout))
501
502                         else:
503                             cherrypy.log("Timeout not passed from API using default: " + str(timeout))
504
505                         EnvParam = {}
506                         if 'EnvParameters' in input_json:
507                             EnvParam = input_json['EnvParameters']
508
509                         LocalParam = {}
510                         if 'LocalParameters' in input_json:
511                             LocalParam = input_json['LocalParameters']
512
513                         FileParam = {}
514                         if 'FileParameters' in input_json:
515                             FileParam = input_json['FileParameters']
516
517                         callback_flag = None
518                         if 'CallBack' in input_json:
519                             callback_flag = input_json['CallBack']
520
521                         # if AnsibleServer is not set to 'na'  don't send AnsibleServer in PENDING responce.
522                         if AnsibleServer != 'na':
523                                 TestRecord[Id] = {'PlaybookName': PlaybookName,
524                                           'Version': version,
525                                           'NodeList': NodeList,
526                                           'HostGroupList': hostgrouplist,
527                                           'HostNameList': hostnamelist,
528                                           'Time': time_now,
529                                           'Duration': timeout,
530                                           'Timeout': timeout,
531                                           'EnvParameters': EnvParam,
532                                           'LocalParameters': LocalParam,
533                                           'FileParameters': FileParam,
534                                           'CallBack': callback_flag,
535                                           'Result': {"StatusCode": 100,
536                                                      "StatusMessage": 'PENDING',
537                                                      "AnsibleServer": str(AnsibleServer),
538                                                      "ExpectedDuration": str(timeout) + "sec"},
539                                           'Log': '',
540                                           'Output': {},
541                                           'Path': PlaybookDir,
542                                           'Mandatory': None}
543                         else:
544                                 TestRecord[Id] = {'PlaybookName': PlaybookName,
545                                           'Version': version,
546                                           'NodeList': NodeList,
547                                           'HostGroupList': hostgrouplist,
548                                           'HostNameList': hostnamelist,
549                                           'Time': time_now,
550                                           'Duration': timeout,
551                                           'Timeout': timeout,
552                                           'EnvParameters': EnvParam,
553                                           'LocalParameters': LocalParam,
554                                           'FileParameters': FileParam,
555                                           'CallBack': callback_flag,
556                                           'Result': {"StatusCode": 100,
557                                                      "StatusMessage": 'PENDING',
558                                                      "ExpectedDuration": str(timeout) + "sec"},
559                                           'Log': '',
560                                           'Output': {},
561                                           'Path': PlaybookDir,
562                                           'Mandatory': None}
563
564                         cherrypy.log("Test_Record: " +  str(TestRecord[Id]))
565                         # Write files
566
567                         if not TestRecord[Id]['FileParameters'] == {}:
568                             for key in TestRecord[Id]['FileParameters']:
569                                 filename = key
570                                 filecontent = TestRecord[Id]['FileParameters'][key]
571                                 f = open(PlaybookDir + "/" + filename, "w")
572                                 f.write(filecontent)
573                                 f.close()
574
575
576                         # Process playbook
577                         if os.path.exists( ansible_path + '/' + PlaybookName):
578                             AnsiblePlaybookFail = False
579
580                         if AnsiblePlaybookFail:
581                             #if os.path.exists(PlaybookDir):
582                                 #shutil.rmtree (PlaybookDir)
583                             del TestRecord[Id]
584                             return {"StatusCode": 101,
585                                     "StatusMessage": "PLAYBOOK NOT FOUND"}
586                         else:
587
588                             # Test EnvParameters
589                             playbook_path = PlaybookDir
590
591                             # Store local vars
592                             if not os.path.exists(playbook_path + "/vars"):
593                                 os.mkdir(playbook_path + "/vars")
594                             if not os.path.isfile(playbook_path + "/vars/defaults.yml"):
595                                 os.mknod(playbook_path + "/vars/defaults.yml")
596
597                             ###################################################
598                             # PAP
599                             #write local parameters passed into defaults.yml
600                             # PAP
601                             f = open(playbook_path + "/vars/defaults.yml","a")
602                             #for id, record in TestRecord.items():
603                             print TestRecord[Id]['LocalParameters']
604                             local_parms = TestRecord[Id]['LocalParameters']
605                             for key, value in local_parms.items():
606                                 f.write(key +"=" + value + "\n");
607                             f.close()
608                             ###################################################
609
610                             for key in TestRecord[Id]['LocalParameters']:
611                                 host_index = []
612                                 for i in range(len(TestRecord[Id]['HostNameList'])):
613                                     if key in TestRecord[Id]['HostNameList'][i]:
614                                         host_index.append(i)
615                                 if len(host_index) == 0:
616                                     for i in range(len(TestRecord[Id]['HostGroupList'])):
617                                         if key in TestRecord[Id]['HostGroupList'][i]:
618                                             host_index.append(i)
619                                 if len(host_index) > 0:
620                                     for i in range(len(host_index)):
621                                         f = open(playbook_path + "/vars/" +
622                                                  TestRecord[Id]['HostNameList'][host_index[i]] +
623                                                  ".yml", "a")
624                                         for param in TestRecord[Id]['LocalParameters'][key]:
625                                             f.write(param + ": " +
626                                              str (TestRecord[Id]['LocalParameters'][key][param]) +
627                                                   "\n")
628                                         f.close()
629
630
631                             # write some info out to files before running
632                             if auth:
633                                f = open(playbook_path + "/User.txt", "a")
634                                f.write(cherrypy.request.login)
635                                f.close()
636                             f = open(playbook_path + "/PlaybookName.txt", "a")
637                             f.write(PlaybookName)
638                             f.close()
639                             f = open(playbook_path + "/PlaybookExDir.txt", "a")
640                             f.write(PlaybookDir + "/" + PlayBookFunction)
641                             f.close()
642                             f = open(playbook_path + "/JsonRequest.txt", "w")
643                             #f.write(str(input_json))
644                             #print( json.dumps(input_json, indent=4, sort_keys=True))
645                             f.write( json.dumps(input_json, indent=4, sort_keys=True))
646                             f.close()
647
648
649                             # Check that HostFile exists
650                             if not os.path.isfile(HostFile):
651                               cherrypy.log("Inventory file Not Found: " + HostFile)
652                               return {"StatusCode": 101,
653                                    "StatusMessage": "PLAYBOOK INVENTORY FILE NOT FOUND"}
654
655                             # Cannot use thread because ansible module uses
656                             # signals which are only supported in main thread.
657                             # So use multiprocess with shared object
658                                        # args = (callback, Id,  PlaybookDir + "/" + AnsibleInv,
659
660                             p = Process(target = RunAnsible_Playbook,
661                                         args = (callback, Id,  HostFile,
662                                                 PlaybookDir + '/' + PlayBookFile,
663                                                 NodeList, TestRecord, PlaybookDir + "/" + PlayBookFunction,
664                                                 ArchiveFlag))
665                             p.start()
666                             ActiveProcess[Id] = p
667                             return TestRecord[Id]['Result']
668                     else:
669                         cherrypy.log("TEST ID ALREADY DEFINED")
670                         return {"StatusCode": 101, "StatusMessage": "TEST ID ALREADY DEFINED"}
671
672                 else:
673                     return {"StatusCode": 500, "StatusMessage": "REQUEST MUST INCLUDE: NODELIST"}
674
675             else:
676                 return {"StatusCode": 500, "StatusMessage": "JSON OBJECT MUST INCLUDE: ID, PLAYBOOKNAME, EnvParameters"}
677
678         elif 'GET' in cherrypy.request.method:
679
680             # Lets pause for a second just incase the resquest was just kicked off
681             time.sleep(1)
682
683             input_data = parse_query_string(cherrypy.request.query_string)
684
685             # Verify we have a Type passed in GET request
686             if not ( 'Type' in input_data):
687                 return {"StatusCode": 500, "StatusMessage": "RESULTS TYPE UNDEFINED"}
688
689             if auth:
690                cherrypy.log( "Request USER:             " + cherrypy.request.login)
691             cherrypy.log("Payload: " + str(input_data) + " Type " + input_data['Type'])
692
693             if 'LogRest' in input_data['Type']:
694                 sys.stdout.close()
695                 sys.stdout = open("/var/log/RestServer.log", "w")
696
697             # Just a debug to dump any records
698             if 'GetStatus' in input_data['Type']:
699                 cherrypy.log( "******** Dump Records **********")
700                 if TestRecord.items():
701                   for id, record in TestRecord.items():
702                     cherrypy.log( "    Id: " + id)
703                     cherrypy.log( "Record: " + str(record))
704                 else:
705                   cherrypy.log(" No Records to dump")
706
707             if 'Id' in input_data and 'Type' in input_data:
708                 if not ('GetResult' in input_data['Type'] or 'GetOutputLog' in input_data['Type'] or'GetTheOutput' in input_data['Type'] or 'GetLog' in input_data['Type']):
709                     return {"StatusCode": 500, "StatusMessage": "RESULTS TYPE UNDEFINED"}
710                 if input_data['Id'] in TestRecord:
711
712                     if 'GetResult' in input_data['Type']:
713
714                         cherrypy.log( " ** GetResult for: " + str (input_data['Id']))
715
716                         if 'StatusMessage' in TestRecord[input_data['Id']]['Result'] and getresults_block:
717
718
719                             #check if playbook is still running
720                             while ActiveProcess[input_data['Id']].is_alive():
721                                 cherrypy.log( "*** Playbook running returning PENDING for " + str(input_data['Id']))
722                                 ##
723                                 ## If still running return PENDING response
724                                 ##
725                                 if AnsibleServer != 'na':
726                                    return {"StatusCode": 100,
727                                                      "StatusMessage": 'PENDING',
728                                                      "AnsibleServer": str(AnsibleServer)}
729                                 else:
730                                    return {"StatusCode": 100,
731                                                      "StatusMessage": 'PENDING'}
732                                 #time.sleep(5)
733
734                             #cherrypy.log( "*** Request released " + input_data['Id'])
735
736                         cherrypy.log(str( TestRecord[input_data['Id']]['Result']))
737                         cherrypy.log("Output: " + str( TestRecord[input_data['Id']]['Output']))
738                         cherrypy.log("StatusCode: " + str( TestRecord[input_data['Id']]['Result']['StatusCode']))
739                         cherrypy.log("StatusMessage: " + str( TestRecord[input_data['Id']]['Result']['StatusMessage']))
740
741                         #out_obj gets returned to GET request
742                         if TestRecord[input_data['Id']]['Result']['StatusCode'] == 500:
743                             out_obj = TestRecord[input_data['Id']]['Result']['Results']
744                         else:
745                             out_obj = {"StatusCode": 200,
746                                    "StatusMessage": "FINISHED",
747                                    "PlaybookName": TestRecord[input_data['Id']]["PlaybookName"],
748                                    "Version": TestRecord[input_data['Id']]["Version"],
749                                    "Duration": TestRecord[input_data['Id']]["Duration"],
750                                    "Output": TestRecord[input_data['Id']]["Output"]["Output"],
751                                    "Results": TestRecord[input_data['Id']]['Result']['Results']}
752                         if not TestRecord[input_data['Id']]['Output']['Output'] == {}:
753                             cherrypy.log("TestRecord has Output:" + str(TestRecord[input_data['Id']]['Output']['Output']))
754                             # PAP
755                             for key in out_obj["Results"]:
756                                 cherrypy.log("Output key: " + str(key))
757                                 if key in TestRecord[input_data['Id']]['Output']['Output']:
758                                     out_obj["Results"][key]["Output"] = TestRecord[input_data['Id']]['Output']['Output'][key]
759
760                         cherrypy.log("***** GET RETURNING RESULTS Back ****")
761                         cherrypy.log(str(out_obj))
762                         return out_obj
763
764                     elif 'GetStatus' in input_data['Type']:
765                         print " Dump Records"
766                         for id, record in TestRecord,items():
767                            print " id: " + id
768                            print "   Record:" + str(reecord)
769
770                     elif 'GetTheOutput' in input_data['Type']:
771
772                         if TestRecord[input_data['Id']]['Output'] == {} and \
773                                getresults_block:
774
775                             cherrypy.log( "*** Request blocked " + input_data['Id'])
776
777                             while TestRecord[input_data['Id']]['Output'] == {} \
778                                       or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
779                                 time.sleep(5)
780
781                             cherrypy.log( "*** Request released " + input_data['Id'])
782
783                         cherrypy.log( "Output: " + str(TestRecord[input_data['Id']]['Output']))
784                         return {"Output": TestRecord[input_data['Id']]['Output']['Output']}
785
786                     elif 'GetOutputLog' in input_data['Type']:
787                        cherrypy.log("GetOutputLog: processing.")
788                        if glob.glob(  ansible_temp + '/*_' + input_data['Id']):
789                           id = input_data['Id']
790                           cherrypy.log("Old directory found for ID: " + id)
791                           run_dir = glob.glob(  ansible_temp + '/*_' + input_data['Id'])
792                           for dir in run_dir:
793                               rdir=dir
794                           if os.path.exists (rdir + "/PlaybookExDir.txt"):
795                                cherrypy.log("Found PlaybookExDir.txt file")
796                                f = open( rdir + '/PlaybookExDir.txt', 'r')
797                                playbookexdir =  f.readline()
798                                rdir = playbookexdir
799                                f.close()
800                           cherrypy.log("Id:     " + id)
801                           cherrypy.log("RunDir: " + rdir)
802                           if os.path.exists( rdir + "/output.log"):
803                                cherrypy.log("Found output.log file")
804                                f = open( rdir + '/output.log', 'r')
805                                output_log =  f.readline()
806                                f.close()
807                                return output_log
808                        else:
809                          cherrypy.log("Globglob failed:")
810                          return
811
812                     else:
813                         # GetLog
814
815                         if TestRecord[input_data['Id']]['Log'] == '' and \
816                                getresults_block:
817
818                             cherrypy.log( "*** Request blocked " + input_data['Id'])
819
820                             while TestRecord[input_data['Id']]['Log'] == '' \
821                                       or 'StatusMessage' in TestRecord[input_data['Id']]['Result']:
822                                 time.sleep(5)
823
824                             cherrypy.log( "*** Request released " + input_data['Id'])
825
826                         cherrypy.log( "Log:" + str(TestRecord[input_data['Id']]['Log']))
827                         return {"Log": TestRecord[input_data['Id']]['Log']}
828                 else:
829                    # Not in memory check for a file
830                    if glob.glob(  ansible_temp + '/*_' + input_data['Id']):
831                        id = input_data['Id']
832                        cherrypy.log("Old directory found for ID: " + id)
833                        run_dir = glob.glob(  ansible_temp + '/*_' + input_data['Id'])
834                        for dir in run_dir:
835                            rdir=dir
836                        if os.path.exists (rdir + "/PlaybookExDir.txt"):
837                             cherrypy.log("Found PlaybookExDir.txt file")
838                             f = open( rdir + '/PlaybookExDir.txt', 'r')
839                             playbookexdir =  f.readline()
840                             rdir = playbookexdir
841                             f.close()
842                        cherrypy.log("Id:     " + id)
843                        cherrypy.log("RunDir: " + rdir)
844                        if 'GetLog' in input_data['Type']:
845                          if os.path.exists( rdir + "/output.log"):
846                             cherrypy.log("Found output.log file")
847                             f = open( rdir + '/output.log', 'r')
848                             output_log =  f.readline()
849                             f.close()
850                             return output_log
851                        elif 'GetOutputLog' in input_data['Type']:
852                             if os.path.exists( rdir + "/output.log"):
853                                cherrypy.log("Found output.log file")
854                                f = open( rdir + '/output.log', 'r')
855                                output_log =  f.readline()
856                                f.close()
857                                return output_log
858                        elif 'GetResult' in input_data['Type']:
859                             if os.path.exists (rdir + "/PlaybookName.txt"):
860                                cherrypy.log("Found PlaybookName.txt file")
861                                f = open( rdir + '/PlaybookName.txt', 'r')
862                                playbooknametxt =  f.readline()
863                                f.close()
864                             else:
865                                playbooknametxt = "NA"
866
867                             # Add code to get other items not just output.log from files
868                             if os.path.exists( rdir + "/log.file"):
869                                cherrypy.log("Found log.file")
870                                out_results = "NA:"
871                                f = open( rdir + '/log.file', 'r')
872
873                                line =  f.readline()
874                                while line :
875                                  if "fatal" in line:
876                                    out_results = out_results +  line
877                                  elif "RECAP" in line:
878                                    out_results = out_results +  line
879                                    recap_line =  f.readline()
880                                    while recap_line :
881                                      out_results = out_results +  recap_line
882                                      recap_line =  f.readline()
883                                  line = f.readline()
884                                f.close()
885                             out_obj = {"StatusCode": 200,
886                                      "StatusMessage": "FINISHED",
887                                      "PlaybookName": playbooknametxt,
888                                      "Version": "Version",
889                                      "Duration": 200,
890                                      "Results": out_results}
891                             return out_obj
892                        else:
893                           return {"StatusCode": 500, "StatusMessage": "PLAYBOOK FAILED "}
894
895
896                    return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
897             else:
898                 return {"StatusCode": 500, "StatusMessage": "MALFORMED REQUEST"}
899         elif 'DELETE' in cherrypy.request.method:
900             input_data = parse_query_string(cherrypy.request.query_string)
901
902             cherrypy.log( "***> in RestServer.DELETE")
903             cherrypy.log("Payload: " + str(input_data))
904
905             if input_data['Id'] in TestRecord:
906                 if not 'PENDING' in TestRecord[input_data['Id']]['Result']:
907                     cherrypy.log(" Path: " + str(TestRecord[input_data['Id']]['Path']))
908                     TestRecord.pop (input_data['Id'])
909                     if input_data['Id'] in ActiveProcess:
910                         ActiveProcess.pop (input_data['Id'])
911
912                     return {"StatusCode": 200, "StatusMessage": "PLAYBOOK EXECUTION RECORDS DELETED"}
913                 else:
914                     return {"StatusCode": 200, "StatusMessage": "PENDING"}
915             else:
916                 return {"StatusCode": 500, "StatusMessage": "TEST ID UNDEFINED"}
917
918
919 if __name__ == '__main__':
920
921     # Read configuration
922
923     config_file_path = "RestServer_config"
924
925     if not os.path.exists(config_file_path):
926         print '[INFO] The config file does not exist'
927         sys.exit(0)
928
929     ip = 'na'
930     AnsibleServer = 'na'
931     port = 'na'
932     tls = False
933     auth = False
934     pub = 'na'
935     priv = 'na'
936     timeout_seconds = 'na'
937     ansible_path = 'na'
938     ansible_temp = 'na'
939     host = 'na'
940     users= 'na'
941     getresults_block = False
942     from_files = False
943
944     file = open(config_file_path, 'r')
945     for line in file.readlines():
946         if '#' not in line:
947             if 'ip:' in line:
948                 ip = line.split(':')[1].strip()
949             elif 'AnsibleServer:' in line:
950                 AnsibleServer = line.split(':')[1].strip()
951             elif 'port:' in line:
952                 port = line.split(':')[1].strip()
953             elif 'ksalt:' in line:
954                 salt = line.split(':')[1].strip()
955             elif 'tls:' in line:
956                 tls = 'YES' in line.split(':')[1].strip().upper()
957             elif 'auth:' in line:
958                 auth = 'YES' in line.split(':')[1].strip().upper()
959             if tls and 'priv:' in line:
960                 priv = line.split(':')[1].strip()
961             if tls and 'pub:' in line:
962                 pub = line.split(':')[1].strip()
963             if tls and 'inter_cert:' in line:
964                 intermediate = line.split(':')[1].strip()
965             if 'timeout_seconds' in line:
966                 timeout_seconds = int (line.split(':')[1].strip())
967             if 'ansible_path' in line:
968                 ansible_path = line.split(':')[1].strip()
969             if 'ansible_temp' in line:
970                 ansible_temp = line.split(':')[1].strip()
971             if 'host' in line:
972                 host = line.split(':')[1].strip()
973             if 'users' in line:
974                 users = line.split(':')[1].strip()
975             if 'getresults_block' in line:
976                 getresults_block = 'YES' in line.split(':')[1].strip().upper()
977             if 'from_files' in line:
978                 from_files = 'YES' in line.split(':')[1].strip().upper()
979     file.close()
980
981     # Initialization
982
983     global_conf = {
984         'global': {
985             'log.screen': True,
986             'response.timeout': 5400,
987             'server.socket_host': ip,
988             'server.socket_port': int(port),
989             'server.protocol_version': 'HTTP/1.1'
990             }
991         }
992
993     if tls:
994         # Use pythons built-in SSL
995         cherrypy.server.ssl_module = 'builtin'
996
997         # Point to certificate files
998
999         if not os.path.exists(pub):
1000             print '[INFO] The public certificate does not exist'
1001             sys.exit(0)
1002
1003         if not os.path.exists(priv):
1004             print '[INFO] The private key does not exist'
1005             sys.exit(0)
1006
1007         if not os.path.exists(intermediate):
1008             print '[INFO] The intermediate certificate does not exist'
1009             sys.exit(0)
1010
1011
1012         cherrypy.server.ssl_certificate = pub
1013         cherrypy.server.ssl_certificate_chain = intermediate
1014         cherrypy.server.ssl_private_key = priv
1015
1016     if auth:
1017         # Read in and build user dictionary
1018         if not os.path.exists(users):
1019           print '[INFO] The users file does not exist: ' + users
1020           sys.exit(0)
1021         userpassdict = {}
1022         user_file = open(users, 'r')
1023         for line in user_file.readlines():
1024           if '#' not in line:
1025             id = line.split(':')[0].strip()
1026             pw = line.split(':')[1].strip()
1027             userpassdict[id] = pw
1028             #print str(userpassdict)
1029
1030         app_conf = {'/':
1031                     {'tools.auth_basic.on': True,
1032                      'tools.auth_basic.realm': 'earth',
1033                      'tools.auth_basic.checkpassword': validate_password,
1034                      }
1035                     }
1036
1037         application = cherrypy.tree.mount(TestManager(), '/', app_conf)
1038     else:
1039         application = cherrypy.tree.mount(TestManager(), '/')
1040
1041     cherrypy.config.update({
1042         'log.access_file': "/var/log/RestServer.access"
1043     })
1044     accessLogName = "/var/log/RestServer.access"
1045     applicationLogName = "/var/log/RestServer.log"
1046     cherrypy.config.update(global_conf)
1047
1048     log = application.log
1049     log.error_file = ""
1050     log.access_file = ""
1051     from logging import handlers
1052     applicationLogFileHandler = handlers.RotatingFileHandler(applicationLogName, 'a', 1000000, 5000)
1053     accessLogFileHandler = handlers.RotatingFileHandler(accessLogName, 'a', 1000000, 5000)
1054     import logging
1055     applicationLogFileHandler.setLevel(logging.DEBUG)
1056     log.error_log.addHandler(applicationLogFileHandler)
1057     log.access_log.addHandler(accessLogFileHandler)
1058
1059     # Start server
1060
1061     cherrypy.engine.start()
1062     cherrypy.engine.block()