Example: Distributed Sleep.

Not a very useful example, but an easy one to show the benefits of 
distributing tasks and how to get started using TaskMaster.

First we start by defining the task we want to distribute.  In this example
the tasks are defined in the file called SleepTask.rb.
There are two classes defined in SleepTask.rb:
SleepTask - A simple class which sleeps for a specified amount of time
  in 'run'. Notice that in addition to 'run' and 'iniitialize' there are
  two other methods defined in the SleepTask class:
    * get_serverTimeLimit - if a task class defines this method it should 
    return a number corresponding to the amount of time that the server
    (or master) should wait until it determines that the task has died 
    on the slave machine.  If the slave hasn't responded after this amount
    of time, it will be considered to have died and it will be removed from
    the Distributor's list of slaves.
    * harvest - define this method in a task class when you want to return 
    one or more values or objects from a task after it has completed 
    (NOTE: you should always return an array if there is more than one 
    object being returned).  In this example harvest returns the machine 
    name that the task ran on, the amount of time the task took and the 
    type of the task that was run (In this example the type will always 
    be SleepTask, but you could potentially subclass SleepTask which would 
    make this field more informative).  Also Note that the results from a 
    task's harvest will get passed to the Distributor's designated reporter
    object.
InitClass - A simple class to demonstrate TaskMaster::Distributor#broadcast.
    We'll send this particular task to all slaves in the master script using
    the master's distributor.broadcast.

Now look at the file, SleepReporter.rb which defines the class 'SleepReporter'
- here is where we define the reporter object which will be used by the 
Distributor object in the master script.  SleepReporter's methods are:
- initialize : We set up a report file with the default name 'SleepReport'
  in the constructor for this class and we write a message to the file
  that indicates what is being run and when it started.
- report : reporter objects must define a 'report' method.  It takes as
  arguments the elements of the array returned by the task's 'harvest' method.
  In this example, report takes the arguments 'machine' (name of the machine
  the task ran on),time (the amount of time it took) and type (the type of
  the task that was run (this corresponds to the description of SleepTask's
  harvest method described above).  These items are then written to the
  report file that was set up in SleepReporter's constructor.
- final_report : this is an 'arbitrary' method (meaning that it isn't required
  by the TaskMaster framework, but it's defined here so that it can be called
  at the end of the master script to write a summary and close the report
  file).

Next we create our master and slave scripts (see master.rb and slave_script.rb).
The slave script (slave_script.rb) is trivial, here's the whole thing:
1   require "../TM_finder"
2   require "TaskMaster"
3  slaveObj = TaskMaster::Slave.new()
4  puts "Slave #{Socket::gethostname} started"
5  slaveObj.wait

Line 1 just requires the file TM_finder.rb which lives in the
examples directory above the current one.  All it does is tell ruby where
to look for the TaskMaster libraries.
Line 2 requires TaskMaster so that you can use TaskMaster elements in the
following lines of the script.  
Line 3 instatiates the TaskMaster::Slave object and line 4 displays a handy
message about how the slave has started.
Line 5 sends the wait message to the slaveObj created in Line 3 - it basically
means that the slaveObj should stay around (without it, the script would
exit immediately).  This slave script is pretty generic and can be used, in
most cases, as-is unchanged.

Now we consider the master script (master.rb):
About the first half of master.rb is devoted to comand line argument handling
(and you can see from the source pretty much what each option does). The 
interesting stuff starts around line 36:

   filesToRequire = ["SleepTask.rb"]
   filesToRequire.each { |file|
      require file
   }

The file 'SleepTask.rb' is where the SleepTask and InitClass classes are defined
we're putting this filename in the list 'filesToRequire' and then we
iterate through that list and require each filename in the list.  "Why do 
we do this?" you ask.  Because SleepTask is going to be instantiated on
the master machine, but it will actually be 'run' on a slave machine. This
means that the slave machine needs to know what the definition of these classes
defined in the file 'SleepTask.rb' are, as well as the master machine needing 
to require them.  But SleepTask.rb may not exist on the slave machine, so the
way we tell the slaves about it is to send it to then via 
TaskMaster::Distributor's remote_require method (you'll notice that:
   distributor.remote_require(filesToRequire) 
is a few lines down in master.rb).

Next we instantiate the SleepReporter object which will be used to report
on the data that comes back when a task is harvested:
   reporter = SleepReporter.new

Then we instantiate the TaskMaster::Distributor object:
   distributor = TaskMaster::Distributor.new(clientList)
The clientList is determined by the comma seperated list given to the '-c'
commandline option.
Next we do the distributor.remote_require(filesToRequire) that was mentioned
above.
Now we have to tell the distributor to use the reporter object that we 
instantiated a few lines back:
   distributor.reporter = reporter
Now we're ready to broadcast the InitClass task to all of the slaves:
   distributor.broadcast(InitClass.new("initializing!"))
In this example, the InitClass doesn't do much, it only displays 
'initializing!' on the slave machines.  But if you were doing something more
complex, you could have the task that is broadcast do some kind of setup that
might be required on the slave side prior to running your 'real' tasks. For
example, you might need to set some environment variables on the slaves
that will be required when later tasks are run.

At this point things are pretty well set up.  The next step is to create
a list of task objects (SleepTasks in this case) and send them to the
distributor for distribution to slave machines, that's what's going on
in the next several lines:

st_of_tasks = []
20.times { |i|
   #just for variety sake:
   list_of_tasks <<  SleepTask.new()
} 
i = 0
list_of_tasks.each { |task|
   puts "sending #{task.type} number: #{i}" #{task.taskNum}"
   puts distributor.send_task(task," task# #{i}")   
   i += 1
}

Now that we've sent all the tasks out, we need to wait until the last one
finishes before going on, which is the purpose for this line:
   distributor.wait
if the wait wasn't there the script would just continue merrily till the end
without waiting for the tasks you sent out to complete.
And finally, after all the tasks have completed we want to finish up the
report file by calling 'final_report' on the reporter object:
   reporter.final_report

That's it.  A very simple, non-realistic example, but it does illustrate 
how to use most of TaskMaster's current features.

To run this example:
1) The slave_script.rb should be started on all slaves (it can even be run on
the designated 'master' machine as well:

> ruby slave_script.rb 

2) The master.rb script should be started on your designated 'master' 
machine as follows:
> ruby master.rb --clients <list_of_clients> --ruby_ver <min_ ruby_version>

Note that the <list_of_clients> should be a comma seperated list of clients.
Also note that in this example, the --ruby_ver option is used to specify
the minimum version of Ruby that a client must have in order to be included
in the master's client list.  

Phil
