Changeset - d1b199f51bbd
[Not reviewed]
default
0 2 0
ethanzonca@localhost.localdomain - 16 years ago 2009-11-24 00:07:50
ethanzonca@localhost.localdomain
Upload failure management
2 files changed with 18 insertions and 4 deletions:
0 comments (0 inline, 0 general)
src/server/slave.c
Show inline comments
 
/*
 
  Copyright 2009 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 <http://www.gnu.org/licenses/>.
 

	
 
*/
 

	
 
#include "protocol.h"
 
#include "options.h"
 
#include "slavefuncs.h"
 
#include "asprintf.h"
 
#include "remoteio.h"
 

	
 
#include <string.h>
 
#include <stdio.h>
 
#include <unistd.h>
 
#include <sys/stat.h>
 
#include <stdlib.h>
 

	
 
int main(int argc, char *argv[])
 
{
 

	
 
  int counter;
 

	
 
  /* Parse arguments */
 
  for(counter = 0; counter < argc; counter ++)
 
    if(strcmp(argv[counter], "-h") == 0)
 
      {
 
	fprintf(stderr, "Usage: distrenslave [option] \nStarts a distren slave\n\t-h\tshow this help\n");
 
	return 2;
 
      }
 

	
 
  /* Get conf data */
 
  char *username;
 
  char *datadir;
 
  cfg_t * my_cfg;
 
  cfg_opt_t myopts[] = {
 
    CFG_SIMPLE_STR("username", &username),
 
    CFG_SIMPLE_STR("datadir", &datadir),
 
    CFG_END()
 
  };
 
  struct options_common *commonopts;
 
  username = NULL;
 
  options_init(argc, argv, &my_cfg, myopts, "slave", &commonopts);
 

	
 

	
 
  /* Notifies the user if there no username in .conf */
 
  if(username == NULL || strcmp(username, "!username") == 0 )
 
    fprintf(stderr, "\nPlease ensure that your username is present in distrenslave.conf\n");
 
  else 
 
    if( username != NULL || strcmp(username, "!username") != 0 )
 
      {
 
	// Log in the user
 
	if(login_user(username) == 1){
 
	  fprintf(stderr, "You should now be logged into distren.\n");
 
	}
 
	else
 
	  {
 
	    fprintf(stderr, "Login failed, please check your username. If you have not registered, please do so on the DistRen website.\n");
 
	    return 0;
 
	  }
 
      }
 
    else
 
      {
 
	fprintf(stderr, "Please check your distrenslave.conf, it appears to be incorrectly formatted.\n");
 
      }
 

	
 

	
 
  int jobnum = 0;
 
  int framenum = 0;
 

	
 
  char *urltoTar;      /* Full URL to the server-side location of job#.tgz */
 
  char *pathtoTar;     /* Full path to the location of the job#.tgz */
 

	
 
  char *urltoOutput;   /* Full URL where output is posted */
 
  char *pathtoJobfile; /* Full path to the job's main file */
 
  char *pathtoXml;     /* Full path to the job's xml file */
 
  char *pathtoOutput;  /* Full path to the output (rendered) file */
 
  char *outputExt;     /* Output Extension (e.g., JPG) */
 

	
 
  char *tarcmd;        /* Command to run for tar. Migrate to libtar sometime */
 
  char *outdir;        /* Output Directory for tar */
 
  char *jobdatapath;   /* Path to job data */
 

	
 
  struct distrenjob *myjob; /* Structure to hold data gathered from the XML file */
 

	
 
  int haveWork = 0;
 

	
 
  while(1)
 
    {
 

	
 
    tell_the_server(0); // Give me some work!
 
    /* If we got a frame */
 
    if(haveWork)
 
      {
 
        /* @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 */
 

	
 
        fprintf(stderr, "Preparing to render frame %d in job %d\n", framenum, jobnum);
 

	
 
        // Variable Preparation
 
        _distren_asprintf(&jobdatapath, "job%d", jobnum);
 
        _distren_asprintf(&urltoTar, "http://protofusion.org/distren/stor/job%d/job%d.tar.gz", jobnum); // Prepares URL to download from
 
        _distren_asprintf(&pathtoTar, "%s/stor/jobdata/job%d.tar.gz", datadir, jobnum); // Prepares destination to save to
 
        _distren_asprintf(&pathtoJobfile, "%s/%s/job.blend", datadir, jobdatapath ); // Prepares the path to the jobfile
 
        _distren_asprintf(&urltoOutput, "http://protofusion.org/distren/stor/tmp/", jobdatapath ); // Prepares the URL where output is posted
 
        _distren_asprintf(&pathtoXml, "%s/job%d/job%d.xml", datadir, jobnum ); // Prepares the path to the job's XML file
 
        free(jobdatapath);
 

	
 
        // Prepare to download the job tar if it isn't  present
 
        struct stat buffer;
 
        int fstatus = stat(pathtoJobfile, &buffer);
 
        if(fstatus == -1)
 
          {
 
            // Download the Tar
 
            if( curlget(urltoTar, pathtoTar) == 0){
 
              fprintf(stderr, "Job data retrieved successfully\n");
 
              free(urltoTar);
 
            }
 
            else {
 
              fprintf(stderr, "Downloading job data from server failed. Check your network connection.\n");
 
              return 1; // for now
 
            }
 
          }
 
        else
 
          fprintf(stderr, "Using cached job file...\n");
 

	
 
        _distren_asprintf(&outdir, "/tmp/distren/job%d", jobnum); /*< @TODO: free() */
 
        mkdir("/tmp/distren", 0750); /* @TODO: Make this less *nix-specific */
 
        mkdir(outdir, 0750);
 

	
 
        _distren_asprintf(&tarcmd, "tar -xvf \"%s\" -C \"%s\"", pathtoTar, outdir); /* @TODO: Make this portable. Libtar or something? */
 
        system(tarcmd);
 
        free(tarcmd);
 
        free(pathtoTar);
 
        free(outdir);
 

	
 
        /* Parses a job's XML file, puts data in the myjob struct */
 
        if(xml2distrenjob(&myjob, pathtoXml) == 0)
 
          {
 
            fprintf(stderr, "Well, the XML craziness may have worked. Maybe. \n");
 
            free(pathtoXml);
 

	
 
            /* Frees things up if it was successful. xml2distrenjob() really (usually) only fails if malloc'ing inside it fails */
 
            distrenjob_free(&myjob);
 
          }
 
        else
 
          {
 
            fprintf(stderr, "I think the XML craziness may have failed, so I'll terminate just for fun.\n");
 
            return 1;
 
          }
 

	
 
        /* Variable-fillers which require XML */
 
        outputExt = myjob->output_format; /* @TODO: FIXME! <-- I WILL! */
 
        /* Prepares the path to the jobfile */
 
        _distren_asprintf(&pathtoOutput, "%s/job%d/output/job%d-frame%d.%s", datadir, jobnum, jobnum, framenum, outputExt );
 
        free(outputExt);
 

	
 
        /* Execute blender */
 
        exec_blender(pathtoJobfile, pathtoOutput, framenum); /* @TODO: This warning should be fixed :D */
 
        free(pathtoJobfile);
 

	
 
        /* When blender is finished, run this... */
 
        fprintf(stderr, "Finished frame %d in job %d, uploading...", framenum, jobnum);
 
        curlpost(pathtoOutput, urltoOutput); /* uploads (HTML POST) the output at outputpath to the server at outputurl */
 
        if( !curlpost(pathtoOutput, urltoOutput)){ // Uploads output
 
          fprintf(stderr,"Upload successful, removing old output...\n");
 
          remove(pathtoOutput); // Delete the file after its uploaded
 
        }
 
        else{
 
          fprintf(stderr,"Upload failed. Check your network connection. Trying again...");
 
          int tries=0;
 
          while(tries<10 && curlpost(pathtoOutput, urltoOutput)){
 
              fprintf(stderr, "Upload failed. Trying again in 10 seconds... (attempt %d of 10)", tries);
 
              tries++;
 
              sleep(10);
 
          }
 
          // @TODO: Keep track of files that we were unable to upload, and upload them later
 
        }
 
        free(urltoOutput);
 
        free(pathtoOutput);
 

	
 
        tell_the_server(DISTREN_REQUEST_DONEFRAME); /* Tells the server that it's done rendering, and upload is done */
 
        /* Slave now becomes idle, doesn't need to tell the server anything, ssh handles this. */
 
      }
 
    else
 
      fprintf(stderr,"Nothing to do. Idling...\n");
 

	
 
    // 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
 
  }
 
  return 0;
 
}
src/server/slavefuncs.c
Show inline comments
 
/*
 
  Copyright 2009 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 <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "protocol.h"
 
#include "asprintf.h"
 
#include "slavefuncs.h"
 
#include "distrenjob.h"
 
#include "execio.h"
 

	
 
#include <curl/curl.h>
 
#include <curl/types.h>
 
#include <curl/easy.h>
 

	
 
#include <stdio.h>
 
#include <string.h>
 
#include <unistd.h>
 
#include <stdlib.h>
 
#include <sys/stat.h>
 
#include <fcntl.h>
 

	
 

	
 

	
 
/**
 
 utility function for XPath-ish stuff:
 
 */
 
xmlNodePtr xml_quickxpath(xmlXPathContextPtr xpathctxt, xmlChar *path)
 
{
 
  xmlNodePtr toreturn;
 

	
 
  xmlXPathObjectPtr xmlxpathobjptr;
 
  xmlxpathobjptr = xmlXPathEval(path, xpathctxt);
 
  if(!xmlxpathobjptr
 
     || !xmlxpathobjptr->nodesetval->nodeNr)
 
    {
 
      fprintf(stderr, "XPath resolution failed for ``%s'' in ``%s'' (``%s'')\n", path, xpathctxt->doc->name, xpathctxt->doc->URL);
 
      return (xmlNodePtr)NULL;
 
    }
 

	
 
  toreturn = *(xmlxpathobjptr->nodesetval->nodeTab);
 

	
 
  xmlXPathFreeObject(xmlxpathobjptr);
 

	
 
  return toreturn;
 
}
 

	
 

	
 
/** Stub for deleting job data from the disk. @TODO: unstubify me! */
 
int delete_jobdata(int jobnum, char *datadir)
 
{
 
  char *jobpath;
 
  _distren_asprintf(&jobpath, "%s/%d", datadir, jobnum);
 
  rmdir(jobpath);
 
  fprintf(stderr, "I just failed to remove all of your job data because I can only delete empty directories! Haha!\nPlease contact the dev team :D\n\tWe'd be overjoyed to know that someone was willing to try to use this in this devel/design/planning stage ;-)\n");
 
  return 0;
 
}
 

	
 
/** Stub stub stubbiness ugh @TODO: Kill me. */
 
void tell_the_server(int stuff){
 
}
 

	
 
/** Function referenced by curlget() to write data to disk. */
 
size_t curl_writetodisk(void *ptr, size_t size, size_t nmemb, FILE *stream)
 
 {
 
    return fwrite(ptr, size, nmemb, stream);
 
  }
 

	
 
/** Helper function for cURL's progress display */
 
int curl_progress( char *Bar,double t,double d,double ultotal,double ulnow)
 
{
 
fprintf(stderr,"Downloading: %f%% complete\r",d/t*100);
 
return 0;
 
}
 

	
 
/** Retrieves a URL with cURL and saves it to disk */
 
int curlget(char *url, char *out){
 
  fprintf(stderr,"Preparing to download %s",url);
 
  double *Bar; // Stores cURL progress display info
 
  CURL *curl;
 
  CURLcode res;
 
  FILE *outfile;
 

	
 
  curl = curl_easy_init();
 
  if(curl) {
 
	outfile = fopen(out, "w"); // Open where we're writing to
 

	
 
  curl_easy_setopt(curl, CURLOPT_URL, url);
 
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile);
 
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_writetodisk); // this MUST be set for win32 compat.
 
  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
 
  curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curl_progress);
 
  curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &Bar);
 
  res = curl_easy_perform(curl);
 
  curl_easy_cleanup(curl);
 
  }
 
  fprintf(stderr,"\n"); // Clears out he progressbar's carriage return
 
  return res; // 0 means OK, nonzero means AAAH badness
 
}
 

	
 
/** Posts a file to a url with cUrl */
 
int curlpost(char *filename, char *url){
 
  char *targetname = "uploadedfile"; // Name of the target in the php file on the server (Don't change me unless you have different PHP code)
 
  CURL *curl;
 
  CURLcode res;
 
  struct curl_httppost *formpost=NULL;
 
  struct curl_httppost *lastptr=NULL;
 
  struct curl_slist *headerlist=NULL;
 
  static const char buf[] = "Expect:";
 

	
 
  curl_global_init(CURL_GLOBAL_ALL);
 

	
 
  /* upload field... */
 
  curl_formadd(&formpost,
 
               &lastptr,
 
               CURLFORM_COPYNAME, targetname,
 
               CURLFORM_FILE, filename,
 
               CURLFORM_END);
 
  /* filename field... */
 
  curl_formadd(&formpost,
 
               &lastptr,
 
               CURLFORM_COPYNAME, "filename",
 
               CURLFORM_COPYCONTENTS, filename,
 
               CURLFORM_END);
 
  /* submit field, not usually needed, just in case... */
 
  curl_formadd(&formpost,
 
               &lastptr,
 
               CURLFORM_COPYNAME, "submit",
 
               CURLFORM_COPYCONTENTS, "send",
 
               CURLFORM_END);
 

	
 
  curl = curl_easy_init();
 
  headerlist = curl_slist_append(headerlist, buf);
 
  if(curl) {
 
    /* Setting the URL to get the post, and the contents of the post */
 
    curl_easy_setopt(curl, CURLOPT_URL, url);
 
    curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
 
    res = curl_easy_perform(curl);
 

	
 
    curl_easy_cleanup(curl);
 
    /* cleanup the formpost junk */
 
    curl_formfree(formpost);
 
    curl_slist_free_all (headerlist);
 
  }
 
  return 0;
 
  return res;
 
}
 

	
 

	
 
/** Logs the user into the server after ensuring that keys exist */
 
int login_user(char *username)
 
{
 
  // @TODO: Put some telnet-style auth code here
 
  return 1; // success
 
}
 

	
 
/** Replaces !username with a username in the slave's conf file */
 
int conf_replace(char *username){
 
  int maxlinelen = 120;
 
  char *fileOrig = SYSCONFDIR "/distrenslave.conf";
 
  char *fileRepl = SYSCONFDIR "/distrenslave.conf.edited";
 
  char *text2find = "!username";
 
  char *text2repl = username;
 
  char buffer[maxlinelen+2];
 
  char *buff_ptr, *find_ptr;
 
  FILE *fp1, *fp2;
 
  size_t find_len = strlen(text2find);
 
  fp1 = fopen(fileOrig,"r");
 
  fp2 = fopen(fileRepl,"w");
 
  if (fp1 ==NULL){
 
    fprintf(stderr, "%s doesn't exist\n",fileOrig);
 
    return 0;
 
  }
 
  else if(fp2 ==NULL){
 
    fprintf(stderr, "Can't write a file to disk! Check permissions.\n");
 
    return 0;
 
  }
 
  else{
 
    while(fgets(buffer,maxlinelen+2,fp1))
 
      {
 
        buff_ptr = buffer;
 
        while ((find_ptr = strstr(buff_ptr,text2find)))
 
        {
 
           while(buff_ptr < find_ptr)
 
             fputc((int)*buff_ptr++,fp2);
 
           fputs(text2repl,fp2);
 
           buff_ptr += find_len;
 
         }
 
         fputs(buff_ptr,fp2);
 
      }
 
    rename(fileRepl, fileOrig);
 
  }
 
  fclose(fp2);
 
  fclose(fp1);
 
  fprintf(stderr,"Wrote conf file...\n");
 
return 1; // Success
 
}
 

	
 

	
 
/* Executors */
 

	
 

	
 
/** Executor function for Blender operations */
 
void exec_blender(char *input, char *output, int frame)
 
{
 
  fprintf(stderr,"Preparing to execute...\n");
 
  int ret;
 
  char *frame_str;
 

	
 
  /* start execio code */
 
  char *command = "blender"; // @TODO: append .exe if win32?
 

	
 
  char *cmd[] = { command, "-b", "-o", output, input, "-f", frame_str, (char *)NULL };
 

	
 
  char buf[10];
 
  struct execio *testrem;
 
  size_t readlen;
 

	
 
  _distren_asprintf(&frame_str, "%i", frame);
 

	
 
  ret = execio_open(&testrem, command, cmd); // This path will be absolute for testing, @TODO: should be relative to install on production
 
  buf[9] = '\0';
 
  while(!execio_read(testrem, buf, 9, &readlen))
 
    {
 
      if(readlen > 9) {
 
        fprintf(stderr, "Something is terribly wrong!\n");
 
       }
 
       buf[readlen] = '\0';
 
       fprintf(stderr, "read \"%s\"\n", buf);
 

	
 
       // @TODO: Better location for this code?
 
      tell_the_server(DISTREN_REQUEST_PROGRESS);
 
      fprintf(stderr, "Rendering...\n");
 
    }
 
  execio_close(testrem);
 
  /* end execio code */
 

	
 
  if(ret == 1){
 
    fprintf(stderr,"Error starting Blender. Check your install.\n");
 
  }
 
  else{
 
    fprintf(stderr,"Execution was successful.\n");
 
  }
 
  // Consider placing the following in the exec_blender() function
 

	
 
}
 

	
 
void xmlinit()
 
{
 
  xmlInitParser();
 
  xmlXPathInit();
 
}
 

	
 
void xmlcleanup()
 
{
 
  xmlCleanupParser();
 
}
0 comments (0 inline, 0 general)