From 48c3238e8cfa3eb0311f73dc446c7ef26f57d751 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Fri, 13 Jun 2025 10:22:57 +0200 Subject: [PATCH 1/5] Initial implementation of sorting in the storage configuration --- .../from_json_conversions/search.rb | 32 ++++ .../to_json_conversions/search.rb | 29 ++++ .../storage/config_solvers/devices_search.rb | 32 +++- service/lib/agama/storage/configs/search.rb | 4 + .../agama/storage/configs/sort_criteria.rb | 88 +++++++++++ .../to_json_conversions/search_test.rb | 21 ++- .../config_solvers/drives_search_test.rb | 137 +++++++++++++++++- .../config_solvers/partitions_search_test.rb | 39 +++++ service/test/fixtures/sizes.yaml | 9 ++ 9 files changed, 383 insertions(+), 8 deletions(-) create mode 100644 service/lib/agama/storage/configs/sort_criteria.rb diff --git a/service/lib/agama/storage/config_conversions/from_json_conversions/search.rb b/service/lib/agama/storage/config_conversions/from_json_conversions/search.rb index b7bae22a56..e2da132090 100644 --- a/service/lib/agama/storage/config_conversions/from_json_conversions/search.rb +++ b/service/lib/agama/storage/config_conversions/from_json_conversions/search.rb @@ -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 @@ -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 } @@ -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 diff --git a/service/lib/agama/storage/config_conversions/to_json_conversions/search.rb b/service/lib/agama/storage/config_conversions/to_json_conversions/search.rb index e6dca17fb2..2779a5ea62 100644 --- a/service/lib/agama/storage/config_conversions/to_json_conversions/search.rb +++ b/service/lib/agama/storage/config_conversions/to_json_conversions/search.rb @@ -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 @@ -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 } @@ -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 diff --git a/service/lib/agama/storage/config_solvers/devices_search.rb b/service/lib/agama/storage/config_solvers/devices_search.rb index 14517f9c18..567b1ebb32 100644 --- a/service/lib/agama/storage/config_solvers/devices_search.rb +++ b/service/lib/agama/storage/config_solvers/devices_search.rb @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/service/lib/agama/storage/configs/search.rb b/service/lib/agama/storage/configs/search.rb index 827094c0cb..b1cc12292e 100644 --- a/service/lib/agama/storage/configs/search.rb +++ b/service/lib/agama/storage/configs/search.rb @@ -62,10 +62,14 @@ def self.new_for_search_all # matched attr_accessor :max + # return [Array] + attr_accessor :sort_criteria + # Constructor def initialize @solved = false @if_not_found = :error + @sort_criteria = [] end # Whether the search was already solved. diff --git a/service/lib/agama/storage/configs/sort_criteria.rb b/service/lib/agama/storage/configs/sort_criteria.rb new file mode 100644 index 0000000000..ddcb5127b2 --- /dev/null +++ b/service/lib/agama/storage/configs/sort_criteria.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +# Copyright (c) [2025] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty 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, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +module Agama + module Storage + module Configs + # Namespace for criteria used to sort the result when searching devices. + module SortCriteria + # Base class for all sorting criteria + class Base + # Constructor + def initialize + @asc = true + end + + # @return [Boolean] Whether sorting must be in ascending order + attr_accessor :asc + alias_method :asc?, :asc + + # @return [Boolean] Whether sorting must be in descending order + def desc + !@asc + end + alias_method :desc?, :desc + + # @see #desc + def desc=(value) + @asc = !value + end + + # Order for the two operators + # + # @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, 0 when + # both are equivalent + def compare(dev_a, dev_b) + asc? ? compare_asc(dev_a, dev_b) : compare_asc(dev_b, dev_a) + end + + # @see #compare + def compare_asc(dev_a, dev_b) + raise NotImplementedError + end + end + + # Compares by device name + class Name < Base + def compare_asc(dev_a, dev_b) + dev_a.name <=> dev_b.name + end + end + + # Compares by device size + class Size < Base + def compare_asc(dev_a, dev_b) + dev_a.size <=> dev_b.size + end + end + + # Compares by partition number + class PartitionNumber < Base + def compare_asc(dev_a, dev_b) + dev_a.number <=> dev_b.number + end + end + end + end + end +end diff --git a/service/test/agama/storage/config_conversions/to_json_conversions/search_test.rb b/service/test/agama/storage/config_conversions/to_json_conversions/search_test.rb index c6e0ca410d..78fd7f17e0 100644 --- a/service/test/agama/storage/config_conversions/to_json_conversions/search_test.rb +++ b/service/test/agama/storage/config_conversions/to_json_conversions/search_test.rb @@ -39,6 +39,7 @@ let(:config_json) do { condition: condition, + sort: sort, ifNotFound: if_not_found, max: max } @@ -47,6 +48,7 @@ let(:condition) { nil } let(:if_not_found) { nil } let(:max) { nil } + let(:sort) { nil } shared_examples "with device" do context "and there is an assigned device" do @@ -103,7 +105,6 @@ config_json = subject.convert expect(config_json[:condition]).to eq({ name: "/dev/vda" }) end - end context "if #condition is configured to search by size" do @@ -141,5 +142,23 @@ expect(subject.convert[:ifNotFound]).to eq("skip") end end + + context "if #sort is configured with a single criterion" do + let(:sort) { "name" } + + it "generates a JSON with a fully defined list of sort criteria" do + config_json = subject.convert + expect(config_json[:sort]).to eq [{ name: "asc" }] + end + end + + context "if #sort is configured with complex criteria" do + let(:sort) { ["size", { name: "desc" }] } + + it "generates a JSON with a fully defined list of sort criteria" do + config_json = subject.convert + expect(config_json[:sort]).to eq [{ size: "asc" }, { name: "desc" }] + end + end end end diff --git a/service/test/agama/storage/config_solvers/drives_search_test.rb b/service/test/agama/storage/config_solvers/drives_search_test.rb index 565c40867a..1feb3330f4 100644 --- a/service/test/agama/storage/config_solvers/drives_search_test.rb +++ b/service/test/agama/storage/config_solvers/drives_search_test.rb @@ -130,6 +130,27 @@ expect(drive3.search.solved?).to eq(true) expect(drive3.search.device.name).to eq("/dev/vdc") end + + context "but ordering by descending device name" do + let(:drives) do + [ + { search: { sort: { name: "desc" } } } + ] + end + + it "expands the number of drive configs to match all the existing disks in order" do + subject.solve(config) + expect(config.drives.size).to eq(3) + + drive1, drive2, drive3 = config.drives + expect(drive1.search.solved?).to eq(true) + expect(drive1.search.device.name).to eq("/dev/vdc") + expect(drive2.search.solved?).to eq(true) + expect(drive2.search.device.name).to eq("/dev/vdb") + expect(drive3.search.solved?).to eq(true) + expect(drive3.search.device.name).to eq("/dev/vda") + end + end end context "if a drive config contains a search without conditions but with a max" do @@ -152,6 +173,25 @@ expect(drive2.search.solved?).to eq(true) expect(drive2.search.device.name).to eq("/dev/vdb") end + + context "but ordering by descending device name" do + let(:drives) do + [ + { search: { sort: { name: "desc" }, max: max } } + ] + end + + it "expands the number of drive configs to match the max" do + subject.solve(config) + expect(config.drives.size).to eq(2) + + drive1, drive2 = config.drives + expect(drive1.search.solved?).to eq(true) + expect(drive1.search.device.name).to eq("/dev/vdc") + expect(drive2.search.solved?).to eq(true) + expect(drive2.search.device.name).to eq("/dev/vdb") + end + end end context "and the max is bigger than the number of disks" do @@ -294,8 +334,8 @@ let(:size) { { equal: value } } context "and there is a disk with equal size" do - let(:value) { "200 GiB" } - include_examples "find device", "/dev/vdb" + let(:value) { "150 GiB" } + include_examples "find device", "/dev/vdc" end context "and there is no disk with equal size" do @@ -308,12 +348,12 @@ let(:size) { { greater: value } } context "and there is a disk with greater size" do - let(:value) { "100 GiB" } - include_examples "find device", "/dev/vdb" + let(:value) { "400 GiB" } + include_examples "find device", "/dev/vde" end context "and there is no disk with greater size" do - let(:value) { "200 GiB" } + let(:value) { "500 GiB" } include_examples "do not find device" end end @@ -322,7 +362,7 @@ let(:size) { { less: value } } context "and there is a disk with less size" do - let(:value) { "200 GiB" } + let(:value) { "150 GiB" } include_examples "find device", "/dev/vda" end @@ -358,5 +398,90 @@ expect(p3.search.device.name).to eq("/dev/vda3") end end + + context "if a drive config sorts the search" do + let(:scenario) { "sizes.yaml" } + + let(:drives) do + [ + { + search: { + condition: condition, + sort: sort, + max: max + } + } + ] + end + + let(:max) { 4 } + let(:condition) { nil } + + context "by size specified as a string" do + let(:sort) { "size" } + + it "matches the disks in the expected order" do + subject.solve(config) + expect(config.drives.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/vda", "/dev/vdc", "/dev/vdb", "/dev/vdd" + ] + end + end + + context "by size specified as 'asc'" do + let(:sort) { { size: "asc" } } + + it "matches the disks in the expected order" do + subject.solve(config) + expect(config.drives.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/vda", "/dev/vdc", "/dev/vdb", "/dev/vdd" + ] + end + end + + context "by size specified as 'desc'" do + let(:sort) { { size: "desc" } } + + it "matches the disks in the expected order" do + subject.solve(config) + expect(config.drives.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/vde", "/dev/vdb", "/dev/vdd", "/dev/vdc" + ] + end + + context "but leaving the biggest disks out" do + let(:condition) { { size: { less: "200 GiB" } } } + + it "matches the filtered disks in the expected order" do + subject.solve(config) + expect(config.drives.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/vdc", "/dev/vda" + ] + end + end + end + + context "by size and then by ascending name" do + let(:sort) { ["size", "name"] } + + it "matches the disks in the expected order" do + subject.solve(config) + expect(config.drives.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/vda", "/dev/vdc", "/dev/vdb", "/dev/vdd" + ] + end + end + + context "by size and then by descending name" do + let(:sort) { [:size, { name: "desc" }] } + + it "matches the disks in the expected order" do + subject.solve(config) + expect(config.drives.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/vda", "/dev/vdc", "/dev/vdd", "/dev/vdb" + ] + end + end + end end end diff --git a/service/test/agama/storage/config_solvers/partitions_search_test.rb b/service/test/agama/storage/config_solvers/partitions_search_test.rb index 3e7ab8c369..6d47d60135 100644 --- a/service/test/agama/storage/config_solvers/partitions_search_test.rb +++ b/service/test/agama/storage/config_solvers/partitions_search_test.rb @@ -335,6 +335,45 @@ include_examples "do not find device" end end + + context "if a partition config has a search with sorting" do + let(:scenario) { "sizes.yaml" } + let(:disk) { devicegraph.find_by_name("/dev/vdb") } + + let(:partitions) do + [ + { + search: { + sort: sort + } + } + ] + end + + context "by size" do + let(:sort) { { size: "desc" } } + + it "matches the partitions in the expected order" do + subject.solve(drive) + partitions = drive.partitions + expect(partitions.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/vdb2", "/dev/vdb3", "/dev/vdb1" + ] + end + end + + context "by size and partition number" do + let(:sort) { [{ size: "desc" }, { number: "desc" }] } + + it "matches the partitions in the expected order" do + subject.solve(drive) + partitions = drive.partitions + expect(partitions.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/vdb3", "/dev/vdb2", "/dev/vdb1" + ] + end + end + end end end end diff --git a/service/test/fixtures/sizes.yaml b/service/test/fixtures/sizes.yaml index b770580e65..38dc12f9a1 100644 --- a/service/test/fixtures/sizes.yaml +++ b/service/test/fixtures/sizes.yaml @@ -27,6 +27,15 @@ - partition: size: 30 GiB name: /dev/vdb3 +- disk: + name: /dev/vdc + size: 150 GiB +- disk: + name: /dev/vdd + size: 200 GiB +- disk: + name: /dev/vde + size: 500 GiB - md: name: "/dev/md0" chunk_size: 16 KiB From bb759c25b13906e2fbe73831c39cb2b5baf9f4e7 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Fri, 13 Jun 2025 16:23:11 +0200 Subject: [PATCH 2/5] Add sort to the schema of the storage configuration --- rust/agama-lib/share/storage.schema.json | 95 +++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/rust/agama-lib/share/storage.schema.json b/rust/agama-lib/share/storage.schema.json index 79247681ee..8e6916909f 100644 --- a/rust/agama-lib/share/storage.schema.json +++ b/rust/agama-lib/share/storage.schema.json @@ -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" }, @@ -381,6 +385,7 @@ "additionalProperties": false, "properties": { "condition": { "$ref": "#/$defs/driveSearchCondition" }, + "sort": { "$ref": "#/$defs/driveSearchSort" }, "max": { "$ref": "#/$defs/searchMax" }, "ifNotFound": { "$ref": "#/$defs/searchActions" } } @@ -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" }, @@ -403,6 +436,7 @@ "additionalProperties": false, "properties": { "condition": { "$ref": "#/$defs/mdRaidSearchCondition" }, + "sort": { "$ref": "#/$defs/mdRaidSearchSort" }, "max": { "$ref": "#/$defs/searchMax" }, "ifNotFound": { "$ref": "#/$defs/searchCreatableActions" } } @@ -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" }, @@ -425,6 +487,7 @@ "additionalProperties": false, "properties": { "condition": { "$ref": "#/$defs/partitionSearchCondition" }, + "sort": { "$ref": "#/$defs/partitionSearchSort" }, "max": { "$ref": "#/$defs/searchMax" }, "ifNotFound": { "$ref": "#/$defs/searchCreatableActions" } } @@ -441,6 +504,7 @@ "additionalProperties": false, "properties": { "condition": { "$ref": "#/$defs/partitionSearchCondition" }, + "sort": { "$ref": "#/$defs/partitionSearchSort" }, "max": { "$ref": "#/$defs/searchMax" }, "ifNotFound": { "$ref": "#/$defs/searchActions" } } @@ -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", @@ -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"] }, From a96918b5aeef19ede18163895aefb89088be1413 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 16 Jun 2025 15:54:23 +0200 Subject: [PATCH 3/5] Reduce SortCriteria API as suggested on code review --- service/lib/agama/storage/configs/sort_criteria.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/service/lib/agama/storage/configs/sort_criteria.rb b/service/lib/agama/storage/configs/sort_criteria.rb index ddcb5127b2..5dc2fa2953 100644 --- a/service/lib/agama/storage/configs/sort_criteria.rb +++ b/service/lib/agama/storage/configs/sort_criteria.rb @@ -41,11 +41,6 @@ def desc end alias_method :desc?, :desc - # @see #desc - def desc=(value) - @asc = !value - end - # Order for the two operators # # @param dev_a [Y2Storage#device] From a35d43a9bba500d94fe94d5d0a1dbd496802da14 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 16 Jun 2025 16:22:53 +0200 Subject: [PATCH 4/5] Add unit tests for sorting when searching RAIDs --- .../config_solvers/md_raids_search_test.rb | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/service/test/agama/storage/config_solvers/md_raids_search_test.rb b/service/test/agama/storage/config_solvers/md_raids_search_test.rb index 04f3844718..abb8eaf699 100644 --- a/service/test/agama/storage/config_solvers/md_raids_search_test.rb +++ b/service/test/agama/storage/config_solvers/md_raids_search_test.rb @@ -125,6 +125,27 @@ expect(md3.search.solved?).to eq(true) expect(md3.search.device.name).to eq("/dev/md2") end + + context "but ordering by descending device name" do + let(:md_raids) do + [ + { search: { sort: { name: "desc" } } } + ] + end + + it "expands the number of MD RAID configs to match all the existing RAIDs in order" do + subject.solve(config) + expect(config.md_raids.size).to eq(3) + + md1, md2, md3 = config.md_raids + expect(md1.search.solved?).to eq(true) + expect(md1.search.device.name).to eq("/dev/md2") + expect(md2.search.solved?).to eq(true) + expect(md2.search.device.name).to eq("/dev/md1") + expect(md3.search.solved?).to eq(true) + expect(md3.search.device.name).to eq("/dev/md0") + end + end end context "if a MD RAID config contains a search without conditions but with a max" do @@ -147,6 +168,25 @@ expect(md2.search.solved?).to eq(true) expect(md2.search.device.name).to eq("/dev/md1") end + + context "but ordering by descending device name" do + let(:md_raids) do + [ + { search: { sort: { name: "desc" }, max: max } } + ] + end + + it "expands the number of MdRaid configs to match the max considering the order" do + subject.solve(config) + expect(config.md_raids.size).to eq(2) + + md1, md2 = config.md_raids + expect(md1.search.solved?).to eq(true) + expect(md1.search.device.name).to eq("/dev/md2") + expect(md2.search.solved?).to eq(true) + expect(md2.search.device.name).to eq("/dev/md1") + end + end end context "and the max is bigger than the number of MD RAIDs" do @@ -352,5 +392,57 @@ expect(p2.search.device.name).to eq("/dev/md0p2") end end + + context "if an mdRaid config sorts the search" do + let(:scenario) { "sizes.yaml" } + + let(:md_raids) do + [ + { + search: { + condition: condition, + sort: sort, + max: max + } + } + ] + end + + let(:max) { 3 } + let(:condition) { nil } + + context "by size specified as a string" do + let(:sort) { "size" } + + it "matches the RAIDs in the expected order" do + subject.solve(config) + expect(config.md_raids.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/md0", "/dev/md1", "/dev/md2" + ] + end + end + + context "by size specified as 'desc'" do + let(:sort) { { size: "desc" } } + + it "matches the RAIDs in the expected order" do + subject.solve(config) + expect(config.md_raids.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/md2", "/dev/md1", "/dev/md0" + ] + end + + context "but leaving the smallest RAID out" do + let(:condition) { { size: { greater: "20 GiB" } } } + + it "matches the RAIDs in the expected order" do + subject.solve(config) + expect(config.md_raids.map(&:search).map(&:device).map(&:name)).to eq [ + "/dev/md2", "/dev/md1" + ] + end + end + end + end end end From 7b697323c66401202cc2b391416336d7d3987ea1 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 16 Jun 2025 16:29:56 +0200 Subject: [PATCH 5/5] Changelog entries --- rust/package/agama.changes | 6 ++++++ service/package/rubygem-agama-yast.changes | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/rust/package/agama.changes b/rust/package/agama.changes index d3dd4c609a..98bc624d35 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Mon Jun 16 14:28:22 UTC 2025 - Ancor Gonzalez Sosa + +- 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 diff --git a/service/package/rubygem-agama-yast.changes b/service/package/rubygem-agama-yast.changes index 8271392181..1363e4a564 100644 --- a/service/package/rubygem-agama-yast.changes +++ b/service/package/rubygem-agama-yast.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Mon Jun 16 14:26:06 UTC 2025 - Ancor Gonzalez Sosa + +- Initial support for sorting storage devices when searching them + at the configuration (gh#agama-project/agama#2474). + ------------------------------------------------------------------- Fri Jun 13 20:51:10 UTC 2025 - Josef Reidinger