Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
46 changes: 45 additions & 1 deletion rust/agama-lib/share/storage.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,9 @@
{ "$ref": "#/$defs/advancedLogicalVolumesGenerator" },
{ "$ref": "#/$defs/logicalVolume" },
{ "$ref": "#/$defs/thinPoolLogicalVolume" },
{ "$ref": "#/$defs/thinLogicalVolume" }
{ "$ref": "#/$defs/thinLogicalVolume" },
{ "$ref": "#/$defs/logicalVolumeToDelete" },
{ "$ref": "#/$defs/logicalVolumeToDeleteIfNeeded" }
]
},
"advancedLogicalVolumesGenerator": {
Expand Down Expand Up @@ -366,6 +368,31 @@
"filesystem": { "$ref": "#/$defs/filesystem" }
}
},
"logicalVolumeToDelete": {
"type": "object",
"additionalProperties": false,
"required": ["delete", "search"],
"properties": {
"search": { "$ref": "#/$defs/deleteLogicalVolumeSearch" },
"delete": {
"description": "Delete the logical volume.",
"const": true
}
}
},
"logicalVolumeToDeleteIfNeeded": {
"type": "object",
"additionalProperties": false,
"required": ["deleteIfNeeded", "search"],
"properties": {
"search": { "$ref": "#/$defs/deleteLogicalVolumeSearch" },
"deleteIfNeeded": {
"description": "Delete the logical volume if needed to make space.",
"const": true
},
"size": { "$ref": "#/$defs/size" }
}
},
"logicalVolumeStripes": {
"description": "Number of stripes.",
"type": "integer",
Expand Down Expand Up @@ -587,6 +614,23 @@
"ifNotFound": { "$ref": "#/$defs/searchCreatableActions" }
}
},
"deleteLogicalVolumeSearch": {
"anyOf": [
{ "$ref": "#/$defs/searchAll" },
{ "$ref": "#/$defs/searchName" },
{ "$ref": "#/$defs/deleteLogicalVolumeAdvancedSearch" }
]
},
"deleteLogicalVolumeAdvancedSearch": {
"type": "object",
"additionalProperties": false,
"properties": {
"condition": { "$ref": "#/$defs/logicalVolumeSearchCondition" },
"sort": { "$ref": "#/$defs/logicalVolumeSearchSort" },
"max": { "$ref": "#/$defs/searchMax" },
"ifNotFound": { "$ref": "#/$defs/searchActions" }
}
},
"logicalVolumeSearchCondition": {
"anyOf": [
{ "$ref": "#/$defs/searchConditionName" },
Expand Down
5 changes: 5 additions & 0 deletions rust/share/system.storage.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"type": "array",
"items": { "type": "integer" }
},
"availableVolumeGroups": {
"description": "SIDs of the available LVM volume groups",
"type": "array",
"items": { "type": "integer" }
},
"candidateDrives": {
"description": "SIDs of the drives that are candidate for installation",
"type": "array",
Expand Down
27 changes: 17 additions & 10 deletions service/lib/agama/dbus/storage/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ module Agama
module DBus
module Storage
# D-Bus object to manage storage installation
class Manager < BaseObject
class Manager < BaseObject # rubocop:disable Metrics/ClassLength
extend Yast::I18n
include Yast::I18n
include WithIssues
Expand Down Expand Up @@ -384,15 +384,16 @@ def serialize_system
return serialize_nil unless manager.probed?

json = {
devices: devices_json(:probed),
availableDrives: available_drives,
availableMdRaids: available_md_raids,
candidateDrives: candidate_drives,
candidateMdRaids: candidate_md_raids,
issues: system_issues_json,
productMountPoints: product_mount_points,
encryptionMethods: encryption_methods,
volumeTemplates: volume_templates
devices: devices_json(:probed),
availableDrives: available_drives,
availableMdRaids: available_md_raids,
availableVolumeGroups: available_volume_groups,
candidateDrives: candidate_drives,
candidateMdRaids: candidate_md_raids,
issues: system_issues_json,
productMountPoints: product_mount_points,
encryptionMethods: encryption_methods,
volumeTemplates: volume_templates
}
JSON.pretty_generate(json)
end
Expand Down Expand Up @@ -510,6 +511,12 @@ def candidate_md_raids
proposal.storage_system.candidate_md_raids.map(&:sid)
end

# @see Storage::System#available_volume_groups
# @return [Array<Integer>]
def available_volume_groups
proposal.storage_system.available_volume_groups.map(&:sid)
end

# Meaningful mount points for the current product.
#
# @return [Array<String>]
Expand Down
31 changes: 25 additions & 6 deletions service/lib/agama/storage/system.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

# Copyright (c) [2025] SUSE LLC
# Copyright (c) [2025-2026] SUSE LLC
#
# All Rights Reserved.
#
Expand Down Expand Up @@ -66,8 +66,7 @@ def candidate_drives

# All devices that can be referenced by an mdRaid entry at the Agama config
#
# This excludes devices with any mounted filesystem and devices that contain a repository
# for installation.
# This excludes MD RAIDs that are not based on available devices.
#
# @return [Array<Y2Storage::Md>]
def available_md_raids
Expand All @@ -92,14 +91,34 @@ def candidate_md_raids
available_md_raids.reject { |r| r.is?(:software_raid) }
end

# Whether the device is usable as drive or mdRaid
# All devices that can be referenced by a volumeGroups entry at the Agama config
#
# See {#available_drives} and {#available_md_raids}
# This excludes volume groups that are not based on available devices.
#
# @return [Array<Y2Storage::LvmVg>]
def available_volume_groups
return [] unless devicegraph

devicegraph.lvm_vgs.select { |v| available?(v) }
end

# Whether the device is usable for the installation.
#
# A device is usable if it contains neither a mounted filesystem nor a repository for the
# installation.
#
# For "compounded" devices like MD RAIDs or volume groups, all the devices used for creating
Comment thread
joseivanlopez marked this conversation as resolved.
Outdated
# it has to be usable for the installation too.
Comment thread
joseivanlopez marked this conversation as resolved.
Outdated
#
# See {#available_drives}, {#available_md_raids} and {#available_volume_groups}
#
# @param device [Y2Storage::Partitionable, Y2Storage::Md]
# @return [Boolean]
def available?(device)
analyzer.available_device?(device)
devices = device.ancestors.select { |a| a.parents.none? }
devices << device if devices.empty?

devices.all? { |d| analyzer.available_device?(d) }
end

# Whether the device can be used for installation, including the boot partitions
Expand Down
25 changes: 25 additions & 0 deletions service/test/agama/dbus/storage/manager_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,14 @@ def parse(string)
allow(proposal.storage_system).to receive(:candidate_md_raids).and_return(candidate_raids)
allow(proposal.storage_system).to receive(:candidate_devices)
.and_return(candidate_drives + candidate_raids)
allow(proposal.storage_system).to receive(:available_volume_groups).and_return(available_vgs)
end

let(:available_drives) { [] }
let(:candidate_drives) { [] }
let(:available_raids) { [] }
let(:candidate_raids) { [] }
let(:available_vgs) { [] }

describe "serialized_system[:availableDrives]" do
context "if there is no available drives" do
Expand Down Expand Up @@ -306,6 +308,29 @@ def parse(string)
end
end

describe "serialized_system[:availableVolumeGroups]" do
context "if there is no available volume groups" do
let(:available_vgs) { [] }

it "returns an empty list" do
expect(parse(subject.serialized_system)[:availableVolumeGroups]).to eq([])
end
end

context "if there are available volume groups" do
let(:available_vgs) { [vg1, vg2, vg3] }

let(:vg1) { instance_double(Y2Storage::LvmVg, sid: 200) }
let(:vg2) { instance_double(Y2Storage::LvmVg, sid: 201) }
let(:vg3) { instance_double(Y2Storage::LvmVg, sid: 202) }

it "retuns the id of each volume group" do
result = parse(subject.serialized_system)[:availableVolumeGroups]
expect(result).to contain_exactly(200, 201, 202)
end
end
end

describe "serialized_system[:issues]" do
context "if there is no candidate drives" do
let(:candidate_drives) { [] }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

# Copyright (c) [2025] SUSE LLC
# Copyright (c) [2025-2026] SUSE LLC
#
# All Rights Reserved.
#
Expand Down Expand Up @@ -68,7 +68,7 @@

context "and any of the devices is not available" do
before do
allow(storage_system.analyzer).to receive(:available_device?) do |dev|
allow(storage_system).to receive(:available?) do |dev|
dev.name != "/dev/md0"
end
end
Expand Down
32 changes: 26 additions & 6 deletions service/test/agama/storage/system_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

# Copyright (c) [2025] SUSE LLC
# Copyright (c) [2025-2026] SUSE LLC
#
# All Rights Reserved.
#
Expand Down Expand Up @@ -67,14 +67,18 @@
end

describe "#available_md_raids" do
let(:scenario) { "md_raids.yaml" }
let(:scenario) { "available_md_raids.yaml" }

before do
allow(disk_analyzer).to receive(:available_device?) { |d| d.name != "/dev/md0" }
it "includes all software RAIDs that are not in use" do
expect(subject.available_md_raids.map(&:name)).to contain_exactly("/dev/md0", "/dev/md1")
end

it "includes all software RAIDs that are not in use" do
expect(subject.available_md_raids.map(&:name)).to contain_exactly("/dev/md1", "/dev/md2")
it "does not include software RAIDs in use" do
expect(subject.available_md_raids.map(&:name)).to_not include("/dev/md2")
end

it "does not include software RAIDs over devices in use" do
expect(subject.available_md_raids.map(&:name)).to_not include("/dev/md3")
end
end

Expand All @@ -85,4 +89,20 @@
expect(subject.candidate_md_raids).to be_empty
end
end

describe "#available_volume_groups " do
let(:scenario) { "available_volume_groups.yaml" }

it "includes all volume groups that are not in use" do
expect(subject.available_volume_groups.map(&:name)).to contain_exactly("/dev/vg0", "/dev/vg1")
end

it "does not include volume groups in use" do
expect(subject.available_volume_groups.map(&:name)).to_not include("/dev/vg2")
end

it "does not include volume groups over devices in use" do
expect(subject.available_volume_groups.map(&:name)).to_not include("/dev/vg3")
end
end
end
Loading