/*
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 "common/config.h"
#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 xres = 0;
int yres = 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; /* 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, outputExt, xres, yres, 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;
}