Created
March 4, 2017 11:48
-
-
Save jonathanstowe/06f510654a5d0b35962ff5f56a62c75a to your computer and use it in GitHub Desktop.
Foo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package MAP::Util::RunRemote; | |
use strict; | |
use warnings; | |
our ($VERSION) = q$Revision: 105583 $ =~ /Revision:\s*(\d+)/; | |
use IPC::Run; | |
use Storable qw(nfreeze thaw); | |
use Scalar::Util qw(blessed); | |
use Params::Util qw(_INVOCANT _INSTANCE _IDENTIFIER); | |
use MAP::Exception; | |
use MAP::Util::Executables; | |
require MAP::Platform; | |
use base qw(MAP::Logger); | |
use base qw(MAP::ElevateAttribute); | |
# handle unexpected objects in serialize structures | |
require MAP::Util::IncHook; | |
=head1 NAME | |
MAP::Util::RunRemote | |
=head1 DESCRIPTION | |
This module provides a method for running a method of an object on a specified | |
remote machine. | |
This is achieved by serializing the provided object and the arguments | |
(using L<Storable>) and then using ssh to execute the script 'elevator' | |
on the remote machine/ which accepts the serialized contents of @_ | |
over STDIN and executing the appropriate method (which was passed as an | |
argument) and then returning the serialized return values over STDOUT. | |
Exceptions are also returned over STDOUT and rethrown, the script will have | |
indicated that it is returning an exception by returning an exit code of 1 | |
(rather than 0). | |
The wrapper code will refuse to run the script if it is writable by the | |
user that invoked the method (throw a MAP::Exception::Security ). | |
=head2 METHODS | |
=over 4 | |
=item run_remote | |
This is a class method that allows a named method to be executed on | |
a remote machine on the supplied object. This is achieved by the | |
method described above. | |
Parameters: $host ; name of target host (or MAP::Host) | |
$method : method name to be executed | |
$object : invocant of the method. | |
@args : arguments to the method (not including object) | |
=cut | |
sub run_remote | |
{ | |
my ( $self, $host, $method, $object, @args ) = @_; | |
my $want = wantarray ? 1 : 0; | |
my $target_user = MAP::Platform->data_owner(); | |
my $target_uid = getpwnam $target_user; | |
if ( !defined $host ) | |
{ | |
MAP::Exception::Remote->throw( error => "no host provided" ); | |
} | |
else | |
{ | |
if ( _INSTANCE( $host, 'MAP::Host' ) ) | |
{ | |
$host = $host->name(); | |
} | |
} | |
if ( !_IDENTIFIER($method) ) | |
{ | |
MAP::Exception::Remote->throw(error => "invalid or missing method name"); | |
} | |
if ( !_INVOCANT($object) ) | |
{ | |
MAP::Exception::Remote->throw( error => "not a valid method invocant"); | |
} | |
my $ssh = MAP::Util::Executables->get_executable_path('ssh'); | |
if ( !defined $ssh ) | |
{ | |
MAP::Exception::Remote->throw( error => "Can't resolve 'ssh'" ); | |
} | |
# of course running remote we'd have to do this remotely which | |
# is a bootstrap problem :-O | |
my $script = '/opt/map/scripts/elevator'; # MAP::Util::Executables->get_platform_script('elevator'); | |
my $class = ref($object) ? ref($object) : $object; | |
my @cmd = ( $ssh, $host ); | |
if ( $> != $target_uid ) | |
{ | |
push @cmd, 'sudo', '-u', $target_user; | |
} | |
push @cmd, ( $script, $class, $method, $want ); | |
$self->log_debug( "About to run: '" . join ' ', @cmd ); | |
my ( $ret, @ret ); | |
unshift @args, $object; # put the object onto the args | |
my $frozen_object = nfreeze( \@args ); | |
my $rr = $self->_run_elevator( \@cmd, $frozen_object ); | |
if ($want) | |
{ | |
if ( defined $rr ) | |
{ | |
@ret = @{$rr}; | |
} | |
} | |
else | |
{ | |
$ret = $rr->[0]; | |
} | |
return $want ? @ret : $ret; | |
} | |
=item _run_elevator | |
This runs the command specified by $cmd (an array ref that will be | |
passed to L<IPC::Run>'s C<run> function.) It is assumed that the | |
command will result in the running of a script that behaves the same | |
as C<elevator> which has arguments of methodname and classname and | |
context and accepts the invocant and arguments on STDIN as a storable | |
frozen object and returns the metod return on STDOUT using Storable. | |
This will execute $cmd using run, passing $object as STDIN and will | |
handle the response, thawing the STDOUT handle or STDERR handle as | |
appropriate and if there has been an exception throwing an exception | |
as appopriate or if the command was run successfully it will return an | |
array ref containing the response from the executed command - it is | |
the callers responsibility to determine the context of the original | |
call and to either dereference this return or select the first element. | |
This is only intended to be used by C<run_remote> above and C<Elevate> | |
in L<MAP::ElevateAttribute> which basically function in the same way. | |
Parameters: $cmd : ARRAYREF containing command to be run | |
$object : $ containing a L<Storable> frozen array of objects. | |
Returns : ARRAYREF containg the response. | |
=cut | |
sub _run_elevator | |
{ | |
my ( $self, $cmd, $object ) = @_; | |
my $ret = []; | |
if ( defined $cmd && defined $object ) | |
{ | |
my ( $rethandle, $errhandle ); | |
$ENV{PATH} = '/usr/bin:/bin'; | |
IPC::Run::run $cmd, \$object, \$rethandle, \$errhandle; | |
$self->log_debug("run_remote $errhandle") if $errhandle; | |
if ($?) | |
{ | |
$self->log_debug("run_remote $errhandle"); | |
if ( $? == -1 ) | |
{ | |
MAP::Exception::Remote->throw(error => "elevator failed to execute - $!" ); | |
} | |
elsif ( $? & 127 ) | |
{ | |
MAP::Exception::Remote->throw(error => "elevator was killed with signal @{[$? & 127]}" ); | |
} | |
else | |
{ | |
my $rr; | |
if ($rethandle) | |
{ | |
eval | |
{ | |
MAP::Util::IncHook->install(); | |
$rr = thaw($rethandle); | |
MAP::Util::IncHook->remove(); | |
}; | |
if ($@) | |
{ | |
MAP::Exception::Remote->throw( error => "Unexpected error while deserializing expected exception - $@" | |
); | |
} | |
} | |
if ( blessed($rr) ) | |
{ | |
if ( $rr->can('rethrow') ) | |
{ | |
$self->log_debug( "looks like we got a " . ref($rr) ); | |
$rr->rethrow(); | |
} | |
else | |
{ | |
die $rr; | |
} | |
} | |
else | |
{ | |
$rr = "Unexpected fault in called method" unless $rr; | |
MAP::Exception::Elevator->throw( error => $rr ); | |
} | |
} | |
} | |
elsif ( defined $rethandle && length($rethandle) ) | |
{ | |
eval | |
{ | |
MAP::Util::IncHook->install(); | |
$ret = thaw($rethandle); | |
MAP::Util::IncHook->remove(); | |
}; | |
if ($@) | |
{ | |
MAP::Exception::Remote->throw( | |
error => "unexpected error while deserializing response - $@" ); | |
} | |
} | |
} | |
return $ret; | |
} | |
=back | |
=head2 METHODS FROM TRAITS | |
=over 4 | |
=back | |
=head1 INHERITS FROM | |
=over 4 | |
=back | |
=cut | |
1; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment