NAME
GRID::Machine - Remote Procedure Calls over a SSH link
SYNOPSIS
use GRID::Machine;
my $host = shift || '[email protected]';
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 = "]"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, are the outputs 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
METHODS ON THE LOCAL SIDE
The Constructor new
The typical call looks like:
my $machine = GRID::Machine->new(host => '[email protected]');
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 authentication:
$ perl -MGRID::Machine -e " GRID::Machine->new( host => '[email protected]')"
ssh: connect to host not.available port 22: No route to host
Can't execute perl in [email protected] 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. Also the port. I.e. it can be something like:
my $machine = GRID::Machine->new(host => 'casiano@orion:2048');
If host is the empty string:
my $machine = GRID::Machine->new(host => '');
a process executing perl
in the local machine is open via open2
(no SSH call will be involved).
Instead of specifying the user, port and other ssh
parameters here, the recommended way to work is to insert a section inside the /home/user/.ssh/config
file:
...
# A new section inside the config file:
# it will be used when writing a command like:
# $ ssh gridyum
Host orion
# My username in the remote machine
user casiano
# The actual name of the machine: by default the one provided in the
# command line
Hostname orion.at.some.domain
# The port to use: by default 22
Port 2048
# The identitiy pair to use. By default ~/.ssh/id_rsa and ~/.ssh/id_dsa
IdentityFile /home/user/.ssh/orionid
# Useful to detect a broken network
BatchMode yes
# Useful when the home directory is shared across machines,
# to avoid warnings about changed host keys when connecting
# to local host
NoHostAuthenticationForLocalhost yes
command
This argument is an alternative to the host
argument. Use one or the other. It allows a more specific control of the command executed.
It can be a reference to a list or a string. It fully specifies the command
to execute.
- Example 1: Using password authentication
-
The following example uses Net::OpenSSH to open a SSH connection using password authentication instead of asymmetric cryptography:
$ cat -n openSSH.pl 1 use strict; 2 use warnings; 3 use Net::OpenSSH; 4 use GRID::Machine; 5 6 my $host = (shift() or $ENV{GRID_REMOTE_MACHINE}); 7 my @ARGS; 8 push @ARGS, (user => $ENV{USR}) if $ENV{USR}; 9 push @ARGS, ( password => $ENV{PASS}) if $ENV{PASS}; 10 11 my $ssh = Net::OpenSSH->new($host, @ARGS); 12 $ssh->error and die "Couldn't establish SSH connection: ". $ssh->error; 13 14 my @cmd = $ssh->make_remote_command('perl'); 15 { local $" = ','; print "@cmd\n"; } 16 my $grid = GRID::Machine->new(command => \@cmd); 17 my $r = $grid->eval('print "hello world!\n"'); 18 print "$r\n";
when executed produces an output like this:
$ perl openSSH.pl ssh,-S,/Users/localuser/.libnet-openssh-perl/user-machine-2413-275647,-o,User=user,--,machine,perl hello world!
- Example 2: X11 forwarding
-
The argument associated with
command
can be a string. The following example initiates a SSH connection with the remote machine with X11 forwarding:$ cat -n testptkdb_2.pl 1 #!/usr/local/bin/perl -w 2 # Execute this program being the user 3 # that initiated the X11 session 4 use strict; 5 use GRID::Machine; 6 7 my $host = $ENV{GRID_REMOTE_MACHINE}; 8 9 my $machine = GRID::Machine->new( 10 command => "ssh -X $host perl", 11 ); 12 13 print $machine->eval(q{ 14 print "$ENV{DISPLAY}\n" if $ENV{DISPLAY}; 15 CORE::system('xclock') and warn "Mmmm.. something went wrong!\n"; 16 print "Hello world!\n"; 17 });
It will produce an output like:
$ pp2_testptkdb.pl localhost:11.0
and a graphics clock will pop-up on your window.
- Example 3: Debugging GRID::Machine programs
-
Another example of use of the
command
option is to put the remote side on debugging mode:pp2@nereida:~/LGRID_Machine/examples$ cat netcat3.pl #!/usr/local/bin/perl -w use strict; use GRID::Machine; my $port = shift || 12345; my $debug = qq{PERLDB_OPTS="RemotePort=beowulf:$port"}; my $machine = GRID::Machine->new( command => qq{ssh beowulf '$debug perl -d'}, ); print $machine->eval(q{ system('ls'); print %ENV,"\n"; });
Start by running netcat on the remote side:
pp2@nereida:~/LGRID_Machine/examples$ ssh beowulf nc -v -l beowulf -p 12345
and now run the program:
pp2@nereida:~/LGRID_Machine/examples$ netcat3.pl
The prompt of the debugger will appear in the netcat terminal
No host
and No command
: no SSH connection. Just a process
If neither the host
nor the command
argument are specified, a process executing perl
in the local machine is open via open2
:
$ cat -n commandlocal.pl
1 use strict;
2 use warnings;
3 use GRID::Machine;
4 use Sys::Hostname;
5
6 my $machine = GRID::Machine->new(uses => [ 'Sys::Hostname' ]);
7
8 my $remote = $machine->eval(q{hostname()});
9 my $local = hostname();
10
11 print "Local and remote machines are the same\n" if ($local eq $remote->result);
When executed, this program produces the following output:
$ perl commandlocal.pl
Local and remote machines are the same
logic_id
An integer. Contains the logical identifier associated with the GRID::Machine. By default, 0 if it was the first GRID::Machine created, 1 if it was the second, etc. See an example:
$ cat -n logic_id.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $m1 = GRID::Machine->new( host => shift());
6 my $m2 = GRID::Machine->new( host => shift());
7 my $m3 = GRID::Machine->new( host => shift());
8
9 print $m1->logic_id."\n";
10 print $m2->logic_id."\n";
11 print $m3->logic_id."\n";
the execution produces the following output:
$ ./logic_id.pl machine othermachine somemachine
0
1
2
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 $TMP/rperl$LOCALPID_$REMOTEPID.log
, where $TMP
is the name of the temporary directory as returned by File::Spec-
tmpdir()>, $LOCALPID
is the PID of the process running in the local machines and $REMOTEPID
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 $TMP/rperl$LOCALPID_$REMOTEPID.err
, where $TMP
is the name of the temporary directory as returned by File::Spec-
tmpdir()>, $LOCALPID
is the PID of the process running in the local machines and $REMOTEPID
is the PID of the process running in the remote machine.
report
Relative path of the report file where the (remote) method remotelog
writes. By default the name of this file is $TMP/rperl$LOCALPID_$REMOTEPID.report
, where $TMP
is the name of the temporary directory as returned by File::Spec-
tmpdir()>, $LOCALPID
is the PID of the process running in the local machines and $REMOTEPID
is the PID of the process running in the remote machine. Set cleanup
to false to keep this file.
When executing the following program:
$ cat logerr.pl
use strict;
use GRID::Machine;
my $machine = GRID::Machine->new( host => $ENV{GRID_REMOTE_MACHINE}, cleanup => 0);
print $machine->eval(q{
print File::Spec->tmpdir()."\n";
my @files = glob(File::Spec->tmpdir().'/rperl/*');
local $" = "\n";
print "@files\n";
SERVER->remotelog("This message will be saved in the report file");
});
the output will be similar to this:
~/grid-machine/examples$ perl logerr.pl
/tmp
/tmp/rperl/1309_4318.err
/tmp/rperl/1309_4318.log
/tmp/rperl/1309_4318.report
The report
file contains:
$ ssh $GRID_REMOTE_MACHINE cat /tmp/rperl/1309_4318.report
4318:Sat Apr 16 20:28:22 2011 => This message will be saved in the report file
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 to ssh
.
sshoptions
An ARRAY
ref or a string. Specifies options for the ssh
command. See an example in which is a string:
my $machine = GRID::Machine->new(
host => $host,
sshoptions => '-p 22 -l casiano',
uses => [ 'Sys::Hostname' ]
);
an another in which is an array ref:
my $machine = GRID::Machine->new(
host => $host,
sshoptions => [ '-l', 'casiano'],
uses => [ 'Sys::Hostname' ]
);
scp
A string defining the program to use to transfer files between the local and remote machines. Defaults to scp -q -p
.
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 = '[email protected]';
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.
perloptions
A string or an array ref. Contains the options to be passed to the Perl interpreter. See an example:
my $host = "orion:22";
my $machine = GRID::Machine->new(
host => $host,
sshoptions => '-p 22 -l casiano',
perloptions => [ '-w', '-MSys::Hostname' ],
);
Take into account that -MSys::Hostname
takes place at a very early stage of the boot process and the functions will be exported to the main
package. Therefore a use of the function hostname
exported by Sys::Hostname inside a remote sub
must be done as in this example:
my $r = $machine->sub(
rmap => q{
...
gprint ::hostname(),": Processing @$_\n";
...
}
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 the GRID::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.
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 machine
unshiftinc
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
includes
A reference to an ARRAY of strings. Determines the "remote modules" that will be included when the remote Perl interpreter is started. The enumerated modules must be available on the local side. For instance, the following program loads the "Module":
pp2@nereida:~/LGRID_Machine/examples$ cat -n includes.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $host = shift || die "Usage:\n$0 machine\n";
6
7 my $machine = GRID::Machine->new(
8 host => $host,
9 includes => [ qw{SomeFunc} ],
10 );
11
12 my $r = $machine->max(7, 9, 2, 8);
13
14 print $r;
15 print "Max of (7, 9, 2, 8) is: ".$r->result."\n";
A "remote module" resembles a module but contains only subroutines and uses
. The functions are directly placed in the space name of the GRID::Machine
object (just like the method sub
does. See section "The sub Method"). Here are the contents of the "remote module" SomeFunc.pm
:
pp2@nereida:~/LGRID_Machine/examples$ cat -n SomeFunc.pm
1 use List::Util qw{max};
2 use Sys::Hostname;
3
4 sub max {
5 print "machine: ".hostname().": Inside sub two(@_)\n";
6 List::Util::max(@_)
7 }
when executed, the program produces the following output:
pp2@nereida:~/LGRID_Machine/examples$ includes.pl beowulf
machine: beowulf: Inside sub two(7 9 2 8)
Max of (7, 9, 2, 8) is: 9
debug
The value must be a port number higher than 1024. Used to run the remote side under the control of the debugger. See the section "REMOTE DEBUGGING"
my $machine = GRID::Machine->new(
host => $host,
debug => $port,
includes => [ qw{SomeFunc} ],
);
survive
No exception will be produced if the connection fails. Instead undef
is returned. Often used when building several GRID::Machines and you don't care (too much) if one of them fails::
$ cat pi8.pl
#!/usr/bin/perl -w
use strict;
use GRID::Machine;
use GRID::Machine::Group;
use Data::Dumper;
my @MACHINE_NAMES = split /\s /, $ENV{MACHINES};
my @m = map { GRID::Machine->new(host => $_, wait => 5, survive => 1) } @MACHINE_NAMES;
my $c = GRID::Machine::Group->new(cluster => [ @m ]);
$c->sub(suma_areas => q{
my ($id, $N, $np) = @_;
my $sum = 0;
for (my $i = $id; $i < $N; $i = $np) {
my $x = ($i 0.5) / $N;
$sum = 4 / (1 $x * $x);
}
$sum /= $N;
});
my ($N, $np, $pi) = (1000, 4, 0);
print Dumper($c->suma_areas(args => [ map { [$_, $N, $np] } 0..$np-1 ]));
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 => '[email protected]');
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):
~/grid-machine/examples$ cat -n syntaxerr2.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 => '[email protected]');
7
8 my $p = { name => 'Peter', familyname => [ 'Smith', 'Garcia'] };
9
10 my $r = $machine->eval( q{ $q = shift; $q->{familyname} }, $p);
11
12 die Dumper($r) unless $r->ok;
13
14 print "Still alive\n";
When executed this code produces something like:
$VAR1 = bless( {
'stderr' => '',
'errmsg' => '[email protected]: Error while compiling eval \'$q = shift; $q->{fam...\'
Global symbol "$q" requires explicit package name at syntaxerr2.pl line 10, <STDIN> line 230.
Global symbol "$q" requires explicit package name at syntaxerr2.pl line 10, <STDIN> line 230.',
'type' => 'DIED',
'stdout' => '',
'errcode' => 0
}, 'GRID::Machine::Result' );
The error message accurately reports the correct source offending line.
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;
Scope and Visibility 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 qw(qc);
4
5 my $machine = GRID::Machine->new(host => 'user@remote');
6
7 $machine->eval(q{
8 our $h;
9 $h = [4..9];
10 });
11
12 my $r = $machine->eval(qc q{
13 $h = [map {$_*$_} @$h];
14 });
15
16 die $r unless $r->noerr;
The interpreter complains about $h
:
$ vars1.pl
user@remote: Error while compiling eval. \
Global symbol "$h" requires explicit package name at ./vars1.pl line 13,\
<STDIN> line 198.
Global symbol "$h" requires explicit package name at ./vars1.pl line 13, \
<STDIN> line 198.
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 (involuntary) closure. See the example:
$ cat -n vars5.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine qw(qc);
4
5 my $machine = GRID::Machine->new(host => '[email protected]');
6
7 my $r = $machine->eval(qc q{
8 my $h = 1;
9
10 sub dumph {
11 print "$h\n";
12 $h
13 }
14
15 dumph();
16 });
17
18 print "Result: ".$r->result."\nWarning: ".$r->stderr;
19
20 $r = $machine->eval(qc q{
21 dumph();
22 });
23
24 print "Result: ".$r->result."\nWarning: ".$r->stderr;
When executed, the program produces the following warning:
$ vars5.pl
Result: 1
Warning: Variable "$h" will not stay shared at ./vars5.pl line 11\
, <STDIN> line 194.
Result: 2
Warning: Variable "$h" will not stay shared at ./vars5.pl line 11,\
<STDIN> line 194.
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 through a reference:
$ cat -n vars6.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine qw(qc);
4
5 my $machine = GRID::Machine->new(host => '[email protected]');
6
7 my $r = $machine->eval(qc q{
8 my $h = 1;
9
10 use vars '$dumph';
11 $dumph = sub {
12 print "$h";
13 $h ;
14 };
15
16 $dumph->();
17 });
18
19 print "$r\n";
20
21 $r = $machine->eval(qc q{
22 $dumph->();
23 });
24
25 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, %args )
Valid arguments (%args
) are::
politely => $politely
filter => $filter
around => sub { ... }
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 installed method $name
can also be accessed as an ordinary function $name
on the remote side. If a function with the same name already exists, the oldest prevails. See the call to function hi
at line 15 in this example:
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $host = $ENV{GRID_REMOTE_MACHINE};
6 my $debug = @ARGV ? 1234 : 0;
7
8 my $machine = GRID::Machine->new(host => $host, debug => $debug);
9
10 $machine->sub( hi => q{ my $n = shift; "Hello $n\n"; } );
11
12 print $machine->hi('Jane')->result;
13
14 # same thing
15 print $machine->eval(q{ hi(shift()) }, 'Jane')->result;
The execution produces the following output:
$ perl subfromserver.pl
Hello Jane
Hello Jane
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 filter is executed in the remote side of the GRID::Machine.
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 around
argument
A CODE
reference. By default GRID::Machine produces a proxy representative in the local side for the sub
being installed. The code of the proxy simply calls the corresponding sub
in the remote side:
sub { my $self = shift; $self->call( $name, @_ ) };
You can substitute the proxy code by your own code using the around
parameter. The proxy code receives the GRID::Machine object and the arguments for the remote side of the subroutine. See the following example:
$ cat around.pl
#!/usr/bin/perl -w
use strict;
use GRID::Machine;
my $machine = GRID::Machine->new( host => $ENV{GRID_REMOTE_MACHINE} || shift);
$machine->sub(
squares => q{ map { $_*$_ } @_ },
filter => 'results',
around => sub {
my $self = shift;
my $r = $self->call( 'squares', @_ );
map { $_ 1 } @$r;
}
);
my @x = (3..5);
my @r = $machine->squares(@x);
print "@r\n";
When called, the squares
function computes the squares on the remote side and adds one to each element in the local side:
$ perl around.pl
10 17 26
The makemethod
Method
Syntax:
$machine->makemethod( $name, %args )
Valid arguments (%args
) are::
politely => $politely
filter => $filter
around => sub { ... }
This method is identical to the sub
method, except that it assumes the sub $name
has been already installed in the remote side. The $name
can be a fully qualified name, but the method call must use the short name. The following example produces a proxy method for the function reduce
which is already available at the remote machine:
$ cat -n makemethod.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $host = shift || $ENV{GRID_REMOTE_MACHINE};
6
7 my $m = GRID::Machine->new(host => $host, uses => [q{List::Util qw{reduce}}]);
8
9 $m->makemethod( 'reduce', filter => 'result' );
10 my $r = $m->reduce(sub { $a > $b ? $a : $b }, (7,6,5,12,1,9));
11 print "\$r = $r\n";
12
13 my $m2 = GRID::Machine->new(host => $host, uses => [q{List::Util}]);
14
15 $m2->makemethod( 'List::Util::reduce' );
16 $r = $m2->reduce(sub { $a > $b ? $a : $b }, (7,6,5,12,1,9));
17 die $r->errmsg unless $r->ok;
18 print "\$r = ".$r->result."\n";
The execution produces:
$ perl -w makemethod.pl
$r = 12
$r = 12
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.
The makemethods
Method
Convenience method to install several methods in a row. The following call installs methods fork
, waitpid
, kill
and poll
:
$self->makemethods(
[ 'fork', filter=>'result',
around => sub {
my $self = shift;
my $r = $self->call( 'fork', @_ );
$r->{machine} = $self;
$r
},
],
[ 'waitpid', filter=>'result', ],
[ 'kill', filter=>'result', ],
[ 'poll', filter=>'result', ],
);
Nested Structures
Nested Perl Data Structures can be transferred between the local and remote machines transparently:
use Data::Dumper;
my $host = shift || '[email protected]';
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 24) 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 qw(qc);
4
5 my $machine = GRID::Machine->new(host => shift(), uses => [ 'Sys::Hostname' ]);
6
7 my $r = $machine->sub( iguales => qc q{
8 my ($first, $sec) = @_;
9
10 print hostname().": $first and $sec are ";
11
12 if ($first == $sec) {
13 print "the same\n";
14 return 1;
15 }
16 print "Different\n";
17 return 0;
18 },
19 );
20 $r->ok or die $r->errmsg;
21
22 my $w = [ 1..3 ];
23 my $z = $w;
24 $r = $machine->iguales($w, $z);
25 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 || '[email protected]';
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 || '[email protected]';
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.
If $machine
is the empty string i.e. $machine eq ''
, it refers to a direct connection to the local machine and thus, it succeeds most of the time.
The Function qc
Prefixes the string passed as argument with the string #line $LINE $FILE
where $LINE
and $FILE
are the calling line and calling file respectively. Used to provide more accurate error messages when evaluating remote code. See section "Errors and Exceptions".
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
The copyandmake
Method
Syntax:
$m->copyandmake(
dir => $dir,
files => [ @files ], # files to transfer
make => $command, # execute $command $commandargs
makeargs => $commandargs, # after the transference
cleanfiles => $cleanup, # remove files at the end
cleandirs => $cleanup, # remove the whole directory at the end
)
copyandmake
copies (using scp
) the files @files
to a directory named $dir
in the remote machine. The directory $dir
will be created if it does not exists. After the file transfer the command
specified by the copyandmake
option
make => 'command'
will be executed with the arguments specified in the option makeargs
. If the make
option isn't specified but there is a file named Makefile
between the transferred files, the make
program will be executed. Set the make
option to number 0 or the string ''
if you want to avoid the execution of any command after the transfer. The transferred files will be removed when the connection finishes if the option cleanfiles
is set. If the option cleandirs
is set, the created directory and all the files below it will be removed. Observe that the directory and the files will be kept if they were'nt created by this connection. The call to copyandmake
by default sets dir
as the current directory in the remote machine. Use the option keepdir => 1
to one to avoid this.
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} || '[email protected]';
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
[email protected] can do LAST
[email protected] 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} || '[email protected]';
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
[email protected] can do one
sub one
[email protected] can do two
sub two
[email protected] 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} || '[email protected]';
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
[email protected]>r( include4.pl ) = <1>
[email protected]>w( include4.pl ) = <1>
[email protected]>e( include4.pl ) = <1>
[email protected]>x( include4.pl ) = <1>
[email protected]>s( include4.pl ) = <498>
[email protected]>t( include4.pl ) = <>
[email protected]>f( include4.pl ) = <1>
[email protected]>d( include4.pl ) = <>
Specifying filters and proxys via #gm
We can include comments grid machine comments
between the name of the subroutine and the open curly bracket to specify the options for the sub
installation. The comments must start with #gm
. See an example:
$ cat -n Include6.pm
1 use strict;
2
3 sub last
4 #gm (filter => 'result', )
5 {
6 $_[-1]
7 }
8
9 sub LASTitem
10 {
11 $_[-1]
12 }
13
14 sub one
15 #gm (
16 #gm filter => 'result',
17 #gm around => sub {
18 #gm my $self = shift;
19 #gm my $r = $self->call( 'one', @_ );
20 #gm use Sys::Hostname;
21 #gm $r."Local machine: ".hostname()."\n"
22 #gm },
23 #gm )
24 {
25 SERVER->host." received: <@_>\n";
26 }
Sub last
will return just the result
instead of the full GRID::Machine::Result object. We have also substituted the proxy representative of method one
using the around
parameter. Consider the following script example:
$ cat -n include6.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4 use Data::Dumper;
5
6 my $host = $ENV{GRID_REMOTE_MACHINE};
7
8 my $machine = GRID::Machine->new( host => $host);
9
10 $machine->include("Include6");
11
12 print $machine->last(4..9)."\n";
13
14 my $r = $machine->LASTitem(4..9);
15 print Dumper($r);
16
17 print $machine->one(4..9)."\n";
when executed, produces:
$ perl include6.pl
9
$VAR1 = bless( {
'stderr' => '',
'errmsg' => '',
'type' => 'RETURNED',
'stdout' => '',
'errcode' => 0,
'results' => [
9
]
}, 'GRID::Machine::Result' );
some.machine received: <4 5 6 7 8 9>
Local machine: my.local.machine
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
.
One of the final goals of the GRID::Machine::Core
remote module is to provide homonymous methods per each of the Perl CORE::
functions. At present time only a few are supported.
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
Executes system
on the remote machine. See an example:
$ cat -n examples/transfer2.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine qw(is_operative);
4 use Data::Dumper;
5
6 my $host = shift || $ENV{GRID_REMOTE_MACHINE};
7
8 my $machine = GRID::Machine->new(
9 host => $host,
10 cleanup => 1,
11 sendstdout => 1,
12 startdir => '/tmp/perl5lib',
13 prefix => '/tmp/perl5lib/',
14 );
15
16 my $dir = $machine->getcwd->result;
17 print "$dir\n";
18
19 $machine->modput('Parse::Eyapp::') or die "can't send module\n";
20
21 print $machine->system('tree');
22 my $r = $machine->system('doesnotexist');
23 print Dumper $r;
Observe the overloading of bool
at line 19. modput
returns a GRID::Machine::Result
object which is evaluated in a Boolean context as a call to the result
getter.
When executed produces an output like:
$ perl -w examples/transfer2.pl
/tmp/perl5lib
.
`---- Parse
`---- Eyapp
|---- Base.pm
|---- Cleaner.pm
|---- Driver.pm
|---- Grammar.pm
|---- Lalr.pm
|---- Node.pm
|---- Options.pm
|---- Output.pm
|---- Parse.pm
|---- Scope.pm
|---- TokenGen.pm
|---- Treeregexp.pm
|---- _TreeregexpSupport.pm
|---- Unify.pm
`---- YATW.pm
2 directories, 15 files
$VAR1 = bless( {
'stderr' => 'Can\'t exec "doesnotexist":',
'errmsg' => '',
'type' => 'RETURNED',
'stdout' => '',
'errcode' => -1,
'results' => [ -1 ]
}, 'GRID::Machine::Result' );
Function qx
Similar to backtick quotes. The result depends on the context. In a list context returns a list with the lines of the output. In a scalar context reurns a string with the output. The value of $"
on the local machine decides the register separator used. See an example:
$ cat -n transfer3.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine qw(is_operative);
4 use Data::Dumper;
5
6 my $host = shift || '[email protected]';
7
8 my $machine = GRID::Machine->new( host => $host );
9 my $DOC = << "DOC";
10 one. two. three.
11 four. five. six.
12 seven.
13 DOC
14
15 # List context: returns a list with the lines
16 {
17 local $/ = '.';
18 my @a = $machine->qx("echo '$DOC'");
19 local $"= ",";
20 print "@a";
21 }
22
23 # scalar context: returns a string with the output
24 my $a = $machine->qx("echo '$DOC'");
25 print $a;
When executed produces the following output:
$ transfer3.pl
one., two., three.,
four., five., six.,
seven.,
one. two. three.
four. five. six.
seven.
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")
File Status Methods
Methods that are equivalent to the tests function
-r -w -e -x -z -s -f -d -t -T -B -M -A -C
are provided. Since hyphens aren't legal in Perl identifiers the hyphen has been substituted by an underscore. See an example:
$ cat -n copyandmkdir.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $host = 'remote.machine.es';
6 my $dir = shift || "somedir";
7 my $file = shift || $0; # By default copy this program
8
9 my $machine = GRID::Machine->new(
10 host => $host,
11 uses => [qw(Sys::Hostname)],
12 );
13
14 my $r;
15 $r = $machine->mkdir($dir, 0777) unless $machine->_w($dir);
16 die "Can't make dir\n" unless $r->ok;
17 $machine->chdir($dir)->ok or die "Can't change dir\n";
18 $machine->put([$file]) or die "Can't copy file\n";
19 print "HOST: ",$machine->eval(" hostname ")->result,"\n",
20 "DIR: ",$machine->getcwd->result,"\n",
21 "FILE: ",$machine->glob('*')->result,"\n";
When this program runs we get an output similar to this:
$ copyandmkdir.pl
HOST: orion
DIR: /home/casiano/somedir
FILE: copyandmkdir.pl
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 scopeGRID::Machine::GPRINT
Most requests go from the local machine to the remote Perl server. However, this and the next go in the other direction. This request is generated in the remote machine and served by the local machine. It is used when inmediate printing is required (see section "Functions gprint and gprintf")
GRID::Machine::GPRINTF
This request is generated in the remote machine and served by the local machine. It is used when inmediate printing is required (see section "Functions gprint and gprintf")
GRID::Machine::CALLBACK
Used to implement callbacks
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 = '[email protected]';
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!
INMEDIATE PRINTING
Functions gprint
and gprintf
When running a RPC the output generated during the execution of the remote subroutine isn't available until the return of the RPC. Use gprint
and gprintf
if what you want is inmediate output (for debugging purposes, for instance). They work as print
and printf
respectively.
See an example:
$ cat -n gprint.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $host = $ENV{GRID_REMOTE_MACHINE};
6
7 my $machine = GRID::Machine->new(host => $host, uses => [ 'Sys::Hostname' ]);
8
9 my $r = $machine->sub(
10 rmap => q{
11 my $f = shift; # function to apply
12 die "Code reference expected\n" unless UNIVERSAL::isa($f, 'CODE');
13
14
15 print "Inside rmap!\n"; # last message
16 my @result;
17 for (@_) {
18 die "Array reference expected\n" unless UNIVERSAL::isa($_, 'ARRAY');
19
20 gprint hostname(),": Processing @$_\n";
21
22
23 push @result, [ map { $f->($_) } @$_ ];
24 }
25
26 gprintf "s:\n",hostname();
27 for (@result) {
28 my $format = "]"x(@$_)."\n";
29 gprintf $format, @$_
30 }
31 return @result;
32 },
33 );
34 die $r->errmsg unless $r->ok;
35
36 my $cube = sub { $_[0]**3 };
37 $r = $machine->rmap($cube, [1..3], [4..6], [7..9]);
38 print $r;
When executed the program produces the following output:
$ gprint.pl
orion: Processing 1 2 3
orion: Processing 4 5 6
orion: Processing 7 8 9
orion:
1 8 27
64 125 216
343 512 729
Inside rmap!
Observe how the message 'Inside rmap!'
generated at line 15 using print
is the last (actually is sent to STDOUT
in line 38). The messages generated using gprint
and gprintf
(lines 20, 26 and 29) were inmediately sent to STDOUT
.
REMOTE DEBUGGING
To run the remote side under the control of the perl debugger use the debug
option of new
. The associated value must be a port number higher than 1024:
my $machine = GRID::Machine->new(
host => $host,
debug => $port,
includes => [ qw{SomeFunc} ],
);
Before running the example open a SSH session to the remote machine in a different terminal and execute netcat
to listen (option -l
) in the chosen port:
pp2@nereida:~/LGRID_Machine$ ssh beowulf 'netcat -v -l -p 12345'
listening on [any] 12345 ...
Now run the program in the first terminal:
pp2@nereida:~/LGRID_Machine/examples$ debug1.pl beowulf:12345
Debugging with 'ssh beowulf PERLDB_OPTS="RemotePort=beowulf:12345" perl -d'
Remember to run 'netcat -v -l -p 12345' in beowulf
The program looks blocked. If you go to the other terminal you will find the familiar perl debugger prompt:
casiano@beowulf:~$ netcat -v -l -p 12345
listening on [any] 12345 ...
connect to [193.145.102.240] from beowulf.pcg.ull.es [193.145.102.240] 38979
Loading DB routines from perl5db.pl version 1.28
Editor support available.
Enter h or `h h' for help, or `man perldebug' for more help.
GRID::Machine::MakeAccessors::(/home/pp2/LGRID_Machine/lib/GRID/Machine/MakeAccessors.pm:33):
33: 1;
auto(-1) DB<1> c GRID::Machine::main
GRID::Machine::main(/home/pp2/LGRID_Machine/lib/GRID/Machine/REMOTE.pm:490):
490: my $server = shift;
DB<2>
From now on you can execute almost any debugger command. Unfortunately you are now inside GRID::Machine
code and - until you gain some familiarity with GRID::Machine
code - it is a bit difficult to find where your code is and where to put your breakpoints. Future work: write a proper debugger front end.
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 noerr
Method
Returns TRUE
if the RPC didn't died and didn't send any messages through stderr. See an example. When running the following program:
$ cat noerrvsok.pl
#!/usr/local/bin/perl -w
use strict;
use GRID::Machine;
my $machine = shift || $ENV{GRID_REMOTE_MACHINE};
my $m = GRID::Machine->new( host => $machine );
my $r = $m->eval( q{print STDERR "This is the end\n" });
print "print to STDERR:\n";
print "<".$r->ok.">\n";
print "<".$r->noerr.">\n";
$r = $m->eval( q{warn "This is a warning\n" });
print "Warn:\n";
print "<".$r->ok.">\n";
print "<".$r->noerr.">\n";
we get the following output:
$ errvsok.pl
print to STDERR:
<1>
<>
Warn:
<1>
<>
The result
Method
Returns the first element of the list referenced by the results
attribute This method is called when a GRID::Machine::Result
object is evaluated in a Boolean context (i.e. bool
is overloaded).
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 M\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.
REMOTE PIPES
Opening pipes for input
The open
method of GRID::Machine
objects can be used to pipe programs as in the following example:
pp2@nereida:~/LGRID_Machine/examples$ cat -n pipes1.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( host => $machine );
7
8 my $f = $m->open('uname -a |');
9 my $x = <$f>;
10 print "UNAME result: $x\n"
In a scalar context open
returns the handler. In list context returns the pair (handler, PID)
. See GRID::Machine::perlparintro for a more detailed example.
When executed the program produces an output similar to this:
pp2@nereida:~/LGRID_Machine/examples$ pipes1.pl
UNAME result: Linux remote 2.6.8-2-686 #1 Tue Aug 16 13:22:48 UTC 2005 i686 GNU/Linux
Opening pipes for output
Pipes can be also for input as the following example shows:
pp2@nereida:~/LGRID_Machine/examples$ cat -n pipes.pl
1 #!/usr/local/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $machine = shift || 'remote.machine';
6 my $m = GRID::Machine->new( host => $machine );
7
8 my $i;
9 my $f = $m->open('| sort -n > /tmp/sorted.txt');
10 for($i=10; $i>=0;$i--) {
11 $f->print("$i\n")
12 }
13 $f->close();
14
15 my $g = $m->open('/tmp/sorted.txt');
16 print while <$g>;
when executed, the program produces the following output:
pp2@nereida:~/LGRID_Machine/examples$ pipes.pl
0
1
2
3
4
5
6
7
8
9
10
When opening a pipe for output like in line 9 in the former example
my $f = $m->open('| sort -n > /tmp/sorted.txt')
be sure to redirect the STDOUT
of the program. Otherwise, GRID::Machine
will redirect it to the null
device and the output will be lost.
Bidirectional pipes: open2
Synopsis:
my $WTR = IO::Handle->new();
my $RDR = IO::Handle->new();
my $pid = $m->open2($fromchild, $tochild, 'command and args');
The open2
method runs the given command in machine $m
and connects $fromchild
for reading from command
and $tochild
for writing from command
. Returns the PID of the process executing command
.
Bidirectional pipes: open3
Synopsis:
my $pid = $m->open3($tochild, $fromchild, $errfromchild, 'command and args');
Spawns the given command and connects $fromchild
for reading from the child, $tochild
for writing to the child, and $errfromchild
for errors.
See an example that opens the Unix calculator bc
in a remote machine:
$ cat -n open3bc.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $machine = shift || 'orion.pcg.ull.es';
6 my $m = GRID::Machine->new( host => $machine );
7
8 my $WTR = IO::Handle->new();
9 my $RDR = IO::Handle->new();
10 my $ERR = IO::Handle->new();
11 my $pid = $m->open3($WTR, $RDR, $ERR, 'bc');
12
13 my $line;
14
15 print $WTR "3*2\n";
16 $line = <$RDR>;
17 print STDOUT "3*2 = $line";
18
19 print $WTR "3/(2-2)\n";
20 $line = <$ERR>;
21 print STDOUT "3/(2-2) produces error = $line\n";
22
23 print $WTR "quit\n";
24 wait;
When executed, the former program produces an output like this:
$ open3bc.pl
3*2 = 6
3/(2-2) produces error = Runtime error (func=(main), adr=11): Divide by zero
REMOTE PROCESSES (FORKING)
The fork
method
The fork
method of GRID::Machine
objects can be used to fork a process in the remote machine, as shown in the following example:
$ cat -n fork5.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4 use Data::Dumper;
5
6 my $host = $ENV{GRID_REMOTE_MACHINE};
7 my $machine = GRID::Machine->new( host => $host );
8
9 my $p = $machine->fork( q{
10
11 print "stdout: Hello from process $$. args = (@_)\n";
12 print STDERR "stderr: Hello from process $$\n";
13
14 use List::Util qw{sum};
15 return { s => sum(@_), args => [ @_ ] };
16 },
17 args => [ 1..4 ],
18 );
19
20 # GRID::Machine::Process objects are overloaded
21 print "Doing something while $p is still alive ...\n" if $p;
22
23 my $r = $machine->waitpid($p);
24
25 print "Result from process '$p': ",Dumper($r),"\n";
26 print "GRID::Machine::Process::Result objects are overloaded in a string context:\n$r\n";
When executed, the former program produces an output similar to this:
$ perl fork5.pl
Doing something while 5220:5230:some.machine:5234:5237 is still alive ...
Result from process '5220:5230:some.machine:5234:5237': $VAR1 = bless( {
'machineID' => 0,
'stderr' => 'stderr: Hello from process 5237
',
'descriptor' => 'some.machine:5234:5237',
'status' => 0,
'waitpid' => 5237,
'errmsg' => '',
'stdout' => 'stdout: Hello from process 5237. args = (1 2 3 4)
',
'results' => [ { 'args' => [ 1, 2, 3, 4 ], 's' => 10 } ]
}, 'GRID::Machine::Process::Result' );
GRID::Machine::Process::Result objects are overloaded in a string context:
stdout: Hello from process 5237. args = (1 2 3 4)
stderr: Hello from process 5237
The fork
method returns a GRID::Machine::Process object. The first argument must be a string containing the code that will be executed by the forked process in the remote machine. Such code is always called in a list context. The fork
method admits the following arguments:
stdin
The name of the file to which
stdin
will be redirectedstdout
The name of the file to which
stdout
will be redirected. If not specified a temporary file will be usedstderr
The name of the file to which
stderr
will be redirected. If not specified a temporary file will be usedresult
The name of the file to which the result computed by the child process will be dumped. If not specified a temporary file will be used
args
The arguments for the code executed by the remote child process
GRID::Machine::Process
objects
GRID::Machine::Process objects have been overloaded. In a string context a GRID::Machine::Process object produces the concatenation hostname:clientPID:remotePID
. In a boolean context it returns true if the process is alive and false otherwise. This way, the execution of line 21 in the program above:
21 print "Doing something while $p is still alive ...\n" if $p;
produces an output like:
Doing something while 5220:5230:some.machine:5234:5237 is still alive ...
if the remote process is still alive. The descriptor of the process 5220:5230:some.machine:5234:5237
is a colon separated sequence of five components:
- 1 - The PID of the local process executing GRID::Machine
- 2 - The PID of the local process in charge of the connection with the remote machine
- 3 - The name of the remote machine
- 4 - The PID of the remote process executing GRID::Machine::REMOTE
- 5 - The PID of the child process created by
fork
When evaluated in a boolean context, a GRID::Machine::Process returns 1 if it is alive and 0 otherwise.
The waitpid
method
The waitpid
method waits for the GRID::Machine::Process received as first argument to terminate. Additional FLAGS
as in perl waitpid
can be passed as arguments. It returns a GRID::Machine::Process::Result object, whose attributes contain:
stdout
A string containing the output to
STDOUT
of the remote child processstderr
A string containing the output to
STDERR
of the remote child processresults
The list of values returned by the child process. The forking code is always called in a list context.
status
The value associated with
$?
as returned by the remote child process.waitpid
The value returned by the Perl
waitpid
function when synchronized with the remote child process. It is usually the value is either thepid
of the deceased process, or-1
if there was no such child process. On some systems, a value of0
indicates that there are processes still running.errmsg
The child error as in
$@
machineID
The logical identifier of the associated GRID::Machine. By default, 0 if it was the first GRID::Machine created, 1 if it was the second, etc.
The waitall
method
It is similar to waitpid
but instead waits for any child process.
Behaves like the wait(2) system call on your system: it waits for a child process to terminate and returns
The
GRID::Machine::Process::Result
object associated with the deceased process if it was called via the GRID::Machinefork
method, orThe PID of the deceased process if there is no
GRID::Machine::Process
associated (it was called using an ordinaryfork
)-1
if there are no child processes. Note that a return value of-1
could mean that child processes are being automatically reaped, as described in perlipc.
See an example:
$ cat -n wait1.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4 use Data::Dumper;
5
6 my $host = $ENV{GRID_REMOTE_MACHINE};
7 my $machine = GRID::Machine->new( host => $host );
8
9 my $p = $machine->fork( q{
10
11 print "stdout: Hello from process $$. args = (@_)\n";
12 print STDERR "stderr: Hello from process $$\n";
13
14 use List::Util qw{sum};
15 return { s => sum(@_), args => [ @_ ] };
16 },
17 args => [ 1..4 ],
18 );
19
20 # GRID::Machine::Process objects are overloaded
21 print "Doing something while $p is still alive ...\n" if $p;
22
23 my $r = $machine->waitall();
24
25 print "Result from process '$p': ",Dumper($r),"\n";
26 print "GRID::Machine::Process::Result objects are overloaded in a string context:\n$r\n";
When executed produces:
$ perl wait1.pl
Doing something while 1271:1280:local:1284:1287 is still alive ...
Result from process '1271:1280:local:1284:1287': $VAR1 = bless( {
'machineID' => 0,
'stderr' => 'stderr: Hello from process 1287
',
'descriptor' => 'local:1284:1287',
'status' => 0,
'waitpid' => 1287,
'errmsg' => '',
'stdout' => 'stdout: Hello from process 1287. args = (1 2 3 4)
',
'results' => [ { 'args' => [ 1, 2, 3, 4 ], 's' => 10 } ]
}, 'GRID::Machine::Process::Result' );
GRID::Machine::Process::Result objects are overloaded in a string context:
stdout: Hello from process 1287. args = (1 2 3 4)
stderr: Hello from process 1287
The following example uses the fork
method and waitall
to compute in parallel a numerical approach to the value of the number pi
:
$ cat -n waitpi.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $host = $ENV{GRID_REMOTE_MACHINE};
6 my $machine = GRID::Machine->new( host => $host );
7
8 my ($N, $np, $pi) = (1000, 4, 0);
9 for (0..$np-1) {
10 $machine->fork( q{
11 my ($id, $N, $np) = @_;
12
13 my $sum = 0;
14 for (my $i = $id; $i < $N; $i = $np) {
15 my $x = ($i 0.5) / $N;
16 $sum = 4 / (1 $x * $x);
17 }
18 $sum /= $N;
19 },
20 args => [ $_, $N, $np ],
21 );
22 }
23
24 $pi = $machine->waitall()->result for 1..$np;
25
26 print "pi = $pi\n";
The async
method
The async
method it is quite similar to the fork method but receives as arguments the name of a GRID::Machine method and the arguments for this method. It executes asynchronously the method. It returns a GRID::Machine::Process object. Basically, the call
$m->async($subname => @args)
is equivalent to:
$m->fork($subname.'(@_)' args => [ @args ] )
The following example uses async
to compute in parallel an approximation to the value of pi:
$ cat -n async.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4
5 my $host = $ENV{GRID_REMOTE_MACHINE};
6 my $machine = GRID::Machine->new( host => $host );
7
8 $machine->sub(sumareas => q{
9 my ($id, $N, $np) = @_;
10
11 my $sum = 0;
12 for (my $i = $id; $i < $N; $i = $np) {
13 my $x = ($i 0.5) / $N;
14 $sum = 4 / (1 $x * $x);
15 }
16 $sum /= $N;
17 });
18
19 my ($N, $np, $pi) = (1000, 4, 0);
20
21 $machine->async( sumareas => $_, $N, $np ) for (0..$np-1);
22 $pi = $machine->waitall()->result for 1..$np;
23
24 print "pi = $pi\n";
GRID::Machine::Process::Result objects
In a string context a GRID::Machine::Process::Result
object produces the concatenation of its output to STDOUT
followed by its output to STDERR
. In a boolean context it evaluates according to its result
attribute. It evaluates to true if it is an array reference with more than one element or if the only element is true. Otherwise it is false.
CALLBACKS
It may happen that the local machine has installed a useful set of modules that are not present on the remote side. It may be also imposible to transfer the modules to the remote machine using the mechanisms provided by GRID::Machine
. In such situations -and many others - the callback mechanism can be helpful to achieve the task at hand.
The callback
method provides a way to make a subroutine on the local side callable from the remote side. The ideas and implementation mechanisms used for callbacks is the work of Dmitriy Kargapolov (Thanks Dmitri!).
The syntax is:
$r = $machine->callback( 'localsubname' );
$r = $machine->callback( localsub => sub { ... } );
$r = $machine->callback( localsub => subref );
$r = $machine->callback( sub { ... } );
$r = $machine->callback( subref );
On success returns true, namely returns the address of the subroutine on the remote side that works as proxy of the localsub
subroutine on the local side. Exceptions will be thrown in case of failure.
The following example shows a remote subroutine (lines 16-20) that calls a subroutine test_callback
that will be executed on the local side (line 19). The call to the method callback
at line 23 makes the local subroutine test_callback
available from the remote side.
$ cat -n callbackbyname2.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4 use Sys::Hostname;
5
6 my $host = shift || $ENV{GRID_REMOTE_MACHINE};
7
8 sub test_callback {
9 print 'Inside test_callback(). Host is '.&hostname."\n";
10
11 return shift() 1;
12 }
13
14 my $machine = GRID::Machine->new(host => $host, uses => [ 'Sys::Hostname' ]);
15
16 my $r = $machine->sub( remote => q{
17 gprint hostname().": inside remote\n";
18
19 return 1 test_callback(2);
20 } );
21 die $r->errmsg unless $r->ok;
22
23 $r = $machine->callback( 'test_callback' );
24 die $r->errmsg unless $r->ok;
25
26 $r = $machine->remote();
27 die $r->errmsg unless $r->noerr;
28
29 print "Result: ".$r->result."\n";
When the former program is executed (local machine is nereida) we get an output similar to this:
$ callbackbyname2.pl
beowulf: inside remote
Inside test_callback(). Host is nereida.deioc.ull.es
Result: 4
Callbacks and Namespaces
The callback subroutine is somewhat exported onto the remote side. That is, when transforming a local subroutine in a callback you can specify it by its full name (see line 24 below) but it is called from the remote side using its single name (line 18):
$ cat -n callbackbyname3.pl
1 #!/usr/bin/perl -w
2 use strict;
3 use GRID::Machine;
4 use Sys::Hostname;
5
6 my $host = $ENV{GRID_REMOTE_MACHINE};
7
8 sub Tutu::test_callback {
9 print 'Inside test_callback() host: ' . &hostname . "\n";
10 return 3.1415;
11 }
12
13 my $machine = GRID::Machine->new(host => $host, uses => [ 'Sys::Hostname' ]);
14
15 my $r = $machine->sub( remote => q{
16 gprint hostname().": inside remote\n";
17
18 my $r = test_callback(); # scalar context
19
20 gprint hostname().": returned value from callback: $r\n";
21 } );
22 die $r->errmsg unless $r->ok;
23
24 $r = $machine->callback( 'Tutu::test_callback' );
25 die $r->errmsg unless $r->ok;
26
27 $r = $machine->remote();
28
29 die $r->errmsg unless $r->noerr;
When executed the former program produces an output similar to this (beowulf is the remote machine):
$ callbackbyname3.pl
beowulf: inside remote
Inside test_callback() host: nereida.deioc.ull.es
beowulf: returned value from callback: 3.1415
Context and Callbacks
When a callback subroutine is called in a scalar context it returns the first element of the returned list. See line 18 in the previous code.
Anonymous Callbacks
The callback subroutine can be anonymous. The callback
method support the syntax:
$machine->callback( sub { ... } )
See an example:
$ cat -n anonymouscallback.pl
1 #!/usr/bin/perl
2 use strict;
3 use GRID::Machine;
4 use Sys::Hostname;
5
6 my $host = shift || $ENV{GRID_REMOTE_MACHINE};
7
8 my $machine = GRID::Machine->new(host => $host, uses => [ 'Sys::Hostname' ]);
9
10 my $r = $machine->sub( remote => q{
11 my $rsub = shift;
12
13 gprint &hostname.": inside remote sub\n";
14 my $retval = $rsub->(3);
15
16 return 1 $retval;
17 } );
18
19 die $r->errmsg unless $r->ok;
20
21 my $a = $machine->callback(
22 sub {
23 print hostname().": inside anonymous inline callback. Args: (@_) \n";
24 return shift() 1;
25 }
26 );
27
28 $r = $machine->remote( $a );
29
30 die $r->errmsg unless $r->noerr;
31
32 print "Result = ".$r->result."\n";
When the previous example is executed using as local machine 'nereida' it produces an output similar to this:
$ anonymouscallback.pl
beowulf: inside remote sub
nereida.deioc.ull.es: inside anonymous inline callback. Args: (3)
Result = 5
Recursive Remote Procedure Calls and Callbacks
The existence of callbacks opens the possibility of nested sets of RPCs and callbacks. The following example recursively computes the factorial of a number. The execution of recursive calls alternates between remote and local sides:
$ cat -n nestedcallback.pl
1 #!/usr/bin/perl
2 use strict;
3 use GRID::Machine;
4 use Sys::Hostname;
5
6 my $host = $ENV{GRID_REMOTE_MACHINE};
7
8 my $machine = GRID::Machine->new( host => $host, uses => [ 'Sys::Hostname' ] );
9
10 my $r = $machine->sub(
11 fact => q{
12 my $x = shift;
13
14 gprint &hostname . ": fact($x)\n";
15
16 if ($x > 1) {
17 my $r = localfact($x-1);
18 return $x*$r;
19 }
20 else {
21 return 1;
22 }
23 }
24 );
25 die $r->errmsg unless $r->ok;
26
27 $r = $machine->callback(
28
29 localfact => sub {
30 my $x = shift;
31
32 print &hostname . ": fact($x)\n";
33
34 if ($x > 1) {
35 my $r = $machine->fact($x-1)->result;
36 return $x*$r;
37 }
38 else {
39 return 1;
40 }
41
42 }
43
44 );
45 die $r->errmsg unless $r->ok;
46
47 my $n = shift;
48
49 $r = $machine->fact($n);
50
51 die $r->errmsg unless $r->ok;
52 print "=============\nfact($n) is ".$r->result."\n";
When executed, the former program produces an output similar to this (beowulf is the remote machine):
$ nestedcallback.pl 6
beowulf: fact(6)
nereida.deioc.ull.es: fact(5)
beowulf: fact(4)
nereida.deioc.ull.es: fact(3)
beowulf: fact(2)
nereida.deioc.ull.es: fact(1)
=============
fact(6) is 720
LIMITATIONS
Operating System
I will be surprised if this module works on anything that is not UNIX.
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 || '[email protected]';
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]
]
Nested Uses of GRID::Machine
The remote server can't use GRID::Machine to connect to a second server. I. e. a program like this fails:
use GRID::Machine;
my $host = shift || 'user@machine';
my $machine = GRID::Machine->new(host => $host, uses => [ 'GRID::Machine' ]);
my $r = $machine->eval(q{ my $t = GRID::Machine->new(host => 'orion'); });
print $r->result;
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 => '[email protected]',
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
"Remote Modules" is the term used for files containing Perl code that will be loaded onto the remote Perl server via the incldue
method or through the includes
argument of new
. These files can only contain
Subroutines. Since these subroutines are anonymous in the remote side, the only way to call them from the remote side is through the attribute
stored_procedures
of theSERVER
object:SERVER->stored_procedures->{subname}
use Module
declarations. TheModule
must exists in the remote server. Furthermote module import arguments as inuse Module qw{ w1 w2 w3}
must be in a single line.POD documentation
Variable declarations and variable initializations are ignored.
The include
method parses Perl code. It is a heuristic one page length parser (72 lines at the moment of writing). It obviously can't parse everything. But works for most of the code restricted to the aforementioned limitations.
EXPORTS
When explicited by the client program GRID::Machine
exports these functions:
is_operative
read_modules
qc
INSTALLATION
To install GRID::Machine follow the traditional steps:
perl Makefile.PL
make
make test
make install
Using Password Authentication
You can make GRID::Machine to work without automatic authentication.
The following example uses Net::OpenSSH to open a SSH connection using password authentication instead of asymmetric cryptography:
$ cat -n openSSH.pl
1 use strict;
2 use warnings;
3 use Net::OpenSSH;
4 use GRID::Machine;
5
6 my $host = (shift() or $ENV{GRID_REMOTE_MACHINE});
7 my @ARGS;
8 push @ARGS, (user => $ENV{USR}) if $ENV{USR};
9 push @ARGS, ( password => $ENV{PASS}) if $ENV{PASS};
10
11 my $ssh = Net::OpenSSH->new($host, @ARGS);
12 $ssh->error and die "Couldn't establish SSH connection: ". $ssh->error;
13
14 my @cmd = $ssh->make_remote_command('perl');
15 { local $" = ','; print "@cmd\n"; }
16 my $grid = GRID::Machine->new(command => \@cmd);
17 my $r = $grid->eval('print "hello world!\n"');
18 print "$r\n";
when executed produces an output like this:
$ perl openSSH.pl
ssh,-S,/Users/localuser/.libnet-openssh-perl/user-machine-2413-275647,-o,User=user,--,machine,perl
hello world!
However, it seems a bad idea to have unencrypted passwords messing around. It is much better to use asymmetric cryptography.
Using Automatic authentication: Asymmetric Cryptography
Set automatic ssh-authentication with the machines where you have an SSH 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 public key. To achieve this automatic ssh-authentication you have to:
Generate a public key use the
ssh-keygen
utility. For example:local.machine$ 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 in
/home/user/.ssh/id_rsa.pub
to file
/home/user/.ssh/authorized_keys
on the remote machine. If the
ssh-copy-id
script is available, you can do it using:local.machine$ ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]
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 a/home/user/.ssh/authorized_keys
files which have loose permissions.Edit your local configuration file
/home/user/.ssh/config
(seeman ssh_config
in UNIX) and create a new section forGRID::Machine
connections to that host. Here follows an example:... # A new section inside the config file: # it will be used when writing a command like: # $ ssh gridyum Host gridyum # My username in the remote machine user my_login_in_the_remote_machine # The actual name of the machine: by default the one provided in the # command line Hostname real.machine.name # The port to use: by default 22 Port 2048 # The identitiy pair to use. By default ~/.ssh/id_rsa and ~/.ssh/id_dsa IdentityFile /home/user/.ssh/yumid # Useful to detect a broken network BatchMode yes # Useful when the home directory is shared across machines, # to avoid warnings about changed host keys when connecting # to local host NoHostAuthenticationForLocalhost yes # Another section ... 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, you don't have to specify the port if it isn't 22, etc. This is the recommended way to work with
GRID::Machine
. Avoid cluttering the constructornew
.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 [email protected]:~$
You can also automatically execute commands in the remote server:
local.machine$ ssh remote.machine uname -a Linux remote.machine 2.6.15-1-686-smp #2 SMP Mon Mar 6 15:34:50 UTC 2006 i686 GNU/Linux
Once you have installed GRID::Machine you can check that
perl
can be executed in that machine using this one-liner:$ perl -e 'use GRID::Machine qw(is_operative); print is_operative("ssh", "beowulf")."\n"' 1
DEPENDENCIES
This module requires these other modules and libraries:
Net::OpenSSH is not needed but recommended. See section "Using Password Authentication".
SEE ALSO
Man pages of
ssh
,ssh-key-gen
,ssh_config
,scp
,ssh-agent
,ssh-add
,sshd
CONTRIBUTORS
Dmitriy Kargapolov (<[email protected]>) suggested, designed and provided an implementation for callbacks.
Eric Busto fixed a problem with
is_operative
hanging on systems that are in an odd state.Alex White fixed bugs in
modput
and the SSH options.Erik Welch fixed a bug in the (local)
DESTROY
method.
AUTHOR
Casiano Rodriguez Leon <[email protected]>
ACKNOWLEDGMENTS
This work has been supported by CEE (FEDER) and the Spanish Ministry of Educacion 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 Alex White, Dmitri Kargapolov, Eric Busto and Erik Welch for their contributions. To the Perl Monks, and the Perl Community for generously sharing their knowledge. Finally, thanks to Juana, Coro and my students at La Laguna.
LICENCE AND COPYRIGHT
Copyright (c) 2007 Casiano Rodriguez-Leon ([email protected]). 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.