rna data path names which are more likely to break animations.
[blender-staging.git] / release / scripts / io / netrender / slave.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 import sys, os, platform, shutil
20 import http, http.client, http.server, urllib
21 import subprocess, time
22
23 from netrender.utils import *
24 import netrender.model
25 import netrender.repath
26
27 BLENDER_PATH = sys.argv[0]
28
29 CANCEL_POLL_SPEED = 2
30 MAX_TIMEOUT = 10
31 INCREMENT_TIMEOUT = 1
32
33 if platform.system() == 'Windows' and platform.version() >= '5': # Error mode is only available on Win2k or higher, that's version 5
34     import ctypes
35     def SetErrorMode():
36         val = ctypes.windll.kernel32.SetErrorMode(0x0002)
37         ctypes.windll.kernel32.SetErrorMode(val | 0x0002)
38         return val
39
40     def RestoreErrorMode(val):
41         ctypes.windll.kernel32.SetErrorMode(val)
42 else:
43     def SetErrorMode():
44         return 0
45
46     def RestoreErrorMode(val):
47         pass
48
49 def clearSlave(path):
50     shutil.rmtree(path)
51
52 def slave_Info():
53     sysname, nodename, release, version, machine, processor = platform.uname()
54     slave = netrender.model.RenderSlave()
55     slave.name = nodename
56     slave.stats = sysname + " " + release + " " + machine + " " + processor
57     return slave
58
59 def testCancel(conn, job_id, frame_number):
60         conn.request("HEAD", "/status", headers={"job-id":job_id, "job-frame": str(frame_number)})
61
62         # canceled if job isn't found anymore
63         if responseStatus(conn) == http.client.NO_CONTENT:
64             return True
65         else:
66             return False
67
68 def testFile(conn, job_id, slave_id, rfile, JOB_PREFIX, main_path = None):
69     job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path)
70     
71     found = os.path.exists(job_full_path)
72     
73     if found:
74         found_signature = hashFile(job_full_path)
75         found = found_signature == rfile.signature
76         
77         if not found:
78             print("Found file %s at %s but signature mismatch!" % (rfile.filepath, job_full_path))
79             job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path, force = True)
80
81     if not found:
82         # Force prefix path if not found
83         job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path, force = True)
84         temp_path = os.path.join(JOB_PREFIX, "slave.temp")
85         conn.request("GET", fileURL(job_id, rfile.index), headers={"slave-id":slave_id})
86         response = conn.getresponse()
87
88         if response.status != http.client.OK:
89             return None # file for job not returned by server, need to return an error code to server
90
91         f = open(temp_path, "wb")
92         buf = response.read(1024)
93
94         while buf:
95             f.write(buf)
96             buf = response.read(1024)
97
98         f.close()
99
100         os.renames(temp_path, job_full_path)
101         
102     rfile.filepath = job_full_path
103
104     return job_full_path
105
106 def render_slave(engine, netsettings, threads):
107     timeout = 1
108
109     engine.update_stats("", "Network render node initiation")
110
111     conn = clientConnection(netsettings.server_address, netsettings.server_port)
112
113     if conn:
114         conn.request("POST", "/slave", repr(slave_Info().serialize()))
115         response = conn.getresponse()
116         response.read()
117
118         slave_id = response.getheader("slave-id")
119
120         NODE_PREFIX = os.path.join(netsettings.path, "slave_" + slave_id)
121         if not os.path.exists(NODE_PREFIX):
122             os.mkdir(NODE_PREFIX)
123
124         engine.update_stats("", "Network render connected to master, waiting for jobs")
125
126         while not engine.test_break():
127             conn.request("GET", "/job", headers={"slave-id":slave_id})
128             response = conn.getresponse()
129
130             if response.status == http.client.OK:
131                 timeout = 1 # reset timeout on new job
132
133                 job = netrender.model.RenderJob.materialize(eval(str(response.read(), encoding='utf8')))
134                 engine.update_stats("", "Network render processing job from master")
135
136                 JOB_PREFIX = os.path.join(NODE_PREFIX, "job_" + job.id)
137                 if not os.path.exists(JOB_PREFIX):
138                     os.mkdir(JOB_PREFIX)
139
140
141                 if job.type == netrender.model.JOB_BLENDER:
142                     job_path = job.files[0].filepath # path of main file
143                     main_path, main_file = os.path.split(job_path)
144
145                     job_full_path = testFile(conn, job.id, slave_id, job.files[0], JOB_PREFIX)
146                     print("Fullpath", job_full_path)
147                     print("File:", main_file, "and %i other files" % (len(job.files) - 1,))
148
149                     for rfile in job.files[1:]:
150                         testFile(conn, job.id, slave_id, rfile, JOB_PREFIX, main_path)
151                         print("\t", rfile.filepath)
152                         
153                     netrender.repath.update(job)
154
155                     engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id)
156
157                 # announce log to master
158                 logfile = netrender.model.LogFile(job.id, slave_id, [frame.number for frame in job.frames])
159                 conn.request("POST", "/log", bytes(repr(logfile.serialize()), encoding='utf8'))
160                 response = conn.getresponse()
161                 response.read()
162
163
164                 first_frame = job.frames[0].number
165
166                 # start render
167                 start_t = time.time()
168
169                 if job.type == netrender.model.JOB_BLENDER:
170                     frame_args = []
171
172                     for frame in job.frames:
173                         print("frame", frame.number)
174                         frame_args += ["-f", str(frame.number)]
175
176                     val = SetErrorMode()
177                     process = subprocess.Popen([BLENDER_PATH, "-b", "-noaudio", job_full_path, "-t", str(threads), "-o", os.path.join(JOB_PREFIX, "######"), "-E", "BLENDER_RENDER", "-F", "MULTILAYER"] + frame_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
178                     RestoreErrorMode(val)
179                 elif job.type == netrender.model.JOB_PROCESS:
180                     command = job.frames[0].command
181                     val = SetErrorMode()
182                     process = subprocess.Popen(command.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
183                     RestoreErrorMode(val)
184
185                 headers = {"slave-id":slave_id}
186
187                 cancelled = False
188                 stdout = bytes()
189                 run_t = time.time()
190                 while not cancelled and process.poll() == None:
191                     stdout += process.stdout.read(1024)
192                     current_t = time.time()
193                     cancelled = engine.test_break()
194                     if current_t - run_t > CANCEL_POLL_SPEED:
195
196                         # update logs if needed
197                         if stdout:
198                             # (only need to update on one frame, they are linked
199                             conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers)
200                             response = conn.getresponse()
201                             response.read()
202                             
203                             # Also output on console
204                             if netsettings.use_slave_output_log:
205                                 print(str(stdout, encoding='utf8'), end="")
206
207                             stdout = bytes()
208
209                         run_t = current_t
210                         if testCancel(conn, job.id, first_frame):
211                             cancelled = True
212
213                 if job.type == netrender.model.JOB_BLENDER:
214                     netrender.repath.reset(job)
215
216                 # read leftovers if needed
217                 stdout += process.stdout.read()
218
219                 if cancelled:
220                     # kill process if needed
221                     if process.poll() == None:
222                         process.terminate()
223                     continue # to next frame
224
225                 # flush the rest of the logs
226                 if stdout:
227                     # Also output on console
228                     if netsettings.use_slave_thumb:
229                         print(str(stdout, encoding='utf8'), end="")
230                     
231                     # (only need to update on one frame, they are linked
232                     conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers)
233                     if responseStatus(conn) == http.client.NO_CONTENT:
234                         continue
235
236                 total_t = time.time() - start_t
237
238                 avg_t = total_t / len(job.frames)
239
240                 status = process.returncode
241
242                 print("status", status)
243
244                 headers = {"job-id":job.id, "slave-id":slave_id, "job-time":str(avg_t)}
245
246
247                 if status == 0: # non zero status is error
248                     headers["job-result"] = str(DONE)
249                     for frame in job.frames:
250                         headers["job-frame"] = str(frame.number)
251                         if job.type == netrender.model.JOB_BLENDER:
252                             # send image back to server
253
254                             filename = os.path.join(JOB_PREFIX, "%06d.exr" % frame.number)
255
256                             # thumbnail first
257                             if netsettings.use_slave_thumb:
258                                 thumbname = thumbnail(filename)
259
260                                 f = open(thumbname, 'rb')
261                                 conn.request("PUT", "/thumb", f, headers=headers)
262                                 f.close()
263                                 responseStatus(conn)
264                                 
265
266                             f = open(filename, 'rb')
267                             conn.request("PUT", "/render", f, headers=headers)
268                             f.close()
269                             if responseStatus(conn) == http.client.NO_CONTENT:
270                                 continue
271
272                         elif job.type == netrender.model.JOB_PROCESS:
273                             conn.request("PUT", "/render", headers=headers)
274                             if responseStatus(conn) == http.client.NO_CONTENT:
275                                 continue
276                 else:
277                     headers["job-result"] = str(ERROR)
278                     for frame in job.frames:
279                         headers["job-frame"] = str(frame.number)
280                         # send error result back to server
281                         conn.request("PUT", "/render", headers=headers)
282                         if responseStatus(conn) == http.client.NO_CONTENT:
283                             continue
284
285                 engine.update_stats("", "Network render connected to master, waiting for jobs")
286             else:
287                 if timeout < MAX_TIMEOUT:
288                     timeout += INCREMENT_TIMEOUT
289
290                 for i in range(timeout):
291                     time.sleep(1)
292                     if engine.test_break():
293                         break
294
295         conn.close()
296
297         if netsettings.use_slave_clear:
298             clearSlave(NODE_PREFIX)
299
300 if __name__ == "__main__":
301     pass