Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
joseivanlopez committed Sep 2, 2024
1 parent 49efe0d commit be32830
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 51 deletions.
13 changes: 2 additions & 11 deletions service/lib/agama/storage/configs/drive.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,9 @@ def initialize
@search = Search.new
end

# Resolves the search, so a devices of the given devicegraph is associated to the drive if
# possible
# Assigned device according to the search.
#
# @param devicegraph [Y2Storage::Devicegraph] source of the search
# @param used_sids [Array<Integer>] SIDs of the devices that are already associated to
# another drive, so they cannot be associated to this
def search_device(devicegraph, used_sids)
devs = devicegraph.blk_devices.select { |d| d.is?(:disk_device, :stray_blk_device) }
search.find(devs, used_sids)
end

# Device resulting from a previous call to {#search_device}
# @see {Y2Storage::Proposal::AgamaSearcher}
#
# @return [Y2Storage::Device, nil]
def found_device
Expand Down
14 changes: 2 additions & 12 deletions service/lib/agama/storage/configs/partition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,9 @@ def initialize
@size = Size.new
end

# Resolves the search if the partition specification contains any, associating a partition
# of the given device if possible
# Assigned device according to the search.
#
# @param partitionable [Y2Storage::Partitionable] scope for the search
# @param used_sids [Array<Integer>] SIDs of the devices that are already associated to
# another partition definition, so they cannot be associated to this
def search_device(partitionable, used_sids)
return unless search

search.find(partitionable.partitions, used_sids)
end

# Device resulting from a previous call to {#search_device}
# @see {Y2Storage::Proposal::AgamaSearcher}
#
# @return [Y2Storage::Device, nil]
def found_device
Expand Down
25 changes: 15 additions & 10 deletions service/lib/agama/storage/configs/search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,29 +42,34 @@ def initialize
@if_not_found = :error
end

# Whether the search does not define any specific condition.
#
# @return [Boolean]
def any_device?
name.nil?
end

# Whether {#find} was already called
#
# @return [Boolean]
def resolved?
!!@resolved
end

# Resolves the search with the given device.
#
# @param device [Y2Storage::Device, nil]
def resolve(device = nil)
@device = device
@resolved = true
end

# Whether the section containing the search should be skipped
#
# @return [Boolean]
def skip_device?
resolved? && device.nil? && if_not_found == :skip
end

# Resolve the search, associating the corresponding device to {#device}
#
# @param candidate_devs [Array<Y2Storage::Device>] candidate devices
# @param used_sids [Array<Integer>] SIDs of the devices that are already used elsewhere
def find(candidate_devs, used_sids)
devices = candidate_devs.reject { |d| used_sids.include?(d.sid) }
@resolved = true
@device = devices.min_by(&:name)
end
end
end
end
Expand Down
40 changes: 38 additions & 2 deletions service/lib/y2storage/agama_proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,37 @@
require "y2storage/planned"

module Y2Storage
# Class to calculate a storage proposal for autoinstallation using Agama
# Class to calculate a storage proposal for auto-installation using Agama.
#
# @note The storage config (initial_settings param in constructor) is modified in several ways:
# * The search configs are resolved.
# * Every config with an unfound search (e.g., a drive config, a partition config) is removed if
# its search has #if_not_found set to skip.
#
# It would be preferable to work over a copy instead of modifying the given config. In some
# cases, the config object is needed to generate its JSON format. The JSON result would not
# be 100% accurate if some elements are removed.
#
# The original config without removing elements is needed if:
# * The current proposal is the initial proposal automatically calculated by Agama. In
# this case, the config is generated from the product definition. The config JSON format is
# obtained by converting the config object to JSON.
# * The current proposal was calculated from a settings following the guided schema. This
# usually happens when a proposal is calculated from the UI. In this case, a config is
# generated from the guided settings. The config JSON format is obtained by converting the
# config object to JSON.
#
# In those two cases (initial proposal and proposal from guided settings) no elements are
# removed from the config because it has no searches with skip:
# * The config from the product definition has a drive that fails with unfound search (i.e.,
# there is no candidate device for installing the system).
# * The config from the guided settings has all drives and partitions with search set to
# error. The proposal fails if the selected devices are not found.
#
# In the future there could be any other scenario in which it would be needed to keep all the
# elements from a initial config containing searches with skip.
#
# @todo Duplicate initial config to avoid changes in the given config object.
#
# @example Creating a proposal from the current Agama configuration
# config = Agama::Storage::Config.new_from_json(config_json)
Expand Down Expand Up @@ -84,7 +114,13 @@ def fatal_error?
#
# @raise [NoDiskSpaceError] if there is no enough space to perform the installation
def calculate_proposal
Proposal::AgamaSearcher.new.search(initial_devicegraph, settings, issues_list)
# TODO: Could the search be moved to the devices planner? If so, the settings object might
# keep untouched, directly generating planned devices associated to the found device and
# skipping planned devices for searches with skip if not found.
Proposal::AgamaSearcher
.new(initial_devicegraph)
.search(settings, issues_list)

if fatal_error?
# This means some IfNotFound is set to "error" and we failed to find a match
@devices = nil
Expand Down
93 changes: 77 additions & 16 deletions service/lib/y2storage/proposal/agama_searcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,46 +28,107 @@ class AgamaSearcher
include Yast::Logger
include Yast::I18n

# Constructor
def initialize
# @param devicegraph [Devicegraph] used to find the corresponding devices that will get
# associated to each search element.
def initialize(devicegraph)
textdomain "agama"

@devicegraph = devicegraph
end

# Resolve all the 'search' elements within a given configuration
#
# The second argument (the storage configuration) gets modified in several ways:
# The first argument (the storage configuration) gets modified in several ways:
#
# - All its 'search' elements get resolved, associating devices from the devicegraph
# (first argument) if some is found.
# - Some device definitions can get removed if configured to be skipped in absence of a
# corresponding device
#
# The third argument (the list of issues) gets modified by adding any found problem.
# The second argument (the list of issues) gets modified by adding any found problem.
#
# @param devicegraph [Devicegraph] used to find the corresponding devices that will get
# associated to each search element
# @param settings [Agama::Storage::Config] storage configuration containing device definitions
# @param config [Agama::Storage::Config] storage configuration containing device definitions
# like drives, volume groups, etc.
# @param issues_list [Array<Agama::Issue>]
def search(devicegraph, settings, issues_list)
def search(config, issues_list)
@sids = []
settings.drives.each do |drive|
drive.search_device(devicegraph, @sids)
process_element(drive, settings.drives, issues_list)
config.drives.each do |drive_config|
device = find_drive(drive_config.search)
drive_config.search.resolve(device)

next unless drive.found_device && drive.partitions?
process_element(drive_config, config.drives, issues_list)

drive.partitions.each do |part|
next unless part.search
next unless drive_config.found_device && drive_config.partitions?

part.search_device(drive.found_device, @sids)
process_element(part, drive.partitions, issues_list)
drive_config.partitions.each do |partition_config|
next unless partition_config.search

partition = find_partition(partition_config.search, drive_config.found_device)
partition_config.search.resolve(partition)
process_element(partition_config, drive_config.partitions, issues_list)
end
end
end

private

# @return [Devicegraph]
attr_reader :devicegraph

# @return sids [Array<Integer>] SIDs of the devices that are already associated to another
# search.
attr_reader :sids

# Finds a drive matching the given search config.
#
# @param search_config [Agama::Storage::Configs::Search]
# @return [Y2Storage::Device, nil]
def find_drive(search_config)
candidates = candidate_devices(search_config, default: devicegraph.blk_devices)
candidates.select! { |d| d.is?(:disk_device, :stray_blk_device) }
next_unassigned_device(candidates)
end

# Finds a partitions matching the given search config.
#
# @param search_config [Agama::Storage::Configs::Search]
# @return [Y2Storage::Device, nil]
def find_partition(search_config, device)
candidates = candidate_devices(search_config, default: device.partitions)
candidates.select! { |d| d.is?(:partition) }
next_unassigned_device(candidates)
end

# Candidate devices for the given search config.
#
# @param search_config [Agama::Storage::Configs::Search]
# @param default [Array<Y2Storage::Device>] Candidates if the search does not indicate
# conditions.
# @return [Array<Y2Storage::Device>]
def candidate_devices(search_config, default: [])
return default if search_config.any_device?

[find_device(search_config)].compact
end

# Perfomrs a search in the devicegraph to find a device matching the given search config.
#
# @param search_config [Agama::Storage::Configs::Search]
# @return [Y2Storage::Device]
def find_device(search_config)
devicegraph.find_by_any_name(search_config.name)
end

# Next unassigned device from the given list.
#
# @param devices [Array<Y2Storage::Device>]
# @return [Y2Storage::Device, nil]
def next_unassigned_device(devices)
devices
.reject { |d| sids.include?(d.sid) }
.min_by(&:name)
end

# @see #search
def process_element(element, collection, issues_list)
found = element.found_device
Expand Down

0 comments on commit be32830

Please sign in to comment.