Skip to content
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
95 changes: 94 additions & 1 deletion rust/agama-lib/share/storage.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,10 @@
"minimum": 1,
"maximum": 128
},
"searchSortCriterionOrder": {
"description": "Direction of sorting at the search results",
"enum": ["asc", "desc"]
},
"driveSearch": {
"anyOf": [
{ "$ref": "#/$defs/searchAll" },
Expand All @@ -381,6 +385,7 @@
"additionalProperties": false,
"properties": {
"condition": { "$ref": "#/$defs/driveSearchCondition" },
"sort": { "$ref": "#/$defs/driveSearchSort" },
"max": { "$ref": "#/$defs/searchMax" },
"ifNotFound": { "$ref": "#/$defs/searchActions" }
}
Expand All @@ -391,6 +396,34 @@
{ "$ref": "#/$defs/searchConditionSize" }
]
},
"driveSearchSort": {
"anyOf": [
{ "$ref": "#/$defs/driveSearchSortCriterion" },
{
"type": "array",
"items": { "$ref": "#/$defs/driveSearchSortCriterion" }
}
]
},
"driveSearchSortCriterion": {
"anyOf": [
{ "$ref": "#/$defs/driveSearchSortCriterionShort" },
{ "$ref": "#/$defs/driveSearchSortCriterionFull" }
]
},
"driveSearchSortCriterionShort": {
"enum": ["name", "size"]
},
"driveSearchSortCriterionFull": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": { "$ref": "#/$defs/searchSortCriterionOrder" },
"size": { "$ref": "#/$defs/searchSortCriterionOrder" }
},
"minProperties": 1,
"maxProperties": 1
},
"mdRaidSearch": {
"anyOf": [
{ "$ref": "#/$defs/searchAll" },
Expand All @@ -403,6 +436,7 @@
"additionalProperties": false,
"properties": {
"condition": { "$ref": "#/$defs/mdRaidSearchCondition" },
"sort": { "$ref": "#/$defs/mdRaidSearchSort" },
"max": { "$ref": "#/$defs/searchMax" },
"ifNotFound": { "$ref": "#/$defs/searchCreatableActions" }
}
Expand All @@ -413,6 +447,34 @@
{ "$ref": "#/$defs/searchConditionSize" }
]
},
"mdRaidSearchSort": {
"anyOf": [
{ "$ref": "#/$defs/mdRaidSearchSortCriterion" },
{
"type": "array",
"items": { "$ref": "#/$defs/mdRaidSearchSortCriterion" }
}
]
},
"mdRaidSearchSortCriterion": {
"anyOf": [
{ "$ref": "#/$defs/mdRaidSearchSortCriterionShort" },
{ "$ref": "#/$defs/mdRaidSearchSortCriterionFull" }
]
},
"mdRaidSearchSortCriterionShort": {
"enum": ["name", "size"]
},
"mdRaidSearchSortCriterionFull": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": { "$ref": "#/$defs/searchSortCriterionOrder" },
"size": { "$ref": "#/$defs/searchSortCriterionOrder" }
},
"minProperties": 1,
"maxProperties": 1
},
"partitionSearch": {
"anyOf": [
{ "$ref": "#/$defs/searchAll" },
Expand All @@ -425,6 +487,7 @@
"additionalProperties": false,
"properties": {
"condition": { "$ref": "#/$defs/partitionSearchCondition" },
"sort": { "$ref": "#/$defs/partitionSearchSort" },
"max": { "$ref": "#/$defs/searchMax" },
"ifNotFound": { "$ref": "#/$defs/searchCreatableActions" }
}
Expand All @@ -441,6 +504,7 @@
"additionalProperties": false,
"properties": {
"condition": { "$ref": "#/$defs/partitionSearchCondition" },
"sort": { "$ref": "#/$defs/partitionSearchSort" },
"max": { "$ref": "#/$defs/searchMax" },
"ifNotFound": { "$ref": "#/$defs/searchActions" }
}
Expand Down Expand Up @@ -511,6 +575,35 @@
}
}
},
"partitionSearchSort": {
"anyOf": [
{ "$ref": "#/$defs/partitionSearchSortCriterion" },
{
"type": "array",
"items": { "$ref": "#/$defs/partitionSearchSortCriterion" }
}
]
},
"partitionSearchSortCriterion": {
"anyOf": [
{ "$ref": "#/$defs/partitionSearchSortCriterionShort" },
{ "$ref": "#/$defs/partitionSearchSortCriterionFull" }
]
},
"partitionSearchSortCriterionShort": {
"enum": ["name", "size", "number"]
},
"partitionSearchSortCriterionFull": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": { "$ref": "#/$defs/searchSortCriterionOrder" },
"size": { "$ref": "#/$defs/searchSortCriterionOrder" },
"number": { "$ref": "#/$defs/searchSortCriterionOrder" }
},
"minProperties": 1,
"maxProperties": 1
},
"searchMax": {
"description": "Maximum devices to match.",
"type": "integer",
Expand All @@ -531,7 +624,7 @@
"const": "*"
},
"searchName": {
"descrition": "Search by device name",
"description": "Search by device name",
"type": "string",
"examples": ["/dev/vda", "/dev/disk/by-id/ata-WDC_WD3200AAKS-75L9"]
},
Expand Down
6 changes: 6 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Mon Jun 16 14:28:22 UTC 2025 - Ancor Gonzalez Sosa <[email protected]>

- Extend the storage schema to allow sorting storage devices as
part of the search (gh#agama-project/agama#2474).

-------------------------------------------------------------------
Fri Jun 13 12:45:49 UTC 2025 - Ladislav Slezák <[email protected]>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
require "agama/storage/config_conversions/from_json_conversions/base"
require "agama/storage/config_conversions/from_json_conversions/search_conditions"
require "agama/storage/configs/search"
require "agama/storage/configs/sort_criteria"

module Agama
module Storage
Expand Down Expand Up @@ -54,6 +55,7 @@ def conversions
name: search_json.dig(:condition, :name),
size: convert_size,
partition_number: search_json.dig(:condition, :number),
sort_criteria: convert_sort,
max: search_json[:max],
if_not_found: search_json[:ifNotFound]&.to_sym
}
Expand All @@ -73,6 +75,36 @@ def convert_size

FromJSONConversions::SearchConditions::Size.new(size_json).convert
end

def convert_sort
Array(search_json[:sort]).map do |entry|
case entry
when Array
sort_criterion(entry.first, entry.last)
when Hash
sort_criterion(entry.keys.first, entry.values.first)
else
sort_criterion(entry)
end
end
end

def sort_criterion(name, order = "asc")
crit = sort_criterion_class(name).new
crit.asc = (order.to_s != "desc")
crit
end

SORT_CRITERIA = {
name: Configs::SortCriteria::Name,
size: Configs::SortCriteria::Size,
number: Configs::SortCriteria::PartitionNumber
}.freeze
private_constant :SORT_CRITERIA

def sort_criterion_class(name)
SORT_CRITERIA[name.to_sym]
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# find current contact information at www.suse.com.

require "agama/storage/config_conversions/to_json_conversions/base"
require "agama/storage/configs/sort_criteria"

module Agama
module Storage
Expand All @@ -39,6 +40,7 @@ def initialize(config)
def conversions
{
condition: convert_condition,
sort: convert_sort,
ifNotFound: config.if_not_found.to_s,
max: config.max
}
Expand Down Expand Up @@ -76,6 +78,33 @@ def convert_condition_size
size: { size.operator => size.value.to_i }
}
end

# @return [Hash, nil]
def convert_sort
criteria = config.sort_criteria
return if criteria.nil? || criteria.empty?

criteria.map do |criterion|
{ criterion_name(criterion) => criterion_order(criterion) }
end
end

SORT_CRITERIA = {
Configs::SortCriteria::Name => :name,
Configs::SortCriteria::Size => :size,
Configs::SortCriteria::PartitionNumber => :number
}.freeze
private_constant :SORT_CRITERIA

# @see #convert_sort
def criterion_name(criterion)
SORT_CRITERIA[criterion.class]
end

# @see #convert_sort
def criterion_order(criterion)
criterion.asc? ? "asc" : "desc"
end
end
end
end
Expand Down
32 changes: 31 additions & 1 deletion service/lib/agama/storage/config_solvers/devices_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "agama/storage/configs/sort_criteria"

module Agama
module Storage
module ConfigSolvers
Expand Down Expand Up @@ -59,6 +61,23 @@ def match_condition?(_device_config, _device)
raise NotImplementedError
end

# Compares the order of two devices, based on the configuration.
#
# @param device_config [#search]
# @param dev_a [Y2Storage#device]
# @param dev_b [Y2Storage#device]
#
# @return [Integer] less than 0 when b follows a, greater than 0 when a follows b
def order(device_config, dev_a, dev_b)
criteria = device_config.search&.sort_criteria || []
criteria.each do |criterion|
comparison = criterion.compare(dev_a, dev_b)
return comparison unless comparison.zero?
end

fallback_sort_criterion.compare(dev_a, dev_b)
end

# Solves the search of given device config.
#
# As result, one or several configs can be generated. For example, if the search condition
Expand All @@ -82,7 +101,7 @@ def solve_search(device_config)
def find_devices(device_config)
devices = unassigned_candidate_devices
.select { |d| match_condition?(device_config, d) }
.sort_by(&:name)
.sort { |a, b| order(device_config, a, b) }

devices.first(device_config.search.max || devices.size)
end
Expand Down Expand Up @@ -112,6 +131,17 @@ def solve_with_device(device_config, device)
@assigned_sids << device.sid
device_config.copy.tap { |d| d.search.solve(device) }
end

# Sorting criterion to use when none is given or to resolve ties in the criteria specified
# in the configuration.
#
# NOTE: To ensure consistency between different executions, comparisons by this criterion
# should never return zero. Bear that in mind if this method is redefined in any subclass.
#
# @return [Configs::SortCriteria]
def fallback_sort_criterion
@fallback_sort_criterion = Configs::SortCriteria::Name.new
end
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions service/lib/agama/storage/configs/search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@ def self.new_for_search_all
# matched
attr_accessor :max

# return [Array<SortCriteria::Base>]
attr_accessor :sort_criteria

# Constructor
def initialize
@solved = false
@if_not_found = :error
@sort_criteria = []
end

# Whether the search was already solved.
Expand Down
Loading