1 | import ConfigParser |
||
2 | import base64 |
||
3 | import sys |
||
4 | import logging |
||
5 | import urllib2 |
||
6 | import tempfile |
||
7 | import shutil |
||
8 | import os |
||
9 | import threading |
||
10 | import json |
||
11 | import socket |
||
12 | from SimpleXMLRPCServer import SimpleXMLRPCServer |
||
13 | |||
14 | import requests |
||
15 | |||
16 | |||
17 | logging.basicConfig(level=logging.DEBUG) |
||
18 | logger = logging.getLogger('FuzzEd') |
||
19 | |||
20 | backends = {} |
||
21 | options = {} |
||
22 | |||
23 | useTestServer = False |
||
24 | |||
25 | |||
26 | class WorkerThread(threading.Thread): |
||
27 | jobtype = "" |
||
28 | joburl = "" |
||
29 | |||
30 | def __init__(self, jobtype, joburl): |
||
31 | self.jobtype = jobtype |
||
32 | self.joburl = joburl |
||
33 | threading.Thread.__init__(self) |
||
34 | |||
35 | def sendResult(self, exit_code, file_data=None, file_name=None): |
||
36 | """ |
||
37 | :rtype : None |
||
38 | """ |
||
39 | results = {'exit_code': exit_code} |
||
40 | if file_data and file_name: |
||
41 | results['file_name'] = file_name |
||
42 | results['file_data'] = base64.b64encode(file_data) |
||
43 | logger.debug("Sending result data to %s" % (self.joburl)) |
||
44 | headers = {'content-type': 'application/json'} |
||
45 | r = requests.patch(self.joburl, data=json.dumps( |
||
46 | results), verify=False, headers=headers) |
||
47 | if r.text: |
||
48 | logger.debug("Data sent, response was: " + str(r.text)) |
||
49 | |||
50 | def run(self): |
||
51 | try: |
||
52 | logger.info("Working for job URL: " + self.joburl) |
||
53 | |||
54 | # Create tmp directories |
||
55 | tmpdir = tempfile.mkdtemp() |
||
56 | tmpfile = tempfile.NamedTemporaryFile(dir=tmpdir, delete=False) |
||
57 | |||
58 | # Fetch input data and store it |
||
59 | input_data = urllib2.urlopen(self.joburl).read() |
||
60 | tmpfile.write(input_data) |
||
61 | # logger.debug(input_data) |
||
62 | tmpfile.close() |
||
63 | |||
64 | # There trick is that we do not need to know the operational details |
||
65 | # of this job here, since the calling convention comes from daemon.ini |
||
66 | # and the input file format is determined by the web server on download. |
||
67 | # Alle backend executables are just expected to follow the same |
||
68 | # command-line pattern as render.py. |
||
69 | cmd = "%s %s %s %s %s" % (backends[self.jobtype]['executable'], |
||
70 | tmpfile.name, |
||
71 | tmpdir + os.sep + |
||
72 | backends[self.jobtype]['output'], |
||
73 | tmpdir, |
||
74 | backends[self.jobtype]['log_file']) |
||
75 | logger.info("Running " + cmd) |
||
76 | output_file = backends[self.jobtype]['output'] |
||
77 | |||
78 | # Run command synchronousely and wait for the exit code |
||
79 | exit_code = os.system(cmd) |
||
80 | if exit_code == 0: |
||
81 | logger.info("Exit code 0, preparing result upload") |
||
82 | # multiple result file upload not implemented |
||
83 | assert(not output_file.startswith("*")) |
||
84 | with open(tmpdir + os.sep + output_file, "rb") as fd: |
||
85 | data = fd.read() |
||
86 | self.sendResult(0, data, output_file) |
||
87 | # logger.debug(data) |
||
88 | else: |
||
89 | logger.error("Error on execution: Exit code " + str(exit_code)) |
||
90 | logger.error( |
||
91 | "Saving input file for later reference: /tmp/lastinput.xml") |
||
92 | os.system("cp %s /tmp/lastinput.xml" % tmpfile.name) |
||
93 | self.sendResult(exit_code) |
||
94 | |||
95 | except Exception as e: |
||
96 | logger.debug( |
||
97 | 'Exception, delivering -1 exit code to frontend: ' + str(e)) |
||
98 | self.sendResult(-1) |
||
99 | |||
100 | finally: |
||
101 | shutil.rmtree(tmpdir, ignore_errors=True) |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
102 | |||
103 | |||
104 | class JobServer(SimpleXMLRPCServer): |
||
105 | |||
106 | def __init__(self, conf): |
||
107 | SimpleXMLRPCServer.__init__( |
||
108 | self, (socket.gethostbyname("0.0.0.0"), int(options['backend_daemon_port']))) |
||
109 | self.register_function(self.handle_request, 'start_job') |
||
110 | |||
111 | def handle_request(self, jobtype, joburl): |
||
112 | logger.debug("Received %s job at %s" % (jobtype, joburl)) |
||
113 | if jobtype not in backends.keys(): |
||
114 | logger.error("Unknown job type " + jobtype) |
||
115 | return False |
||
116 | else: |
||
117 | # Start worker thread for this task |
||
118 | worker = WorkerThread(jobtype, joburl) |
||
119 | worker.start() |
||
120 | return True |
||
121 | |||
122 | |||
123 | if __name__ == '__main__': |
||
124 | # Read configuration |
||
125 | assert(len(sys.argv) < 4) |
||
126 | conf = ConfigParser.ConfigParser() |
||
127 | if len(sys.argv) == 1: |
||
128 | # Use default INI file in local directory |
||
129 | conf.readfp(open('./daemon.ini')) |
||
130 | else: |
||
131 | conf.readfp(open(sys.argv[1])) |
||
132 | # Read backends from configuration |
||
133 | for section in conf.sections(): |
||
134 | if section.startswith('backend_'): |
||
135 | settings = dict(conf.items(section)) |
||
136 | backends[settings['job_kind']] = settings |
||
137 | elif section == 'server': |
||
138 | options = dict(conf.items('server')) |
||
139 | logger.info("Configured backends: " + str(backends.keys())) |
||
140 | logger.info("Options: " + str(options)) |
||
141 | # Start server |
||
142 | server = JobServer(conf) |
||
143 | server.serve_forever() |
||
144 |