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

Port kernel features support to fedora #5414

Merged
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
3 changes: 3 additions & 0 deletions data/anaconda.conf
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ password_policies =
user (quality 1, length 6, empty)
luks (quality 1, length 6)

# Should kernel options be shown in the software selection spoke?
show_kernel_options = True

[License]
# A path to EULA (if any)
#
Expand Down
1 change: 1 addition & 0 deletions data/profile.d/rhel.conf
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ swap_is_recommended = True

[User Interface]
custom_stylesheet = /usr/share/anaconda/pixmaps/redhat.css
show_kernel_options = True

[License]
eula = /usr/share/redhat-release/EULA
4 changes: 4 additions & 0 deletions pyanaconda/core/configuration/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,7 @@ def _validate_policy_attributes(attrs):

if "quality" not in attrs:
raise ValueError("The minimal quality is not specified.")

@property
def show_kernel_options(self):
return self._get_option("show_kernel_options", bool)
8 changes: 8 additions & 0 deletions pyanaconda/modules/payloads/payload/dnf/dnf.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,3 +477,11 @@ def post_install_with_tasks(self):
dnf_manager=self.dnf_manager
)
]

def match_available_packages(self, pattern):
"""Find available packages that match the specified pattern.

:param pattern: a pattern for package names
:return: a list of matched package names
"""
return self.dnf_manager.match_available_packages(pattern)
8 changes: 8 additions & 0 deletions pyanaconda/modules/payloads/payload/dnf/dnf_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,11 @@ def GetRepoConfigurations(self) -> List[Structure]:
return RepoConfigurationData.to_structure_list(
self.implementation.get_repo_configurations()
)

def MatchAvailablePackages(self, pattern: Str) -> List[Str]:
"""Find available packages that match the specified pattern.

:param pattern: a pattern for package names
:return: a list of matched package names
"""
return self.implementation.match_available_packages(pattern)
222 changes: 162 additions & 60 deletions pyanaconda/ui/gui/spokes/software_selection.glade

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions pyanaconda/ui/gui/spokes/software_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@
from pyanaconda.ui.gui.spokes.lib.detailederror import DetailedErrorDialog
from pyanaconda.ui.gui.spokes.lib.software_selection import GroupListBoxRow, SeparatorRow, \
EnvironmentListBoxRow
from pyanaconda.ui.gui.utils import escape_markup
from pyanaconda.ui.lib.software import SoftwareSelectionCache, get_software_selection_status, \
is_software_selection_complete, get_group_data, get_environment_data
from pyanaconda.ui.lib.subscription import is_cdn_registration_required
from pyanaconda.ui.lib.software import FEATURE_64K, KernelFeatures, get_kernel_from_properties, \
get_available_kernel_features, get_kernel_titles_and_descriptions
from pyanaconda.core.configuration.anaconda import conf

import gi
gi.require_version("Gtk", "3.0")
Expand Down Expand Up @@ -94,6 +98,23 @@ def __init__(self, *args, **kwargs):
Gtk.Scrollable.get_vadjustment(addon_viewport)
)

# Display a group of options for selecting desired properties of a kernel
self._kernel_box = self.builder.get_object("kernelBox")
self._combo_kernel_page_size = self.builder.get_object("kernelPageSizeCombo")
self._label_kernel_page_size = self.builder.get_object("kernelPageSizeLabel")

# Normally I would create these in the .glade file but due to a bug they weren't
# created properly
self._model_kernel_page_size = Gtk.ListStore(str, str)

kernel_labels = get_kernel_titles_and_descriptions()
for i in ["4k", "64k"]:
self._model_kernel_page_size.append([i, "<b>%s</b>\n%s" %
(escape_markup(kernel_labels[i][0]),
escape_markup(kernel_labels[i][1]))])
self._combo_kernel_page_size.set_model(self._model_kernel_page_size)
self._available_kernels = get_available_kernel_features(self.payload.proxy)

@property
def _selection(self):
"""The packages selection."""
Expand Down Expand Up @@ -214,10 +235,12 @@ def refresh(self):
# Create a new software selection cache.
self._selection_cache = SoftwareSelectionCache(self.payload.proxy)
self._selection_cache.apply_selection_data(self._selection)
self._available_kernels = get_available_kernel_features(self.payload.proxy)

# Refresh up the UI.
self._refresh_environments()
self._refresh_groups()
self._refresh_kernel_features()

# Set up the info bar.
self.clear_info()
Expand Down Expand Up @@ -291,13 +314,50 @@ def _clear_listbox(self, listbox):
listbox.remove(child)
del child

def _refresh_kernel_features(self):
"""Display options for selecting kernel features."""

# Only showing parts of kernel box relevant for current system.
self._available_kernels = get_available_kernel_features(self.payload.proxy)

show_kernels = False
if conf.ui.show_kernel_options:
for (_key, val) in self._available_kernels.items():
if val:
show_kernels = True
break

if show_kernels:
self._kernel_box.set_visible(True)
self._kernel_box.set_no_show_all(False)

# Arm 64k page size kernel combo
self._combo_kernel_page_size.set_visible(self._available_kernels[FEATURE_64K])
self._combo_kernel_page_size.set_no_show_all(not self._available_kernels[FEATURE_64K])
self._label_kernel_page_size.set_visible(self._available_kernels[FEATURE_64K])
self._label_kernel_page_size.set_no_show_all(not self._available_kernels[FEATURE_64K])
else:
# Hide the entire box.
self._kernel_box.set_visible(False)
self._kernel_box.set_no_show_all(True)

def apply(self):
"""Apply the changes."""
self._kickstarted = False

selection = self._selection_cache.get_selection_data()
log.debug("Setting new software selection: %s", selection)

# Select kernel
property_64k = self._available_kernels[FEATURE_64K] and \
self._combo_kernel_page_size.get_active_id() == FEATURE_64K
kernel_properties = KernelFeatures(property_64k)
kernel = get_kernel_from_properties(kernel_properties)
if kernel is not None and conf.ui.show_kernel_options:
log.debug("Selected kernel package: %s", kernel)
selection.packages.append(kernel)
selection.excluded_packages.append("kernel")

self.payload.set_packages_selection(selection)

hubQ.send_not_ready(self.__class__.__name__)
Expand Down
40 changes: 40 additions & 0 deletions pyanaconda/ui/lib/software.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
from collections import namedtuple
from blivet.arch import is_aarch64

from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core.i18n import _
from pyanaconda.modules.common.structures.comps import CompsEnvironmentData, CompsGroupData
from pyanaconda.modules.common.structures.packages import PackagesSelectionData

log = get_module_logger(__name__)

FEATURE_64K = "64k"
KernelFeatures = namedtuple("KernelFeatures", ["page_size_64k"])

def get_environment_data(dnf_proxy, environment_name):
"""Get the environment data.
Expand Down Expand Up @@ -96,6 +101,41 @@ def get_software_selection_status(dnf_proxy, selection, kickstarted=False):
return environment_data.name


def get_available_kernel_features(dnf_proxy):
"""Returns a dictionary with that shows which kernels should be shown in the UI.
"""
features = {
FEATURE_64K: is_aarch64() and any(dnf_proxy.MatchAvailablePackages("kernel-64k"))
}

return features


def get_kernel_titles_and_descriptions():
"""Returns a dictionary with descriptions and titles for different kernel options.
"""
kernel_features = {
"4k": (_("4k"), _("More efficient memory usage in smaller environments")),
"64k": (_("64k"), _("System performance gains for memory-intensive workloads")),
}

return kernel_features


def get_kernel_from_properties(features):
"""Translates the selection of required properties into a kernel package name and returns it
or returns None if no properties were selected.
"""
kernels = {
# ARM 64k Package Name
(False): None,
(True): "kernel-64k",
}

kernel_package = kernels[features[0]]
return kernel_package


class SoftwareSelectionCache(object):
"""The cache of the user software selection.

Expand Down
101 changes: 97 additions & 4 deletions pyanaconda/ui/tui/spokes/software_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
is_software_selection_complete, SoftwareSelectionCache, get_group_data, get_environment_data
from pyanaconda.ui.tui.spokes import NormalTUISpoke
from pyanaconda.core.threads import thread_manager
from pyanaconda.ui.lib.software import FEATURE_64K, KernelFeatures, \
get_kernel_from_properties, get_available_kernel_features, get_kernel_titles_and_descriptions
from pyanaconda.core.i18n import N_, _
from pyanaconda.core.constants import THREAD_PAYLOAD, THREAD_CHECK_SOFTWARE, \
THREAD_SOFTWARE_WATCHER, PAYLOAD_TYPE_DNF
from pyanaconda.core.configuration.anaconda import conf

from simpleline.render.containers import ListColumnContainer
from simpleline.render.prompt import Prompt
Expand Down Expand Up @@ -69,6 +72,8 @@ def __init__(self, data, storage, payload):

# Get the packages configuration.
self._selection_cache = SoftwareSelectionCache(self.payload.proxy)
self._kernel_selection = None
self._available_kernels = None

# Are we taking values (package list) from a kickstart file?
self._kickstarted = flags.automatedInstall and self.payload.proxy.PackagesKickstarted
Expand All @@ -92,6 +97,9 @@ def _initialize(self):
"""Initialize the spoke in a separate thread."""
thread_manager.wait(THREAD_PAYLOAD)

self._available_kernels = get_available_kernel_features(self.payload.proxy)
self._kernel_selection = dict.fromkeys(self._available_kernels, False)

# Initialize and check the software selection.
self._initialize_selection()

Expand Down Expand Up @@ -252,7 +260,8 @@ def input(self, args, key):
self.data,
self.storage,
self.payload,
self._selection_cache
self._selection_cache,
self._kernel_selection
)
ScreenHandler.push_screen_modal(spoke)
self.apply()
Expand All @@ -269,6 +278,19 @@ def apply(self):
selection = self._selection_cache.get_selection_data()
log.debug("Setting new software selection: %s", selection)

# Processing chosen kernel
if conf.ui.show_kernel_options:
self._available_kernels = get_available_kernel_features(self.payload.proxy)
feature_64k = self._available_kernels[FEATURE_64K] and \
self._kernel_selection[FEATURE_64K]
features = KernelFeatures(feature_64k)
kernel = get_kernel_from_properties(features)
if kernel:
log.debug("Selected kernel package: %s", kernel)
selection.packages.append(kernel)
selection.excluded_packages.append("kernel")

log.debug("Setting new software selection: %s", self._selection)
self.payload.set_packages_selection(selection)

def execute(self):
Expand Down Expand Up @@ -297,11 +319,12 @@ class AdditionalSoftwareSpoke(NormalTUISpoke):
"""The spoke for choosing the additional software."""
category = SoftwareCategory

def __init__(self, data, storage, payload, selection_cache):
def __init__(self, data, storage, payload, selection_cache, kernel_selection):
super().__init__(data, storage, payload)
self.title = N_("Software selection")
self._container = None
self._selection_cache = selection_cache
self._kernel_selection = kernel_selection

def refresh(self, args=None):
"""Refresh the screen."""
Expand Down Expand Up @@ -342,11 +365,81 @@ def _select_group(self, group):
else:
self._selection_cache.deselect_group(group)

def _show_kernel_features_screen(self, kernels):
"""Returns True if at least one non-standard kernel is available.
"""
if not conf.ui.show_kernel_options:
return False
for val in kernels.values():
if val:
return True
return False

def input(self, args, key):
if self._container.process_user_input(key):
return InputState.PROCESSED_AND_REDRAW
else:
return super().input(args, key)
if key.lower() == Prompt.CONTINUE:
available_kernels = get_available_kernel_features(self.payload.proxy)
if self._show_kernel_features_screen(available_kernels):
spoke = KernelSelectionSpoke(self.data, self.storage, self.payload,
self._selection_cache, self._kernel_selection,
available_kernels)
ScreenHandler.push_screen_modal(spoke)
self.execute()
self.close()
return InputState.PROCESSED

return super().input(args, key)

def apply(self):
pass


class KernelSelectionSpoke(NormalTUISpoke):
"""A subspoke for selecting kernel features.
"""
def __init__(self, data, storage, payload, selection_cache,
_kernel_selection, available_kernels):
super().__init__(data, storage, payload)
self.title = N_("Kernel Options")
self._container = None
self._selection_cache = selection_cache
self._kernel_selection = _kernel_selection
self._available_kernels = available_kernels

def refresh(self, args=None):
NormalTUISpoke.refresh(self)

# Retrieving translated UI strings
labels = get_kernel_titles_and_descriptions()

# Updating kernel availability
self._available_kernels = get_available_kernel_features(self.payload.proxy)
self._container = ListColumnContainer(2, columns_width=38, spacing=2)

# Rendering kernel checkboxes
for (name, val) in self._kernel_selection.items():
if not self._available_kernels[name]:
continue
(title, text) = labels[name]
widget = CheckboxWidget(title="%s" % title, text="%s" % text, completed=val)
self._container.add(widget, callback=self._set_kernel_callback, data=name)

self.window.add_with_separator(TextWidget(_("Kernel options")))
self.window.add_with_separator(self._container)

def _set_kernel_callback(self, data):
self._kernel_selection[data] = not self._kernel_selection[data]

def input(self, args, key):
if self._container.process_user_input(key):
return InputState.PROCESSED_AND_REDRAW

if key.lower() == Prompt.CONTINUE:
self.close()
return InputState.PROCESSED

return super().input(args, key)

def apply(self):
pass
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,16 @@ def test_url_get_repo_configurations(self, publisher):

assert self.interface.GetRepoConfigurations() == [expected]

def test_match_available_packages(self):
"""Test the MatchAvailablePackages method."""
assert self.interface.MatchAvailablePackages("p") == []

dnf_manager = Mock(spec=DNFManager)
dnf_manager.match_available_packages.return_value = ["p1", "p2"]
self.module._dnf_manager = dnf_manager

assert self.interface.MatchAvailablePackages("p") == ["p1", "p2"]


class DNFModuleTestCase(unittest.TestCase):
"""Test the DNF module."""
Expand Down
Loading
Loading