323 lines
11 KiB
Python
323 lines
11 KiB
Python
|
#
|
||
|
# Copyright (C) 2021 Red Hat, Inc.
|
||
|
#
|
||
|
# This copyrighted material is made available to anyone wishing to use,
|
||
|
# modify, copy, or redistribute it subject to the terms and conditions of
|
||
|
# the GNU General Public License v.2, or (at your option) any later version.
|
||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
# ANY WARRANTY expressed or implied, including the implied warranties of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||
|
# Public License for more details. You should have received a copy of the
|
||
|
# GNU General Public License along with this program; if not, write to the
|
||
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
||
|
# source code or documentation are not subject to the GNU General Public
|
||
|
# 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.
|
||
|
|
||
|
:param dnf_proxy: a proxy of the DNF payload
|
||
|
:param environment_name: an environment name
|
||
|
:return CompsEnvironmentData: an environment data
|
||
|
"""
|
||
|
return CompsEnvironmentData.from_structure(
|
||
|
dnf_proxy.GetEnvironmentData(environment_name)
|
||
|
)
|
||
|
|
||
|
|
||
|
def get_group_data(dnf_proxy, group_name):
|
||
|
"""Get the group data.
|
||
|
|
||
|
:param dnf_proxy: a proxy of the DNF payload
|
||
|
:param group_name: a group name
|
||
|
:return CompsGroupData: a group data
|
||
|
"""
|
||
|
return CompsGroupData.from_structure(
|
||
|
dnf_proxy.GetGroupData(group_name)
|
||
|
)
|
||
|
|
||
|
|
||
|
def is_software_selection_complete(dnf_proxy, selection, kickstarted=False):
|
||
|
"""Check the completeness of the software selection.
|
||
|
|
||
|
:param dnf_proxy: a proxy of the DNF payload
|
||
|
:param selection: a packages selection data
|
||
|
:param kickstarted: is the selection configured by a kickstart file?
|
||
|
:return: True if the selection is complete, otherwise False
|
||
|
"""
|
||
|
# The environment doesn't have to be set for the automated installation.
|
||
|
if kickstarted and not selection.environment:
|
||
|
return True
|
||
|
|
||
|
# The selected environment has to be valid.
|
||
|
return bool(dnf_proxy.ResolveEnvironment(selection.environment))
|
||
|
|
||
|
|
||
|
def get_software_selection_status(dnf_proxy, selection, kickstarted=False):
|
||
|
"""Get the software selection status.
|
||
|
|
||
|
:param dnf_proxy: a proxy of the DNF payload
|
||
|
:param selection: a packages selection data
|
||
|
:param kickstarted: is the selection configured by a kickstart file?
|
||
|
:return: a translated string with the selection status
|
||
|
"""
|
||
|
if kickstarted:
|
||
|
# The %packages section is present in kickstart, but environment is not set.
|
||
|
if not selection.environment:
|
||
|
return _("Custom software selected")
|
||
|
# The environment is set to an invalid value.
|
||
|
elif not dnf_proxy.ResolveEnvironment(selection.environment):
|
||
|
return _("Invalid environment specified in kickstart")
|
||
|
else:
|
||
|
if not selection.environment:
|
||
|
# No environment is set.
|
||
|
return _("Please confirm software selection")
|
||
|
elif not dnf_proxy.ResolveEnvironment(selection.environment):
|
||
|
# Selected environment is not valid, this can happen when a valid environment
|
||
|
# is selected (by default, manually or from kickstart) and then the installation
|
||
|
# source is switched to one where the selected environment is no longer valid.
|
||
|
return _("Selected environment is not valid")
|
||
|
|
||
|
# The valid environment is set.
|
||
|
environment_data = CompsEnvironmentData.from_structure(
|
||
|
dnf_proxy.GetEnvironmentData(selection.environment)
|
||
|
)
|
||
|
|
||
|
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.
|
||
|
|
||
|
Use the software selection cache to select environments
|
||
|
and groups in the user interface.
|
||
|
|
||
|
The cache remembers groups that were selected by default
|
||
|
based on the current environment, explicitly selected by
|
||
|
the user and explicitly deselected.
|
||
|
|
||
|
Selected groups that are not available in the current
|
||
|
environment are ignored.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, dnf_proxy):
|
||
|
"""Create a new cache.
|
||
|
|
||
|
:param dnf_proxy: a proxy of the DNF payload
|
||
|
"""
|
||
|
self._dnf_proxy = dnf_proxy
|
||
|
self._environment = ""
|
||
|
self._available_groups = set()
|
||
|
self._default_groups = set()
|
||
|
self._selected_groups = set()
|
||
|
self._deselected_groups = set()
|
||
|
|
||
|
@property
|
||
|
def environment(self):
|
||
|
"""The selected environment.
|
||
|
|
||
|
:return: an environment id
|
||
|
"""
|
||
|
return self._environment
|
||
|
|
||
|
@property
|
||
|
def available_environments(self):
|
||
|
"""The available environments.
|
||
|
|
||
|
:return: a list of environment ids
|
||
|
"""
|
||
|
return self._dnf_proxy.GetEnvironments()
|
||
|
|
||
|
@property
|
||
|
def groups(self):
|
||
|
"""The selected groups.
|
||
|
|
||
|
:return: a list of group ids
|
||
|
"""
|
||
|
return sorted(filter(self.is_group_selected, self._available_groups))
|
||
|
|
||
|
@property
|
||
|
def available_groups(self):
|
||
|
"""The available groups.
|
||
|
|
||
|
:return: a list of group ids
|
||
|
"""
|
||
|
return sorted(self._available_groups)
|
||
|
|
||
|
def select_environment(self, environment):
|
||
|
"""Select the specified environment.
|
||
|
|
||
|
:param str environment: an identifier of an environment
|
||
|
"""
|
||
|
log.debug("Selecting the '%s' environment.", environment)
|
||
|
|
||
|
# Reset the environment configuration.
|
||
|
self._environment = ""
|
||
|
self._default_groups = set()
|
||
|
self._available_groups = set()
|
||
|
|
||
|
# No environment is specified.
|
||
|
if not environment:
|
||
|
return
|
||
|
|
||
|
# Get the environment data.
|
||
|
environment_data = CompsEnvironmentData.from_structure(
|
||
|
self._dnf_proxy.GetEnvironmentData(environment)
|
||
|
)
|
||
|
|
||
|
# Select the environment.
|
||
|
self._environment = environment_data.id
|
||
|
|
||
|
# Select the default groups.
|
||
|
self._default_groups = set(environment_data.default_groups)
|
||
|
|
||
|
# Select the available groups.
|
||
|
self._available_groups = set(environment_data.get_available_groups())
|
||
|
|
||
|
def is_environment_selected(self, environment):
|
||
|
"""Is the specified environment marked as selected?
|
||
|
|
||
|
:param str environment: an identifier of an environment
|
||
|
:return: True if the environment is marked as selected, otherwise False
|
||
|
"""
|
||
|
return environment == self._environment
|
||
|
|
||
|
def select_group(self, group):
|
||
|
"""Select the specified group.
|
||
|
|
||
|
:param str group: an identifier of a group
|
||
|
"""
|
||
|
log.debug("Selecting the '%s' group.", group)
|
||
|
|
||
|
# Get the group data.
|
||
|
group_data = CompsGroupData.from_structure(
|
||
|
self._dnf_proxy.GetGroupData(group)
|
||
|
)
|
||
|
|
||
|
# Remove the group from the deselected groups.
|
||
|
self._deselected_groups.discard(group_data.id)
|
||
|
|
||
|
# Add the group to the selected groups if it is not a default.
|
||
|
if group_data.id not in self._default_groups:
|
||
|
self._selected_groups.add(group_data.id)
|
||
|
|
||
|
def deselect_group(self, group):
|
||
|
"""Select or deselect the specified group.
|
||
|
|
||
|
:param str group: an identifier of a group
|
||
|
"""
|
||
|
log.debug("Deselecting the '%s' group.", group)
|
||
|
|
||
|
# Get the group data.
|
||
|
group_data = CompsGroupData.from_structure(
|
||
|
self._dnf_proxy.GetGroupData(group)
|
||
|
)
|
||
|
|
||
|
# Add the group to the deselected groups. We don't need
|
||
|
# to remove the group from selected or default groups,
|
||
|
# because deselected groups have a higher priority.
|
||
|
self._deselected_groups.add(group_data.id)
|
||
|
|
||
|
def is_group_selected(self, group):
|
||
|
"""Is the specified group marked as selected?
|
||
|
|
||
|
:param str group: an identifier of a group
|
||
|
:return: True if the group is marked as selected, otherwise False
|
||
|
"""
|
||
|
# The group is not available in the current environment.
|
||
|
if group not in self._available_groups:
|
||
|
return False
|
||
|
|
||
|
# The group is explicitly deselected by the user.
|
||
|
if group in self._deselected_groups:
|
||
|
return False
|
||
|
|
||
|
# The group is selected by default or by the user.
|
||
|
return group in self._default_groups or group in self._selected_groups
|
||
|
|
||
|
def apply_selection_data(self, selection: PackagesSelectionData):
|
||
|
"""Apply the selection data.
|
||
|
|
||
|
Invalid environments and groups will be ignored. If the environment
|
||
|
cannot be selected, we will choose a default one.
|
||
|
|
||
|
:param selection: a packages selection data
|
||
|
"""
|
||
|
# Select the environment.
|
||
|
if self._dnf_proxy.ResolveEnvironment(selection.environment):
|
||
|
self.select_environment(selection.environment)
|
||
|
else:
|
||
|
log.warning("The '%s' environment couldn't be selected.", selection.environment)
|
||
|
self.select_environment(self._dnf_proxy.GetDefaultEnvironment())
|
||
|
|
||
|
# Select groups.
|
||
|
self._selected_groups = set()
|
||
|
self._deselected_groups = set()
|
||
|
|
||
|
for group in selection.groups:
|
||
|
if self._dnf_proxy.ResolveGroup(group):
|
||
|
self.select_group(group)
|
||
|
continue
|
||
|
|
||
|
log.warning("The '%s' group couldn't be selected.", group)
|
||
|
|
||
|
def get_selection_data(self) -> PackagesSelectionData:
|
||
|
"""Generate the selection data.
|
||
|
|
||
|
:return: a packages selection data
|
||
|
"""
|
||
|
selection = PackagesSelectionData()
|
||
|
selection.environment = self.environment
|
||
|
selection.groups = self.groups
|
||
|
return selection
|