-
Notifications
You must be signed in to change notification settings - Fork 278
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
290 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# SUSE's openQA tests | ||
# | ||
# Copyright SUSE LLC | ||
# SPDX-License-Identifier: FSFAP | ||
# Maintainer: QE-SAP <[email protected]> | ||
|
||
package sles4sap::console_redirection; | ||
use strict; | ||
use warnings; | ||
use testapi; | ||
use Carp qw(croak); | ||
use Exporter qw(import); | ||
use serial_terminal qw(select_serial_terminal); | ||
use Regexp::Common qw(net); | ||
|
||
=head1 SYNOPSIS | ||
Library that enables console redirection and file transfers from worker based VM to another host. | ||
Can be used for cases where worker VM is not the target host for API calls and command execution, but serves only as a jumphost. | ||
=cut | ||
|
||
our @EXPORT = qw( | ||
connect_target_to_serial | ||
disconnect_target_from_serial | ||
redirection_init | ||
check_serial_redirection | ||
); | ||
|
||
my $ssh_opt = '-o StrictHostKeyChecking=no -o ServerAliveInterval=60 -o ServerAliveCountMax=120'; | ||
|
||
=head2 handle_login_prompt | ||
handle_login_prompt(); | ||
B<ssh_user> Login user | ||
Detects if login prompt appears and types the password. | ||
In case of ssh keys being in place and command prompt appears, the fucntion does not type anything. | ||
=cut | ||
|
||
sub handle_login_prompt { | ||
my $pwd = get_var('_SECRET_SUT_PASSWORD', $testapi::password); | ||
set_serial_term_prompt(); | ||
# look for either password prompt or command prompt to appear | ||
my $serial_response = wait_serial(qr/Password:\s*$|:~/i, timeout => 20); | ||
|
||
die 'Neither password not command prompt appeared.' unless grep /Password:\s*$|:~/, $serial_response; | ||
# Handle password prompt if it appears | ||
if (grep /Password:\s*$/, $serial_response) { | ||
type_password $pwd; | ||
send_key 'ret'; | ||
# wait for command prompt to be ready | ||
die 'Command prompt did not appear withing timeout' unless wait_serial((qr/:~|#|>/i), timeout => 20); | ||
} | ||
set_serial_term_prompt(); # set correct serial prompt | ||
} | ||
|
||
=head2 redirection_init | ||
redirection_init(); | ||
Do preparation before redirecting console. Gets base VM id and initial setup. | ||
This is required to be done only once at the beginning of the whole test. | ||
If you have a multiumachine setup, execute this on each worker VM. | ||
=cut | ||
|
||
sub redirection_init { | ||
# This should get worker VM id before any redirection happening | ||
# ID serves as identification of the 'base' VM to return to. | ||
set_var('WORKER_VM_ID', script_output 'cat /etc/machine-id'); | ||
} | ||
|
||
=head2 set_serial_term_prompt | ||
set_serial_term_prompt(); | ||
Set expected serial prompt according to user which is currently active. | ||
This changes global setting $testapi::distri->{serial_term_prompt} which is important for calls like wait_for_serial. | ||
=cut | ||
|
||
sub set_serial_term_prompt { | ||
$testapi::distri->{serial_term_prompt} = ''; # reset previous prompt first | ||
my $current_user = script_output('whoami'); | ||
croak 'SSH user not defined and/or "whoami" script failed.' unless defined($current_user); | ||
$testapi::distri->{serial_term_prompt} = ($current_user eq 'root' ? '# ' : '> '); | ||
} | ||
|
||
=head2 connect_target_to_serial | ||
connect_target_to_serial([ssh_user=>ssh_user, target_ip=>$target_ip]); | ||
B<ssh_user> Login user - default value is defined by OpenQA parameter REDIRECT_TARGET_USER | ||
B<target_ip> Target host IP - default value is defined by OpenQA parameter REDIRECT_TARGET_IP | ||
Establishes ssh connection to target and redirects serial output to serial concole on worker VM. | ||
This allows OpenQA access to command return codes and output for evaulation by standard API call. | ||
=cut | ||
|
||
sub connect_target_to_serial { | ||
my (%args) = @_; | ||
my $redirect_ip = $args{'target_ip'} // get_required_var('REDIRECT_TARGET_IP'); | ||
my $ssh_user = $args{'ssh_user'} // get_required_var('REDIRECT_TARGET_USER'); | ||
|
||
croak 'Missing "ssh_user" argument' unless $ssh_user; | ||
croak 'Missing "redirect_ip" argument' unless $redirect_ip; | ||
croak "OpenQA variable WORKER_VM_ID undefined. Run 'redirection_init()' first" unless get_var('WORKER_VM_ID'); | ||
croak "IP address '$redirect_ip' is not valid." unless grep(/^$RE{net}{IPv4}$/, $redirect_ip); | ||
croak 'Global variable "$searialdev" undefined' unless $serialdev; | ||
croak "Console is already redirected to:" . script_output('hostname') if check_serial_redirection(); | ||
|
||
enter_cmd "ssh $ssh_opt $ssh_user\@$redirect_ip 2>&1 | tee -a /dev/$serialdev"; | ||
handle_login_prompt($ssh_user); | ||
check_serial_redirection(); | ||
record_info('Redirect ON', "Serial redirection established to: $redirect_ip"); | ||
} | ||
|
||
=head2 disconnect_target_from_serial | ||
disconnect_target_from_serial([worker_machine_id=$worker_machine_id]); | ||
B<worker_machine_id> Target host IP - default value is defined by OpenQA parameter WORKER_VM_ID from redirect_init() | ||
Disconnects target from serial console by typing 'exit' command until host machine ID matches ID of the worker VM. | ||
=cut | ||
|
||
sub disconnect_target_from_serial { | ||
my (%args) = @_; | ||
my $worker_machine_id = $args{worker_machine_id} // get_required_var('WORKER_VM_ID'); | ||
set_serial_term_prompt(); | ||
my $serial_redirection_status = check_serial_redirection(worker_machine_id => $worker_machine_id); | ||
while ($serial_redirection_status ne 0) { | ||
enter_cmd('exit'); # Enter command and wait for screen start changing | ||
$testapi::distri->{serial_term_prompt} = ''; # reset console prompt | ||
wait_serial(qr/Connection.*closed./, timeout => 10); # Wait for connection to close | ||
wait_serial(qr/# |> /, timeout => 10); # Wait for console prompt to appear | ||
set_serial_term_prompt(); # after logout user might change and prompt with it. | ||
$serial_redirection_status = check_serial_redirection($worker_machine_id); | ||
} | ||
record_info('Redirect OFF', "Serial redirection closed. Console set to: " . script_output('hostname')); | ||
} | ||
|
||
=head2 check_serial_redirection | ||
check_serial_redirection([worker_machine_id=$worker_machine_id]); | ||
B<worker_machine_id> Target host IP - default value is defined by OpenQA parameter WORKER_VM_ID from redirect_init() | ||
Compares current machine-id to the worker VM ID either defined by WORKER_VM_ID variable or positional argument. | ||
Machine ID is used instead of IP addr since cloud VM IP might not be visible from the inside (for example via 'ip a') | ||
=cut | ||
|
||
sub check_serial_redirection { | ||
my (%args) = @_; | ||
my $expected_machine_id = $args{worker_machine_id} // get_required_var('WORKER_VM_ID'); | ||
my $current_id = script_output 'cat /etc/machine-id'; | ||
|
||
my $redirection_status = $current_id eq $expected_machine_id ? 0 : 1; | ||
record_info('Redir check', 'Console redirection is not active') unless $redirection_status; | ||
record_info('Redir check', "Console is redirected to: " . script_output('hostname')) if $redirection_status; | ||
return $redirection_status; | ||
} | ||
|
||
1; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
use strict; | ||
use warnings; | ||
use Test::Mock::Time; | ||
use Test::More; | ||
use Test::Exception; | ||
use Test::Warnings; | ||
use Test::MockModule; | ||
use testapi; | ||
use sles4sap::console_redirection; | ||
|
||
our $serialdev = 'ttyS0'; # this is a global OpenQA variable | ||
|
||
# make cleaning vars easier at the end of the unit test | ||
sub unset_vars { | ||
my @variables = ('REDIRECT_TARGET_IP', 'WORKER_VM_ID'); | ||
set_var($_, undef) foreach @variables; | ||
} | ||
|
||
subtest '[connect_target_to_serial] Expected failures' => sub { | ||
my $redirect = Test::MockModule->new('sles4sap::console_redirection', no_auto => 1); | ||
$redirect->redefine(enter_cmd => sub { return 1; }); | ||
$redirect->redefine(handle_login_prompt => sub { return 1; }); | ||
$redirect->redefine(record_info => sub { return 1; }); | ||
$redirect->redefine(script_output => sub { return 'castleinthesky'; }); | ||
set_var('WORKER_VM_ID', '7902847fcc554911993686a1d5eca2c8'); | ||
$redirect->redefine(check_serial_redirection => sub { return 0; }); | ||
|
||
dies_ok { connect_target_to_serial(target_ip => '192.168.1.1') } 'Fail with missing ssh user'; | ||
dies_ok { connect_target_to_serial(ssh_user => 'totoro') } 'Fail with missing ip address'; | ||
$redirect->redefine(check_serial_redirection => sub { return 1; }); | ||
dies_ok { connect_target_to_serial(ssh_user => 'totoro', target_ip => '192.168.1.1') } 'Fail with console already being redirected'; | ||
unset_vars(); | ||
}; | ||
|
||
subtest '[connect_target_to_serial] Test passing behavior' => sub { | ||
my $redirect = Test::MockModule->new('sles4sap::console_redirection', no_auto => 1); | ||
my $ssh_cmd; | ||
$redirect->redefine(enter_cmd => sub { $ssh_cmd = $_[0]; return 1; }); | ||
$redirect->redefine(handle_login_prompt => sub { return 1; }); | ||
$redirect->redefine(record_info => sub { return 1; }); | ||
$redirect->redefine(check_serial_redirection => sub { return 0; }); | ||
$redirect->redefine(script_output => sub { return 'castleinthesky'; }); | ||
set_var('WORKER_VM_ID', '7902847fcc554911993686a1d5eca2c8'); | ||
connect_target_to_serial(target_ip => '192.168.1.1', ssh_user => 'totoro'); | ||
is $ssh_cmd, 'ssh -o StrictHostKeyChecking=no -o ServerAliveInterval=60 -o ServerAliveCountMax=120 [email protected] 2>&1 | tee -a /dev/ttyS0', 'Pass with corect command executed'; | ||
unset_vars(); | ||
}; | ||
|
||
subtest '[handle_login_prompt] Test via "connect_to_serial"' => sub { | ||
my $redirect = Test::MockModule->new('sles4sap::console_redirection', no_auto => 1); | ||
my $type_pass_executed = 0; | ||
$redirect->redefine(enter_cmd => sub { return 1; }); | ||
$redirect->redefine(type_password => sub { $type_pass_executed = 1; }); | ||
$redirect->redefine(send_key => sub { return 1; }); | ||
$redirect->redefine(set_serial_term_prompt => sub { return 1; }); | ||
$redirect->redefine(record_info => sub { return 1; }); | ||
$redirect->redefine(check_serial_redirection => sub { return 0; }); | ||
$redirect->redefine(script_output => sub { return 'castleinthesky'; }); | ||
set_var('WORKER_VM_ID', '7902847fcc554911993686a1d5eca2c8'); | ||
|
||
my @command_prompts = ('laputa@castleinthesky:~>', 'castleinthesky:~ # '); | ||
foreach (@command_prompts) { | ||
$redirect->redefine(wait_serial => sub { return $_; }); | ||
connect_target_to_serial(target_ip => '192.168.1.1', ssh_user => 'totoro'); | ||
is $type_pass_executed, 0, "Pass with command prompt detected: $_"; | ||
$type_pass_executed = 0; # reset flag | ||
} | ||
|
||
$redirect->redefine(wait_serial => sub { return '(laputa@castleinthesky) Password:'; }); | ||
$redirect->redefine(croak => sub { return; }); # Need to disable croak since wait_serial won't return second response here. | ||
|
||
connect_target_to_serial(target_ip => '192.168.1.1', ssh_user => 'totoro'); | ||
is $type_pass_executed, 1, 'Pass with password prompt detected'; | ||
unset_vars(); | ||
}; | ||
|
||
subtest '[disconnect_target_from_serial]' => sub { | ||
my $redirect = Test::MockModule->new('sles4sap::console_redirection', no_auto => 1); | ||
my $wait_serial_done = 0; # Flag that code entered while loop | ||
$redirect->redefine(record_info => sub { return 1; }); | ||
$redirect->redefine(wait_serial => sub { $wait_serial_done = 1; return ':~'; }); | ||
$redirect->redefine(enter_cmd => sub { return 1; }); | ||
$redirect->redefine(check_serial_redirection => sub { return $wait_serial_done; }); | ||
$redirect->redefine(set_serial_term_prompt => sub { return 1; }); | ||
$redirect->redefine(script_output => sub { return ''; }); | ||
|
||
ok disconnect_target_from_serial(worker_machine_id => '7902847fcc554911993686a1d5eca2c8'), 'Pass with machine ID defined by positional argument'; | ||
|
||
set_var('WORKER_VM_ID', '7902847fcc554911993686a1d5eca2c8'); | ||
ok disconnect_target_from_serial(), 'Pass with machine ID defined by parameter WORKER_VM_ID'; | ||
unset_vars(); | ||
|
||
dies_ok { disconnect_target_from_serial() } 'Fail without specifying machine ID and WORKER_VM_ID undefined'; | ||
}; | ||
|
||
subtest '[redirection_init]' => sub { | ||
my $redirect = Test::MockModule->new('sles4sap::console_redirection', no_auto => 1); | ||
$redirect->redefine(script_output => sub { return '7902847fcc554911993686a1d5eca2c8'; }); | ||
|
||
redirection_init(); | ||
is get_var('WORKER_VM_ID'), '7902847fcc554911993686a1d5eca2c8', 'Pass with WORKER_VM_ID being set correctly'; | ||
unset_vars(); | ||
}; | ||
|
||
subtest '[check_serial_redirection]' => sub { | ||
my $redirect = Test::MockModule->new('sles4sap::console_redirection', no_auto => 1); | ||
$redirect->redefine(script_output => sub { return '7902847fcc554911993686a1d5eca2c8'; }); | ||
$redirect->redefine(record_info => sub { return; }); | ||
|
||
set_var('WORKER_VM_ID', '7902847fcc554911993686a1d5eca2c8'); | ||
is check_serial_redirection(), '0', 'Return 0 if machine IDs match'; | ||
set_var('WORKER_VM_ID', '999999999999999999999999'); | ||
is check_serial_redirection(), '1', 'Return 1 if machine IDs do not match'; | ||
|
||
unset_vars(); | ||
|
||
is check_serial_redirection(worker_machine_id => '123456'), '1', 'Pass with specifying ID via positional argument'; | ||
dies_ok { check_serial_redirection() } 'Fail with WORKER_VM_ID being unset'; | ||
}; | ||
|
||
done_testing; |