Shell Scripting with a Distributed Twist: Using the Sleep Scripting Language

Learn a Perl-like language whose scripts move around your network.
Remote Processes (Automate SSH)

System administration is all about reaching out and touching everything. And, doing that requires automation. Sleep can automate SSH sessions with ease. Here is the &ssh_cmd function in action:




@output = ssh_cmd($user => "root",
                  $pass => "123456",
                  $host => "",
                  $command => "cat /etc/shadow");


This script authenticates to via SSH, executes "cat /etc/shadow", and prints the result on the local machine. Before we go further, there is something you should know. Sleep doesn't have an &ssh_cmd function. We have to build it.

Adding SSH to Sleep

Perl has the CPAN for modules. Sleep scripts can take advantage of the Java class library to add functionality. Here, I walk you through the code for

import com.trilead.ssh2.* from: 

Sleep uses import to get access to classes in another package. Unlike Java, Sleep can import directly from a third-party Java archive file at runtime. This is useful for trying things out quickly. Here I use the Trilead SSH for Java library to add SSH to Sleep:

sub ssh_cmd
   local('$conn $sess $data $handle @data');

   # create a connection
   $conn = [new Connection: $host, 22];
   [$conn connect];

This code creates a new com.trilead.ssh2.Connection object. Next, I call the connect method on this object to set up an SSH connection:

   # authenticate
   [$conn authenticateWithPassword: $user, $pass];

Then, I call the authenticateWithPassword method on the connection. The Java library expects two string parameters. Sleep is smart enough to convert scalars to Java types as necessary:

   # execute the command
   $sess = [$conn openSession];
   [$sess execCommand: $command];

Here, I create an SSH session from the connection with the openSession method. This method returns a com.trilead.ssh2.Session object. Sleep places the object into a scalar variable. If you want to execute more than one command, create a session for each command as I've done here:

   # wire up a Sleep I/O handle for STDOUT
   $handle = [SleepUtils getIOHandle: 
                      [$sess getStdout], $null];

The next thing to do is get the output from the session. Sleep has a class called SleepUtils with useful functionality. One of the methods constructs an I/O handle from Java input and output stream objects. Here, I made a readable I/O object from [$sess getStdout]. To write values, replace $null with the STDIN value for the session. This is available as [$sess getStdin]:

   # read output into an array
   @data = readAll($handle);

From this point, you can manipulate the remote process like any other handle. Below, I read the entire contents of the handle into the array @data:

   # close it all down
   [$sess close];
   [$conn close];

   return @data;

The last step is to close down the session and connection. The &ssh_cmd function returns the contents of @data.

Run This Example

To execute this code, create from the example above, download trilead-ssh2-build212.jar, and re-use the SSH automation code for your own purposes. Place all these files in the same directory. Then, type:

$ java -jar sleep.jar

Distribute Tasks with Mobile Agents

Programs that move from computer to computer are mobile agents. Agent programming is a way of thinking about distributed computing. Some tasks fit very well into the mobile agent paradigm. For example, if you have to search all files in a network for some string, it makes no sense to download every single file and search it. It is much more efficient to move the search code to each computer and let the searching happen locally. Mobile agents make this possible.

Mobile agents also save you from the need to define a client and server protocol. You can place the entire interaction between two or more computers into a single function and let it start hopping around to complete the task.

So, what does a mobile agent look like? A mobile agent is a function that calls &move to relocate itself. Here is a syslog patrol agent. This agent patrols your network, checking the syslog dæmon on each box. If the dæmon is down, it tries to restart it. After each patrol, the agent starts over again:



Before this script can do anything, I include the agent library file (I dissect this file in the next section):

sub syslog_patrol
   local('$host @computers @proc $handle');

   $handle = openf("computers.txt");
   @computers = readAll($handle);

The first task is to get a list of all computers. For this, I read in the contents of computers.txt. I assume each line has the hostname or IP address of a computer ready to receive my agents:

   $handle = $null;

When an agent moves, it takes its variables, call stack and program counter with it. Sleep has to serialize this data to move a function. Serialization is the process of converting data to bytes. Scripts cannot serialize I/O handles. To prevent a disaster, I set the handle to $null before moving:

   while (size(@computers) > 0)
      $host = @computers[0];

The next task is to loop through each host. In this script, I use a list iteration approach. This approach removes the first item from @computers with each execution. @computers gets smaller and smaller until nothing is left. The item we want to work with always is at the front. I use list iteration here because foreach loops are not serializable:


This one function call is all it takes to relocate the agent. The statement after this function will execute from $host with its variables and state intact. In this example, I don't have any error handling. I assume the host is up and that the agent can move itself there. Error handling isn't hard to add, and the Sleep documentation provides more on this topic:

      @proc = filter({ 
              return iff("*syslogd" iswm $1);
           }, `ps ax`); 

This code gets a list of all processes that match the wild card "*syslogd*". &filter applies the anonymous function to each item in the array given by `ps ax`. And, &filter collects the non-$null return values of these operations and puts them into an array. This is Sleep's version of grep. I can use the size of the @proc array to check whether syslog is running:

      if (size(@proc) == 0)
         `./syslog start`;

Here, I check whether syslog is running. To start it, I change directories, and execute the syslog dæmon:

      @computers = sublist(@computers, 1);

The last step of the loop is to remove the first item from @computers. I use &ublist to do this:

   sendAgent($home, lambda($this, \$home));

At the end of the patrol, I send the agent back to the starting computer. I use &lambda to make a fresh copy of the agent function with no saved state. I pass the $home variable into the copy so it knows where to go when it restarts:

sendAgent(@ARGV[0], lambda(&syslog_patrol, 
                               $home => @ARGV[0]));

This code launches the agent into the system. I assume @ARGV[0] is the hostname of the home system with the computers.txt file.