NAME
GRID::Machine - Remote Procedure Calls over a SSH link
SYNOPSIS
use GRID::Machine;
my $host = shift || 'mylogin@remote.machine';
my $machine = GRID::Machine->new(host => $host, uses => [ 'Sys::Hostname' ]);
# Install function 'rmap' on remote.machine
my $r = $machine->sub(
rmap => q{
my $f = shift;
die "Code reference expected\n" unless UNIVERSAL::isa($f, 'CODE');
my @result;
for (@_) {
die "Array reference expected\n" unless UNIVERSAL::isa($_, 'ARRAY');
print hostname().": processing row [ @$_ ]\n";
push @result, [ map { $f->($_) } @$_ ];
}
return @result;
},
);
die $r->errmsg unless $r->ok;
my $cube = sub { $_[0]**3 };
# RPC involving code references and nested structures ...
$r = $machine->rmap($cube, [1..3], [4..6], [7..9]);
print $r; # Dumps remote stdout and stderr
for ($r->Results) { # Output:
my $format = "%5d"x(@$_)."\n"; # 1 8 27
printf $format, @$_ # 64 125 216
} # 343 512 729
DESCRIPTION
This module is inspired in the IPC::PerlSSH module by Paul Evans. It provides Remote Procedure Calls (RPC) via a SSH connection. What made IPC::PerlSSH appealing to me was that
'no special software is required on the remote end, other than the
ability to run perl nor are any special administrative rights required;
any account that has shell access and can execute the perl binary on
the remote host can use this module'.
The only requirement being that automatic SSH autentification between the local and remote hosts has been established. I have tried to expand the capabilities but preserving this feature.
Provide Remote Procedure Calls (RPC). Subroutines on the remote side can be called with arbitrary nested structures as arguments from the local side.
The result of a remote call is a GRID::Machine::Result object. Among the attributes of such object are the
results
of the call, the ouput produced instdout
andstderr
,errmsg
etc. The remote function can produce output without risk of misleading the protocol.Services for the transference of files are provided
Support for writing and management Remote Modules and the transference of Classes and Modules between machines
An Extensible Protocol
INSTALLATION
To install this module, follow these two steps:
Set automatic ssh-authentification with a machine where you have an account.
SSH includes the ability to authenticate users using public keys. Instead of authenticating the user with a password, the SSH server on the remote machine will verify a challenge signed by the user's private key against its copy of the user's publick key. To achieve this automatic ssh-authentification you have to:
Generate a public key use the
ssh-keygen
utility. For example:$ ssh-keygen -t rsa -N ''
The option
-t
selects the type of key you want to generate. There are three types of keys: rsa1, rsa and dsa. The-N
option is followed by the passphrase. The-N ''
setting indicates that no pasphrase will be used. This is useful when used with key restrictions or when dealing with cron jobs, batch commands and automatic processing which is the context in which this module was designed. If still you don't like to have a private key without passphrase, provide a passphrase and usessh-agent
to avoid the inconvenience of typing the passphrase each time.ssh-agent
is a program you run once per login sesion and load your keys into. From that moment on, anyssh
client will contactssh-agent
and no more passphrase typing will be needed.By default, your identification will be saved in a file
/home/user/.ssh/id_rsa
. Your public key will be saved in/home/user/.ssh/id_rsa.pub
.Once you have generated a key pair, you must install the public key on the remote machine. To do it, append the public component of the key
/home/user/.ssh/id_rsa.pub
to file/home/user/.ssh/authorized_keys
on the remote machine. If thessh-copy-id
script is available, you can do it using:$ ssh-copy-id -i ~/.ssh/id_rsa.pub user@machine1
Alternatively you can write the following command:
$ ssh remote.machine "umask 077; cat >> .ssh/authorized_keys" < home/user/.ssh/id_rsa.pub
The
umask
command is needed since the SSH server will refuse to read ahome/user/.ssh/authorized_keys
files which have loose permissions.Edit your local
/home/.ssh/config
file and add lines like:Host remote.machine user my_login_in_the_remote_machine Host another.remote.machine an.alias.for.this.machine user mylogin_there
This way you don't have to specify your login name on the remote machine even if it differs from your login name in the local machine.
Once the public key is installed on the server you should be able to authenticate using your private key
$ ssh remote.machine Linux remote.machine 2.6.15-1-686-smp #2 SMP Mon Mar 6 15:34:50 UTC 2006 i686 Last login: Sat Jul 7 13:34:00 2007 from local.machine user@remote.machine:~$
Before running the tests. Set on the local machine the environment variable
GRID_REMOTE_MACHINE
to point to a machine that is available using automatic authenticatication. For example, on abash
:export GRID_REMOTE_MACHINE=user@machine.domain
Otherwise most connectivity tests will be skipped. This and the previous steps are optional.
Follow the traditional steps:
perl Makefile.PL make make test make install
DEPENDENCIES
This module requires these other modules and libraries:
Module::Which version '0.0205'
METHODS ON THE LOCAL SIDE
The Constructor new
The typical call looks like:
my $machine = GRID::Machine->new(host => 'user@remote.machine.domain');
This function returns a new instance of an object. The object is blessed in a unique class that inherits from GRID::Machine
. That is, the new object is a singleton. When later the machine object is provided with new methods, those are installed in the singleton class. The following example illustrates the point.
$ cat -n classes.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my @m = qw(orion beowulf);
6
7 my $m = GRID::Machine->new( host => shift @m, uses => [qw(Sys::Hostname)]);
8 print ref($m)."\n";
9
10 $m->sub( one => q { print hostname().": one\n"; } );
11 print $m->one;
12
13 my $p = GRID::Machine->new( host => shift @m, uses => [qw(Sys::Hostname)] );
14 print ref($p)."\n";
15
16 $p->sub( one => q { print hostname().": 1\n"; } );
17 print $p->one;
There are two GRID::Machine
objects involved: $m
(for a connection to a machine named orion
) and $p
(connection to a machine named beowulf
) created at lines 7 and 13. Two subroutines with the same name one
are installed on both machines (lines 10 and 16). As remote functions they don't collide since they are being executed in two different machines. As local methods they don't collide too since the method one
of $m
lives in a different namespace than the method one
of $p
. The remote functions are called in lines 11 and 17. The result of such call is a GRID::Machine::Result
object. Such GRID::Machine::Result
object describes the result of the RPC. It has attributes like:
results
-
A reference to an
ARRAY
holding the results returned by the call stdout
-
The ouput produced in the remote
stdout
during the execution of the RPC stderr
-
The ouput produced in the remote
stderr
during the execution of the RPC
etc.
Wherever is evaluated in a string context a GRID::Machine::Result
object returns a string containing the output produced (to both stdout
and stderr
plus any specific perl error messages as in $@
) during the execution of the RPC. When executed the former program will produce an output similar to this:
$ classes.pl
GRID::Machine::138737228
orion: one
GRID::Machine::139666876
beowulf: 1
Exceptions
The constructor doesn't return on failure: It raises an exception if the connection can't be established. See the result of an attempt to connect to a machine when there is no automatic authentification:
$ perl -MGRID::Machine -e " GRID::Machine->new( host => 'user@not.available')"
ssh: connect to host not.available port 22: No route to host
Can't execute perl in user@not.available using ssh connection with automatic authentication
Arguments of new
The following arguments are legal:
host
The host to connect. The user can be specified here.
log
Relative path of the file where remote
STDOUT
will be redirected. Each time a RPC occurs STDOUT is redirected to a file. By default the name of this file is$ENV{HOME}/rperl$$.log
, where$$
is the PID of the process running in the remote machine.err
Relative path of the file where remote
STDERR
will be redirected. Each time a RPC occurs STDERR is redirected to a file. By default the name of this file is$ENV{HOME}/rperl$$.err
, where$$
is the PID of the process running in the remote machine.When executing the following program:
use strict; use GRID::Machine; my $machine = GRID::Machine->new( host => $ENV{GRID_REMOTE_MACHINE} || shift); print $machine->eval(q{ system('ls -l *.err *.log') })->stdout;
the output will be similar to this:
$ logerr.pl -rw-r--r-- 1 casiano casiano 0 2007-09-05 13:15 rperl22694.err -rw-r--r-- 1 casiano casiano 0 2007-09-05 13:15 rperl22694.log
wait
Maximum number of seconds to wait for the setting of the connection. If an automatic connection can't be established in such time. The constructor calls the
is_operative function
(see section "The Function is_operative") to check this. The default value is 15 seconds.ssh
A string. Specifies the
ssh
command to be used. Take advantage of this if you want to specify some special parameters. Defaults tossh
.scp
A string defining the program to use to transfer files between the local and remote machines. Defaults to
scp -q -p
.readfunc
Reference to the function used to read from the other side of the pipe. Returns the length of the message. Receives the buffer where the message will be stored. The second optional argument is the number of characters to read.
writefunc
Reference to the function used to write to the other side of the pipe. Receives the message to write. By default is:
cleanup
Boolean. If true the remote log files for STDOUT and STDERR will be erased when the connection ends. True by default.
sendstdout
Boolean. If true the contents of STDOUT and STDERR after each RPC are sent to the client. By default is true. The following example illustrates its use:
$ cat -n package.pl 1 #!/usr/local/bin/perl -w 2 use strict; 3 use GRID::Machine; 4 5 my $s = shift || 0; 6 my $machine = 'user@remote.machine.domain'; 7 8 my $m = GRID::Machine->new( host => $machine, sendstdout => $s); 9 10 my $p = $m->eval( 11 q{ 12 print "Name of the Caller Package: "; 13 return caller(0) 14 } 15 ); 16 print "$p",$p->result,"\n";
when executed with argument 0 the remote output is not saved and sent, but the returned result is still available:
$ package.pl 1 Name of the Caller Package: GRID::Machine $ package.pl 0 GRID::Machine
perl
A string. The perl interpreter to use in the remote machine. See an example:
$ cat -n poption.pl 1 #!/usr/local/bin/perl -w 2 use strict; 3 use GRID::Machine; 4 5 my $machine = shift || 'remote.machine.domain'; 6 my $m = GRID::Machine->new( 7 host => $machine, 8 perl => 'perl -I/home/user/prefix -I/home/user/perl', 9 ); 10 11 print $m->eval( q{ 12 local $" = "\n"; 13 print "@INC"; 14 }
when executed the program produces an output similar to this:
$ poption.pl /home/user/prefix /home/user/perl /etc/perl /usr/local/lib/perl/5.8.4 etc. etc.
remotelibs
An
ARRAY
reference. The referenced array contain the list of modules that will be loaded when bootstrapping the remote perl server. It is used to extend theGRID::Machine
protocol. By default the following modules are loaded:GRID::Machine::MakeAccessors GRID::Machine::Message GRID::Machine::Result GRID::Machine::REMOTE
See the section "EXTENDING THE PROTOCOL" for a full example.
startdir
The string specifying the directory where the remote execution starts. By default the home directory. For example:
my $m = GRID::Machine->new(host => $host, startdir => '/tmp');
If it does not exist is created.
See the section "EXAMPLE: RUNNING DISTRIBUTION TESTS ON REMOTE MACHINES" for a full example.
startenv
A reference to a hash. It will be used to modify the remote
%ENV
.pushinc
Reference to a list of directories. All this directories will be
push
ed in the@INC
list of the remote machineunshiftinc
Reference to a list of directories. All this directories will be
unshift
ed in the@INC
list of the remote machine. See an example:use GRID::Machine; my $m = GRID::Machine->new( host => 'remote.machine.domain', unshiftinc => [ qw(/home/user/prefix /home/user/perl) ], ); print $m->eval(q{ local $" = "\n"; print "@INC"; });
prefix
Libraries can be transferred from the local to the remote server. The prefix option is a string containing the directory where the libraries will be stored. By default is
$ENV{HOME}/perl5lib
.uses
A reference to an ARRAY of strings. Determines the modules that will be loaded when the remote Perl interpreter is started. The enumerated modules must be available on the remote side. For instance:
my $machine = GRID::Machine->new(host => $host, uses => [ 'POSIX qw( uname )' ])
See the section "Opaque Structures" for a full example
The eval
Method
The syntax is:
$result = $machine->eval( $code, @args )
This method evaluates code in the remote host, passing arguments and returning a GRID::Machine::Result
object. See an example:
use GRID::Machine qw(is_operative);
use Data::Dumper;
my $machine = GRID::Machine->new(host => 'user@remote.machine.domain');
my $p = { name => 'Peter', familyname => [ 'Smith', 'Garcia'], age => 31 };
print Dumper($machine->eval(q{
my $q = shift;
$q->{familyname}
}, $p
));
The Result of a RPC
When executed, the former code produces the following output:
$ struct.pl
$VAR1 = bless( {
'stderr' => '',
'errmsg' => '',
'type' => 'RETURNED',
'stdout' => '',
'errcode' => 0,
'results' => [
[ 'Smith', 'Garcia' ]
]
}, 'GRID::Machine::Result' );
A GRID::Machine::Result
result object describes the result of a RPC. The results
attribute is an ARRAY reference holding the result returned by the call. The other attributes stdout
, stderr
, etc. hold the respective outputs. See section "THE GRID::Machine::Result CLASS" for a more detailed description of GRID::Machine::Result
objects.
The Algorithm of eval
When a call
$result = $machine->eval( $code, @args )
occurs, the code $code
should be passed in a string, and is compiled using a string eval
in the remote host:
my $subref = eval "use strict; sub { $code }";
Files STDOUT
and STDERR
are redirected and the subroutine referenced by $subref
is called inside an eval with the specified arguments:
my @results = eval { $subref->( @_ ) };
Errors and Exceptions
If there are errors at compile time, they will be collected into the GRID::Machine::Result
object. In the following example the code to eval has an error (variable $q
is not declared):
use GRID::Machine;
use Data::Dumper;
my $machine = GRID::Machine->new(host => 'user@remote.machine.domain');
my $p = { name => 'Peter', familyname => [ 'Smith', 'Garcia'] };
my $r = $machine->eval( q{ $q = shift; $q->{familyname} }, $p);
die Dumper($r) unless $r->ok;
print "Still alive\n";
When executed this code produces something like:
$ syntaxerr2.pl
$VAR1 = bless( {
'stderr' => '',
'errmsg' => 'user@remote.machine.domain: Error compiling code $q = shift; $q->{familyname}. \
Global symbol "$q" requires explicit package name at (eval 27) line 1, <STDIN> line 32. \
Global symbol "$q" requires explicit package name at (eval 27) line 1, <STDIN> line 32.',
'type' => 'DIED',
'stdout' => '',
'errcode' => 0
}, 'GRID::Machine::Result' );
GRID::Machine::Result
objects have an ok
method which returns TRUE if the RPC call didn't died. Therefore a common idiom after a RPC is:
die "$r" unless $r->ok;
The former error message though accurate
Global symbol "$q" requires ...
does not tell the exact line number and file source. Use a #line
pragma (see line 11 below) to get better diagnostics:
$ cat -n syntaxerr4.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4 use Data::Dumper;
5
6 my $machine = GRID::Machine->new(host => 'user@remote.machine.domain');
7
8 my $p = { name => 'Peter', familyname => [ 'Smith', 'Garcia'] };
9
10 my $r = $machine->eval( q{
11 #line 12 syntaxerr4.pl
12 $q = shift;
13 $q->{familyname}
14 }, $p
15 );
16
17 die "$r" unless $r->ok;
Now the execution produces the following output:
$ syntaxerr4.pl
user@remote.machine.domain: Error compiling code. \
Global symbol "$q" requires explicit package name at syntaxerr4.pl line 12, \
<STDIN> line 32.
Global symbol "$q" requires explicit package name at syntaxerr4.pl line 13, \
<STDIN> line 32.
Scope and Visbility Issues
Since the eval
method wraps the code into a subroutine (see section "The Algorithm of eval") like this
my $subref = eval "use strict; sub { $code }";
variables declared using our
inside an eval
must be redeclared in subsequent evals
to make them visible. The following code produces an error message:
$ cat -n vars1.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $machine = GRID::Machine->new(host => 'user@remote.machine.domain');
6
7 $machine->eval(q{
8 our $h;
9 $h = [4..9];
10 });
11
12 my $r = $machine->eval(q{
13 #line 14 "vars1.pl"
14 $h = [map {$_*$_} @$h];
15 });
The interpreter complains about $h
:
$ vars1.pl
Variable "$h" is not imported at vars1.pl line 14, <STDIN> line 36.
Variable "$h" is not imported at vars1.pl line 14, <STDIN> line 36.
The problem can be solved by redeclaring our $h
in the second eval
or changing the declaration at line 8 by use vars
:
7 $machine->eval(q{
8 use vars qw{$h};
9 $h = [4..9];
10 });
Closures
One of the consequences of wrapping $code
inside a sub is that any lexical variable is limited to the scope of the eval
. Another is that nested subroutines inside $code
will live in a (involutary) closure. See the example:
$ cat -n vars5.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $machine = GRID::Machine->new(host => 'user@remote.machine.domain');
6
7 my $r = $machine->eval(q{
8 #line 9 "vars5.pl"
9 my $h = 1;
10
11 sub dumph {
12 print "$h";
13 $h++
14 }
15
16 dumph();
17 });
18
19 print "$r\n";
20
21 $r = $machine->eval(q{
22 #line 23 "vars5.pl"
23 dumph();
24 });
25
26 print "$r\n";
When executed, the program produces the following warning:
$ vars5.pl
Variable "$h" will not stay shared at vars5.pl line 12, <STDIN> line 32.
1
2
The warning announces that later calls (in subsequent eval
s) to sub dumph
can no longer reach $h
(Other than trhough dumph
itself). If you want lexical nested subroutines declare them thorugh a reference:
$ cat -n vars6.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $machine = GRID::Machine->new(host => 'user@remote.machine.domain');
6
7 my $r = $machine->eval(q{
8 #line 9 "vars6.pl"
9 my $h = 1;
10
11 use vars '$dumph';
12 $dumph = sub {
13 print "$h";
14 $h++;
15 };
16
17 $dumph->(); # Prints 1
18 });
19
20 print "$r\n";
21
22 $r = $machine->eval(q{
23 #line 24 "vars6.pl"
24 $dumph->(); # Prints 2
25 });
26
27 print "$r\n";
The compile
Method
Syntax:
$machine->compile( $name, $code )
$machine->compile( $name, $code, politely => $politely )
$machine->compile( $name, $code, filter => $filter )
$machine->compile( $name, $code, politely => $politely, filter => $filter )
This method sends code to the remote host to store it inside the remote side of the GRID::Machine
object. Namely, the stored_procedures
attribute of the remote object is a hash reference containing the stored subroutines. The string $code
is compiled into a CODE
reference which can be executed later through the call
method.
The two first arguments are the name $name
of the subroutine and the code $code
. The order of the other arguments is irrelevant.
The subroutine name $name
must be an identifier, i. e. must match the regexp [a-zA-Z_]\w*
. Full names aren't allowed.
The following example uses compile
to install handlers for the most common file-testing functions -r
(is readable), -w
(writeable), etc. (lines 8-15):
$ cat -n compile.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $machine = $ENV{GRID_REMOTE_MACHINE} || shift;
6 my $m = GRID::Machine->new( host => $machine );
7
8 for (qw(r w e x z s f d t T B M A C)) {
9 $m->compile( "$_" => qq{
10 my \$file = shift;
11
12 return -$_ \$file;
13 }
14 );
15 }
16
17 my @files = $m->eval(q{ glob('*') })->Results;
18
19 for (@files) {
20 print "$_ is a directory\n" if $m->call('d', $_)->result;
21 }
After the testing functions are installed (lines 8-15), a list of files in the current (remote) directory is obtained (line 17) and those which are directories are printed (lines 19-21).
Collisions
When two functions are installed with the same name the last prevails:
use GRID::Machine;
my $machine = shift || 'remote.machine.domain';
my $m = GRID::Machine->new( host => $machine );
$m->compile(one => q{print "one\n"; });
$m->compile(one => q{ print "1\n"; });
my $r= $m->call("one");
print $r; # prints 1
To avoid overwriting an existent function the exists
method can be used:
use GRID::Machine;
my $machine = shift || 'remote.machine.domain';
my $m = GRID::Machine->new( host => $machine );
$m->compile(one => q{ print "one\n"; });
$m->compile(one => q{ print "1"; }) unless $m->exists('one');
my $r= $m->call("one");
print $r; # prints "one"
The politely
argument
An alternative solution is to use the politely
argument of compile
. If true the function won't be overwritten:
use GRID::Machine;
my $machine = shift || 'remote.machine.domain';
my $m = GRID::Machine->new( host => $machine );
my $r = $m->compile(one => q{ print "one\n"; });
$r = $m->compile(
one => q{ print "1"; },
politely => 1 # Don't overwrite if exists
);
print $r->errmsg."\n";
$r= $m->call("one");
print $r; # prints "one"
When executed, the former program produces this output:
$ compile5.pl
Warning! Attempt to overwrite sub 'one'. New version was not installed.
one
The sub
Method
Syntax:
$machine->sub( $name, $code )
$machine->sub( $name, $code, politely => $politely )
$machine->sub( $name, $code, filter => $filter )
$machine->sub( $name, $code, filter => $filter, politely => $politely )
This method is identical to the compile
method, except that the remote $code
will be available as a (singleton) method of the $machine
object within the local perl program. Therefore, two methods of two different GRID::Machine
objects with the same $name
are installed on different name spaces. See the example in section the constructor new
The filter
Argument
By default, the result of a subroutine call is a GRID::Machine::Result
object. However, for a given subroutine this behavior can be changed using the filter
argument. Thus, the subroutine filter_results
installed in lines 12-15 of the code below, when called returns the results
attribute instead of the whole GRID::Machine::Result
object. The subroutine filter_result
installed in lines 17-20 returns the first element of the resulting list:
$ cat -n filter.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4 use Data::Dumper;
5
6 my $machine = GRID::Machine->new( host => $ENV{GRID_REMOTE_MACHINE} || shift);
7
8 $machine->sub(
9 nofilter => q{ map { $_*$_ } @_ },
10 );
11
12 $machine->sub(
13 filter_results => q{ map { $_*$_ } @_ },
14 filter => 'results'
15 );
16
17 $machine->sub(
18 filter_result => q{ map { $_*$_ } @_ },
19 filter => 'result',
20 );
21
22 my @x = (3..5);
23 my $content = $machine->nofilter(@x);
24 print Dumper($content);
25
26 $content = $machine->filter_results(@x);
27 print Dumper($content);
28
29 $content = $machine->filter_result(@x);
30 print Dumper($content);
When executed the former program produces this output:
$ filter.pl
$VAR1 = bless( {
'stderr' => '',
'errmsg' => '',
'type' => 'RETURNED',
'stdout' => '',
'errcode' => 0,
'results' => [ 9, 16, 25 ]
}, 'GRID::Machine::Result' );
$VAR1 = [ 9, 16, 25 ];
$VAR1 = 9;
In general, the result of a call to a subroutine installed using
$machine->sub( $name, $code, filter => $filter )
will apply the $filter
subroutine to the GRID::Machine::Result
object resulting from the call. The filter $filter
must be a GRID::Machine::Result
method and must return a scalar.
The usage of the results
and result
filters can be convenient when the programmer isn't interested in the other attributes i.e. stdout
, stderr
, etc.
The call
Method
Syntax:
$result = $machine->call( $name, @args )
This method invokes a remote method that has earlier been defined using the compile
or sub
methods. The arguments are passed and the result is returned in the same way as with the eval
method.
Nested Structures
Nested Perl Data Structures can be transferred between the local and remote machines transparently:
use Data::Dumper;
my $host = shift || 'user@remote.machine.domain';
my $machine = GRID::Machine->new(host => $host);
my $r = $machine->sub(
rpush => q{
my $f = shift;
my $s = shift;
push @$f, $s;
return $f;
},
);
$r->ok or die $r->errmsg;
my $f = [[1..3], { a => [], b => [2..4] } ];
my $s = { x => 1, y => 2};
$r = $machine->rpush($f, $s);
die $r->errmsg unless $r->ok;
$Data::Dumper::Indent = 0;
print Dumper($r->result)."\n";
when executed the program above produces:
$ nested4.pl
$VAR1 = [[1,2,3],{'a' => [],'b' => [2,3,4]},{'y' => 2,'x' => 1}];
Aliasing
Aliasing between parameters is correctly catched. The following code presents (line 25) a remote procedure call to a function iguales
where the two local arguments $w
and $z
are the same:
$ cat -n alias.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $machine = GRID::Machine->new(host => shift(), uses => [ 'Sys::Hostname' ]);
6
7 my $r = $machine->sub( iguales => q{
8 #line 9 "alias.pl"
9 my ($first, $sec) = @_;
10
11 print hostname().": $first and $sec are ";
12
13 if ($first == $sec) {
14 print "the same\n";
15 return 1;
16 }
17 print "Different\n";
18 return 0;
19 },
20 );
21 $r->ok or die $r->errmsg;
22
23 my $w = [ 1..3 ];
24 my $z = $w;
25 $r = $machine->iguales($w, $z);
26 print $r;
when executed the program produces the following output:
$ alias.pl beowulf
beowulf: ARRAY(0x8275040) and ARRAY(0x8275040) are the same
The reciprocal is true. Equality on the remote side translate to equality on the local side. The program:
$ cat -n aliasremote.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $machine = GRID::Machine->new(host => shift(), uses => [ 'Sys::Hostname' ]);
6
7 $machine->sub( iguales => q{
8 my $first = [1..3];
9 my $sec = $first;
10
11 return (hostname(), $first, $sec);
12 },
13 );
14
15 my ($h, $f, $s) = $machine->iguales->Results;
16 print "$h: same\n" if $f == $s;
17 print "$h: different\n" if $f != $s;
produces the following output:
$ aliasremote.pl beowulf
beowulf: same
The run
Method
Syntax:
$m->run($command)
Is equivalent to
print $m->system($command)
Returns true if there were no messages on stderr
.
The exists
Method
Syntax:
$machine->exists(q{subname})
Returns true if, and only if, a subroutine named subname
has been previously installed on that machine (via sub
, compile
or some other trick). See an example:
use GRID::Machine;
my $host = shift || 'user@remote.machine.domain';
my $machine = GRID::Machine->new(host => $host);
$machine->sub( one => q{ print "one\n" });
print "<".$machine->exists(q{one}).">\n";
print "<".$machine->exists(q{two}).">\n";
when executed the former code produces the following output:
$ exists.pl
<1>
<>
FUNCTIONS ON THE LOCAL SIDE
The read_modules
Function
Syntax:
use GRID::Machine qw(read_modules)
read_modules(qw(Module:One Module::Two, Module::Three, ...))
Searches for the specified modules Module:One
, etc. in the local Perl installation. Returns a string with the concatenation of the contents of these modules.
For example, the line:
read_modules(qw(Parse::Eyapp Parse::Eyapp::))
returns a string containing the concatenation of the contents of all the modules in the Parse::Eyapp distribution. Modules are searched by name (like 'YAML'
) or by subcategories ('DBD::'
means all modules under the DBD subdirectories of your Perl installation, matching both 'DBD::Oracle
' and 'DBD::ODBC::Changes
').
The Function is_operative
The syntax is:
is_operative($ssh, $machine, $command, $wait)
Returns true if $machine
is available through ssh
using automatic authentication and $command
can be executed on the remote machine in less than $wait
seconds. The following example illustrates its use:
$ cat notavailable.pl
#!/usr/local/bin/perl -w
use strict;
use GRID::Machine qw(is_operative);
my $host = shift || 'user@machine.domain.es';
my $command = shift || 'perl -v';
my $delay = shift || 1;
die "$host is not operative\n" unless is_operative('ssh', $host, $command, $delay);
print "host is operative\n";
When not specified $command is perl -v
and $wait
is 15 seconds. The following two executions of the former example check the availability of machine beowulf
:
$ notavailable.pl beowulf
host is operative
pp2@nereida:~/LGRID_Machine/examples$ notavailable.pl beowulf chum
beowulf is not operative
The negative answer for the second execution is due to the fact that no command called chum
is available on that machine.
THE TRANSFERENCE OF FILES
The put
Method
Syntax:
$m->put([ 'file1', 'file2', ... ], 'targetdir/')
$m->put([ 'file1', 'file2', ... ])
Transfer files from the local machine to the remote machine. When no target directory is specified the files will be copied into the current directory (i.e. $ENV{PWD}
). If targetdir/
is a relative path, it is meant to be relative to the current directory on the remote machine. It returns TRUE on success. See an example:
$ cat put.pl
#!/usr/local/bin/perl -w
use strict;
use GRID::Machine;
my $m = GRID::Machine->new( host => shift());
$m->chdir('/tmp');
$m->put([ $0 ]);
$m->run("uname -a; ls -l $0");
When executed the program produces:
$ put.pl orion
Linux orion 2.6.8-2-686 #1 Tue Aug 16 13:22:48 UTC 2005 i686 GNU/Linux
-rwxr-xr-x 1 casiano casiano 171 2007-07-01 11:46 ./put.pl
If there is only one source file we can specify a new name for the target. Thus, the line:
$m->put([ $0 ], '/tmp/newname.pl')
will copy the file containing the current program on the remote machine as /tmp/newname.pl
The get
Method
Syntax:
$m->get( [ 'file1', 'file2'], ... ], 'targetdir/')
$m->get( [ 'file1', 'file2'], ... ])
Performs the reverse action of put
. Transfer files from the remote machine to the local machine. When the paths of the files to transfer 'file1'
, 'filer2'
, etc. are relative, they are interpreted as relative to the current directory on the remote machine. See an example:
use GRID::Machine;
my $m = GRID::Machine->new( host => shift(), startdir => 'tutu',);
$m->put([ glob('nes*.pl') ]);
$m->run('uname -a; pwd; ls -l n*.pl');
print "*******************************\n";
my $progs = $m->glob('nes*.pl')->results;
$m->get($progs, '/tmp/');
system('uname -a; pwd; ls -l n*.pl');
When executed the program produces an output similar to this:
$ get.pl remote
Linux remote 2.6.15-1-686-smp #2 SMP Mon Mar 6 15:34:50 UTC 2006 i686 GNU/Linux
/home/casiano/tutu
-rwxr-xr-x 1 casiano casiano 569 2007-05-16 13:45 nested2.pl
-rwxr-xr-x 1 casiano casiano 756 2007-05-22 10:10 nested3.pl
-rwxr-xr-x 1 casiano casiano 511 2007-06-27 13:08 nested4.pl
-rwxr-xr-x 1 casiano casiano 450 2007-06-27 15:20 nested5.pl
-rwxr-xr-x 1 casiano casiano 603 2007-05-16 14:49 nested.pl
*******************************
Linux local 2.4.20-perfctr #6 SMP vie abr 2 18:36:12 WEST 2004 i686 GNU/Linux
/tmp
-rwxr-xr-x 1 pp2 pp2 569 2007-05-16 13:45 nested2.pl
-rwxr-xr-x 1 pp2 pp2 756 2007-05-22 10:10 nested3.pl
-rwxr-xr-x 1 pp2 pp2 511 2007-06-27 13:08 nested4.pl
-rwxr-xr-x 1 pp2 pp2 450 2007-06-27 15:20 nested5.pl
-rwxr-xr-x 1 pp2 pp2 603 2007-05-16 14:49 nested.pl
LOADING CLASSES ONTO THE REMOTE SIDE
The modput
Method
Syntax:
$machine->modput(@Modulenames)
Where @Modulenames
is a list of strings describing modules. Descriptors can be names (like 'YAML'
) or subcategories (like 'DBD::'
meaning all modules under the DBD subdirectories of your Perl installation, matching both 'DBD::Oracle
' and 'DBD::ODBC::Changes
').
The following example will copy all the files in the distribution of Parse::Eyapp
to the remote machine inside the directory $machine->prefix
. After the call to
my $r = $machine->install('Parse::Eyapp', 'Parse::Eyapp::')
the module is available for use on the remote machine:
use GRID::Machine;
use Data::Dumper;
my $host = $ENV{GRID_REMOTE_MACHINE} ||shift;
my $machine = GRID::Machine->new(host => $host, prefix => q{perl5lib/});
my $r = $machine->modput('Parse::Eyapp', 'Parse::Eyapp::');
$r = $machine->eval(q{
use Parse::Eyapp;
print Parse::Eyapp->VERSION."\n";
}
);
print Dumper($r);
When executed, the former program produces an output like this:
$ modput.pl
$VAR1 = bless( {
'stderr' => '',
'errmsg' => '',
'type' => 'RETURNED',
'stdout' => "1.07\n",
'errcode' => 0,
'results' => [ 1 ]
}, 'GRID::Machine::Result' );
The include
Method
"The sub Method" permits the installation of a remote subroutine as a method of the GRID::Machine
object. This is efficient when only a few subroutines are involved. However for large number of subroutines that procedure is error prone. It is better to have the code in some separated module. This way we can test the components on the local machine and, once we are confident of their correct behavior, proceed to load them onto the remote machine. This is what include
is for.
Syntax of include
$m->include(
"Some::Module",
exclude => [ qw( f1 f2 ) ],
alias => { g1 => 'min', g2 => 'max' }
)
This call will search in the paths in @INC
for Some/Module.pm
. Once Some/Module.pm
is found all the subroutines inside the module will be loaded as methods of the GRID::Machine
(singleton) object $m
. Code outside subroutines, plain old documentation and comments will be ignored. Everything after the markers __END__
or __DATA__
will also be ignored. The presence of the parameter 'exclude => [ qw( f1 f2 ) ]
' means that subroutines f1
and f2
will be excluded from the process. Subroutine g1
will be renamed as min
and subroutine g2
will be renamed as max
.
Consider the following Remote Module:
$ cat -n Include5.pm
1 use strict;
2
3 sub last {
4 $_[-1]
5 }
6
7 sub one {
8 print 'sub one'."\n";
9 }
10
11 sub two {
12 print "sub two\n";
13 }
The following program includes the remote module Include5.pm
:
$ cat -n include5.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $host = $ENV{GRID_REMOTE_MACHINE} || 'user@remote.machine';
6
7 my $machine = GRID::Machine->new( host => $host);
8
9 $machine->include("Include5", exclude => [ qw(two) ], alias => { last => 'LAST' });
10
11 for my $method (qw(last LAST one two)) {
12 if ($machine->can($method)) {
13 print $machine->host." can do $method\n";
14 }
15 }
16
17 print $machine->LAST(4..9)->result."\n";
Then function two
is excluded and the subroutine last
is renamed as LAST
:
$ include5.pl
user@remote.machine can do LAST
user@remote.machine can do one
9
Remote Modules
The use of include
lead us to the concept of Remote Modules. A Remote Module contains a family of subroutines that will be loaded onto the remote machine via the sub
method of GRID::Machine
objects.
Here is a small example of Remote Module:
$ cat -n Include.pm
1 sub one {
2 print "sub one\n";
3 }
4
5 sub two {
6 print 'sub two'."\n";
7 }
8
9 sub three {
10 print "sub three\n";
11 }
12
13 my $a = "sub five {}\n";
14 my $b = 'sub six {}';
15
16 __DATA__
17
18 sub four {
19 print "four\n";
20 }
Source after the __DATA__
or __END__
delimiters are ignored. Also, code outside subroutines (for example lines 13 and 14) and pod
documentation are ignored. Only the subroutines defined in the module are loaded. See a program that includes the former remote module:
$ cat -n include.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $host = $ENV{GRID_REMOTE_MACHINE} || 'user@remote.machine.es';
6
7 my $machine = GRID::Machine->new( host => $host);
8
9 $machine->include(shift() || "Include");
10
11 for my $method (qw(one two three four five six)) {
12 if ($machine->can($method)) {
13 print $machine->host." can do $method\n";
14 print $machine->$method();
15 }
16 }
When executed the former program produces an output like:
$ include.pl
user@remote.machine.es can do one
sub one
user@remote.machine.es can do two
sub two
user@remote.machine.es can do three
sub three
The use
and LOCAL
directives in Remote Modules
Two directives that can be used isinde a Remote Module are use
and LOCAL
:
A
use Something
pragma inside a Remote Module indicates that such moduleSomething
must be loaded onto the remote machine. Of course, the module must be available there. An alternative to install it is to transfer the module(s) on the local machine to the remote machine usingmodput
(see section "The modput Method").A
LOCAL { code }
directive inside a Remote Module wrapscode
that will be executed on the local machine.LOCAL
directives can be used to massively load subroutines as in the example below.
The following remote module contains a use
pragma in line 2.
$ cat -n Include4.pm
1 use strict;
2 use List::Util qw(sum); # List::Util will be loaded on the Remote Side
3
4 sub sigma {
5 sum(@_);
6 }
7
8 LOCAL {
9 print "Installing new functions\n";
10 for (qw(r w e x z s f d t T B M A C)) {
11 SERVER->sub( "$_" => qq{
12 my \$file = shift;
13
14 return -$_ \$file;
15 }
16 );
17 }
18 }
Lines 9-17 are surrounded by a LOCAL
directive and thus they will be executed on the local side. The effect is to install new methods for the GRID::Machine
object that will be equivalent to the classic Perl file tests: -r
, -w
, etc. Inside a LOCAL
directive the function SERVER
returns a reference to the current GRID::Machine
object (see line 11).
See a program that loads the former Remote Module. The call to include
will load List::Util
on the remote machine importing the sum
function. Furthermore, methods with names sigma
, r
, w
, etc. will be installed:
$ cat -n include4.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $host = $ENV{GRID_REMOTE_MACHINE} || 'user@remote.machine.es';
6
7 my $machine = GRID::Machine->new(host => $host,);
8
9 $machine->include(shift() || "Include4");
10
11 print "1+..+5 = ".$machine->sigma( 1..5 )->result."\n";
12
13 $machine->put([$0]);
14
15 for my $method (qw(r w e x s t f d)) {
16 if ($machine->can($method)) {
17 my $r = $machine->$method($0)->result || "";
18 print $machine->host."->$method( include4.pl ) = <$r>\n";
19 }
20 }
When executed the program produces an output like:
$ include4.pl
Installing new functions
1+..+5 = 15
user@remote.machine.es->r( include4.pl ) = <1>
user@remote.machine.es->w( include4.pl ) = <1>
user@remote.machine.es->e( include4.pl ) = <1>
user@remote.machine.es->x( include4.pl ) = <1>
user@remote.machine.es->s( include4.pl ) = <498>
user@remote.machine.es->t( include4.pl ) = <>
user@remote.machine.es->f( include4.pl ) = <1>
user@remote.machine.es->d( include4.pl ) = <>
THE GRID::Machine::Core
REMOTE MODULE
The creation of a GRID::Machine
object through a call to GRID::Machine->new
implies the loading of a Remote Module called GRID::Machine::Core
which is delivered with the GRID::Machine
distribution. Another module that is being included at construction time is GRID::Machine::RIOHandle
.
The following functions defined in the Remote Module GRID::Machine::Core
are loaded via the include
mechanism on the remote machine. Therefore, they work as methods of the GRID::Machine
object on the local machine. They perform the same operations than their Perl aliases:
Function getcwd
Function chdir
Function umask
Function mkdir
Function system
Function glob
Function tar
Is equivalent to:
system('tar', $options, ,'-f', $file)
Where $options
is a string containing the options. Returns the error code from tar
. Example:
$m->tar($dist, '-xz')->ok or warn "$host: Can't extract files from $dist\n";
Function version
Syntax:
$machine->version('Some::Module')
Returns the VERSION of the module if the given module is installed on the remote machine and has a VERSION number.
See an example of use:
$ cat version.pl
#!/usr/bin/perl -w
use strict;
use GRID::Machine;
use Data::Dumper;
my $host = $ENV{GRID_REMOTE_MACHINE} ||shift;
my $machine = GRID::Machine->new(host => $host,);
print Dumper($machine->version('Data::Dumper'));
print Dumper($machine->version('Does::Not::Exist::Yet'));
When executed the program produces an output similar to this:
$ version.pl
$VAR1 = bless( {
'stderr' => '',
'errmsg' => '',
'type' => 'RETURNED',
'stdout' => '',
'errcode' => 0,
'results' => [ '2.121_08' ]
}, 'GRID::Machine::Result' );
$VAR1 = bless( {
'stderr' => 'Can\'t locate Does/Not/Exist/Yet.pm in @INC \
(@INC contains: /etc/perl /usr/local/lib/perl/5.8.8 ...
BEGIN failed--compilation aborted.
',
'errmsg' => '',
'type' => 'RETURNED',
'stdout' => '',
'errcode' => 0,
'results' => [ '' ]
}, 'GRID::Machine::Result' );
Function installed
Syntax:
$machine->installed('Some::Module')
Returns TRUE if the given module is installed on the remote machine. Is equivalent to:
system("$^X -M$module -e 0")
ON THE REMOTE SIDE
The Structure of the Remote Server
As with most servers, the server side of the GRID::Machine
object consists of an infinite loop waiting for requests:
while( 1 ) {
my ( $operation, @args ) = $server->read_operation();
if ($server->can($operation)) {
$server->$operation(@args);
next;
}
$server->send_error( "Unknown operation $operation\nARGS: @args\n" );
}
The Protocol
The protocol simply consists of the name of the method to execute and the arguments for such method. The programmer - using inheritance - can extend the protocol with new methods (see the section "EXTENDING THE PROTOCOL"). The following operations are currently supported:
GRID::Machine::EVAL
Used by the local method
eval
GRID::Machine::STORE
Used by the local methods
compile
andsub
to install code on the remote side.GRID::Machine::EXISTS
Used by the local method
exists
GRID::Machine::CALL
Used by the local method
call
GRID::Machine::MODPUT
Used by the
modput
method. A list of pairs (Module::Name, code for Module::Name
) is sent to the remote machine. For each pair, the remote side writes to disk a fileModule/Name.pm
with the contents of the stringcode for Module::Name
. The file is stored in the directory referenced by theprefix
attribute of theGRID::Machine
object.GRID::Machine::OPEN
Used by the
open
method. As arguments receives a string defining the way the file will be accessed.GRID::Machine::QUIT
Usually is automatically called when the
GRID::Machine
object goes out of scope
The SERVER
function
The SERVER
function is available on the remote machine. Returns the object representing the remote side of the GRID::Machine
object. This way code on the remote side can gain access to the GRID::Machine
object. See an example:
my $m = GRID::Machine->new( host => 'beowulf');
$m->sub(installed => q { return keys %{SERVER->stored_procedures}; });
my @functions = $m->installed()->Results;
local $" = "\n";
print "@functions\n";
The stored_procedures
method returns a reference to the hash containing the subroutines installed via the sub
and compile
methods. The keys are the names of the subroutines, the values are the CODE
references implementing them. When executed the former program produces the list of installed subroutines:
$ accessobject.pl
tar
system
installed
getcwd
etc.
The read_operation
Method
Syntax:
my ( $operation, @args ) = $server->read_operation( );
Reads from the link. Returns the type of operation/tag and the results of the operation.
The send_error
Method
Syntax:
$server->send_error( "Error message" );
Inside code to be executed on the remote machine we can use the function send_error
to send error messages to the client
The send_result
Method
Syntax:
$server->send_result(
stdout => $stdout,
stderr => $stderr,
errmsg => $errmsg,
results => [ @results ],
);
Inside code to be executed on the remote machine we can use the function send_result
to send results to the client
EXTENDING THE PROTOCOL
Let us see a simple example. We will extend the protocol with a new tag MYTAG
. We have to write a module that will be used in the remote side of the link:
$ cat -n MyRemote.pm
1 package GRID::Machine;
2 use strict;
3
4 sub MYTAG {
5 my ($server, $name) = @_;
6
7 $server->send_operation("RETURNED", "Hello $name!\n") if defined($name);
8 $server->send_operation("DIED", "Error: Provide a name to greet!\n");
9 }
10
11 1;
This component will be loaded on the remote machine via the ssh link. The name of the handling method MYTAG
must be the same than the name of the tag (operation type) used to send the request. Here is a client program using the new tag:
$ cat -n extendprotocol.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $name = shift;
6 my $host = 'user@remote.machine';
7
8 my $machine = GRID::Machine->new(host => $host, remotelibs => [ qw(MyRemote) ]);
9
10 $machine->send_operation( "MYTAG", $name);
11 my ($type, $result) = $machine->read_operation();
12
13 die $result unless $type eq 'RETURNED';
14 print $result;
When the program is executed we get the following output:
$ extendprotocol.pl Larry
Hello Larry!
$ extendprotocol.pl
Error: Provide a name to greet!
THE GRID::Machine::Result
CLASS
The class GRID::Machine::Result
is used by both the local and remote sides of the GRID::Machine
, though most of its methods are called on the remote side.
The result of a RPC is a GRID::Machine::Result
object. Such object has the following attributes:
type
The
type
of result returned. A string. Fixed by the protocol. Common values areRETURNED
andDIED
.stdout
A string containing the contents of
STDOUT
produced during the duration of the RPCstderr
A string containing the contents of
STDERR
produced during the duration of the RPCresults
A reference to an
ARRAY
containing the results returned by the RPCerrcode
The contents of
$?
as produced during the RPCerrmsg
The contents of
$@
as produced during the RPC
The Constructor new
Syntax:
GRID::Machine::Result->new(
stdout => $rstdout,
errmsg => $err,
stderr => $rstderr,
results => \@results
)
Builds a new result object.
The ok
Method
Returns TRUE
if the RPC didn't died, i.e. if the type
attribute is not the string 'DIED'
The result
Method
Returns the first element of the list referenced by the results
attribute
The Results
Method
Returns the list referenced by the results
attribute
The str
Method. Stringification of a Result
object
Returns the string made of concatenating stdout
, stderr
and errmsg
. The Perl operator q("")
is overloaded using this method. Thus, wherever a GRID::Machine::Result
object is used on a scalar string context the str
will be called.
THE GRID::Machine::Message
CLASS
This class is used by both the local and the remote sides of the GRID::Machine
. It implements the low level communication layer. It is responsible of marshalling the data.
The read_operation
Method
Syntax:
my ( $operation, @args ) = $server->read_operation( );
Returns the kind of operation and the data sent by the other side of the SSH link.
The send_operation
Method
Examples:
$server->send_operation("RETURNED", GRID::Machine::Result->new( %arg ));
$server->send_operation("DIED", GRID::Machine::Result->new(
errmsg => "$server->{host}: $message")
);
$server->send_operation("RETURNED", exists($server->{stored_procedures}{$name}));
Sends to other side of the link the type
of the message and the arguments. It uses Data::Dumper to serialize the data structures.
REMOTE INPUT/OUTPUT
GRID::Machine
objects have the open
method. The open
method returns a GRID::Machine::IOHandle object. Such objects very much behave as IO::Handle objects but instead they refer to handles and files on the associated machine. See a simple example:
use GRID::Machine;
my $machine = shift || 'remote.machine';
my $m = GRID::Machine->new( host => $machine );
my $f = $m->open('> tutu.txt'); # Creates a GRID::Machine::IOHandle object
$f->print("Hola Mundo!\n");
$f->print("Hello World!\n");
$f->printf("%s %d %4d\n","Bona Sera Signorina", 44, 77);
$f->close();
$f = $m->open('tutu.txt');
my $x = <$f>;
print "\n******diamond scalar********\n$x\n";
$f->close();
$f = $m->open('tutu.txt');
my $old = $m->input_record_separator(undef);
$x = <$f>;
print "\n******diamond scalar context and \$/ = undef********\n$x\n";
$f->close();
$old = $m->input_record_separator($old);
A remote GRID::Machine::IOHandle
object is created through the call
my $f = $m->open('> tutu.txt')
from that moment on we can write in the file using the print
and printf
methods of GRID::Machine::IOHandle
objects. You can see later in the former code how the diamond operator can be called to read on a remote file:
my $x = <$f>;
When we run the former example we get an ouput similar to this:
$ synopsisiohandle.pl
******diamond scalar********
Hola Mundo!
******diamond scalar context and $/ = undef********
Hola Mundo!
Hello World!
Bona Sera Signorina 44 77
See also the documentation in GRID::Machine::IOHandle for more detailed information.
EXAMPLE: RUNNING DISTRIBUTION TESTS ON REMOTE MACHINES
The following script examples/remotetest.pl
loads a CPAN distribution on a set of remote machines and run the tests on each of them:
$ examples/remotetest.pl GRID-Machine-0.07.tar.gz orion
************orion************
Writing Makefile for GRID::Machine
Warning: prerequisite Module::Which 0.0205 not found.
Here are the contents:
$ cat -n examples/remotetest.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $dist = shift or die "Usage:\n$0 distribution.tar.gz machine1 machine2 ... \n";
6
7 die "No distribution $dist found\n" unless -r $dist;
8
9 die "Distribution does not follow standard name convention\n"
10 unless $dist =~ m{([\w.-]+)\.tar\.gz$};
11 my $dir = $1;
12
13 die "Usage:\n$0 distribution.tar.gz machine1 machine2 ... \n" unless @ARGV;
14 for my $host (@ARGV) {
15 my $m = GRID::Machine->new(host => $host, startdir => '/tmp');
16
17 $m->put([$dist]);
18
19 $m->tar($dist, '-xz')->ok or do {
20 warn "$host: Can't extract files from $dist\n";
21 next;
22 };
23
24 $m->chdir($dir)->ok or do {
25 warn "$host: Can't change to directory $dir\n";
26 next;
27 };
28
29 print "************$host************\n";
30 next unless $m->run('perl Makefile.PL');
31 next unless $m->run('make');
32 next unless $m->run('make test');
33 }
LIMITATIONS
Operating System
I will be surprised if this module works on anything that is not UNIX.
X Forwarding
X forwarding via ssh -X
does not work. GRID::Machine
uses open2
(see IPC::Open2) to set the connection and shares its limitations. The following program produces an error:
use GRID::Machine qw(is_operative);
use Data::Dumper;
my $host = 'user@remote.machine.domain';
my $machine = GRID::Machine->new(host => $host, ssh => 'ssh -X');
print $machine->eval(q{ system('xclock &') });
When we execute it we get this message:
$ xclock.pl
Warning: No xauth data; using fake authentication data for X11 forwarding.
Can't exec "ssh -X": File or dir does not exists at /usr/share/perl/5.8/IPC/Open3.pm line 168.
open2: exec of ssh -X user@remote.machine.domain perl failed at \
/home/pp2/LGRID_Machine/lib/GRID/Machine.pm line 168
Opaque Structures
The RPC provided by GRID::Machine
uses Data::Dumper to serialize the data. It consequently suffers the same limitations than Data::Dumper.
Namely, Opaque structures like those built by modules written using external languages like C can't be correctly transferred by the RPC system provided by GRID::Machine
. An example is the transference of PDL objects (see PDL). In such cases, the programmer must transform (i.e. marshalling or project) the structure into a (linear) string on one side and rebuild (uplift) the (multidimensional) structure from the string on the other side. See an example:
use GRID::Machine;
use PDL;
use PDL::IO::Dumper;
my $host = shift || 'user@remote.machine.domain';
my $machine = GRID::Machine->new(host => $host, uses => [qw(PDL PDL::IO::Dumper)]);
my $r = $machine->sub( mp => q{
my ($f, $g) = @_;
my $h = (pdl $f) x (pdl $g);
sdump($h);
},
);
$r->ok or die $r->errmsg;
my $f = [[1,2],[3,4]];
$r = $machine->mp($f, $f);
die $r->errmsg unless $r->ok;
my $matrix = eval($r->result);
print "\$matrix is a ".ref($matrix)." object\n";
print "[[1,2],[3,4]] x [[1,2],[3,4]] = $matrix";
Here the sdump
method of PDL::IO::Dumper solves the problem: it gives a string representation of the PDL
object that is eval
ued later to have the matrix data structure. When executed this program produces the following output:
$ uses.pl
$matrix is a PDL object
[[1,2],[3,4]] x [[1,2],[3,4]] =
[
[ 7 10]
[15 22]
]
Wish List
It will benefit this module to have a more OOP oriented dumper
so that if the objet being dumped is opaque but has a Dumper
method, such method will be called. The idea being that anyone developing some sort of external object (C, XS, etc.) will collaboratively define a Dumper
method specifying how the object will be dumped. That is a goal to fulfill.
Call by Reference
Remote Subroutine Call by reference is not supported in this version. See the following example:
use GRID::Machine;
my $machine = GRID::Machine->new(
host => 'user@remote.machine.domain',
startdir => '/tmp',
);
my $r = $machine->sub(byref => q{ $_[0] = 4; });
die $r->errmsg unless $r->ok;
my ($x, $y) = (1, 1);
$y = $machine->byref($x)->result;
print "$x, $y\n"; # 1, 4
Observe that variable $x
is not modified. The only way to modify a variable on the local side by a remote subroutine is by result, like is done for $y
in the previous example.
Limitations of the include
Method
The include
parses Perl code. It is a one page length parser (72 lines at the moment of writing). It obviously can't parse everything. But works for most of the code.
EXPORTS
When explicited by the client program it exports these functions:
is_operative
read_modules
SEE ALSO
Man pages of
ssh
,ssh-key-gen
,ssh_config
,scp
,ssh-agent
,ssh-add
,sshd
CONTRIBUTORS
Dmitriy Kargapolov
AUTHOR
Casiano Rodriguez Leon <casiano@ull.es>
ACKNOWLEDGMENTS
This work has been supported by CEE (FEDER) and the Spanish Ministry of Educación y Ciencia through Plan Nacional I+D+I number TIN2005-08818-C04-04 (ULL::OPLINK project http://www.oplink.ull.es/). Support from Gobierno de Canarias was through GC02210601 (Grupos Consolidados). The University of La Laguna has also supported my work in many ways and for many years.
I wish to thank Paul Evans for his IPC::PerlSSH
module: it was the source of inspiration for this module. To Dmitri Kargapolov for his contributions. Thanks also to Juana, Coro, my students at La Laguna and to the Perl Community.
LICENCE AND COPYRIGHT
Copyright (c) 2007 Casiano Rodriguez-Leon (casiano@ull.es). All rights reserved.
These modules are free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.
This program 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.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 2178:
Non-ASCII character seen before =encoding in 'I<Educación'. Assuming CP1252