Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RHEL-10: Fix user systemd error printed to console and other minor RDP fixes #6004

Merged
merged 8 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions anaconda.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
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
Loading