From b93a197c22c1ac42e629a3f38bbc161c69879c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20R=C5=AF=C5=BEi=C4=8Dka?= Date: Wed, 6 Nov 2019 13:55:27 +0100 Subject: [PATCH] Enable Anaconda Text install via serial console. This adds the Anaconda text installation test over serial console and FIXES #115. --- lib/anacondatest.pm | 8 ++ lib/fedoradistribution.pm | 10 +- lib/installedtest.pm | 13 ++- lib/utils.pm | 199 ++++++++++++++++++++++------------- templates | 48 +++++++++ tests/_boot_to_anaconda.pm | 42 +++++++- tests/_console_wait_login.pm | 4 + tests/install_text.pm | 159 +++++++++++++++++----------- 8 files changed, 344 insertions(+), 139 deletions(-) diff --git a/lib/anacondatest.pm b/lib/anacondatest.pm index 6d892e0e..f07ee0d3 100644 --- a/lib/anacondatest.pm +++ b/lib/anacondatest.pm @@ -120,6 +120,14 @@ sub root_console { if (get_var("LIVE") && get_var("DESKTOP") eq "gnome") { send_key "ctrl-alt-f3"; } + elsif (get_var("SERIAL_CONSOLE")) { + # select first virtio terminal, we rely on anaconda having run + # a root shell on it for us + select_console("root-virtio-terminal"); + # as we don't have any live image serial install tests, we + # know we don't need to login + return; + } else { send_key "ctrl-alt-f2"; } diff --git a/lib/fedoradistribution.pm b/lib/fedoradistribution.pm index e551af6d..773601d2 100644 --- a/lib/fedoradistribution.pm +++ b/lib/fedoradistribution.pm @@ -1,5 +1,6 @@ package fedoradistribution; use base 'distribution'; +use Cwd; # Fedora distribution class @@ -15,12 +16,19 @@ use base 'distribution'; # importing whole testapi creates circular dependency, so import only # necessary functions from testapi -use testapi qw(send_key type_string wait_idle assert_screen); +use testapi qw(check_var get_var send_key type_string wait_idle assert_screen); sub init() { my ($self) = @_; $self->SUPER::init(); + # This initiates the serial console as "root-virtio-terminal". + if (check_var('BACKEND', 'qemu')) { + $self->add_console('root-virtio-terminal', 'virtio-terminal', {}); + for (my $num = 1; $num < get_var('VIRTIO_CONSOLE_NUM', 1); $num++) { + $self->add_console('root-virtio-terminal' . $num, 'virtio-terminal', {socked_path => cwd() . '/virtio_console' . $num}); + } + } } sub x11_start_program { diff --git a/lib/installedtest.pm b/lib/installedtest.pm index 956c37f9..940797b4 100644 --- a/lib/installedtest.pm +++ b/lib/installedtest.pm @@ -15,9 +15,16 @@ sub root_console { my %args = ( tty => 1, # what TTY to login to @_); - - send_key "ctrl-alt-f$args{tty}"; - console_login; + if (get_var("SERIAL_CONSOLE")) { + # select the first virtio terminal, for now we assume we can + # always use that (we may have to make this smarter in future) + select_console("root-virtio-terminal"); + } + else { + # For normal terminal emulation, use key combo to reach a tty + send_key "ctrl-alt-f$args{tty}"; + } + console_login; # Do the login. } sub post_fail_hook { diff --git a/lib/utils.pm b/lib/utils.pm index ee0b8e86..c7f8eda6 100644 --- a/lib/utils.pm +++ b/lib/utils.pm @@ -15,9 +15,21 @@ our @application_list; sub run_with_error_check { my ($func, $error_screen) = @_; - die "Error screen appeared" if (check_screen $error_screen, 5); - $func->(); - die "Error screen appeared" if (check_screen $error_screen, 5); + # Check screen does not work for serial console, so we need to use + # different checking mechanism for it. + if (testapi::is_serial_terminal) { + # by using 'unless' and 'expect_not_found=>1' here we avoid + # the web UI showing each failure to see the error message as + # a 'failed match' + die "Error screen appeared" unless (wait_serial($error_screen, timeout=>5, expect_not_found=>1)); + $func->(); + die "Error screen appeared" unless (wait_serial($error_screen, timeout=>5, expect_not_found=>1)); + } + else { + die "Error screen appeared" if (check_screen $error_screen, 5); + $func->(); + die "Error screen appeared" if (check_screen $error_screen, 5); + } } # high-level 'type this string quite safely but reasonably fast' @@ -78,18 +90,27 @@ sub desktop_vt { sub boot_to_login_screen { my %args = @_; $args{timeout} //= 300; - # we may start at a screen that matches one of the needles; if so, - # wait till we don't (e.g. when rebooting at end of live install, - # we match text_console_login until the console disappears) - my $count = 5; - while (check_screen("login_screen", 3) && $count > 0) { - sleep 5; - $count -= 1; + if (testapi::is_serial_terminal) { + # For serial console, just wait for the login prompt + unless (wait_serial "login:", timeout=>$args{timeout}) { + die "No login prompt shown on serial console."; + } } - assert_screen "login_screen", $args{timeout}; - if (match_has_tag "graphical_login") { - wait_still_screen 10, 30; - assert_screen "login_screen"; + else { + # we may start at a screen that matches one of the needles; if so, + # wait till we don't (e.g. when rebooting at end of live install, + # we match text_console_login until the console disappears). + # The following is true for non-serial console. + my $count = 5; + while (check_screen("login_screen", 3) && $count > 0) { + sleep 5; + $count -= 1; + } + assert_screen "login_screen", $args{timeout}; + if (match_has_tag "graphical_login") { + wait_still_screen 10, 30; + assert_screen "login_screen"; + } } } @@ -128,8 +149,16 @@ sub desktop_switch_layout { # failed (the prompt looks different in this case). We treat this as # a soft failure. sub _console_login_finish { - if (match_has_tag "bash_noprofile") { - record_soft_failure "It looks like profile sourcing failed"; + # The check differs according to the console used. + if (testapi::is_serial_terminal) { + unless (wait_serial("-bash-.*[#\$]", timeout=>5, expect_not_found=>1)) { + record_soft_failure "It looks like profile sourcing failed"; + } + } + else { + if (match_has_tag "bash_noprofile") { + record_soft_failure "It looks like profile sourcing failed"; + } } } @@ -145,66 +174,94 @@ sub console_login { @_); $args{timeout} ||= 10; - # There's a timing problem when we switch from a logged-in console - # to a non-logged in console and immediately call this function; - # if the switch lags a bit, this function will match one of the - # logged-in needles for the console we switched from, and get out - # of sync (e.g. https://openqa.stg.fedoraproject.org/tests/1664 ) - # To avoid this, we'll sleep a few seconds before starting - sleep 4; - - my $good = ""; - my $bad = ""; - if ($args{user} eq "root") { - $good = "root_console"; - $bad = "user_console"; + # Since we do not test many serial console tests, and we probably + # only want to test serial console on a minimal installation only, + # let us not do all the magic to handle different console logins + # and let us simplify the process. + # We will check if we are logged in, and if so, we will log out to + # enable a new proper login based on the user variable. + if (get_var("SERIAL_CONSOLE")) { + # Check for the usual prompt. + if (wait_serial("~\][#\$]", timeout=>5, quiet=>1)) { + type_string "logout\n"; + # Wait a bit to let the logout properly finish. + sleep 10; + } + # Do the new login. + type_string $args{user}; + type_string "\n"; + sleep 2; + type_string $args{password}; + type_string "\n"; + # Let's perform a simple login test. This is the same as + # whoami, but has the advantage of existing in installer env + assert_script_run "id -un"; + unless (wait_serial $args{user}, timeout=>5) { + die "Logging onto the serial console has failed."; + } } else { - $good = "user_console"; - $bad = "root_console"; - } + # There's a timing problem when we switch from a logged-in console + # to a non-logged in console and immediately call this function; + # if the switch lags a bit, this function will match one of the + # logged-in needles for the console we switched from, and get out + # of sync (e.g. https://openqa.stg.fedoraproject.org/tests/1664 ) + # To avoid this, we'll sleep a few seconds before starting + sleep 4; - if (check_screen $bad, 0) { - # we don't want to 'wait' for this as it won't return - script_run "exit", 0; - sleep 2; - } + my $good = ""; + my $bad = ""; + if ($args{user} eq "root") { + $good = "root_console"; + $bad = "user_console"; + } + else { + $good = "user_console"; + $bad = "root_console"; + } - assert_screen [$good, 'text_console_login'], $args{timeout}; - # if we're already logged in, all is good - if (match_has_tag $good) { - _console_login_finish(); - return; - } - # otherwise, we saw the login prompt, type the username - type_string("$args{user}\n"); - assert_screen [$good, 'console_password_required'], 30; - # on a live image, just the user name will be enough - if (match_has_tag $good) { - _console_login_finish(); - return; - } - # otherwise, type the password - type_string "$args{password}"; - if (get_var("SWITCHED_LAYOUT") and $args{user} ne "root") { - # see _do_install_and_reboot; when layout is switched - # user password is doubled to contain both US and native - # chars - console_switch_layout; + if (check_screen $bad, 0) { + # we don't want to 'wait' for this as it won't return + script_run "exit", 0; + sleep 2; + } + + assert_screen [$good, 'text_console_login'], $args{timeout}; + # if we're already logged in, all is good + if (match_has_tag $good) { + _console_login_finish(); + return; + } + # otherwise, we saw the login prompt, type the username + type_string("$args{user}\n"); + assert_screen [$good, 'console_password_required'], 30; + # on a live image, just the user name will be enough + if (match_has_tag $good) { + _console_login_finish(); + return; + } + # otherwise, type the password type_string "$args{password}"; - console_switch_layout; - } - send_key "ret"; - # make sure we reached the console - unless (check_screen($good, 30)) { - # as of 2018-10 we have a bug in sssd which makes this take - # unusually long in the FreeIPA tests, let's allow longer, - # with a soft fail - RHBZ #1644919 - record_soft_failure "Console login is taking a long time - #1644919?"; - my $timeout = 30; - # even an extra 30 secs isn't long enough on aarch64... - $timeout = 90 if (get_var("ARCH") eq "aarch64"); - assert_screen($good, $timeout); + if (get_var("SWITCHED_LAYOUT") and $args{user} ne "root") { + # see _do_install_and_reboot; when layout is switched + # user password is doubled to contain both US and native + # chars + console_switch_layout; + type_string "$args{password}"; + console_switch_layout; + } + send_key "ret"; + # make sure we reached the console + unless (check_screen($good, 30)) { + # as of 2018-10 we have a bug in sssd which makes this take + # unusually long in the FreeIPA tests, let's allow longer, + # with a soft fail - RHBZ #1644919 + record_soft_failure "Console login is taking a long time - #1644919?"; + my $timeout = 30; + # even an extra 30 secs isn't long enough on aarch64... + $timeout = 90 if (get_var("ARCH") eq "aarch64"); + assert_screen($good, $timeout); + } } _console_login_finish(); } diff --git a/templates b/templates index fb6c2847..1067c49e 100755 --- a/templates +++ b/templates @@ -700,6 +700,17 @@ }, test_suite => { name => "install_anaconda_text" }, }, + { + machine => { name => "64bit" }, + prio => 30, + product => { + arch => "x86_64", + distri => "fedora", + flavor => "universal", + version => "*", + }, + test_suite => { name => "install_anaconda_serial_console" }, + }, { machine => { name => "64bit" }, prio => 31, @@ -2386,6 +2397,18 @@ }, test_suite => { name => "install_anaconda_text" }, }, + { + group_name => "Fedora PowerPC", + machine => { name => "ppc64le" }, + prio => 30, + product => { + arch => "ppc64le", + distri => "fedora", + flavor => "universal", + version => "*", + }, + test_suite => { name => "install_anaconda_serial_console" }, + }, { group_name => "Fedora PowerPC", machine => { name => "ppc64le" }, @@ -3474,6 +3497,18 @@ }, test_suite => { name => "install_anaconda_text" }, }, + { + group_name => "Fedora AArch64", + machine => { name => "aarch64" }, + prio => 30, + product => { + arch => "aarch64", + distri => "fedora", + flavor => "universal", + version => "*", + }, + test_suite => { name => "install_anaconda_serial_console" }, + }, { group_name => "Fedora AArch64", machine => { name => "aarch64" }, @@ -4685,6 +4720,19 @@ { key => "ANACONDA_TEXT", value => "1" }, ], }, + { + name => "install_anaconda_serial_console", + settings => [ + { key => "ANACONDA_TEXT", value => "1" }, + { key => "SERIAL_CONSOLE", value => "1" }, + # we want one console for anaconda and one for a root + # terminal + { key => "VIRTIO_CONSOLE_NUM", value => "2" }, + # we don't need to check this here and it doesn't work + # with serial console + { key => "NO_UEFI_POST", value => "1" }, + ], + }, { name => "install_rescue_encrypted", settings => [ diff --git a/tests/_boot_to_anaconda.pm b/tests/_boot_to_anaconda.pm index ec58d133..8afaedb1 100644 --- a/tests/_boot_to_anaconda.pm +++ b/tests/_boot_to_anaconda.pm @@ -37,8 +37,30 @@ sub run { } if (get_var("ANACONDA_TEXT")) { $params .= "inst.text "; - # we need this on aarch64 till #1594402 is resolved + # we need this on aarch64 till #1594402 is resolved, + # and we also can utilize this if we want to run this + # over the serial console. $params .= "console=tty0 " if (get_var("ARCH") eq "aarch64"); + # when the text installation should run over the serial console, + # we have to add some more parametres to grub. Although, the written + # test case recommends using ttyS0, OpenQA only uses that console for + # displaying information but does not accept key strokes. Therefore, + # let us use a real virtio console here. + if (get_var("SERIAL_CONSOLE")) { + # this is icky. on ppc64 (OFW), virtio-terminal is hvc1 and + # virtio-terminal1 is hvc2, because the 'standard' serial + # terminal is hvc0 (the firmware does this or something). + # On other arches, the 'standard' serial terminal is ttyS0, + # so virtio-terminal becomes hvc0 and virtio-terminal1 is + # hvc1. We want anaconda to wind up on the console that is + # virtio-terminal1 in both cases + if (get_var("OFW")) { + $params .= "console=hvc2 "; + } + else { + $params .= "console=hvc1 "; + } + } } # inst.debug enables memory use tracking $params .= "debug" if get_var("MEMCHECK"); @@ -73,10 +95,20 @@ sub run { else { if (get_var("ANACONDA_TEXT")) { # select that we don't want to start VNC; we want to run in text mode - assert_screen "anaconda_use_text_mode", 300; - type_string "2\n"; - # wait for text version of Anaconda main hub - assert_screen "anaconda_main_hub_text", 300; + if (get_var("SERIAL_CONSOLE")) { + # we direct the installer to virtio-terminal1, and use + # virtio-terminal as a root console + select_console('root-virtio-terminal1'); + unless (wait_serial "Use text mode", timeout=>120) { die "Anaconda has not started."; } + type_string "2\n"; + unless (wait_serial "Installation") { die "Text version of Anaconda has not started."; } + } + else { + assert_screen "anaconda_use_text_mode", 300; + type_string "2\n"; + # wait for text version of Anaconda main hub + assert_screen "anaconda_main_hub_text", 300; + } } else { # on lives, we have to explicitly launch anaconda diff --git a/tests/_console_wait_login.pm b/tests/_console_wait_login.pm index d13313e4..cbc8e8ad 100644 --- a/tests/_console_wait_login.pm +++ b/tests/_console_wait_login.pm @@ -20,7 +20,11 @@ sub run { # do user login unless USER_LOGIN is set to string 'false' unless (get_var("USER_LOGIN") eq "false") { + # this avoids us waiting 90 seconds for a # to show up + my $origprompt = $self->{serial_term_prompt}; + $testapi::distri->{serial_term_prompt} = '$ '; console_login(user=>get_var("USER_LOGIN", "test"), password=>get_var("USER_PASSWORD", "weakpassword")); + $testapi::distri->{serial_term_prompt} = $origprompt; } if (get_var("ROOT_PASSWORD")) { console_login(user=>"root", password=>get_var("ROOT_PASSWORD")); diff --git a/tests/install_text.pm b/tests/install_text.pm index 19c2ca3c..972875e5 100644 --- a/tests/install_text.pm +++ b/tests/install_text.pm @@ -3,11 +3,38 @@ use strict; use testapi; use utils; + +# this enables you to send a command and some post-command wait time +# in one step and also distinguishes between serial console and normal +# VNC based console and handles the wait times differently. +sub console_type_wait { + my ($string, $wait) = @_; + $wait ||= 5; + type_string $string; + if (testapi::is_serial_terminal) { + sleep $wait; + } + else { + wait_still_screen $wait; + } +} + sub run { my $self = shift; - assert_screen "anaconda_main_hub_text"; - # IMHO it's better to use sleeps than to have needle for every text screen - wait_still_screen 5; + + # First, preset the environment according to the chosen console. This test + # can run both on a VNC based console, or a serial console. + if (get_var("SERIAL_CONSOLE")) { + select_console('root-virtio-terminal1'); + unless (testapi::is_serial_terminal) { + die "The test does not run on a serial console when it should."; + } + } + else { + assert_screen "anaconda_main_hub_text"; + # IMHO it's better to use sleeps than to have needle for every text screen + wait_still_screen 5; + } # prepare for different number of spokes (e. g. as in Atomic DVD) my %spoke_number = ( @@ -21,77 +48,76 @@ sub run { "user" => 8 ); + # The error message that we are going to check for in the text installation + # must be different for serial console and a VNC terminal emulator. + my $error = ""; + if (testapi::is_serial_terminal) { + $error = "unknown error has occured"; + } + else { + $error = "anaconda_text_error"; + } + # Set timezone - run_with_error_check(sub {type_string $spoke_number{"timezone"} . "\n"}, "anaconda_text_error"); - wait_still_screen 5; - type_string "1\n"; # Set timezone - wait_still_screen 5; - type_string "1\n"; # Europe - wait_still_screen 5; - type_string "37\n"; # Prague - wait_still_screen 7; + run_with_error_check(sub {console_type_wait($spoke_number{"timezone"} . "\n")}, $error); + console_type_wait("1\n"); # Set timezone + console_type_wait("1\n"); # Europe + console_type_wait("37\n", 7); # Prague # Select disk - run_with_error_check(sub {type_string $spoke_number{"destination"} . "\n"}, "anaconda_text_error"); - wait_still_screen 5; - type_string "c\n"; # first disk selected, continue - wait_still_screen 5; - type_string "c\n"; # use all space selected, continue - wait_still_screen 5; - type_string "c\n"; # LVM selected, continue - wait_still_screen 7; + run_with_error_check(sub {console_type_wait($spoke_number{"destination"} . "\n")}, $error); + console_type_wait("c\n"); # first disk selected, continue + console_type_wait("c\n"); # use all space selected, continue + console_type_wait("c\n", 7); # LVM selected, continue # Set root password - run_with_error_check(sub {type_string $spoke_number{"rootpwd"} . "\n"}, "anaconda_text_error"); - wait_still_screen 5; - type_string get_var("ROOT_PASSWORD", "weakpassword"); - send_key "ret"; - wait_still_screen 5; - type_string get_var("ROOT_PASSWORD", "weakpassword"); - send_key "ret"; - wait_still_screen 7; + my $rootpwd = get_var("ROOT_PASSWORD", "weakpassword"); + run_with_error_check(sub {console_type_wait($spoke_number{"rootpwd"} . "\n")}, $error); + console_type_wait("$rootpwd\n"); + console_type_wait("$rootpwd\n"); # Create user - run_with_error_check(sub {type_string $spoke_number{"user"} . "\n"}, "anaconda_text_error"); - wait_still_screen 5; - type_string "1\n"; # create new - wait_still_screen 5; - type_string "3\n"; # set username - wait_still_screen 5; - type_string get_var("USER_LOGIN", "test"); - send_key "ret"; - wait_still_screen 5; + my $userpwd = get_var("USER_PASSWORD", "weakpassword"); + my $username = get_var("USER_LOGIN", "test"); + run_with_error_check(sub {console_type_wait($spoke_number{"user"} . "\n")}, $error); + console_type_wait("1\n"); # create new + console_type_wait("3\n"); # set username + console_type_wait("$username\n"); # from Rawhide-20190503.n.0 (F31) onwards, 'use password' is default if (get_release_number() < 31) { # typing "4\n" on abrt screen causes system to reboot, so be careful - run_with_error_check(sub {type_string "4\n"}, "anaconda_text_error"); # use password + run_with_error_check(sub {console_type_wait("4\n")}, $error); # use password } - wait_still_screen 5; - type_string "5\n"; # set password - wait_still_screen 5; - type_string get_var("USER_PASSWORD", "weakpassword"); - send_key "ret"; - wait_still_screen 5; - type_string get_var("USER_PASSWORD", "weakpassword"); - send_key "ret"; - wait_still_screen 5; - type_string "6\n"; # make him an administrator - wait_still_screen 5; - type_string "c\n"; - wait_still_screen 7; + console_type_wait("5\n"); # set password + console_type_wait("$userpwd\n"); + console_type_wait("$userpwd\n"); + console_type_wait("6\n"); # make him an administrator + console_type_wait("c\n", 7); my $counter = 0; - while (check_screen "anaconda_main_hub_text_unfinished", 2) { - if ($counter > 10) { - die "There are unfinished spokes in Anaconda"; + if (testapi::is_serial_terminal) { + while (wait_serial("[!]", timeout=>5, quiet=>1)) { + if ($counter > 10) { + die "There are unfinished spokes in Anaconda"; + } + sleep 10; + $counter++; + console_type_wait("r\n"); # refresh + } + } + else { + while (check_screen "anaconda_main_hub_text_unfinished", 2) { + if ($counter > 10) { + die "There are unfinished spokes in Anaconda"; + } + sleep 10; + $counter++; + console_type_wait("r\n"); # refresh } - sleep 10; - $counter++; - type_string "r\n"; # refresh } # begin installation - type_string "b\n"; + console_type_wait("b\n"); # Wait for install to end. Give Rawhide a bit longer, in case # we're on a debug kernel, debug kernel installs are really slow. @@ -99,8 +125,23 @@ sub run { if (lc(get_var('VERSION')) eq "rawhide") { $timeout = 2400; } - assert_screen "anaconda_install_text_done", $timeout; - type_string "\n"; + + if (testapi::is_serial_terminal) { + wait_serial("Installation complete", timeout=>$timeout); + if (get_var("SERIAL_CONSOLE") && get_var("OFW")) { + $self->root_console(); + # we need to force the system to load a console on both hvc1 + # and hvc2 for ppc64 serial console post-install tests + assert_script_run 'chroot /mnt/sysimage systemctl enable serial-getty@hvc1'; + assert_script_run 'chroot /mnt/sysimage systemctl enable serial-getty@hvc2'; + # back to anaconda ui + select_console("root-virtio-terminal1"); + } + } + else { + assert_screen "anaconda_install_text_done", $timeout; + } + console_type_wait("\n"); }