salva / p5-Net-SSH-Any

Compatibility layer for common Perl SSH client modules
6 stars 6 forks source link

feature request: truly use plink batch mode #2

Open glasswalk3r opened 8 years ago

glasswalk3r commented 8 years ago

Hi!

I've being use your distro successfully until I moved my code to use Perl ithreads: after that, some connections just fail... and I don't get any error message or something like that. I'm connecting to multiple SSH server from Windows. Considering that ithreads is somehow messy and Net::SSH and IPC messy on Windows, I decided to take a shot and try to run plink by using the batch mode and logging messages directly to a file. I forked your project and tried to implemented "true batch mode" with Putty plink, but it seems I would broke the current interface, so not sure if you would be interested in implementing this (and how). I was able to implemented that using Moo with partial success: the log feature of plink doesn't block the program to print to STDOUT. Here is a example witha workaround

sub exec {
    my ($self, $cmds_ref) = @_;
    confess('command parameter must be an array reference') unless (ref($cmds_ref) eq 'ARRAY');
    my $log = tmpnam();
    my $cmds = File::Temp->new();

    foreach my $cmd(@{$cmds_ref}) {
        print $cmds "$cmd\n";
    }

    $cmds->close();
    my @params = ('-ssh','-batch', '-l', $self->get_user, '-pw', $self->get_password, '-m', $cmds->filename, $self->get_host, '>', $log, '2>&1');
    my $prog = File::Spec->catfile($self->get_putty_path,'plink.exe');
    my $cmd = '"' . $prog . '" ' . join(' ',@params);
    my $exec_ok  = 0;
    my $ret = system($cmd);

    unless ($ret == 0) {

        if ($? == -1) {
            print "failed to execute: $!\n";
        }
        elsif ($? & 127) {
            printf "child died with signal %d, %s coredump\n",
                ($? & 127),  ($? & 128) ? 'with' : 'without';
        }
        else {
            printf "child exited with value %d\n", $? >> 8;
        }

    } else {
        $exec_ok = 1;
    }

    $self->_set_last_cmd( $self->_read_out($log) );
    return $exec_ok;
}

Not elegant, but it works. The messy part is using system() and allowing the shell interpreter taking action. Not very safe, but at least I'm not getting external data (I think, I should turn taint mode on to validate that).

Error detection is a bit primitive too... the code is not reading the log file and searching for error messages.

Best news is that I'm able to connect to multiple SSH server with ithreads on Windows... if you want to implement such a thing, I'm willing to give a hand.

salva commented 8 years ago

Hi,

I was able to implemented that using Moo with partial success: the log feature of plink doesn't block the program to print to STDOUT. Here is a example witha workaround sub exec {...}

You can use Net::SSH::Any in a pretty similar fashion:

$ssh->system({ stdout_fh => $log_fh, stderr_to_stdout => 1}, $cmd);

Though, underneath, things are done in an equivalent but different way, so it may still trigger the same bug you were seeing before.

The thing is that I would not like to add a workaround for a bug that has not been properly identified because any code change could reintroduce it again.

Could you create a bug report with some further information about the issue and a minimal program able to reproduce it?

salva commented 8 years ago

Hi,

I was able to implemented that using Moo with partial success: the log feature of plink doesn't block the program to print to STDOUT. Here is a example witha workaround sub exec {...}

You can use Net::SSH::Any in a pretty similar fashion:

$ssh->system({ stdout_fh => $log_fh, stderr_to_stdout => 1}, $cmd);

Though, underneath, things are done in an equivalent but different way, so it may still trigger the same bug you were seeing before.

The thing is that I would not like to add a workaround for a bug that has not been properly identified because any code change could reintroduce it again.

Could you create a bug report with some further information about the issue and a minimal program able to reproduce it?

glasswalk3r commented 7 years ago

Understood you point and it's very reasonable. I'm not sure how provide evidences in those cases... the threads just stuck, I get no output at all. The "main" thread uses a "tee like" feature (prints messages to STDOUT and to a log file), but when those errors arise, I stop seeing messages on STDOUT (and sometimes in the log files either). At the end, I need to manually kill the plink and perl processes. Maybe it could be possible to turn on the -sshlog option of plink? Let me know if you're willing to have a patch with that.

glasswalk3r commented 7 years ago

Hi,

I think that if I share some code with you would be easier for you to try to see the errors yourself. Of course you will need to use Windows (I'm with Windows 7, Strawberry Perl 5.24.0) and you will not have the program that is actually executing on the servers, but I think you can replace that quite easily (take a look at ssh_2_server sub).

sub ssh_2_server {
    my $opts_ref = shift;
    my $log = File::Spec->catfile( getcwd(), 'logs', ( $opts_ref->{server} . '.log' ) );
    my $is_log_ok = open( my $out, '>>', $log );
    my $cmd;

    if ( exists($opts_ref->{os_only}) and $opts_ref->{os_only} ) {
        $cmd = 'cview.pl -i ' . $opts_ref->{instance} . ' -o';
    } elsif ( exists($opts_ref->{clean_cache}) and $opts_ref->{clean_cache} ) {
        $cmd = 'cview.pl -i ' . $opts_ref->{instance} . ' -s ' . $opts_ref->{sr_num} . ' -e 0';
    } else {
        $cmd = 'cview.pl -i ' . $opts_ref->{instance} . ' -s ' . $opts_ref->{sr_num};
    }

    if ($is_log_ok) {

        my $start = DateTime->now();
        my $errors = 0;
        print $out join(' ', 'Connecting to', $opts_ref->{server}, 'under', $opts_ref->{instance},'...');
        my $ssh;
        my $fqdn = $opts_ref->{server} . '.oracleoutsourcing.com';

        try { 
            $ssh = Net::SSH::Any->new($fqdn, user => $user, password => $password, backend => 'Plink_Cmd', local_plink_cmd => 'C:\\Program Files (x86)\\PuTTY\\plink.exe');
        } catch {
            $errors++;
            warn $_;
            return ssh_exception( $out, $_, $errors, $start, $opts_ref->{server}, $opts_ref->{instance});
        };

        unless ( defined(blessed($ssh)) ) {
            $errors++;
            return ssh_exception( $out, 'Could not create an instance of Net::SSH::Any', $errors, $start, $opts_ref->{server}, $opts_ref->{instance});
        } else {
            print $out "done\n";
            print $out "Executing commands '$cmd'...\n";

            my ($stdout, $stderr) = $ssh->capture2(". .bash_profile; $cmd");
            if ( $ssh->error ) {
                $errors++;
                warn "STDERR:\n[$stderr]\n";
                warn $ssh->error;
                return ssh_exception( $out, $ssh->error, $errors, $start, $opts_ref->{server}, $opts_ref->{instance});
            }

            print $out "STDOUT:\n[$stdout]\n";
            print $out "STDERR:\n[$stderr]\n";
            print $out "Done executing commands\n";
            print $out 'Trying to copy response file... ';

            try {
                my $sftp = $ssh->sftp;
                unless ( defined(blessed($sftp)) ) {
                    confess $ssh->error;
                }
                get_response( $sftp, $opts_ref->{server}, $opts_ref->{instance} ); 
            } catch {
                print $out $_;
                $errors++;
            };

            print $out "Done copying files\n";

        }

        my $elapsed = calc_elapsed( $start );
        print $out join(' ', 'Operation took', $elapsed, "seconds to finish\n");
        close($out);
        return ( $errors, $elapsed, $opts_ref->{server}, $opts_ref->{instance} );
    }
    else {
        warn "Failed to create log: $!";
        return ( 1, 0, $opts_ref->{server}, $opts_ref->{instance} );
    }

}

The program will create threads (limited by parameter) and then collect results from the remote program (a XML file) by using SFTP. The target would be different Linux hosts, and last time I executed I had about 90 servers to loop over.

I don't see any particular behavior when errors occurred, and not with a specific host: the plink connection just "hangs" and I have to kill it manually. No error message is generated. I updated the full code, just let me know if you need some pointers with it.

rcview-180.zip