From c5c37473fd92df31f3e45db27184c7fe4e1e7aa6 Mon Sep 17 00:00:00 2001 From: Jiri Konecny Date: Mon, 2 Sep 2024 16:00:22 +0200 Subject: [PATCH 1/8] Remove dead spice_vd_agent code This code is not used since migration to Wayland. We need another solution for this feature. (cherry picked from commit 37fa08bf2354a989aa7a83ae0bc768764e52ab7d) Related: RHEL-67911 --- pyanaconda/display.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pyanaconda/display.py b/pyanaconda/display.py index 186daea8bb3..b09b8c03602 100644 --- a/pyanaconda/display.py +++ b/pyanaconda/display.py @@ -85,25 +85,6 @@ def start_user_systemd(): os.environ["DBUS_SESSION_BUS_ADDRESS"] = session_bus_address log.info("The session bus address is set to %s.", session_bus_address) -# Spice - -def start_spice_vd_agent(): - """Start the spice vdagent. - - For certain features to work spice requires that the guest os - is running the spice vdagent. - """ - try: - status = util.execWithRedirect("spice-vdagent", []) - except OSError as e: - log.warning("spice-vdagent failed: %s", e) - return - - if status: - log.info("spice-vdagent exited with status %d", status) - else: - log.info("Started spice-vdagent.") - # RDP From cf8709a8cf0e1ba3a03c3a1977eb6df89f152288 Mon Sep 17 00:00:00 2001 From: Jiri Konecny Date: Tue, 3 Sep 2024 10:31:58 +0200 Subject: [PATCH 2/8] Do not create GRDServer on Live ISO The GRDServer class have checks in the __init__ method which starts to complain about missing GRD in the system. That doesn't apply in Live environment where we don't support GRD in the first place. (cherry picked from commit fde3d1b27536271992408eb1c0ae245ce4208129) Related: RHEL-67911 --- pyanaconda/core/configuration/system.py | 5 ++ pyanaconda/display.py | 21 +++--- pyanaconda/startup_utils.py | 17 +++++ .../core/test_startup_utils.py | 66 ++++++++++++++++++- 4 files changed, 97 insertions(+), 12 deletions(-) diff --git a/pyanaconda/core/configuration/system.py b/pyanaconda/core/configuration/system.py index caedd1e5fa3..b1205ce8d6d 100644 --- a/pyanaconda/core/configuration/system.py +++ b/pyanaconda/core/configuration/system.py @@ -73,6 +73,11 @@ def can_start_user_systemd(self): """Can we start the user instance of systemd?""" return self._is_boot_iso + @property + def can_start_compositor(self): + """Can we start our own Wayland session?""" + return self._is_boot_iso + @property def can_switch_tty(self): """Can we change the foreground virtual terminal?""" diff --git a/pyanaconda/display.py b/pyanaconda/display.py index b09b8c03602..2ffaeec051a 100644 --- a/pyanaconda/display.py +++ b/pyanaconda/display.py @@ -22,7 +22,6 @@ import os import time import textwrap -import pkgutil import signal from pyanaconda.mutter_display import MutterDisplay, MutterConfigError @@ -37,8 +36,6 @@ from pyanaconda.modules.common.constants.services import NETWORK from pyanaconda.ui.tui.spokes.askrd import AskRDSpoke, RDPAuthSpoke from pyanaconda.ui.tui import tui_quit_callback -# needed for checking if the pyanaconda.ui.gui modules are available -import pyanaconda.ui import blivet @@ -278,6 +275,8 @@ def setup_display(anaconda, options): anaconda.display_mode = options.display_mode anaconda.interactive_mode = not options.noninteractive + # TODO: Refactor this method or maybe whole class, ideally this class should be usable only + # on boot.iso where compositor could be set if flags.rescue_mode: return @@ -286,6 +285,14 @@ def setup_display(anaconda, options): anaconda.initialize_interface() return + # we can't start compositor so not even RDP is supported, do only base initialization + if not conf.system.can_start_compositor: + anaconda.log_display_mode() + anaconda.initialize_interface() + startup_utils.fallback_to_tui_if_gtk_ui_is_not_available(anaconda) + startup_utils.check_memory(anaconda, options) + return + try: xtimeout = int(options.xtimeout) except ValueError: @@ -306,13 +313,7 @@ def setup_display(anaconda, options): rdp_credentials_sufficient = options.rdp_username and options.rdp_password # check if GUI without WebUI - if anaconda.gui_mode and not anaconda.is_webui_supported: - mods = (tup[1] for tup in pkgutil.iter_modules(pyanaconda.ui.__path__, "pyanaconda.ui.")) - if "pyanaconda.ui.gui" not in mods: - stdout_log.warning("Graphical user interface not available, falling back to text mode") - anaconda.display_mode = constants.DisplayModes.TUI - flags.use_rd = False - flags.rd_question = False + startup_utils.fallback_to_tui_if_gtk_ui_is_not_available(anaconda) # check if remote desktop mode can be started rd_can_be_started, rd_error_messages = check_rd_can_be_started(anaconda) diff --git a/pyanaconda/startup_utils.py b/pyanaconda/startup_utils.py index fc68f35ec41..4a8aec612b9 100644 --- a/pyanaconda/startup_utils.py +++ b/pyanaconda/startup_utils.py @@ -20,6 +20,7 @@ import sys import time import os +import pkgutil from blivet.arch import is_s390 from blivet.util import total_memory from dasbus.typing import get_variant, Int @@ -174,6 +175,22 @@ def set_storage_checker_minimal_ram_size(display_mode): ) +def fallback_to_tui_if_gtk_ui_is_not_available(anaconda): + """Check if GTK UI is available in this environment and fallback to TUI if not. + + Also take into account Web UI. + """ + if anaconda.gui_mode and not anaconda.is_webui_supported: + import pyanaconda.ui + + mods = (tup[1] for tup in pkgutil.iter_modules(pyanaconda.ui.__path__, "pyanaconda.ui.")) + if "pyanaconda.ui.gui" not in mods: + stdout_log.warning("Graphical user interface not available, falling back to text mode") + anaconda.display_mode = DisplayModes.TUI + flags.use_rd = False + flags.rd_question = False + + def setup_logging_from_options(options): """Configure logging according to Anaconda command line/boot options. diff --git a/tests/unit_tests/pyanaconda_tests/core/test_startup_utils.py b/tests/unit_tests/pyanaconda_tests/core/test_startup_utils.py index c027e245654..2c2156f221c 100644 --- a/tests/unit_tests/pyanaconda_tests/core/test_startup_utils.py +++ b/tests/unit_tests/pyanaconda_tests/core/test_startup_utils.py @@ -26,8 +26,10 @@ from textwrap import dedent from pyanaconda.startup_utils import print_dracut_errors, check_if_geolocation_should_be_used, \ - start_geolocation_conditionally, wait_for_geolocation_and_use, apply_geolocation_result -from pyanaconda.core.constants import GEOLOC_CONNECTION_TIMEOUT, TIMEZONE_PRIORITY_GEOLOCATION + start_geolocation_conditionally, wait_for_geolocation_and_use, apply_geolocation_result, \ + fallback_to_tui_if_gtk_ui_is_not_available +from pyanaconda.core.constants import GEOLOC_CONNECTION_TIMEOUT, TIMEZONE_PRIORITY_GEOLOCATION, \ + DisplayModes from pyanaconda.modules.common.structures.timezone import GeolocationData class StartupUtilsTestCase(unittest.TestCase): @@ -303,3 +305,63 @@ def test_apply_tz_missing(self, has_trans_mock, setup_locale_mock, geodata_mock, assert tz_proxy.Timezone == "" setup_locale_mock.assert_called_once_with("es_ES.UTF-8", loc_proxy, text_mode=False) assert os.environ == {"LANG": "es_ES.UTF-8"} + + +class TestUIHelpers(unittest.TestCase): + + @patch("pyanaconda.startup_utils.pkgutil") + @patch("pyanaconda.startup_utils.flags") + def test_fallback_tui_when_gtk_ui_not_available(self, mocked_flags, mocked_pkgutil): + mocked_anaconda = Mock() + + def check_method(gui_mode, + webui_supported, + gtk_available, + expected_display_mode, + expected_rd_output): + mocked_anaconda.gui_mode = gui_mode + mocked_anaconda.is_webui_supported = webui_supported + + # prefilled values + mocked_anaconda.display_mode = "" + mocked_flags.use_rd = None + mocked_flags.rd_question = None + + if gtk_available: + mocked_pkgutil.iter_modules.return_value = [(None, "pyanaconda.ui.gui")] + else: + mocked_pkgutil.iter_modules.return_value = [(None, "pyanaconda.ui.webui")] + + fallback_to_tui_if_gtk_ui_is_not_available(mocked_anaconda) + + assert mocked_flags.use_rd is expected_rd_output + assert mocked_flags.rd_question is expected_rd_output + assert mocked_anaconda.display_mode == expected_display_mode + + # UI is not wanted + check_method(gui_mode=False, + webui_supported=False, + gtk_available=True, + expected_display_mode="", + expected_rd_output=None) + + # check result when web ui is supported + check_method(gui_mode=True, + webui_supported=True, + gtk_available=True, + expected_display_mode="", + expected_rd_output=None) + + # check result when gtk UI is not available + check_method(gui_mode=True, + webui_supported=False, + gtk_available=False, + expected_display_mode=DisplayModes.TUI, + expected_rd_output=False) + + # check result when GTK is available + check_method(gui_mode=True, + webui_supported=False, + gtk_available=True, + expected_display_mode="", + expected_rd_output=None) From 03ae4445f6b1af7f6c9679a98c123817bfdcc6bd Mon Sep 17 00:00:00 2001 From: Jiri Konecny Date: Tue, 17 Sep 2024 16:24:41 +0200 Subject: [PATCH 3/8] Fix typo in the GRD source file name (cherry picked from commit a267c322b5f35824b46daed7700bb76fdc228db6) Related: RHEL-67911 --- anaconda.py | 4 ++-- pyanaconda/display.py | 2 +- .../{gnome_remote_destop.py => gnome_remote_desktop.py} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename pyanaconda/{gnome_remote_destop.py => gnome_remote_desktop.py} (100%) diff --git a/anaconda.py b/anaconda.py index af304eac889..c0513d77933 100755 --- a/anaconda.py +++ b/anaconda.py @@ -47,7 +47,7 @@ def exitHandler(rebootData): WatchProcesses.unwatch_all_processes() if flags.use_rd: - gnome_remote_destop.shutdown_server() + gnome_remote_desktop.shutdown_server() if "nokill" in kernel_arguments: util.vtActivate(1) @@ -284,7 +284,7 @@ def setup_environment(): opts.display_mode = constants.DisplayModes.TUI opts.noninteractive = True - from pyanaconda import gnome_remote_destop + from pyanaconda import gnome_remote_desktop from pyanaconda import kickstart # we are past the --version and --help shortcut so we can import display & # startup_utils, which import Blivet, without slowing down anything critical diff --git a/pyanaconda/display.py b/pyanaconda/display.py index 2ffaeec051a..00313f5b398 100644 --- a/pyanaconda/display.py +++ b/pyanaconda/display.py @@ -30,7 +30,7 @@ from pyanaconda.core.process_watchers import WatchProcesses from pyanaconda import startup_utils from pyanaconda.core import util, constants, hw -from pyanaconda.gnome_remote_destop import GRDServer +from pyanaconda.gnome_remote_desktop import GRDServer from pyanaconda.core.i18n import _ from pyanaconda.flags import flags from pyanaconda.modules.common.constants.services import NETWORK diff --git a/pyanaconda/gnome_remote_destop.py b/pyanaconda/gnome_remote_desktop.py similarity index 100% rename from pyanaconda/gnome_remote_destop.py rename to pyanaconda/gnome_remote_desktop.py From 6c3db102ada6f2125c35c85944ec7cc67266771a Mon Sep 17 00:00:00 2001 From: Jiri Konecny Date: Wed, 25 Sep 2024 17:37:04 +0200 Subject: [PATCH 4/8] Create GRDServer class only when required Move the GRDServer class creation to the end of the `setup_display` method. This class have checks for binaries in the `__init__` method which is causing early failures. Also do not create the class if it is not really used. (cherry picked from commit ee3c0d44a091c29c8d5e614292d3a5a9d8f9fa3b) Related: RHEL-67911 --- pyanaconda/display.py | 53 ++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/pyanaconda/display.py b/pyanaconda/display.py index 00313f5b398..205c6cde158 100644 --- a/pyanaconda/display.py +++ b/pyanaconda/display.py @@ -24,6 +24,8 @@ import textwrap import signal +from collections import namedtuple + from pyanaconda.mutter_display import MutterDisplay, MutterConfigError from pyanaconda.core.configuration.anaconda import conf from pyanaconda.core.path import join_paths @@ -50,6 +52,10 @@ log = get_module_logger(__name__) stdout_log = get_stdout_logger() + +rdp_credentials = namedtuple("rdp_credentials", ["username", "password"]) + + WAYLAND_TIMEOUT_ADVICE = \ "Do not load the stage2 image over a slow network link.\n" \ "Wait longer for Wayland startup with the inst.xtimeout= boot option." \ @@ -85,15 +91,21 @@ def start_user_systemd(): # RDP -def ask_rd_question(anaconda, grd_server, message): +def ask_rd_question(anaconda, message): """ Ask the user if TUI or GUI-over-RDP should be started. + Return Tuple(should use RDP, NameTuple rdp_credentials(username, password)) + + e.g.: + (True, rdp_credentials) + rdp_credentials.username + rdp_credentials.password + :param anaconda: instance of the Anaconda class - :param grd_server: instance of the GRD server object :param str message: a message to show to the user together with the question - :return: if remote desktop should be used - :rtype: bool + :return: (use_rd, rdp_credentials(username, password)) + :rtype: Tuple(bool, NameTuple(username, password)) """ App.initialize() loop = App.get_event_loop() @@ -107,19 +119,18 @@ def ask_rd_question(anaconda, grd_server, message): log.info("RDP requested via RDP question, switching Anaconda to GUI mode.") anaconda.display_mode = constants.DisplayModes.GUI flags.use_rd = True - grd_server.rdp_username = spoke.rdp_username - grd_server.rdp_password = spoke.rdp_password - return spoke.use_remote_desktop + return (spoke.use_remote_desktop, rdp_credentials(spoke.rdp_username, spoke.rdp_password)) -def ask_for_rd_credentials(anaconda, grd_server, username=None, password=None): + +def ask_for_rd_credentials(anaconda, username=None, password=None): """ Ask the user to provide RDP credentials interactively. :param anaconda: instance of the Anaconda class - :param grd_server: instance of the GRD server object :param str username: user set username (if any) :param str password: user set password (if any) - :rtype: bool + + :return: namedtuple rdp_credentials(username, password) """ App.initialize() loop = App.get_event_loop() @@ -131,8 +142,7 @@ def ask_for_rd_credentials(anaconda, grd_server, username=None, password=None): log.info("RDP credentials set") anaconda.display_mode = constants.DisplayModes.GUI flags.use_rd = True - grd_server.rdp_username = spoke._username - grd_server.rdp_password = spoke._password + return rdp_credentials(spoke._username, spoke._password) def check_rd_can_be_started(anaconda): """Check if we can start an RDP session in the current environment. @@ -299,16 +309,15 @@ def setup_display(anaconda, options): log.warning("invalid inst.xtimeout option value: %s", options.xtimeout) xtimeout = constants.X_TIMEOUT - grd_server = GRDServer(anaconda) # The RDP server object rdp_credentials_sufficient = False + rdp_creds = rdp_credentials("", "") if options.rdp_enabled: flags.use_rd = True if not anaconda.gui_mode: log.info("RDP requested via boot/CLI option, switching Anaconda to GUI mode.") anaconda.display_mode = constants.DisplayModes.GUI - grd_server.rdp_username = options.rdp_username - grd_server.rdp_password = options.rdp_password + rdp_creds = rdp_credentials(options.rdp_username, options.rdp_password) # note if we have both set rdp_credentials_sufficient = options.rdp_username and options.rdp_password @@ -323,7 +332,9 @@ def setup_display(anaconda, options): # or inst.rdp and insufficient credentials are provided # via boot options, ask interactively. if options.rdp_enabled and not rdp_credentials_sufficient: - ask_for_rd_credentials(anaconda, grd_server, options.rdp_username, options.rdp_password) + rdp_creds = ask_for_rd_credentials(anaconda, + options.rdp_username, + options.rdp_password) else: # RDP can't be started - disable the RDP question and log # all the errors that prevented RDP from being started @@ -337,9 +348,12 @@ def setup_display(anaconda, options): "options. It does not offer custom partitioning for " "full control over the disk layout. Would you like " "to use remote graphical access via the RDP protocol instead?") - if not ask_rd_question(anaconda, grd_server, message): + use_rd, credentials = ask_rd_question(anaconda, message) + if not use_rd: # user has explicitly specified text mode flags.rd_question = False + else: + rdp_creds = credentials anaconda.log_display_mode() startup_utils.check_memory(anaconda, options) @@ -385,11 +399,14 @@ def on_mutter_ready(observer): "an RDP session to connect to this computer from another computer and " "perform a graphical installation or continue with a text mode " "installation?") - ask_rd_question(anaconda, grd_server, message) + rdp_creds = ask_rd_question(anaconda, message) # if they want us to use RDP do that now if anaconda.gui_mode and flags.use_rd: do_startup_wl_actions(xtimeout, headless=True, headless_resolution=options.runres) + grd_server = GRDServer(anaconda) # The RDP server object + grd_server.rdp_username = rdp_creds.username + grd_server.rdp_password = rdp_creds.password grd_server.start_grd_rdp() # with Wayland running we can initialize the UI interface From 09988fd3f2f6be71edbbf09515442398fb6decd2 Mon Sep 17 00:00:00 2001 From: Katerina Koukiou Date: Tue, 30 Apr 2024 15:04:30 +0200 Subject: [PATCH 5/8] Stop pretending liveinst+vnc is supported (#678354) Anaconda runs as an app on Live images and can hardly control remote access there. (cherry picked from commit ff77c70a4d1c782303532d4e3fb89cad6e985543) Related: RHEL-67911 --- data/liveinst/liveinst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data/liveinst/liveinst b/data/liveinst/liveinst index dadcd0f573f..0843fb0084c 100755 --- a/data/liveinst/liveinst +++ b/data/liveinst/liveinst @@ -113,6 +113,17 @@ for opt in $(cat /proc/cmdline) "$@"; do fi exit 1 ;; + vnc|--vnc) + title="Configuration not supported" + text="VNC is not supported on live media." + if which zenity &> /dev/null; then + zenity --warning --title="$title" --text="$text" + else + echo "$title" >&2 + echo "$text" >&2 + fi + exit 1 + ;; esac done From eec38a2ac0abe88b471870640aba24b07f1ef0ce Mon Sep 17 00:00:00 2001 From: Jiri Konecny Date: Tue, 3 Sep 2024 13:45:17 +0200 Subject: [PATCH 6/8] Set --rdp in liveinst unsupported Previously it was vnc but we switched to RDP instead. Related: RHEL-67911 --- data/liveinst/liveinst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/liveinst/liveinst b/data/liveinst/liveinst index 0843fb0084c..70554b6b408 100755 --- a/data/liveinst/liveinst +++ b/data/liveinst/liveinst @@ -113,9 +113,9 @@ for opt in $(cat /proc/cmdline) "$@"; do fi exit 1 ;; - vnc|--vnc) + rdp|rdp.username|rdp.password|--rdp|--rdp.username|--rdp.password) title="Configuration not supported" - text="VNC is not supported on live media." + text="RDP is not supported on live media." if which zenity &> /dev/null; then zenity --warning --title="$title" --text="$text" else From 0718e64e12ec12ac187f4daab4def919d617e04e Mon Sep 17 00:00:00 2001 From: Jiri Konecny Date: Tue, 12 Nov 2024 15:42:02 +0100 Subject: [PATCH 7/8] Fix RDP var contains string instead of bool The issue with the original code is in python behavior. Python on this code: "a" or "b" will give you "b" instead of probably expected 'True' value. This is fix to avoid break of the code in the future because this variable should keep boolean value. (cherry picked from commit 56590cdeba4375b7b9fb8a2e2ab869ebbf3649d7) Related: RHEL-67911 --- pyanaconda/display.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyanaconda/display.py b/pyanaconda/display.py index 205c6cde158..2a9d2b062d6 100644 --- a/pyanaconda/display.py +++ b/pyanaconda/display.py @@ -319,7 +319,10 @@ def setup_display(anaconda, options): anaconda.display_mode = constants.DisplayModes.GUI rdp_creds = rdp_credentials(options.rdp_username, options.rdp_password) # note if we have both set - rdp_credentials_sufficient = options.rdp_username and options.rdp_password + if options.rdp_username and options.rdp_password: + rdp_credentials_sufficient = True + else: + rdp_credentials_sufficient = False # check if GUI without WebUI startup_utils.fallback_to_tui_if_gtk_ui_is_not_available(anaconda) From ebf1c4f01581f045dbd5935dac95fe9aed11bcab Mon Sep 17 00:00:00 2001 From: Jiri Konecny Date: Tue, 5 Nov 2024 10:13:50 +0100 Subject: [PATCH 8/8] Suppress warning from systemd user session Anaconda is starting systemd --user for RDP session. This unfortunately, will result in message: ``` Failed to adjust memory pressure threshold, ignoring: Device or resource busy ``` This message is printed directly to terminal and bypassing stdout or stderr. Force this message to be logged to the journal instead of terminal. Suggested-by: Lukas Nykryn (cherry picked from commit f47ca11bfe4f082258612450323b9268010190b2) Resolves: RHEL-67911 --- pyanaconda/display.py | 5 ++++- tests/unit_tests/pyanaconda_tests/test_display.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pyanaconda/display.py b/pyanaconda/display.py index 2a9d2b062d6..112462e2fbd 100644 --- a/pyanaconda/display.py +++ b/pyanaconda/display.py @@ -77,7 +77,10 @@ def start_user_systemd(): # Start the user instance of systemd. This call will also cause the launch of # dbus-broker and start a session bus at XDG_RUNTIME_DIR/bus. - childproc = util.startProgram(["/usr/lib/systemd/systemd", "--user"]) + # Without SYSTEMD_LOG_TARGET variable the systemd is logging directly to terminal + # bypassing stdout and stderr + childproc = util.startProgram(["/usr/lib/systemd/systemd", "--user"], + env_add={"SYSTEMD_LOG_TARGET": "journal-or-kmsg"}) WatchProcesses.watch_process(childproc, "systemd") # Set up the session bus address. Some services started by Anaconda might call diff --git a/tests/unit_tests/pyanaconda_tests/test_display.py b/tests/unit_tests/pyanaconda_tests/test_display.py index adf5b37ca59..231061b063a 100644 --- a/tests/unit_tests/pyanaconda_tests/test_display.py +++ b/tests/unit_tests/pyanaconda_tests/test_display.py @@ -45,7 +45,8 @@ def test_start_user_systemd(self, util_mock, conf_mock, watch_mock): start_user_systemd() util_mock.startProgram.assert_called_once_with( - ["/usr/lib/systemd/systemd", "--user"] + ["/usr/lib/systemd/systemd", "--user"], + env_add={"SYSTEMD_LOG_TARGET": "journal-or-kmsg"} ) watch_mock.watch_process.assert_called_once_with( 100, "systemd"