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
13 changes: 11 additions & 2 deletions service/lib/agama/storage/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,24 @@ def boot_device
boot_drive&.found_device&.name
end

# return [Array<Configs::Partition>]
# @return [Array<Configs::Partition>]
def partitions
drives.flat_map(&:partitions)
end

# return [Array<Configs::LogicalVolume>]
# @return [Array<Configs::LogicalVolume>]
def logical_volumes
volume_groups.flat_map(&:logical_volumes)
end

# @return [Array<Configs::Filesystem>]
def filesystems
(
drives.map(&:filesystem) +
partitions.map(&:filesystem) +
logical_volumes.map(&:filesystem)
).compact
end
end
end
end
9 changes: 9 additions & 0 deletions service/lib/agama/storage/config_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

require "agama/config"
require "agama/storage/config_checkers/boot"
require "agama/storage/config_checkers/filesystems"
require "agama/storage/config_checkers/drive"
require "agama/storage/config_checkers/volume_group"
require "agama/storage/config_checkers/volume_groups"
Expand All @@ -41,6 +42,7 @@ def initialize(storage_config, product_config = nil)
# @return [Array<Issue>]
def issues
[
filesystems_issues,
boot_issues,
drives_issues,
volume_groups_issues
Expand All @@ -62,6 +64,13 @@ def boot_issues
ConfigCheckers::Boot.new(storage_config, product_config).issues
end

# Issues related to the list of filesystems (mount paths)
#
# @return [Array<Issue>]
def filesystems_issues
ConfigCheckers::Filesystems.new(storage_config, product_config).issues
end

# Issues from drives.
#
# @return [Array<Issue>]
Expand Down
11 changes: 7 additions & 4 deletions service/lib/agama/storage/config_checkers/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,16 @@ def issues
# Creates an error issue.
#
# @param message [String]
# @param kind [Symbol, nil] if nil or ommited, default value defined by Agama::Issue
# @return [Issue]
def error(message)
Agama::Issue.new(
message,
def error(message, kind: nil)
issue_args = {
source: Agama::Issue::Source::CONFIG,
severity: Agama::Issue::Severity::ERROR
)
}
issue_args[:kind] = kind if kind

Agama::Issue.new(message, **issue_args)
end
end
end
Expand Down
8 changes: 6 additions & 2 deletions service/lib/agama/storage/config_checkers/boot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ def missing_alias_issue
# solver logic changes.
error(
_("The boot device cannot be automatically selected because there is no root (/) " \
"file system")
"file system"),
kind: :no_root
)
end

Expand All @@ -69,7 +70,10 @@ def invalid_alias_issue
return unless configure? && device_alias && !valid_alias?

# TRANSLATORS: %s is replaced by a device alias (e.g., "boot").
error(format(_("There is no boot device with alias '%s'"), device_alias))
error(
format(_("There is no boot device with alias '%s'"), device_alias),
kind: :no_such_alias
)
end

# @return [Boolean]
Expand Down
5 changes: 5 additions & 0 deletions service/lib/agama/storage/config_checkers/encryption.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ def encryption
config.encryption
end

# @see Base
def error(message)
super(message, kind: :encryption)
end

# @return [Issue, nil]
def missing_password_issue
return unless encryption.missing_password?
Expand Down
7 changes: 6 additions & 1 deletion service/lib/agama/storage/config_checkers/filesystem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,17 @@ def filesystem
config.filesystem
end

# @see Base
def error(message)
super(message, kind: :filesystem)
end

# @return [Issue, nil]
def missing_filesystem_issue
return if filesystem.reuse?
return if filesystem.type&.fs_type

# TRANSLATORS: %s is the replaced by a mount path (e.g., "/home").
# TRANSLATORS: %s is replaced by a mount path (e.g., "/home").
error(format(_("Missing file system type for '%s'"), filesystem.path))
end

Expand Down
78 changes: 78 additions & 0 deletions service/lib/agama/storage/config_checkers/filesystems.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# 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.

require "agama/storage/config_checkers/base"
require "agama/storage/proposal_settings_reader"
require "yast/i18n"

module Agama
module Storage
module ConfigCheckers
# Class for checking the overal configuration of filesystems.
class Filesystems < Base
include Yast::I18n

# Issues related to the configured set of filesystems.
#
# @return [Array<Issue>]
def issues
[missing_paths_issue].compact
end

private

# @return [Issue, nil]
def missing_paths_issue
missing_paths = mandatory_paths.reject { |p| configured_path?(p) }
return if missing_paths.empty?

error(
format(
# TRANSLATORS: %s is a path like "/" or a list of paths separated by commas
n_(
"A separate file system for %s is required.",
"Separate file systems are required for the following paths: %s",
missing_paths.size
),
missing_paths.join(", ")
),
kind: :required_filesystems
)
end

# @return [Boolean]
def configured_path?(path)
filesystems.any? { |fs| fs.path?(path) }
end

# @return [Array<Configs::Filesystem>]
def filesystems
@filesystems ||= storage_config.filesystems
end

# @return [Array<String>]
def mandatory_paths
product_config.mandatory_paths
end
end
end
end
end
7 changes: 5 additions & 2 deletions service/lib/agama/storage/config_checkers/logical_volume.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@ def missing_thin_pool_issue

return if pool

# TRANSLATORS: %s is the replaced by a device alias (e.g., "pv1").
error(format(_("There is no LVM thin pool volume with alias '%s'"), config.used_pool))
error(
# TRANSLATORS: %s is the replaced by a device alias (e.g., "pv1").
format(_("There is no LVM thin pool volume with alias '%s'"), config.used_pool),
kind: :no_such_alias
)
end
end
end
Expand Down
5 changes: 5 additions & 0 deletions service/lib/agama/storage/config_checkers/search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ def search
config.search
end

# @see Base
def error(message)
super(message, kind: :search)
end

# @return [Issue, nil]
def not_found_issue
return if search.device || search.skip_device?
Expand Down
10 changes: 7 additions & 3 deletions service/lib/agama/storage/config_checkers/volume_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,11 @@ def missing_physical_volume_issue(pv_alias)
configs = storage_config.drives + storage_config.drives.flat_map(&:partitions)
return if configs.any? { |c| c.alias == pv_alias }

# TRANSLATORS: %s is the replaced by a device alias (e.g., "pv1").
error(format(_("There is no LVM physical volume with alias '%s'"), pv_alias))
error(
# TRANSLATORS: %s is the replaced by a device alias (e.g., "pv1").
format(_("There is no LVM physical volume with alias '%s'"), pv_alias),
kind: :no_such_alias
)
end

# Issues from physical volumes devices (target devices).
Expand All @@ -109,7 +112,8 @@ def missing_physical_volumes_device_issue(device_alias)
# TRANSLATORS: %s is the replaced by a device alias (e.g., "disk1").
_("There is no target device for LVM physical volumes with alias '%s'"),
device_alias
)
),
kind: :no_such_alias
)
end

Expand Down
3 changes: 2 additions & 1 deletion service/lib/agama/storage/config_checkers/volume_groups.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def overused_physical_volumes_devices_issues
# TRANSLATORS: %s is the replaced by a device alias (e.g., "disk1").
_("The device '%s' is used several times as target device for physical volumes"),
device
)
),
kind: :vg_target_devices
)
end
end
Expand Down
2 changes: 1 addition & 1 deletion service/lib/agama/storage/proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ def storage_manager
# @return [Issue]
def failed_issue
Issue.new(
_("Cannot accommodate the required file systems for installation"),
_("Cannot calculate a valid storage setup with the current configuration"),
source: Issue::Source::SYSTEM,
severity: Issue::Severity::ERROR
)
Expand Down
1 change: 0 additions & 1 deletion service/lib/y2storage/agama_proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ def calculate_proposal
issues_list.concat(issues)

if fatal_error?
# This means some IfNotFound is set to "error" and we failed to find a match
@devices = nil
return @devices
end
Expand Down
4 changes: 2 additions & 2 deletions service/test/agama/storage/autoyast_proposal_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def root_filesystem(disk)
subject.calculate_autoyast(partitioning)
expect(subject.issues).to include(
an_object_having_attributes(
description: /Cannot accommodate/, severity: Agama::Issue::Severity::ERROR
description: /Cannot calculate/, severity: Agama::Issue::Severity::ERROR
)
)
end
Expand Down Expand Up @@ -375,7 +375,7 @@ def root_filesystem(disk)
subject.calculate_autoyast(partitioning)
expect(subject.issues).to include(
an_object_having_attributes(
description: /Cannot accommodate/, severity: Agama::Issue::Severity::ERROR
description: /Cannot calculate/, severity: Agama::Issue::Severity::ERROR
)
)
end
Expand Down
71 changes: 64 additions & 7 deletions service/test/agama/storage/config_checker_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -231,17 +231,24 @@
let(:product_data) do
{
"storage" => {
"volume_templates" => [
{
"mount_path" => "/",
"filesystem" => "btrfs",
"outline" => { "filesystems" => ["btrfs", "xfs"] }
}
]
"volumes" => volumes,
"volume_templates" => volume_templates
}
}
end

let(:volumes) { ["/"] }

let(:volume_templates) do
[
{
"mount_path" => "/",
"filesystem" => "btrfs",
"outline" => { "filesystems" => ["btrfs", "xfs"] }
}
]
end

before do
mock_storage(devicegraph: scenario)
# To speed-up the tests. Use #allow_any_instance because #allow introduces marshaling problems
Expand Down Expand Up @@ -785,6 +792,56 @@
end
end

context "if some volumes are required" do
let(:volumes) { ["/", "swap"] }

let(:volume_templates) do
[
{
"mount_path" => "/",
"filesystem" => "btrfs",
"outline" => { "required" => true }
},
{
"mount_path" => "swap",
"filesystem" => "swap",
"outline" => { "required" => true }
}
]
end

context "and one of them is omitted at the configuration" do
let(:config_json) do
{
drives: [
{
partitions: [
{ filesystem: { path: "swap" } }
]
}
]
}
end

it "includes an issue for the missing mount path" do
issues = subject.issues
expect(issues).to include an_object_having_attributes(
error?: true,
kind: :required_filesystems,
description: /file system for \/ is/
)
end

it "does not include an issue for the present mount path" do
issues = subject.issues
expect(issues).to_not include an_object_having_attributes(
kind: :required_filesystems,
description: /file system for swap/
)
end
end
end

context "if the config has several issues" do
let(:config_json) do
{
Expand Down
Loading