Skip to content

Commit

Permalink
Merge pull request #6004 from jkonecny12/rhel-10-backport-rdp-fixes
Browse files Browse the repository at this point in the history
RHEL-10: Fix user systemd error printed to console and other minor RDP fixes
  • Loading branch information
elkoniu authored Nov 27, 2024
2 parents 1864182 + ebf1c4f commit 3c74fbd
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 55 deletions.
4 changes: 2 additions & 2 deletions anaconda.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,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)
Expand Down Expand Up @@ -274,7 +274,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
Expand Down
11 changes: 11 additions & 0 deletions data/liveinst/liveinst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ for opt in $(cat /proc/cmdline) "$@"; do
fi
exit 1
;;
rdp|rdp.username|rdp.password|--rdp|--rdp.username|--rdp.password)
title="Configuration not supported"
text="RDP 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

Expand Down
5 changes: 5 additions & 0 deletions pyanaconda/core/configuration/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?"""
Expand Down
105 changes: 55 additions & 50 deletions pyanaconda/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,22 @@
import os
import time
import textwrap
import pkgutil
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
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
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

Expand All @@ -53,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=<SECONDS> boot option." \
Expand All @@ -74,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
Expand All @@ -85,37 +91,24 @@ 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

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()
Expand All @@ -129,19 +122,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()
Expand All @@ -153,8 +145,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.
Expand Down Expand Up @@ -297,6 +288,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

Expand All @@ -305,33 +298,37 @@ 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:
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
if options.rdp_username and options.rdp_password:
rdp_credentials_sufficient = True
else:
rdp_credentials_sufficient = False

# 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)
Expand All @@ -341,7 +338,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
Expand All @@ -355,9 +354,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)
Expand Down Expand Up @@ -403,11 +405,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
Expand Down
File renamed without changes.
17 changes: 17 additions & 0 deletions pyanaconda/startup_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
66 changes: 64 additions & 2 deletions tests/unit_tests/pyanaconda_tests/core/test_startup_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
3 changes: 2 additions & 1 deletion tests/unit_tests/pyanaconda_tests/test_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 3c74fbd

Please sign in to comment.