/* Copyright 2010 Nathan Phillip Brink, Ethan Zonca, Matthew Orlando This file is a part of DistRen. DistRen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. DistRen is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with DistRen. If not, see . */ #include "slavefuncs.h" #include "common/asprintf.h" #include "common/multiio.h" #include "common/options.h" #include "common/protocol.h" #include "common/remoteio.h" #include "common/request.h" #include #include #include #include #include #define DEBUG 0 struct slave_state { /* whether or not we've gotten past the copyright line */ short copyright_done; /* number of bytes that we need to read before we have a whole packet */ size_t expectlen; /* whether or not we should exit */ int quit; /* the server's remoteio handle */ struct remoteio *rem; }; static void distren_slave_remoteio_close_handle(void *blah, void *data); static size_t distren_slave_remoteio_read_handle(struct remoteio *rem, void *blah, void *buf, size_t len, void *data); int main(int argc, char *argv[]) { multiio_context_t multiio; struct slave_state slave_state; char *datadir; char *server; char *username; char *password; char *hostname; cfg_opt_t myopts[] = { CFG_SIMPLE_STR("username", &username), CFG_SIMPLE_STR("password", &password), CFG_SIMPLE_STR("datadir", &datadir), CFG_SIMPLE_STR("server", &server), CFG_SIMPLE_STR("hostname", &hostname), CFG_END() }; cfg_t * my_cfg; struct options_common *commonopts; // struct distrenjob *myjob; /* Structure to hold data gathered from the XML file - not needed anymore? */ /** initializations */ datadir = NULL; server = NULL; username = NULL; password = NULL; hostname = NULL; char curopt; int runBenchmark = 0; multiio = multiio_context_new(); while(((char)-1) != (curopt = getopt(argc, argv, "u:rh"))) { if(curopt == ':') { fprintf(stderr, "-%c: is missing an argument\n", optopt); return 1; } else if(curopt == '?') { fprintf(stderr, "-%c: invalid option specified\n", optopt); return 1; } else if(curopt == 'h') { fprintf(stderr, "Usage: distrenslave [option] \nStarts a distren slave\n\t-u\tset username (run after fresh install)\n\t-r\tRecalculate render power (benchmark)\n\t-h\tshow this help\n"); return 2; } else if(curopt == 'r') { runBenchmark = 1; break; } else if(curopt == 'u') username = strdup(optarg); if(DEBUG) fprintf(stderr, "Putting username \"%s\" in distrenslave.conf\n", username); conf_replace("distrenslave.conf", "!username", username); fprintf(stderr, "Please invoke distrenslave with no arguments to run with the username you just set\n"); return 0; } /* Get conf data */ options_init(argc, argv, &my_cfg, myopts, "slave", &commonopts, multiio); if(!datadir) { fprintf(stderr, "datadir not set\n"); return 1; } if(!server) { fprintf(stderr, "server not set\n"); return 1; } if(!username) { fprintf(stderr, "username not set\n"); return 1; } if(!password) { fprintf(stderr, "password not set\n"); return 1; } if(!hostname) { fprintf(stderr, "hostname not set\n"); return 1; } /* Notifies the user if there no username in .conf */ if(checkUsername(username)) return 1; if(!strncmp(password, "!password",10)) { fprintf(stderr, "You haven't specified a password. Please edit distrenslave.conf!\n"); return 1; } // Variables needed for main loop int jobnum = 0; int framenum = 0; int slavekey = atoi(username); // @TODO: Make this more friendly char *urltoTar; /* Full URL to the server-side location of job#.tgz */ char *pathtoTar; /* Full path to the location of the job#.tgz */ char *pathtoTardir; char *urltoOutput; /* Full URL where output is posted */ char *pathtoOutput; /* Full path to the output (rendered) file */ char *pathtoOutdir; /* Full path to output directory */ char *pathtoRenderOutput; /* Contains blender framenum placeholder */ char *urltoJobfile; /* No longer used, url to .blend on server */ char *pathtoJob; /* Full path to job data folder */ char *pathtoJobfile; /* Full path to the job's main file */ char *outputExt = "jpg"; /* Output Extension (e.g., JPG) */ int haveWork = 0; int benchmarkTime = 0; int renderPower = 0; slave_state.copyright_done = 0; slave_state.expectlen = 0; slave_state.quit = 0; fprintf(stderr, "Connecting to server...\n"); if(remoteio_open_server(&slave_state.rem, commonopts->remoteio, &distren_slave_remoteio_read_handle, &slave_state, &distren_slave_remoteio_close_handle, server)) { fprintf(stderr, "Error connecting to server; exiting\n"); return 1; } greet_server(slave_state.rem); fprintf(stderr,"\nDistRen Slave Pre-Alpha %s\n- Experimental build: Use at your own risk!\n\n", PACKAGE_VERSION); // @TODO: add call to function to force recalc if $render_power == "" if(runBenchmark) { if(slaveBenchmark(datadir, &benchmarkTime, &renderPower)) { fprintf(stderr,"Benchmark failed! Exiting.\n"); return 1; } else { fprintf(stderr,"Benchmark successful, time taken was %d seconds, giving you a render power of %d.\n", benchmarkTime, renderPower); _web_setrenderpower(slavekey, password, renderPower); return 0; } } if(!DEBUG) fprintf(stderr, "Running.."); // Main loop while(!slave_state.quit) { multiio_poll(multiio, 15000); if(slave_state.quit) break; // request work fprintf(stderr, "Waiting...\n"); haveWork = getwork(slave_state.rem, &jobnum, &framenum); /* If we got a frame */ if(haveWork) { fprintf(stderr,"Got work from server...\n"); /* @TODO: Add remotio hooks */ // jobnum = remoteio_read(jobnum); /* Set jobnum from remoteio (we could use info from struct, but we need this info to download the xmlfile */ // framenum = remoteio_read(jobnum); /* Set framenum from remoteio */ // outputExt = remotio)read(outputExt); /* Set output extension from remotio */ if(DEBUG) fprintf(stderr, "Preparing to render frame %d in job %d\n", framenum, jobnum); prepareJobPaths(jobnum, framenum, outputExt, datadir, &urltoTar, &pathtoTar,&pathtoTardir,&pathtoJob, &pathtoJobfile, &urltoJobfile, &urltoOutput, &pathtoOutput, &pathtoRenderOutput, &pathtoOutdir); int dlret = downloadTar(urltoTar, pathtoTar); if(dlret == 0) fprintf(stderr,"Data retrieved successfully!\n"); else if(dlret == 3){ resetframe(slave_state.rem, jobnum, framenum); // Unassign the frame on the server so other slaves can render it return 0; // ouput dir doesn't exist } else if(DEBUG) fprintf(stderr,"Using existing tarball %s...\n", pathtoTar); // Decompress tarball struct stat jbuffer; int jstatus = stat(pathtoTar, &jbuffer); if(jstatus == -1){ if(DEBUG) fprintf(stderr,"Main job file does not exist, extracting...\n"); // If error unpacking tarball if(unpackJob(pathtoJob, pathtoTar)){ resetframe(slave_state.rem, jobnum, framenum); // Unassign the frame on the server so other slaves can render it fprintf(stderr,"Error decompressing tarball! Exiting.\n"); return 1; } } /* ignore return because directory may exist already */ if(DEBUG) fprintf(stderr,"Creating output directory %s\n", pathtoOutdir); mkdir(pathtoOutdir, 0700); if(DEBUG) fprintf(stderr,"Marking frame started on server... "); _web_startframe(slavekey, password, jobnum, framenum); /* Execute blender */ if(DEBUG){ fprintf(stderr,"Executing blender on file %s\n", pathtoJobfile); fprintf(stderr,"Directing output to file %s\n", pathtoOutput); } /* Execute blender */ if(exec_blender(pathtoJobfile, pathtoOutput, framenum)) { fprintf(stderr,"Error running Blender. Check your installation and/or your PATH.\n"); resetframe(slave_state.rem, jobnum, framenum); // Unassign the frame on the server so other slaves can render it return 1; } free(pathtoJobfile); pathtoJobfile = NULL; struct stat buffer; int fstatus = stat(pathtoOutput, &buffer); if(fstatus == -1){ fprintf(stderr,"*** %s doesn't exist! Scene may not have camera, or your blender installation is not working.\n", pathtoOutput); resetframe(slave_state.rem, jobnum, framenum); // Unassign the frame on the server so other slaves can render it return 1; } else{ /* Post-execution */ if(DEBUG) fprintf(stderr, "Finished frame %d in job %d, uploading...\n", framenum, jobnum); else fprintf(stderr,"Finished frame.\n"); uploadOutput(pathtoOutput, urltoOutput, jobnum, framenum, slavekey); // @TODO: Handle return value free(urltoOutput); free(pathtoOutput); urltoOutput = NULL; pathtoOutput = NULL; // Tell the server that rendering and upload are complete of "jobjum.framenum" finishframe(slave_state.rem, jobnum, framenum); free(urltoTar); free(pathtoTar); free(pathtoTardir); free(pathtoJob); free(pathtoJobfile); free(urltoJobfile); free(urltoOutput); free(pathtoRenderOutput); free(pathtoOutdir); } } else{ if(DEBUG) fprintf(stderr,"Nothing to do. Idling...\n"); else fprintf(stderr,"."); /** to prevent infinite loops from burning CPU, we just sleep(1) ;-) */ sleep(1); } /* @TODO: If the server says that every frame for the last jobnum is finished, OR if the data is getting old */ if(1 == 0) { // Note: individual frames are already deleted after uploading, // except for ones that couldn't be uploaded delete_jobdata(jobnum, datadir); } sleep(5); // Poll 5 seconds. @TODO: Remove all polling } fprintf(stderr, "Quitting...\n"); free(my_cfg); free(datadir); fprintf(stderr, "Goodbye!\n"); return 0; } static void distren_slave_remoteio_close_handle(void *blah, void *data) { struct slave_state *slave_state = (struct slave_state *)data; fprintf(stderr, "Lost connection to server\n"); slave_state->quit = 1; } static size_t distren_slave_remoteio_read_handle(struct remoteio *rem, void *blah, void *buf, size_t len, void *data) { struct slave_state *slave_state = (struct slave_state *)data; struct distren_request *req, *my_req; void *req_data, *my_req_data; size_t to_return; size_t counter; fprintf(stderr, "expected to eat %d bytes\n", len); /* to_return shall record how much of buf we've eaten already */ to_return = 0; if(!slave_state->copyright_done) { putchar('\n'); /* we have to flush through data until we hit a newline */ for(counter = 0; counter + slave_state->expectlen < 256 && counter < len && ((char *)buf)[counter] != '\n'; counter ++) { putchar(((char *)buf)[counter]); } slave_state->expectlen += counter - 1; if(slave_state->expectlen == 256) { fprintf(stderr, "\nThe server's greeting is too long. Maybe it speaks a foreign language\n"); slave_state->quit = 1; } if(counter < len && ((char *)buf)[counter] == '\n') { putchar('\n'); counter ++; slave_state->expectlen = 0; slave_state->copyright_done = 1; to_return += counter; buf += counter; len -= counter; } if(!slave_state->copyright_done) return to_return; } while(1) { /* if we haven't read a full header yet: */ if(!slave_state->expectlen) { if(len < sizeof(struct distren_request)) return to_return; /* figure out how much we need to read in before we can get anywhere */ if(distren_request_new_fromdata(&req, buf, len)) { fprintf(stderr, "Failing to interpret data from server, exiting\n"); slave_state->quit = 1; return to_return; } slave_state->expectlen = sizeof(struct distren_request) + req->len; distren_request_free(req); } if(slave_state->expectlen && slave_state->expectlen <= len) { distren_request_new_fromdata(&req, buf, len); req_data = buf + sizeof(struct distren_request); switch((enum distren_request_type)req->type) { case DISTREN_REQUEST_VERSION: fprintf(stderr, "The server runs "); for(counter = 0; counter < req->len; counter ++) putc(((char *)req_data)[counter], stderr); putc('\n', stderr); break; case DISTREN_REQUEST_PING: fprintf(stderr, "PONG ! :-D\n"); distren_request_poing(&my_req, &my_req_data, 0, req_data, req->len); remoteio_write(slave_state->rem, my_req, sizeof(struct distren_request)); remoteio_write(slave_state->rem, my_req_data, req->len); distren_request_free_with_data(my_req, my_req_data); break; case DISTREN_REQUEST_DISCONNECT: /* hopefully this ends up being a useful message... */ printf("You have been disconnected: \""); for(counter = 0; counter < req->len; counter ++) putchar(((char *)buf)[counter]); putchar('"'); putchar('\n'); break; default: fprintf(stderr, "something\n"); break; } counter = req->len + sizeof(struct distren_request); distren_request_free(req); slave_state->expectlen = 0; len -= counter; buf += counter; to_return += counter; } } fprintf(stderr, "ate %d bytes\n", to_return); return to_return; }