From 042e0d2d6200dc9a729320bfd67910e08dbd2c01 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 17 Jul 2024 10:05:47 +0200 Subject: [PATCH 01/21] WIP: some starting point for AgamaProposal --- service/lib/agama/storage/config.rb | 89 ++++++++ service/lib/agama/storage/configs.rb | 37 ++++ service/lib/agama/storage/configs/boot.rb | 45 ++++ service/lib/agama/storage/configs/drive.rb | 60 +++++ service/lib/agama/storage/configs/encrypt.rb | 35 +++ service/lib/agama/storage/configs/format.rb | 32 +++ .../{boot_settings.rb => configs/mount.rb} | 22 +- .../lib/agama/storage/configs/partition.rb | 52 +++++ service/lib/agama/storage/configs/search.rb | 46 ++++ .../lib/agama/storage/configs/size_range.rb | 31 +++ .../lib/agama/storage/proposal_settings.rb | 8 +- .../from_json.rb | 4 +- service/lib/y2storage/agama_proposal.rb | 206 ++++++++++++++++++ .../proposal/agama_device_planner.rb | 117 ++++++++++ .../proposal/agama_devices_creator.rb | 176 +++++++++++++++ .../proposal/agama_devices_planner.rb | 73 +++++++ .../y2storage/proposal/agama_drive_planner.rb | 66 ++++++ .../y2storage/proposal/agama_lvm_helper.rb | 46 ++++ .../lib/y2storage/proposal/agama_searcher.rb | 76 +++++++ .../y2storage/proposal/agama_space_maker.rb | 47 ++++ service/test/y2storage/agama_proposal_test.rb | 92 ++++++++ 21 files changed, 1340 insertions(+), 20 deletions(-) create mode 100644 service/lib/agama/storage/config.rb create mode 100644 service/lib/agama/storage/configs.rb create mode 100644 service/lib/agama/storage/configs/boot.rb create mode 100644 service/lib/agama/storage/configs/drive.rb create mode 100644 service/lib/agama/storage/configs/encrypt.rb create mode 100644 service/lib/agama/storage/configs/format.rb rename service/lib/agama/storage/{boot_settings.rb => configs/mount.rb} (65%) create mode 100644 service/lib/agama/storage/configs/partition.rb create mode 100644 service/lib/agama/storage/configs/search.rb create mode 100644 service/lib/agama/storage/configs/size_range.rb create mode 100644 service/lib/y2storage/agama_proposal.rb create mode 100644 service/lib/y2storage/proposal/agama_device_planner.rb create mode 100644 service/lib/y2storage/proposal/agama_devices_creator.rb create mode 100644 service/lib/y2storage/proposal/agama_devices_planner.rb create mode 100644 service/lib/y2storage/proposal/agama_drive_planner.rb create mode 100644 service/lib/y2storage/proposal/agama_lvm_helper.rb create mode 100644 service/lib/y2storage/proposal/agama_searcher.rb create mode 100644 service/lib/y2storage/proposal/agama_space_maker.rb create mode 100644 service/test/y2storage/agama_proposal_test.rb diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb new file mode 100644 index 0000000000..2711422baa --- /dev/null +++ b/service/lib/agama/storage/config.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +# Copyright (c) [2022-2024] 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/configs" + +module Agama + module Storage + # Settings used to calculate an storage proposal. + class Config + # Boot settings. + # + # @return [Configs::Boot] + attr_accessor :boot + + attr_accessor :drives + attr_accessor :volume_groups + attr_accessor :md_raids + attr_accessor :btrfs_raids + attr_accessor :nfs_mounts + attr_accessor :original_graph + + def initialize + @boot = Configs::Boot.new + @drives = [] + @volume_groups = [] + @md_raids = [] + @btrfs_raids = [] + @nfs_mounts = [] + end + + # Creates a new proposal settings object from JSON hash according to schema. + # + # @param settings_json [Hash] + # @param config [Config] + # + # @return [Settings] + def self.new_from_json(settings_json, config:) + Storage::SettingsConversions::FromJSON.new(settings_json).convert + end + + # Generates a JSON hash according to schema. + # + # @return [Hash] + def to_json_settings + Storage::ProposalSettingsConversions::ToJSON.new(self).convert + end + + def boot_device + explicit_boot_device || implicit_boot_device + end + + # Device used for booting. + # + # @return [String, nil] + def explicit_boot_device + return nil unless boot.configure? + + boot.device + end + + def implicit_boot_device + # TODO: preliminary implementation with very simplistic checks + root_drive = drives.find do |drive| + drive.partitions.any? { |p| p.mount&.path == "/" } + end + + root_drive&.found_device.name + end + end + end +end diff --git a/service/lib/agama/storage/configs.rb b/service/lib/agama/storage/configs.rb new file mode 100644 index 0000000000..abb1359440 --- /dev/null +++ b/service/lib/agama/storage/configs.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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 + # Namespace for all the supported settings to configure storage + module Configs + end + end +end + +require "agama/storage/configs/boot" +require "agama/storage/configs/drive" +require "agama/storage/configs/encrypt" +require "agama/storage/configs/format" +require "agama/storage/configs/mount" +require "agama/storage/configs/partition" +require "agama/storage/configs/search" +require "agama/storage/configs/size_range" diff --git a/service/lib/agama/storage/configs/boot.rb b/service/lib/agama/storage/configs/boot.rb new file mode 100644 index 0000000000..a5c76657b5 --- /dev/null +++ b/service/lib/agama/storage/configs/boot.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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 + # Boot configuration. + class Boot + # Whether to configure partitions for booting. + # + # @return [Boolean] + attr_accessor :configure + alias_method :configure?, :configure + + # Device to use for booting. + # + # @return [String, nil] if nil, then the proposal decides the booting device, normally the + # device for allocating root. + attr_accessor :device + + def initialize + @configure = true + end + end + end + end +end diff --git a/service/lib/agama/storage/configs/drive.rb b/service/lib/agama/storage/configs/drive.rb new file mode 100644 index 0000000000..a0a3ac4001 --- /dev/null +++ b/service/lib/agama/storage/configs/drive.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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/configs/search" + +module Agama + module Storage + module Configs + class Drive + attr_accessor :search + attr_accessor :encrypt + attr_accessor :format + attr_accessor :mount + attr_accessor :ptable_type + attr_accessor :partitions + attr_accessor :search + + # @param mount_path [String] + def initialize + @partitions = [] + end + + def search_device(devicegraph, used_sids) + @search ||= default_search + search.find(self, devicegraph, used_sids) + end + + def default_search + Search.new + end + + def found_device + search&.device + end + + def partitions? + partitions.any? + end + end + end + end +end diff --git a/service/lib/agama/storage/configs/encrypt.rb b/service/lib/agama/storage/configs/encrypt.rb new file mode 100644 index 0000000000..f129373f77 --- /dev/null +++ b/service/lib/agama/storage/configs/encrypt.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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 + class Mount + attr_accessor :method + attr_accessor :key + attr_accessor :pbkd_function + attr_accessor :label + attr_accessor :cipher + attr_accessor :key_size + end + end + end +end diff --git a/service/lib/agama/storage/configs/format.rb b/service/lib/agama/storage/configs/format.rb new file mode 100644 index 0000000000..abf0faebff --- /dev/null +++ b/service/lib/agama/storage/configs/format.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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 + class Format + attr_accessor :filesystem + attr_accessor :label + attr_accessor :mkfs_options + end + end + end +end diff --git a/service/lib/agama/storage/boot_settings.rb b/service/lib/agama/storage/configs/mount.rb similarity index 65% rename from service/lib/agama/storage/boot_settings.rb rename to service/lib/agama/storage/configs/mount.rb index 228668280e..9b3be11224 100644 --- a/service/lib/agama/storage/boot_settings.rb +++ b/service/lib/agama/storage/configs/mount.rb @@ -21,21 +21,15 @@ module Agama module Storage - # Class for configuring the boot settings of the Agama storage proposal. - class BootSettings - # Whether to configure partitions for booting. - # - # @return [Boolean] - attr_accessor :configure - alias_method :configure?, :configure + module Configs + class Mount + attr_accessor :path + attr_accessor :options + attr_accessor :mount_by - # Device to use for booting. - # - # @return [String, nil] nil means use installation device. - attr_accessor :device - - def initialize - @configure = true + def initialize + @options = [] + end end end end diff --git a/service/lib/agama/storage/configs/partition.rb b/service/lib/agama/storage/configs/partition.rb new file mode 100644 index 0000000000..2133327b34 --- /dev/null +++ b/service/lib/agama/storage/configs/partition.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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 + class Partition + attr_accessor :search + attr_accessor :id + attr_accessor :type + attr_accessor :size + attr_accessor :resize + attr_accessor :delete + attr_accessor :encrypt + attr_accessor :format + attr_accessor :mount + attr_accessor :search + + def search_device(devicegraph, parent_sid, used_sids) + @search ||= default_search + search.find(self, devicegraph, used_sids, parent: parent_sid) + end + + def default_search + Search.new + end + + def found_device + search&.device + end + end + end + end +end diff --git a/service/lib/agama/storage/configs/search.rb b/service/lib/agama/storage/configs/search.rb new file mode 100644 index 0000000000..15ae284a5c --- /dev/null +++ b/service/lib/agama/storage/configs/search.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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 + class Search + attr_reader :device + + def find(setting, devicegraph, used_sids, parent: nil) + devices = candidate_devices(setting, devicegraph, parent) + devices.reject! { |d| used_sids.include?(d.sid) } + @device = devices.sort_by(&:name).first + end + + def candidate_devices(setting, devicegraph, parent) + if setting.kind_of?(Drive) + devicegraph.blk_devices.select do |dev| + dev.is?(:disk_device, :stray_blk_device) + end + else + devicegraph.find_device(parent).partitions + end + end + end + end + end +end diff --git a/service/lib/agama/storage/configs/size_range.rb b/service/lib/agama/storage/configs/size_range.rb new file mode 100644 index 0000000000..164816d23d --- /dev/null +++ b/service/lib/agama/storage/configs/size_range.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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 + class SizeRange + attr_accessor :min + attr_accessor :max + end + end + end +end diff --git a/service/lib/agama/storage/proposal_settings.rb b/service/lib/agama/storage/proposal_settings.rb index 8ec12201ae..595d8da953 100644 --- a/service/lib/agama/storage/proposal_settings.rb +++ b/service/lib/agama/storage/proposal_settings.rb @@ -19,7 +19,7 @@ # 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/boot_settings" +require "agama/storage/configs/boot" require "agama/storage/device_settings" require "agama/storage/encryption_settings" require "agama/storage/proposal_settings_conversions" @@ -34,9 +34,9 @@ class ProposalSettings # @return [DeviceSettings::Disk, DeviceSettings::NewLvmVg, DeviceSettings::ReusedLvmVg] attr_accessor :device - # Boot settings. + # Boot config. # - # @return [BootSettings] + # @return [Configs::Boot] attr_accessor :boot # Encryption settings. @@ -56,7 +56,7 @@ class ProposalSettings def initialize @device = DeviceSettings::Disk.new - @boot = BootSettings.new + @boot = Configs::Boot.new @encryption = EncryptionSettings.new @space = SpaceSettings.new @volumes = [] diff --git a/service/lib/agama/storage/proposal_settings_conversions/from_json.rb b/service/lib/agama/storage/proposal_settings_conversions/from_json.rb index 6cd1a0b872..e709c98aa7 100644 --- a/service/lib/agama/storage/proposal_settings_conversions/from_json.rb +++ b/service/lib/agama/storage/proposal_settings_conversions/from_json.rb @@ -19,7 +19,7 @@ # 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/boot_settings" +require "agama/storage/configs/boot" require "agama/storage/device_settings" require "agama/storage/encryption_settings" require "agama/storage/proposal_settings_reader" @@ -87,7 +87,7 @@ def boot_conversion boot_json = settings_json[:boot] return unless boot_json - Agama::Storage::BootSettings.new.tap do |boot_settings| + Agama::Storage::Configs::Boot.new.tap do |boot_settings| boot_settings.configure = boot_json[:configure] boot_settings.device = boot_json[:device] end diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb new file mode 100644 index 0000000000..e2178c507d --- /dev/null +++ b/service/lib/y2storage/agama_proposal.rb @@ -0,0 +1,206 @@ +# Copyright (c) [2024] 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 "yast" +require "y2storage/proposal" +require "y2storage/proposal/agama_searcher" +require "y2storage/proposal/agama_space_maker" +require "y2storage/proposal/agama_devices_planner" +require "y2storage/proposal/agama_devices_creator" +require "y2storage/exceptions" +require "y2storage/planned" + +module Y2Storage + # Class to calculate a storage proposal for autoinstallation using Agama + # + # @example Creating a proposal from the current AutoYaST profile + # partitioning = Yast::Profile.current["partitioning"] + # proposal = Y2Storage::AutoinstProposal.new(partitioning: partitioning) + # proposal.proposed? # => false + # proposal.devices # => nil + # proposal.planned_devices # => nil + # + # proposal.propose # Performs the calculation + # + # proposal.proposed? # => true + # proposal.devices # => Proposed layout + # + class AgamaProposal < Proposal::Base + # @return [Agama::Storage::Config] + attr_reader :settings + + # @return [Array] List of found issues + attr_reader :issues_list + + # Constructor + # + # @param settings [Agama::Storage::Settings] proposal settings + # @param devicegraph [Devicegraph] starting point. If nil, then probed devicegraph + # will be used + # @param disk_analyzer [DiskAnalyzer] by default, the method will create a new one + # based on the initial devicegraph or will use the one in {StorageManager} if + # starting from probed (i.e. 'devicegraph' argument is also missing) + # @param issues_list [Array e + raise NotBootableError, e.message + end + + # Removes partition tables from candidate devices with empty partition table + # + # @note The devicegraph is modified. + # + # @param devicegraph [Y2Storage::Devicegraph] + # @return [Array] sid of devices where partition table was deleted from + def remove_empty_partition_tables(devicegraph) + devices = drives_with_empty_partition_table(devicegraph) + devices.each(&:delete_partition_table) + devices.map(&:sid) + end + + # All candidate devices with an empty partition table + # + # @param devicegraph [Y2Storage::Devicegraph] + # @return [Array] + def drives_with_empty_partition_table(devicegraph) + devices = settings.drives.map(&:found_device).compact + devices.select { |d| d.partition_table && d.partitions.empty? } + end + + # Planned partitions that will hold the given planned devices + # + # TODO: + # Extracted to a separate method because it's something that may need some extra logic + # in the future. See the equivalent method at DevicegraphGenerator. + # + # @param planned_devices [Array] list of planned devices + # @return [Array] + def partitions_for_clean + # NOTE: take into account (partitions on) pre-existing RAIDs? + planned_devices.partitions + end + + # Configures SpaceMaker#protected_sids according to the given list of planned devices + def protect_sids + space_maker.protected_sids = planned_devices.all.select(&:reuse?).map(&:reuse_sid) + end + + # Creates planned devices on a given devicegraph + # + def create_devices(devicegraph) + # Almost for sure, this should happen as part of the creation of devices below + add_partition_tables(devicegraph) + + devices_creator = Proposal::AgamaDevicesCreator.new(devicegraph, issues_list) + names = settings.drives.map(&:found_device).compact.map(&:name) + protect_sids + result = devices_creator.populated_devicegraph(planned_devices, names, space_maker) + end + + # Add partition tables + # + # This method create/change partitions tables according to information + # specified in the profile. Disks containing any partition will be ignored. + # + # The devicegraph which is passed as first argument will be modified. + # + # @param devicegraph [Devicegraph] Starting point + def add_partition_tables(devicegraph) + # TODO: if needed, will very likely be moved to AgamaDevicesCreator + end + end +end diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb new file mode 100644 index 0000000000..b96c800fdd --- /dev/null +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -0,0 +1,117 @@ +# Copyright (c) [2024] 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 "y2storage/planned" + +module Y2Storage + module Proposal + # Base class used by Agama planners. + class AgamaDevicePlanner + # @!attribute [r] devicegraph + # @return [Devicegraph] + attr_reader :devicegraph + + # @!attribute [r] issues_list + attr_reader :issues_list + + # @param devicegraph [Devicegraph] Devicegraph to be used as starting point. + # @param issues_list [AutoinstIssues::List] List of issues to register them. + def initialize(devicegraph, issues_list) + @devicegraph = devicegraph + @issues_list = issues_list + end + + # Planned devices according to the given settings. + # + # @return [Array] Array of planned devices. + def planned_devices(_setting) + raise NotImplementedError + end + + private + + # @param planned [Planned::Disk, Planned::Partition] + # @param settings [#format, #mount] + def configure_device(planned, settings) + # TODO configure_encrypt + configure_format(planned, settings.format) if settings.format + configure_mount(planned, settings.mount) if settings.mount + end + + # @param planned [Planned::Disk, Planned::Partition] + # @param settings [Agama::Storage::Settings::Format] + def configure_format(planned, settings) + planned.label = settings.label + planned.mkfs_options = settings.mkfs_options + configure_filesystem(planned, settings.filesystem) if settings.filesystem + end + + # @param planned [Planned::Disk, Planned::Partition] + # @param settings [Agama::Storage::Settings::Filesystem] + def configure_filesystem(planned, settings) + planned.filesystem_type = settings.type + configure_btrfs(planned, settings.btrfs) if settings.btrfs + end + + # @param planned [Planned::Disk, Planned::Partition] + # @param settings [Agama::Storage::Settings::Btrfs] + def configure_btrfs(planned, settings) + planned.snapshots = settings.snapshots? + planned.default_subvolume = settings.default_subvolume + planned.subvolumes = settings.subvolumes + end + + # @param planned [Planned::Disk, Planned::Partition] + # @param settings [Agama::Storage::Settings::Mount] + def configure_mount(planned, settings) + planned.mount_point = settings.path + planned.mount_by = settings.mount_by + planned.fstab_options = settings.options + # FIXME: Is this needed? Or #options is enough? + # planned.read_only = settings.read_only? + end + + # @param planned [Planned::Partition] + # @param settings [Agama::Storage::Settings::Size] + def configure_size(planned, settings) + planned.min_size = settings.min + planned.max_size = settings.max + planned.weight = 100 + end + + # @param planned [Planned::Disk] + # @param settings [Agama::Storage::Settings::Drive] + def configure_partitions(planned, settings) + planned.partitions = settings.partitions.map do |partition_settings| + planned_partition(partition_settings).tap { |p| p.disk = settings.found_device.name } + end + end + + # @param settings [Agama::Storage::Settings::Partition] + # @return [Planned::Partition] + def planned_partition(settings) + Planned::Partition.new(nil, nil).tap do |planned| + planned.partition_id = settings.id + configure_device(planned, settings) + configure_size(planned, settings.size) + end + end + end + end +end diff --git a/service/lib/y2storage/proposal/agama_devices_creator.rb b/service/lib/y2storage/proposal/agama_devices_creator.rb new file mode 100644 index 0000000000..af1bbef378 --- /dev/null +++ b/service/lib/y2storage/proposal/agama_devices_creator.rb @@ -0,0 +1,176 @@ +# Copyright (c) [2017-2020] 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 "y2storage/proposal/agama_lvm_helper" +require "y2storage/exceptions" + +module Y2Storage + module Proposal + # Class to create and reuse devices during the Agama proposal + class AgamaDevicesCreator + include Yast::Logger + + # @return [AutoinstIssues::List] List of found AutoYaST issues + attr_reader :issues_list + + # Constructor + # + # @param original_graph [Devicegraph] Devicegraph to be used as starting point + # @param issues_list [Array] List of issues to register the problems + # found during devices creation + def initialize(original_graph, issues_list) + @original_graph = original_graph + @issues_list = issues_list + end + + # Devicegraph including all the specified planned devices + # + # @param planned_devices [Planned::DevicesCollection] Devices to create/reuse + # @param disk_names [Array] Disks to consider + # + # @return [AutoinstCreatorResult] Result with new devicegraph in which all the + # planned devices have been allocated + def populated_devicegraph(planned_devices, disk_names, space_maker) + # Process planned partitions + log.info "planned devices = #{planned_devices.to_a.inspect}" + log.info "disk names = #{disk_names.inspect}" + + reset + + @planned_devices = planned_devices + @disk_names = disk_names + @space_maker = space_maker + + process_devices + end + + protected + + # @return [Devicegraph] Original devicegraph + attr_reader :original_graph + + # @return [Planned::DevicesCollection] Devices to create/reuse + attr_reader :planned_devices + + # @return [Array] Disks to consider + attr_reader :disk_names + + attr_reader :space_maker + + # @return [Proposal::CreatorResult] Current result containing the devices that have been created + attr_reader :creator_result + + # @return [Devicegraph] Current devicegraph + attr_reader :devicegraph + + private + + # Sets the current creator result + # + # The current devicegraph is properly updated. + # + # @param result [Proposal::CreatorResult] + def creator_result=(result) + @creator_result = result + @devicegraph = result.devicegraph + end + + # Resets values before create devices + # + # @see #populated_devicegraph + def reset + @creator_result = nil + @devicegraph = original_graph.duplicate + end + + # Reuses and creates planned devices + # + # @return [AutoinstCreatorResult] Result with new devicegraph in which all the + # planned devices have been allocated + def process_devices + process_existing_partitionables + creator_result + end + + def process_existing_partitionables + partitions = partitions_for_existing(planned_devices) + + # lvm_lvs = system_lvm_over_existing? ? system_lvs(planned_devices) : [] + lvm_lvs = [] + lvm_helper = AgamaLvmHelper.new(lvm_lvs) + + # Check whether there is any chance of getting an unwanted order for the planned partitions + # within a disk + space_result = provide_space(partitions, original_graph, lvm_helper) + + partition_creator = PartitionCreator.new(space_result[:devicegraph]) + self.creator_result = partition_creator.create_partitions(space_result[:partitions_distribution]) + + # This may be here or before create_partitions. + # + # What about resizing if needed? + # Likely shrinking is fine and should be always handled at the SpaceMaker. + # But I'm not so sure if growing is so fine (we may need to make some space first). + # I don't think we have the growing case covered by SpaceMaker, the distribution + # calculator, etc. + # + planned_devices.each do |planned| + next unless planned.reuse? + + planned.reuse!(devicegraph) + end + + # graph = create_separate_vgs(planned_devices, creator_result).devicegraph + + # if settings.use_lvm + # new_pvs = new_physical_volumes(space_result[:devicegraph], graph) + # graph = lvm_helper.create_volumes(graph, new_pvs) + #end + + # Needed or already part of other components? + # graph.mount_points.each(&:adjust_mount_options) + end + + def provide_space(planned_partitions, devicegraph, lvm_helper) + result = space_maker.provide_space(devicegraph, planned_partitions, lvm_helper) + log.info "Found enough space" + result + end + + def partitions_for_existing(planned_devices) + # Maybe in the future this can include partitions on top of existing MDs + # TODO: simplistic implementation + planned_devices.partitions.reject(&:reuse?) + end + + # Formats and/or mounts the disk like block devices (Xen virtual partitions and full disks) + # + # Add planned disk like devices to reuse list so they can be considered for lvm and raids + # later on. + def process_disk_like_devs + # Do we do something about SpaceMaker here? I assume it was already done as mandatory + planned_devs = planned_devices.select do |dev| + dev.is_a?(Planned::StrayBlkDevice) || dev.is_a?(Planned::Disk) + end + + planned_devs.each { |d| d.reuse!(devicegraph) } + end + end + end +end diff --git a/service/lib/y2storage/proposal/agama_devices_planner.rb b/service/lib/y2storage/proposal/agama_devices_planner.rb new file mode 100644 index 0000000000..efffaeeea9 --- /dev/null +++ b/service/lib/y2storage/proposal/agama_devices_planner.rb @@ -0,0 +1,73 @@ +# Copyright (c) [2024] 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 "y2storage/proposal/agama_drive_planner" +require "y2storage/planned" + +module Y2Storage + module Proposal + class AgamaDevicesPlanner + include Yast::Logger + + # Settings used to calculate the planned devices. + # + # @return [Agama::Storage::Profile] + attr_reader :settings + + # @param settings [Agama::Storage::Profile] + # @param issues_list [Array] + def initialize(settings, issues_list) + @settings = settings + @issues_list = issues_list + end + + # List of devices that need to be created to satisfy the settings. Does not include + # devices needed for booting. + # + # For the time being, this implements only stuff coming from partitition elements within + # drive elements. + # + # In the future this will also include planned devices that are a direct translations of + # those typically generated by the Guided Proposal. For those, note that: + # - For dedicated VGs it creates a Planned VG containing a Planned LV, but no PVs + # - For LVM volumes it create a Planned LV but associated to no planned VG + # - For partition volumes, it creates a planned partition, of course + # + # @param target [Symbol] see #planned_devices + # @param devicegraph [Devicegraph] + # @return [Array] + def initial_planned_devices(devicegraph) + devs = settings.drives.flat_map { |d| planned_for_drive(d, devicegraph) }.compact + Planned::DevicesCollection.new(devs) + end + + protected + + # @return [Array] List to register any found issue + attr_reader :issues_list + + # I'm leaving out intentionally support for StrayBlkDevice. As far as I know, + # the plan for SLE/Leap 16 is to drop XEN support + def planned_for_drive(drive, devicegraph) + planner = AgamaDrivePlanner.new(devicegraph, issues_list) + planner.planned_devices(drive) + end + end + end +end diff --git a/service/lib/y2storage/proposal/agama_drive_planner.rb b/service/lib/y2storage/proposal/agama_drive_planner.rb new file mode 100644 index 0000000000..59fd0eed2d --- /dev/null +++ b/service/lib/y2storage/proposal/agama_drive_planner.rb @@ -0,0 +1,66 @@ +# Copyright (c) [2024] 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 "y2storage/proposal/agama_device_planner" + +module Y2Storage + module Proposal + class AgamaDrivePlanner < AgamaDevicePlanner + # @param settings [Agama::Storage::Settings::Drive] + # @return [Array] + def planned_devices(settings) + [planned_drive(settings)] + end + + private + + # @param settings [Agama::Storage::Settings::Drive] + # @return [Planned::Disk] + def planned_drive(settings) + return planned_full_drive(settings) unless settings.partitions? + + planned_partitioned_drive(settings) + end + + # @param settings [Agama::Storage::Settings::Drive] + # @return [Planned::Disk] + def planned_full_drive(settings) + Planned::Disk.new.tap do |planned| + configure_drive(planned, settings) + configure_device(planned, settings) + end + end + + # @param settings [Agama::Storage::Settings::Drive] + # @return [Planned::Disk] + def planned_partitioned_drive(settings) + Planned::Disk.new.tap do |planned| + configure_drive(planned, settings) + configure_partitions(planned, settings) + end + end + + # @param planned [Planned::Disk] + # @param settings [Agama::Storage::Settings::Drive] + def configure_drive(planned, settings) + planned.assign_reuse(settings.found_device) + end + end + end +end diff --git a/service/lib/y2storage/proposal/agama_lvm_helper.rb b/service/lib/y2storage/proposal/agama_lvm_helper.rb new file mode 100644 index 0000000000..78518b3dd4 --- /dev/null +++ b/service/lib/y2storage/proposal/agama_lvm_helper.rb @@ -0,0 +1,46 @@ +# Copyright (c) [2024] 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 "y2storage/proposal/lvm_helper" +require "y2storage/proposal_settings" + +module Y2Storage + module Proposal + class AgamaLvmHelper < LvmHelper + # Initialize. + def initialize(lvm_lvs) + super(lvm_lvs, guided_settings) + end + + def guided_settings + # Despite the "current_product" part in the name of the constructor, it only applies + # generic default values that are independent of the product (there is no YaST + # ProductFeatures mechanism in place). + Y2Storage::ProposalSettings.new_for_current_product.tap do |target| + target.lvm_vg_strategy = :use_needed + target.lvm_vg_reuse = false + # TODO: + target.encryption_password = nil + # target.encryption_pbkdf + # target.encryption_method + end + end + end + end +end diff --git a/service/lib/y2storage/proposal/agama_searcher.rb b/service/lib/y2storage/proposal/agama_searcher.rb new file mode 100644 index 0000000000..fc103ee988 --- /dev/null +++ b/service/lib/y2storage/proposal/agama_searcher.rb @@ -0,0 +1,76 @@ +# Copyright (c) [2024] 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/issue" + +module Y2Storage + module Proposal + class AgamaSearcher + include Yast::Logger + include Yast::I18n + + # Constructor + # + def initialize + textdomain "agama" + end + + # The last two arguments get modified + def search(devicegraph, settings, issues_list) + settings.original_graph = devicegraph + + sids = [] + settings.drives.each do |drive| + drive.search_device(devicegraph, sids) + + found = drive.found_device + if found.nil? + # TODO: If IfNotFound is 'skip' => + # invalidate somehow the device definition (registering issue?) + # + # Let's assume IfNotFound is 'error' + issues_list << issue_missing_drive(drive) + return false + end + + sids << found.sid + next unless drive.partitions? + + drive.partitions.each do |part| + part.search_device(devicegraph, found.sid, sids) + part_sid = part.found_device&.sid + sids << part_sid if part_sid + end + end + + true + end + + private + + def issue_missing_drive(drive) + Agama::Issue.new( + _("No device found for a given drive"), + source: Issue::Source::CONFIG, + severity: Issue::Severity::ERROR + ) + end + end + end +end diff --git a/service/lib/y2storage/proposal/agama_space_maker.rb b/service/lib/y2storage/proposal/agama_space_maker.rb new file mode 100644 index 0000000000..baf92aa017 --- /dev/null +++ b/service/lib/y2storage/proposal/agama_space_maker.rb @@ -0,0 +1,47 @@ +# Copyright (c) [2024] 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 "y2storage/proposal/space_maker" +require "y2storage/proposal_settings" + +module Y2Storage + module Proposal + class AgamaSpaceMaker < SpaceMaker + # Initialize. + def initialize(disk_analyzer, settings) + super(disk_analyzer, guided_settings(settings)) + end + + def guided_settings(settings) + # Despite the "current_product" part in the name of the constructor, it only applies + # generic default values that are independent of the product (there is no YaST + # ProductFeatures mechanism in place). + Y2Storage::ProposalSettings.new_for_current_product.tap do |target| + target.space_settings.strategy = :bigger_resize + target.space_settings.actions = [] + + boot_device = settings.boot_device + + target.root_device = boot_device + target.candidate_devices = [boot_device].compact + end + end + end + end +end diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb new file mode 100644 index 0000000000..6b7224b80e --- /dev/null +++ b/service/test/y2storage/agama_proposal_test.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_relative "../agama/storage/storage_helpers" +require "agama/config" +require "agama/storage/config" +require "y2storage/agama_proposal" + +describe Y2Storage::AgamaProposal do + include Agama::RSpec::StorageHelpers + + before do + mock_storage(devicegraph: "empty-hd-50GiB.yaml") + end + + subject(:proposal) do + described_class.new(initial_settings, issues_list: issues_list) + end + let(:initial_settings) do + Agama::Storage::Config.new.tap do |settings| + settings.drives = [root_drive] + end + end + let(:root_drive) do + Agama::Storage::Configs::Drive.new.tap do |drive| + drive.partitions = [ + Agama::Storage::Configs::Partition.new.tap do |part| + part.mount = Agama::Storage::Configs::Mount.new.tap { |m| m.path = "/" } + part.size = Agama::Storage::Configs::SizeRange.new.tap do |size| + size.min = Y2Storage::DiskSize.GiB(8.5) + size.max = Y2Storage::DiskSize.unlimited + end + end + ] + end + end + let(:issues_list) { [] } + + describe "#propose" do + context "when only the root partition is specified" do + context "if no configuration about boot devices is specified" do + it "proposes to create the root device and the boot-related partition" do + proposal.propose + partitions = proposal.devices.partitions + expect(partitions.size).to eq 2 + expect(partitions.first.id).to eq Y2Storage::PartitionId::BIOS_BOOT + root_part = partitions.last + expect(root_part.size).to be > Y2Storage::DiskSize.GiB(49) + # root_fs = root_part.filesystem + # expect(root_fs.root?).to eq true + # expect(root_fs.type.is?(:btrfs)).to eq true + end + end + + context "if no boot devices should be created" do + before do + initial_settings.boot = Agama::Storage::BootSettings.new.tap { |b| b.configure = false } + end + + it "proposes to create only the root device" do + proposal.propose + partitions = proposal.devices.partitions + expect(partitions.size).to eq 1 + root_part = partitions.first + expect(root_part.id).to eq Y2Storage::PartitionId::LINUX + expect(root_part.size).to be > Y2Storage::DiskSize.GiB(49) + # root_fs = root_part.filesystem + # expect(root_fs.root?).to eq true + # expect(root_fs.type.is?(:btrfs)).to eq true + end + end + end + end +end From e6a3e38968221897691deb83db96a0352689215b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Wed, 24 Jul 2024 13:30:54 +0100 Subject: [PATCH 02/21] WIP: Initial conversion from JSON --- service/lib/agama/storage/config.rb | 21 +-- .../lib/agama/storage/config_conversions.rb | 30 ++++ .../config_conversions/block_device.rb | 32 +++++ .../block_device/from_json.rb | 129 ++++++++++++++++++ .../agama/storage/config_conversions/drive.rb | 32 +++++ .../config_conversions/drive/from_json.rb | 83 +++++++++++ .../storage/config_conversions/encrypt.rb | 32 +++++ .../config_conversions/encrypt/from_json.rb | 83 +++++++++++ .../storage/config_conversions/format.rb | 32 +++++ .../config_conversions/format/from_json.rb | 66 +++++++++ .../storage/config_conversions/from_json.rb | 100 ++++++++++++++ .../agama/storage/config_conversions/mount.rb | 32 +++++ .../config_conversions/mount/from_json.rb | 53 +++++++ .../storage/config_conversions/partition.rb | 32 +++++ .../config_conversions/partition/from_json.rb | 108 +++++++++++++++ .../config_conversions/partitionable.rb | 32 +++++ .../partitionable/from_json.rb | 88 ++++++++++++ .../agama/storage/config_conversions/size.rb | 32 +++++ .../config_conversions/size/from_json.rb | 71 ++++++++++ service/lib/agama/storage/configs.rb | 2 +- service/lib/agama/storage/configs/drive.rb | 7 +- service/lib/agama/storage/configs/encrypt.rb | 2 +- service/lib/agama/storage/configs/format.rb | 1 + .../lib/agama/storage/configs/partition.rb | 4 - .../configs/{size_range.rb => size.rb} | 2 +- .../agama/storage/proposal_settings_reader.rb | 1 + .../config_conversions/from_json_test.rb | 88 ++++++++++++ service/test/y2storage/agama_proposal_test.rb | 4 +- 28 files changed, 1175 insertions(+), 24 deletions(-) create mode 100644 service/lib/agama/storage/config_conversions.rb create mode 100644 service/lib/agama/storage/config_conversions/block_device.rb create mode 100644 service/lib/agama/storage/config_conversions/block_device/from_json.rb create mode 100644 service/lib/agama/storage/config_conversions/drive.rb create mode 100644 service/lib/agama/storage/config_conversions/drive/from_json.rb create mode 100644 service/lib/agama/storage/config_conversions/encrypt.rb create mode 100644 service/lib/agama/storage/config_conversions/encrypt/from_json.rb create mode 100644 service/lib/agama/storage/config_conversions/format.rb create mode 100644 service/lib/agama/storage/config_conversions/format/from_json.rb create mode 100644 service/lib/agama/storage/config_conversions/from_json.rb create mode 100644 service/lib/agama/storage/config_conversions/mount.rb create mode 100644 service/lib/agama/storage/config_conversions/mount/from_json.rb create mode 100644 service/lib/agama/storage/config_conversions/partition.rb create mode 100644 service/lib/agama/storage/config_conversions/partition/from_json.rb create mode 100644 service/lib/agama/storage/config_conversions/partitionable.rb create mode 100644 service/lib/agama/storage/config_conversions/partitionable/from_json.rb create mode 100644 service/lib/agama/storage/config_conversions/size.rb create mode 100644 service/lib/agama/storage/config_conversions/size/from_json.rb rename service/lib/agama/storage/configs/{size_range.rb => size.rb} (97%) create mode 100644 service/test/agama/storage/config_conversions/from_json_test.rb diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index 2711422baa..4f1a83ffc8 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Copyright (c) [2022-2024] SUSE LLC +# Copyright (c) [2024] SUSE LLC # # All Rights Reserved. # @@ -46,21 +46,14 @@ def initialize @nfs_mounts = [] end - # Creates a new proposal settings object from JSON hash according to schema. + # Creates a config from JSON hash according to schema. # - # @param settings_json [Hash] - # @param config [Config] + # @param config_json [Hash] + # @param product_config [Agama::Config] # - # @return [Settings] - def self.new_from_json(settings_json, config:) - Storage::SettingsConversions::FromJSON.new(settings_json).convert - end - - # Generates a JSON hash according to schema. - # - # @return [Hash] - def to_json_settings - Storage::ProposalSettingsConversions::ToJSON.new(self).convert + # @return [Storage::Config] + def self.new_from_json(config_json, product_config:) + ConfigConversions::FromJSON.new(config_json, product_config: product_config).convert end def boot_device diff --git a/service/lib/agama/storage/config_conversions.rb b/service/lib/agama/storage/config_conversions.rb new file mode 100644 index 0000000000..c1284c76bf --- /dev/null +++ b/service/lib/agama/storage/config_conversions.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/from_json" + +module Agama + module Storage + # Conversions for the storage config. + module ConfigConversions + end + end +end diff --git a/service/lib/agama/storage/config_conversions/block_device.rb b/service/lib/agama/storage/config_conversions/block_device.rb new file mode 100644 index 0000000000..7fcbb01077 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/block_device.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/block_device/from_json" + +module Agama + module Storage + module ConfigConversions + # Conversions for block device. + module BlockDevice + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/block_device/from_json.rb b/service/lib/agama/storage/config_conversions/block_device/from_json.rb new file mode 100644 index 0000000000..febeca586f --- /dev/null +++ b/service/lib/agama/storage/config_conversions/block_device/from_json.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/encrypt/from_json" +require "agama/storage/config_conversions/format/from_json" +require "agama/storage/config_conversions/mount/from_json" +require "agama/storage/configs/encrypt" +require "agama/storage/configs/format" +require "agama/storage/configs/mount" + +module Agama + module Storage + module ConfigConversions + module BlockDevice + # Block device conversion from JSON hash according to schema. + class FromJSON + # @todo Replace settings and volume_builder params by a ProductDefinition. + # + # @param drive_json [Hash] + # @param settings [ProposalSettings] + # @param volume_builder [VolumeTemplatesBuilder] + def initialize(blk_device_json, settings:, volume_builder:) + @blk_device_json = blk_device_json + @settings = settings + @volume_builder = volume_builder + end + + # Performs the conversion from Hash according to the JSON schema. + # + # @param config [#encrypt=, #format=, #mount=] + def convert(config) + config.encrypt = convert_encrypt + config.format = convert_format + config.mount = convert_mount + config + end + + private + + # @return [Hash] + attr_reader :blk_device_json + + # @return [ProposalSettings] + attr_reader :settings + + # @return [VolumeTemplatesBuilder] + attr_reader :volume_builder + + # @return [Configs::Encrypt, nil] + def convert_encrypt + encrypt_json = blk_device_json[:encrypt] + return unless encrypt_json + + Encrypt::FromJSON.new(encrypt_json, default: default_encrypt_config).convert + end + + # @return [Configs::Format, nil] + def convert_format + format_json = blk_device_json[:format] + mount_json = blk_device_json[:mount] + + return if format_json == false # "format": false + return if format_json.nil? && mount_json.nil? + + default = default_format_config(mount_json&.dig(:path)) + return default unless format_json + + # @todo Check whether the given filesystem can be used for the mount point. + + Format::FromJSON.new(format_json, default: default).convert + end + + # @return [Configs::Mount, nil] + def convert_mount + mount_json = blk_device_json[:mount] + return unless mount_json + + Mount::FromJSON.new(mount_json).convert + end + + # @todo Recover values from ProductDefinition instead of ProposalSettings. + # + # Default encryption config from the product definition. + # + # @return [Configs::Encrypt] + def default_encrypt_config + Configs::Encrypt.new.tap do |config| + config.key = settings.encryption.password + config.method = settings.encryption.method + config.pbkd_function = settings.encryption.pbkd_function + end + end + + # @todo Recover values from ProductDefinition instead of VolumeTemplatesBuilder. + # + # Default format config from the product definition. + # + # @param mount_path [String] + # @return [Configs::Format] + def default_format_config(mount_path) + volume = volume_builder.for(mount_path || "") + + Configs::Format.new.tap do |config| + config.filesystem = volume.fs_type + end + end + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/drive.rb b/service/lib/agama/storage/config_conversions/drive.rb new file mode 100644 index 0000000000..f42dd41358 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/drive.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/drive/from_json" + +module Agama + module Storage + module ConfigConversions + # Conversions for drive. + module Drive + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/drive/from_json.rb b/service/lib/agama/storage/config_conversions/drive/from_json.rb new file mode 100644 index 0000000000..698e74c69e --- /dev/null +++ b/service/lib/agama/storage/config_conversions/drive/from_json.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/block_device/from_json" +require "agama/storage/config_conversions/partitionable/from_json" +require "agama/storage/configs/drive" + +module Agama + module Storage + module ConfigConversions + module Drive + # Drive conversion from JSON hash according to schema. + class FromJSON + # @todo Replace settings and volume_builder params by a ProductDefinition. + # + # @param drive_json [Hash] + # @param settings [ProposalSettings] + # @param volume_builder [VolumeTemplatesBuilder] + def initialize(drive_json, settings:, volume_builder:) + @drive_json = drive_json + @settings = settings + @volume_builder = volume_builder + end + + # Performs the conversion from Hash according to the JSON schema. + # + # @return [Configs::Drive] + def convert + Configs::Drive.new.tap do |config| + convert_block_device(config) + convert_partitionable(config) + end + end + + private + + # @return [Hash] + attr_reader :drive_json + + # @return [ProposalSettings] + attr_reader :settings + + # @return [VolumeTemplatesBuilder] + attr_reader :volume_builder + + # @param config [Configs::Drive] + def convert_block_device(config) + converter = BlockDevice::FromJSON.new(drive_json, + settings: settings, volume_builder: volume_builder) + + converter.convert(config) + end + + # @param config [Configs::Drive] + def convert_partitionable(config) + converter = Partitionable::FromJSON.new(drive_json, + settings: settings, volume_builder: volume_builder) + + converter.convert(config) + end + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/encrypt.rb b/service/lib/agama/storage/config_conversions/encrypt.rb new file mode 100644 index 0000000000..185894e084 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/encrypt.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/encrypt/from_json" + +module Agama + module Storage + module ConfigConversions + # Conversions for encrypt. + module Encrypt + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/encrypt/from_json.rb b/service/lib/agama/storage/config_conversions/encrypt/from_json.rb new file mode 100644 index 0000000000..a01304a20d --- /dev/null +++ b/service/lib/agama/storage/config_conversions/encrypt/from_json.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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/configs/encrypt" +require "y2storage/encryption_method" +require "y2storage/pbkd_function" + +module Agama + module Storage + module ConfigConversions + module Encrypt + # Encrypt conversion from JSON hash according to schema. + class FromJSON + # @param encrypt_json [Hash] + # @param default [Configs::Encrypt] + def initialize(encrypt_json, default: nil) + @encrypt_json = encrypt_json + @default_config = default || Configs::Encrypt.new + end + + # Performs the conversion from Hash according to the JSON schema. + # + # @return [Configs::Encrypt] + def convert + default_config.dup.tap do |config| + key = convert_key + method = convert_method + pbkdf = convert_pbkd_function + + config.key = key if key + config.method = method if method + config.pbkd_function = pbkdf if pbkdf + end + end + + private + + # @return [Hash] + attr_reader :encrypt_json + + # @return [Configs::Encrypt] + attr_reader :default_config + + # @return [String, nil] + def convert_key + encrypt_json[:password] + end + + # @return [Y2Storage::EncryptionMethod, nil] + def convert_method + value = encrypt_json[:method] + return unless value + + Y2Storage::EncryptionMethod.find(value.to_sym) + end + + # @return [Y2Storage::PbkdFunction, nil] + def convert_pbkd_function + Y2Storage::PbkdFunction.find(encrypt_json[:pbkdFunction]) + end + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/format.rb b/service/lib/agama/storage/config_conversions/format.rb new file mode 100644 index 0000000000..f65f978a5b --- /dev/null +++ b/service/lib/agama/storage/config_conversions/format.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/format/from_json" + +module Agama + module Storage + module ConfigConversions + # Conversions for format. + module Format + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/format/from_json.rb b/service/lib/agama/storage/config_conversions/format/from_json.rb new file mode 100644 index 0000000000..d8a4b220fc --- /dev/null +++ b/service/lib/agama/storage/config_conversions/format/from_json.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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/configs/format" +require "y2storage/filesystems/type" + +module Agama + module Storage + module ConfigConversions + module Format + # Format conversion from JSON hash according to schema. + class FromJSON + # @param format_json [Hash] + # @param default [Configs::Format, nil] + def initialize(format_json, default: nil) + @format_json = format_json + @default_config = default || Configs::Format.new + end + + # @todo Add support for Btrfs options (snapshots, subvols). + # + # Performs the conversion from Hash according to the JSON schema. + # + # @return [Configs::Format] + def convert + default_config.dup.tap do |config| + config.filesystem = convert_filesystem + config.label = format_json[:label] + config.mkfs_options = format_json[:mkfsOptions] || [] + end + end + + private + + # @return [Hash] + attr_reader :format_json + + # @return [Configs::Format] + attr_reader :default_config + + def convert_filesystem + Y2Storage::Filesystems::Type.find(format_json[:filesystem].to_sym) + end + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/from_json.rb b/service/lib/agama/storage/config_conversions/from_json.rb new file mode 100644 index 0000000000..ec8cbad386 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/from_json.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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" +require "agama/storage/config_conversions/drive/from_json" +require "agama/storage/configs/boot" +require "agama/storage/proposal_settings_reader" + +module Agama + module Storage + module ConfigConversions + # Config conversion from JSON hash according to schema. + class FromJSON + # @todo Replace product_config param by a ProductDefinition. + # + # @param config_json [Hash] + # @param product_config [Agama::Config] + def initialize(config_json, product_config:) + @config_json = config_json + @product_config = product_config + end + + # Performs the conversion from Hash according to the JSON schema. + # + # @return [Storage::Config] + def convert + # @todo Raise error if config_json does not match the JSON schema. + Storage::Config.new.tap do |config| + boot = convert_boot + drives = convert_drives + + config.boot = boot if boot + config.drives = drives if drives + end + end + + private + + # @return [Hash] + attr_reader :config_json + + # @return [Agama::Config] + attr_reader :product_config + + # @return [Configs::Boot, nil] + def convert_boot + boot_json = config_json[:boot] + return unless boot_json + + Configs::Boot.new.tap do |config| + config.configure = boot_json[:configure] + config.device = boot_json[:device] + end + end + + # @return [Array, nil] + def convert_drives + drives_json = config_json[:drives] + return unless drives_json + + drives_json.map { |d| convert_drive(d) } + end + + # @return [Configs::Drive] + def convert_drive(drive_json) + Drive::FromJSON.new(drive_json, + settings: settings, volume_builder: volume_builder).convert + end + + # @return [ProposalSettings] + def settings + @settings ||= ProposalSettingsReader.new(product_config).read + end + + # @return [VolumeTemplatesBuilder] + def volume_builder + @volume_builder ||= VolumeTemplatesBuilder.new_from_config(product_config) + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/mount.rb b/service/lib/agama/storage/config_conversions/mount.rb new file mode 100644 index 0000000000..cd5ff6dec8 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/mount.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/mount/from_json" + +module Agama + module Storage + module ConfigConversions + # Conversions for mount. + module Mount + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/mount/from_json.rb b/service/lib/agama/storage/config_conversions/mount/from_json.rb new file mode 100644 index 0000000000..a095b6bed3 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/mount/from_json.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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/configs/mount" + +module Agama + module Storage + module ConfigConversions + module Mount + # Mount conversion from JSON hash according to schema. + class FromJSON + # @param mount_json [Hash] + def initialize(mount_json) + @mount_json = mount_json + end + + # Performs the conversion from Hash according to the JSON schema. + # + # @return [Configs::Mount] + def convert + Configs::Mount.new.tap do |config| + config.path = mount_json[:path] + config.options = mount_json[:options] || [] + end + end + + private + + # @return [Hash] + attr_reader :mount_json + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/partition.rb b/service/lib/agama/storage/config_conversions/partition.rb new file mode 100644 index 0000000000..52b67d2f50 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/partition.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/partition/from_json" + +module Agama + module Storage + module ConfigConversions + # Conversions for partition. + module Partition + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/partition/from_json.rb b/service/lib/agama/storage/config_conversions/partition/from_json.rb new file mode 100644 index 0000000000..748701fed4 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/partition/from_json.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/block_device/from_json" +require "agama/storage/config_conversions/size/from_json" +require "agama/storage/configs/partition" +require "y2storage/partition_id" + +module Agama + module Storage + module ConfigConversions + module Partition + # Partition conversion from JSON hash according to schema. + class FromJSON + # @todo Replace settings and volume_builder params by a ProductDefinition. + # + # @param partition_json [Hash] + # @param settings [ProposalSettings] + # @param volume_builder [VolumeTemplatesBuilder] + def initialize(partition_json, settings:, volume_builder:) + @partition_json = partition_json + @settings = settings + @volume_builder = volume_builder + end + + # Performs the conversion from Hash according to the JSON schema. + # + # @return [Configs::Partition] + def convert + Configs::Partition.new.tap do |config| + config.id = convert_id + config.size = convert_size + convert_block_device(config) + end + end + + private + + # @return [Hash] + attr_reader :partition_json + + # @return [ProposalSettings] + attr_reader :settings + + # @return [VolumeTemplatesBuilder] + attr_reader :volume_builder + + # @return [Y2Storage::PartitionId, nil] + def convert_id + # @todo Decide whether to use "create" in JSON schema. + value = partition_json.dig(:create, :id) + return unless value + + Y2Storage::PartitionId.find(value) + end + + # @return [Configs::Size] + def convert_size + # @todo Decide whether to use "create" in JSON schema. + size_json = partition_json.dig(:create, :size) + return default_size_config unless size_json + + Size::FromJSON.new(size_json).convert + end + + # @param config [Configs::Partition] + def convert_block_device(config) + converter = BlockDevice::FromJSON.new(partition_json, + settings: settings, volume_builder: volume_builder) + + converter.convert(config) + end + + # @todo Auto size? + # + # @return [Configs::Size] + def default_size_config + mount_path = partition_json.dig(:mount, :path) + volume = volume_builder.for(mount_path || "") + + Configs::Size.new.tap do |config| + config.min = volume.min_size + config.max = volume.max_size + end + end + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/partitionable.rb b/service/lib/agama/storage/config_conversions/partitionable.rb new file mode 100644 index 0000000000..95094cb0a4 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/partitionable.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/partitionable/from_json" + +module Agama + module Storage + module ConfigConversions + # Conversions for partitionable. + module Partitionable + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/partitionable/from_json.rb b/service/lib/agama/storage/config_conversions/partitionable/from_json.rb new file mode 100644 index 0000000000..3b9d20ab9c --- /dev/null +++ b/service/lib/agama/storage/config_conversions/partitionable/from_json.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/partition/from_json" +require "y2storage/partition_tables/type" + +module Agama + module Storage + module ConfigConversions + module Partitionable + # Partitionable device conversion from JSON hash according to schema. + class FromJSON + # @todo Replace settings and volume_builder params by a ProductDefinition. + # + # @param partitionable_json [Hash] + # @param settings [ProposalSettings] + # @param volume_builder [VolumeTemplatesBuilder] + def initialize(partitionable_json, settings:, volume_builder:) + @partitionable_json = partitionable_json + @settings = settings + @volume_builder = volume_builder + end + + # Performs the conversion from Hash according to the JSON schema. + # + # @param config [#ptable_type=, #partitions=] + def convert(config) + config.ptable_type = convert_ptable_type + config.partitions = convert_partitions + config + end + + private + + # @return [Hash] + attr_reader :partitionable_json + + # @return [ProposalSettings] + attr_reader :settings + + # @return [VolumeTemplatesBuilder] + attr_reader :volume_builder + + # @return [Y2Storage::PartitionTables::Type, nil] + def convert_ptable_type + value = partitionable_json[:ptableType] + return unless value + + Y2Storage::PartitionTables::Type.find(value) + end + + # @return [Array] + def convert_partitions + partitions_json = partitionable_json[:partitions] + return [] unless partitions_json + + partitions_json.map { |p| convert_partition(p) } + end + + # @param partition_json [Hash] + # @return [Settings::Partition] + def convert_partition(partition_json) + Partition::FromJSON.new(partition_json, + settings: settings, volume_builder: volume_builder).convert + end + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/size.rb b/service/lib/agama/storage/config_conversions/size.rb new file mode 100644 index 0000000000..b85b9e734d --- /dev/null +++ b/service/lib/agama/storage/config_conversions/size.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/size/from_json" + +module Agama + module Storage + module ConfigConversions + # Conversions for size. + module Size + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/size/from_json.rb b/service/lib/agama/storage/config_conversions/size/from_json.rb new file mode 100644 index 0000000000..4172341401 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/size/from_json.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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/configs/size" +require "y2storage/disk_size" + +module Agama + module Storage + module ConfigConversions + module Size + # Size conversion from JSON hash according to schema. + class FromJSON + # @param size_json [Hash] + def initialize(size_json) + @size_json = size_json + end + + # @todo Add support for auto. + # @todo For now only {min: number, max: number} schema is supported. Add support for a + # direct value (e.g., 1024, "2 GiB"), and array format ([min, max]). + # + # Performs the conversion from Hash according to the JSON schema. + # + # @return [Configs::Size] + def convert + Configs::Size.new.tap do |config| + config.min = convert_min + config.max = convert_max || Y2Storage::DiskSize.unlimited + end + end + + private + + # @return [Hash] + attr_reader :size_json + + # @return [Y2Storage::DiskSize] + def convert_min + Y2Storage::DiskSize.new(size_json[:min]) + end + + # @return [Y2Storage::DiskSize, nil] + def convert_max + value = size_json[:max] + return unless value + + Y2Storage::DiskSize.new(value) + end + end + end + end + end +end diff --git a/service/lib/agama/storage/configs.rb b/service/lib/agama/storage/configs.rb index abb1359440..060273754a 100644 --- a/service/lib/agama/storage/configs.rb +++ b/service/lib/agama/storage/configs.rb @@ -34,4 +34,4 @@ module Configs require "agama/storage/configs/mount" require "agama/storage/configs/partition" require "agama/storage/configs/search" -require "agama/storage/configs/size_range" +require "agama/storage/configs/size" diff --git a/service/lib/agama/storage/configs/drive.rb b/service/lib/agama/storage/configs/drive.rb index a0a3ac4001..214bc7791e 100644 --- a/service/lib/agama/storage/configs/drive.rb +++ b/service/lib/agama/storage/configs/drive.rb @@ -26,12 +26,17 @@ module Storage module Configs class Drive attr_accessor :search + + # @return [Encrypt] attr_accessor :encrypt + + # @return [Format] attr_accessor :format + + # @return [Mount] attr_accessor :mount attr_accessor :ptable_type attr_accessor :partitions - attr_accessor :search # @param mount_path [String] def initialize diff --git a/service/lib/agama/storage/configs/encrypt.rb b/service/lib/agama/storage/configs/encrypt.rb index f129373f77..2062eda0d3 100644 --- a/service/lib/agama/storage/configs/encrypt.rb +++ b/service/lib/agama/storage/configs/encrypt.rb @@ -22,7 +22,7 @@ module Agama module Storage module Configs - class Mount + class Encrypt attr_accessor :method attr_accessor :key attr_accessor :pbkd_function diff --git a/service/lib/agama/storage/configs/format.rb b/service/lib/agama/storage/configs/format.rb index abf0faebff..5afb814e01 100644 --- a/service/lib/agama/storage/configs/format.rb +++ b/service/lib/agama/storage/configs/format.rb @@ -23,6 +23,7 @@ module Agama module Storage module Configs class Format + # btrfs options like snapshots, subvols? attr_accessor :filesystem attr_accessor :label attr_accessor :mkfs_options diff --git a/service/lib/agama/storage/configs/partition.rb b/service/lib/agama/storage/configs/partition.rb index 2133327b34..42fb8b7c7d 100644 --- a/service/lib/agama/storage/configs/partition.rb +++ b/service/lib/agama/storage/configs/partition.rb @@ -25,14 +25,10 @@ module Configs class Partition attr_accessor :search attr_accessor :id - attr_accessor :type attr_accessor :size - attr_accessor :resize - attr_accessor :delete attr_accessor :encrypt attr_accessor :format attr_accessor :mount - attr_accessor :search def search_device(devicegraph, parent_sid, used_sids) @search ||= default_search diff --git a/service/lib/agama/storage/configs/size_range.rb b/service/lib/agama/storage/configs/size.rb similarity index 97% rename from service/lib/agama/storage/configs/size_range.rb rename to service/lib/agama/storage/configs/size.rb index 164816d23d..49e2a5507b 100644 --- a/service/lib/agama/storage/configs/size_range.rb +++ b/service/lib/agama/storage/configs/size.rb @@ -22,7 +22,7 @@ module Agama module Storage module Configs - class SizeRange + class Size attr_accessor :min attr_accessor :max end diff --git a/service/lib/agama/storage/proposal_settings_reader.rb b/service/lib/agama/storage/proposal_settings_reader.rb index e0c038fd6f..90cdda64a4 100644 --- a/service/lib/agama/storage/proposal_settings_reader.rb +++ b/service/lib/agama/storage/proposal_settings_reader.rb @@ -19,6 +19,7 @@ # 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/proposal_settings" require "agama/storage/device_settings" require "agama/storage/space_settings" require "agama/storage/volume_templates_builder" diff --git a/service/test/agama/storage/config_conversions/from_json_test.rb b/service/test/agama/storage/config_conversions/from_json_test.rb new file mode 100644 index 0000000000..c45c1e8ccc --- /dev/null +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_relative "../../../test_helper" +require "agama/storage/config_conversions/from_json" +require "agama/config" +require "y2storage/encryption_method" +require "y2storage/pbkd_function" + +describe Agama::Storage::ConfigConversions::FromJSON do + subject { described_class.new(config_json, product_config: product_config) } + + let(:product_config) { Agama::Config.new(product_data) } + + let(:product_data) do + { + "storage" => { + "lvm" => false, + "space_policy" => "delete", + "encryption" => { + "method" => "luks2", + "pbkd_function" => "argon2id" + }, + "volumes" => ["/", "swap"], + "volume_templates" => [ + { + "mount_path" => "/", + "outline" => { "required" => true } + }, + { + "mount_path" => "/home", + "outline" => { "required" => false } + }, + { + "mount_path" => "swap", + "outline" => { "required" => false } + } + ] + } + } + end + + describe "#convert" do + let(:config_json) do + { + boot: { + configure: true, + device: "/dev/sdb" + }, + drives: [ + { + ptableType: "gpt", + partitions: [ + { + format: { filesystem: "ext4" }, + mount: { path: "/" } + } + ] + } + ] + } + end + + it "generates settings with the values provided from JSON" do + config = subject.convert + + expect(config).to be_a(Agama::Storage::Config) + end + end +end diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index 6b7224b80e..a9396447b8 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -44,7 +44,7 @@ drive.partitions = [ Agama::Storage::Configs::Partition.new.tap do |part| part.mount = Agama::Storage::Configs::Mount.new.tap { |m| m.path = "/" } - part.size = Agama::Storage::Configs::SizeRange.new.tap do |size| + part.size = Agama::Storage::Configs::Size.new.tap do |size| size.min = Y2Storage::DiskSize.GiB(8.5) size.max = Y2Storage::DiskSize.unlimited end @@ -72,7 +72,7 @@ context "if no boot devices should be created" do before do - initial_settings.boot = Agama::Storage::BootSettings.new.tap { |b| b.configure = false } + initial_settings.boot = Agama::Storage::Configs::Boot.new.tap { |b| b.configure = false } end it "proposes to create only the root device" do From c3b10c50ae2e6190acb760dca8d526f29e68d4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Wed, 24 Jul 2024 16:16:31 +0100 Subject: [PATCH 03/21] Move btrfs settings to Configs namespace --- service/lib/agama/storage/configs.rb | 1 + .../{btrfs_settings.rb => configs/btrfs.rb} | 42 ++++++++++--------- service/lib/agama/storage/volume.rb | 6 +-- .../agama/storage/volume_templates_builder.rb | 6 +-- 4 files changed, 29 insertions(+), 26 deletions(-) rename service/lib/agama/storage/{btrfs_settings.rb => configs/btrfs.rb} (51%) diff --git a/service/lib/agama/storage/configs.rb b/service/lib/agama/storage/configs.rb index 060273754a..c1db75157a 100644 --- a/service/lib/agama/storage/configs.rb +++ b/service/lib/agama/storage/configs.rb @@ -28,6 +28,7 @@ module Configs end require "agama/storage/configs/boot" +require "agama/storage/configs/btrfs" require "agama/storage/configs/drive" require "agama/storage/configs/encrypt" require "agama/storage/configs/format" diff --git a/service/lib/agama/storage/btrfs_settings.rb b/service/lib/agama/storage/configs/btrfs.rb similarity index 51% rename from service/lib/agama/storage/btrfs_settings.rb rename to service/lib/agama/storage/configs/btrfs.rb index 51b990b330..db2627decb 100644 --- a/service/lib/agama/storage/btrfs_settings.rb +++ b/service/lib/agama/storage/configs/btrfs.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Copyright (c) [2023] SUSE LLC +# Copyright (c) [2024] SUSE LLC # # All Rights Reserved. # @@ -21,29 +21,31 @@ module Agama module Storage - # Settings regarding Btrfs for a given Volume - class BtrfsSettings - # Whether the volume contains Btrfs snapshots - # - # @return [Boolean] - attr_accessor :snapshots - alias_method :snapshots?, :snapshots + module Configs + # Btrfs configuration. + class Btrfs + # Whether there are snapshots. + # + # @return [Boolean] + attr_accessor :snapshots + alias_method :snapshots?, :snapshots - # @return [Boolean] - attr_accessor :read_only - alias_method :read_only?, :read_only + # @return [Boolean] + attr_accessor :read_only + alias_method :read_only?, :read_only - # @return [Array, nil] if nil, a historical fallback list may - # be applied depending on the mount path of the volume - attr_accessor :subvolumes + # @return [Array, nil] if nil, a historical fallback list may + # be applied depending on the mount path of the volume + attr_accessor :subvolumes - # @return [String] - attr_accessor :default_subvolume + # @return [String] + attr_accessor :default_subvolume - def initialize - @snapshots = false - @read_only = false - @default_subvolume = "" + def initialize + @snapshots = false + @read_only = false + @default_subvolume = "" + end end end end diff --git a/service/lib/agama/storage/volume.rb b/service/lib/agama/storage/volume.rb index 023c4df78a..c1ce6e3614 100644 --- a/service/lib/agama/storage/volume.rb +++ b/service/lib/agama/storage/volume.rb @@ -22,7 +22,7 @@ require "forwardable" require "json" require "y2storage/disk_size" -require "agama/storage/btrfs_settings" +require "agama/storage/configs/btrfs" require "agama/storage/volume_conversions" require "agama/storage/volume_location" require "agama/storage/volume_outline" @@ -52,7 +52,7 @@ class Volume # # Only relevant if #fs_type is Btrfs # - # @return [BtrfsSettings] + # @return [Configs::Btrfs] attr_accessor :btrfs # @return [Array] @@ -91,7 +91,7 @@ def initialize(mount_path) @auto_size = false @min_size = Y2Storage::DiskSize.zero @max_size = Y2Storage::DiskSize.unlimited - @btrfs = BtrfsSettings.new + @btrfs = Configs::Btrfs.new @outline = VolumeOutline.new @location = VolumeLocation.new end diff --git a/service/lib/agama/storage/volume_templates_builder.rb b/service/lib/agama/storage/volume_templates_builder.rb index d955c691c7..062d94dcfe 100644 --- a/service/lib/agama/storage/volume_templates_builder.rb +++ b/service/lib/agama/storage/volume_templates_builder.rb @@ -21,9 +21,9 @@ require "pathname" require "y2storage" +require "agama/storage/configs/btrfs" require "agama/storage/volume" require "agama/storage/volume_outline" -require "agama/storage/btrfs_settings" module Agama module Storage @@ -102,7 +102,7 @@ def path_key(path) # Temporary method to avoid crashes if there is no default template def empty_data { - btrfs: BtrfsSettings.new, + btrfs: Configs::Btrfs.new, outline: VolumeOutline.new, mount_options: [], filesystem: Y2Storage::Filesystems::Type::EXT4 @@ -130,7 +130,7 @@ def values(data) def btrfs(data) btrfs_data = fetch(data, "btrfs", {}) - BtrfsSettings.new.tap do |btrfs| + Configs::Btrfs.new.tap do |btrfs| btrfs.snapshots = fetch(btrfs_data, "snapshots", false) btrfs.read_only = fetch(btrfs_data, "read_only", false) btrfs.default_subvolume = fetch(btrfs_data, "default_subvolume", "") From 786397e1b7e8a28bc2e9d6e8b7ca18d179bcdf29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Wed, 24 Jul 2024 16:36:13 +0100 Subject: [PATCH 04/21] WIP: Convert filesystem config from JSON --- .../lib/agama/storage/config_conversions.rb | 9 +++ .../block_device/from_json.rb | 27 +++++-- .../storage/config_conversions/filesystem.rb | 32 ++++++++ .../filesystem/from_json.rb | 81 +++++++++++++++++++ .../config_conversions/format/from_json.rb | 33 ++++---- service/lib/agama/storage/configs.rb | 1 + .../lib/agama/storage/configs/filesystem.rb | 34 ++++++++ service/lib/agama/storage/configs/format.rb | 2 +- 8 files changed, 195 insertions(+), 24 deletions(-) create mode 100644 service/lib/agama/storage/config_conversions/filesystem.rb create mode 100644 service/lib/agama/storage/config_conversions/filesystem/from_json.rb create mode 100644 service/lib/agama/storage/configs/filesystem.rb diff --git a/service/lib/agama/storage/config_conversions.rb b/service/lib/agama/storage/config_conversions.rb index c1284c76bf..c17ee67683 100644 --- a/service/lib/agama/storage/config_conversions.rb +++ b/service/lib/agama/storage/config_conversions.rb @@ -19,7 +19,16 @@ # 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_conversions/block_device" +require "agama/storage/config_conversions/drive" +require "agama/storage/config_conversions/encrypt" +require "agama/storage/config_conversions/filesystem" +require "agama/storage/config_conversions/format" require "agama/storage/config_conversions/from_json" +require "agama/storage/config_conversions/mount" +require "agama/storage/config_conversions/partition" +require "agama/storage/config_conversions/partitionable" +require "agama/storage/config_conversions/size" module Agama module Storage diff --git a/service/lib/agama/storage/config_conversions/block_device/from_json.rb b/service/lib/agama/storage/config_conversions/block_device/from_json.rb index febeca586f..25801dae9c 100644 --- a/service/lib/agama/storage/config_conversions/block_device/from_json.rb +++ b/service/lib/agama/storage/config_conversions/block_device/from_json.rb @@ -23,6 +23,7 @@ require "agama/storage/config_conversions/format/from_json" require "agama/storage/config_conversions/mount/from_json" require "agama/storage/configs/encrypt" +require "agama/storage/configs/filesystem" require "agama/storage/configs/format" require "agama/storage/configs/mount" @@ -80,12 +81,13 @@ def convert_format return if format_json == false # "format": false return if format_json.nil? && mount_json.nil? - default = default_format_config(mount_json&.dig(:path)) + default = default_format_config(mount_json&.dig(:path) || "") return default unless format_json # @todo Check whether the given filesystem can be used for the mount point. + # @todo Check whether snapshots can be configured and restore to default if needed. - Format::FromJSON.new(format_json, default: default).convert + Format::FromJSON.new(format_json).convert(default) end # @return [Configs::Mount, nil] @@ -109,17 +111,28 @@ def default_encrypt_config end end - # @todo Recover values from ProductDefinition instead of VolumeTemplatesBuilder. - # # Default format config from the product definition. # # @param mount_path [String] # @return [Configs::Format] def default_format_config(mount_path) - volume = volume_builder.for(mount_path || "") - Configs::Format.new.tap do |config| - config.filesystem = volume.fs_type + config.filesystem = default_filesystem_config(mount_path) + end + end + + # @todo Recover values from ProductDefinition instead of VolumeTemplatesBuilder. + # + # Default filesystem config from the product definition. + # + # @param mount_path [String] + # @return [Configs::Filesystem] + def default_filesystem_config(mount_path) + volume = volume_builder.for(mount_path) + + Configs::Filesystem.new.tap do |config| + config.type = volume.fs_type + config.btrfs = volume.btrfs end end end diff --git a/service/lib/agama/storage/config_conversions/filesystem.rb b/service/lib/agama/storage/config_conversions/filesystem.rb new file mode 100644 index 0000000000..35828920a5 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/filesystem.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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_conversions/filesystem/from_json" + +module Agama + module Storage + module ConfigConversions + # Conversions for filesystem. + module Filesystem + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/filesystem/from_json.rb b/service/lib/agama/storage/config_conversions/filesystem/from_json.rb new file mode 100644 index 0000000000..2045e39bc8 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/filesystem/from_json.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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/configs/btrfs" +require "agama/storage/configs/filesystem" +require "y2storage/filesystems/type" + +module Agama + module Storage + module ConfigConversions + module Filesystem + # Filesystem conversion from JSON hash according to schema. + class FromJSON + # @param filesystem_json [Hash, String] + def initialize(filesystem_json) + @filesystem_json = filesystem_json + end + + # Performs the conversion from Hash according to the JSON schema. + # + # @param default [Configs::Filesystem, nil] + # @return [Configs::Filesystem] + def convert(default = nil) + default_config = default.dup || Configs::Filesystem.new + + default_config.tap do |config| + btrfs = convert_btrfs(config.btrfs) + + config.type = convert_type + config.btrfs = btrfs if btrfs + end + end + + private + + # @return [Hash] + attr_reader :filesystem_json + + # @return [Y2Storage::Filesystems::Type] + def convert_type + value = filesystem_json.is_a?(String) ? filesystem_json : "btrfs" + Y2Storage::Filesystems::Type.find(value.to_sym) + end + + # @param default [Configs::Btrfs] + # @return [Configs::Btrfs, nil] + def convert_btrfs(default = nil) + return nil if filesystem_json.is_a?(String) + + btrfs_json = filesystem_json[:btrfs] + default_config = default.dup || Configs::Btrfs.new + + default_config.tap do |config| + snapshots = btrfs_json[:snapshots] + + config.snapshots = snapshots if snapshots + end + end + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/format/from_json.rb b/service/lib/agama/storage/config_conversions/format/from_json.rb index d8a4b220fc..2f49d8ec82 100644 --- a/service/lib/agama/storage/config_conversions/format/from_json.rb +++ b/service/lib/agama/storage/config_conversions/format/from_json.rb @@ -19,8 +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/config_conversions/filesystem/from_json" require "agama/storage/configs/format" -require "y2storage/filesystems/type" module Agama module Storage @@ -29,22 +29,24 @@ module Format # Format conversion from JSON hash according to schema. class FromJSON # @param format_json [Hash] - # @param default [Configs::Format, nil] - def initialize(format_json, default: nil) + def initialize(format_json) @format_json = format_json - @default_config = default || Configs::Format.new end - # @todo Add support for Btrfs options (snapshots, subvols). - # # Performs the conversion from Hash according to the JSON schema. # + # @param default [Configs::Format, nil] # @return [Configs::Format] - def convert - default_config.dup.tap do |config| - config.filesystem = convert_filesystem - config.label = format_json[:label] - config.mkfs_options = format_json[:mkfsOptions] || [] + def convert(default = nil) + default_config = default.dup || Configs::Format.new + + default_config.tap do |config| + label = format_json[:label] + mkfs_options = format_json[:mkfsOptions] + + config.filesystem = convert_filesystem(config.filesystem) + config.label = label if label + config.mkfs_options = mkfs_options if mkfs_options end end @@ -53,11 +55,10 @@ def convert # @return [Hash] attr_reader :format_json - # @return [Configs::Format] - attr_reader :default_config - - def convert_filesystem - Y2Storage::Filesystems::Type.find(format_json[:filesystem].to_sym) + # @param default [Configs::Filesystem, nil] + # @return [Configs::Filesystem] + def convert_filesystem(default = nil) + Filesystem::FromJSON.new(format_json[:filesystem]).convert(default) end end end diff --git a/service/lib/agama/storage/configs.rb b/service/lib/agama/storage/configs.rb index c1db75157a..8b6ed6755a 100644 --- a/service/lib/agama/storage/configs.rb +++ b/service/lib/agama/storage/configs.rb @@ -31,6 +31,7 @@ module Configs require "agama/storage/configs/btrfs" require "agama/storage/configs/drive" require "agama/storage/configs/encrypt" +require "agama/storage/configs/filesystem" require "agama/storage/configs/format" require "agama/storage/configs/mount" require "agama/storage/configs/partition" diff --git a/service/lib/agama/storage/configs/filesystem.rb b/service/lib/agama/storage/configs/filesystem.rb new file mode 100644 index 0000000000..7843e239b4 --- /dev/null +++ b/service/lib/agama/storage/configs/filesystem.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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 + class Filesystem + # @return [Y2Storage::Filesystems::Type, nil] + attr_accessor :type + + # @return [Configs::Btrfs, nil] + attr_accessor :btrfs + end + end + end +end diff --git a/service/lib/agama/storage/configs/format.rb b/service/lib/agama/storage/configs/format.rb index 5afb814e01..4927ee70ee 100644 --- a/service/lib/agama/storage/configs/format.rb +++ b/service/lib/agama/storage/configs/format.rb @@ -23,7 +23,7 @@ module Agama module Storage module Configs class Format - # btrfs options like snapshots, subvols? + # @return [Configs::Filesystem] attr_accessor :filesystem attr_accessor :label attr_accessor :mkfs_options From a19df4ea60e37cdbf808316cd49c7d9803d1201b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Thu, 25 Jul 2024 12:00:50 +0100 Subject: [PATCH 05/21] Fix documentation types --- .../storage/config_conversions/partitionable/from_json.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/lib/agama/storage/config_conversions/partitionable/from_json.rb b/service/lib/agama/storage/config_conversions/partitionable/from_json.rb index 3b9d20ab9c..fd6141b249 100644 --- a/service/lib/agama/storage/config_conversions/partitionable/from_json.rb +++ b/service/lib/agama/storage/config_conversions/partitionable/from_json.rb @@ -67,7 +67,7 @@ def convert_ptable_type Y2Storage::PartitionTables::Type.find(value) end - # @return [Array] + # @return [Array] def convert_partitions partitions_json = partitionable_json[:partitions] return [] unless partitions_json @@ -76,7 +76,7 @@ def convert_partitions end # @param partition_json [Hash] - # @return [Settings::Partition] + # @return [Configs::Partition] def convert_partition(partition_json) Partition::FromJSON.new(partition_json, settings: settings, volume_builder: volume_builder).convert From 214d4bf08151374f4ac2f189c87a3b2a866ee670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Thu, 25 Jul 2024 16:33:09 +0100 Subject: [PATCH 06/21] Add note --- .../storage/config_conversions/from_json.rb | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/service/lib/agama/storage/config_conversions/from_json.rb b/service/lib/agama/storage/config_conversions/from_json.rb index ec8cbad386..9625798616 100644 --- a/service/lib/agama/storage/config_conversions/from_json.rb +++ b/service/lib/agama/storage/config_conversions/from_json.rb @@ -28,6 +28,27 @@ module Agama module Storage module ConfigConversions # Config conversion from JSON hash according to schema. + # + # TODO: The approach for generating a Config from JSON could be improved: + # * All the FromJSON classes receive only a JSON and an optional default config to start + # converting from it. + # * There should be a "config generator" class which knows the ProductDefinition and creates + # config objects calling to the proper FromJSON classes, passing the default config for + # each case (drive, partition, etc). + # + # For example: + # + # def generate_drive(drive_json) + # default = default_drive(drive_json.dig(:filesystem, :path)) + # drive = Drive::FromJson.new(drive_json).convert(default) + # drive.partitions = drive_json[:partitions].map do |partition_json| + # default = default_partition(partition_json.dig(:fileystem, :path)) + # Partition::FromJSON.new(partition_json).convert(default) + # end + # drive + # end + # + # This improvement could be done at the time of introducing the ProductDefinition class. class FromJSON # @todo Replace product_config param by a ProductDefinition. # From 46d64394a2226f1df30489fa9b80265d103657d3 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 29 Jul 2024 15:43:31 +0200 Subject: [PATCH 07/21] Adapt classes to new agreements on config format --- .../block_device/from_json.rb | 64 ++++++--------- .../{encrypt.rb => encryption.rb} | 6 +- .../{encrypt => encryption}/from_json.rb | 26 +++--- .../filesystem/from_json.rb | 40 ++++----- .../{mount.rb => filesystem_type.rb} | 6 +- .../filesystem_type/from_json.rb | 81 +++++++++++++++++++ .../storage/config_conversions/format.rb | 32 -------- .../config_conversions/format/from_json.rb | 67 --------------- .../config_conversions/mount/from_json.rb | 53 ------------ service/lib/agama/storage/configs.rb | 5 +- service/lib/agama/storage/configs/drive.rb | 12 +-- .../configs/{encrypt.rb => encryption.rb} | 2 +- .../lib/agama/storage/configs/filesystem.rb | 13 ++- .../configs/{mount.rb => filesystem_type.rb} | 12 ++- service/lib/agama/storage/configs/format.rb | 33 -------- .../lib/agama/storage/configs/partition.rb | 5 +- .../config_conversions/from_json_test.rb | 21 +++-- 17 files changed, 180 insertions(+), 298 deletions(-) rename service/lib/agama/storage/config_conversions/{encrypt.rb => encryption.rb} (87%) rename service/lib/agama/storage/config_conversions/{encrypt => encryption}/from_json.rb (75%) rename service/lib/agama/storage/config_conversions/{mount.rb => filesystem_type.rb} (86%) create mode 100644 service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb delete mode 100644 service/lib/agama/storage/config_conversions/format.rb delete mode 100644 service/lib/agama/storage/config_conversions/format/from_json.rb delete mode 100644 service/lib/agama/storage/config_conversions/mount/from_json.rb rename service/lib/agama/storage/configs/{encrypt.rb => encryption.rb} (97%) rename service/lib/agama/storage/configs/{mount.rb => filesystem_type.rb} (83%) delete mode 100644 service/lib/agama/storage/configs/format.rb diff --git a/service/lib/agama/storage/config_conversions/block_device/from_json.rb b/service/lib/agama/storage/config_conversions/block_device/from_json.rb index 25801dae9c..2a40ec59ea 100644 --- a/service/lib/agama/storage/config_conversions/block_device/from_json.rb +++ b/service/lib/agama/storage/config_conversions/block_device/from_json.rb @@ -19,13 +19,12 @@ # 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_conversions/encrypt/from_json" -require "agama/storage/config_conversions/format/from_json" -require "agama/storage/config_conversions/mount/from_json" -require "agama/storage/configs/encrypt" +require "agama/storage/config_conversions/encryption/from_json" +require "agama/storage/config_conversions/filesystem/from_json" +require "agama/storage/config_conversions/filesystem_type/from_json" +require "agama/storage/configs/encryption" require "agama/storage/configs/filesystem" -require "agama/storage/configs/format" -require "agama/storage/configs/mount" +require "agama/storage/configs/filesystem_type" module Agama module Storage @@ -48,9 +47,8 @@ def initialize(blk_device_json, settings:, volume_builder:) # # @param config [#encrypt=, #format=, #mount=] def convert(config) - config.encrypt = convert_encrypt - config.format = convert_format - config.mount = convert_mount + config.encryption = convert_encrypt + config.filesystem = convert_filesystem config end @@ -67,44 +65,34 @@ def convert(config) # @return [Configs::Encrypt, nil] def convert_encrypt - encrypt_json = blk_device_json[:encrypt] + encrypt_json = blk_device_json[:encryption] return unless encrypt_json - Encrypt::FromJSON.new(encrypt_json, default: default_encrypt_config).convert + Encryption::FromJSON.new(encrypt_json, default: default_encrypt_config).convert end - # @return [Configs::Format, nil] - def convert_format - format_json = blk_device_json[:format] - mount_json = blk_device_json[:mount] + # @return [Configs::Filesystem, nil] + def convert_filesystem + filesystem_json = blk_device_json[:filesystem] - return if format_json == false # "format": false - return if format_json.nil? && mount_json.nil? + return if filesystem_json == false # "filesystem": false + return if filesystem_json.nil? - default = default_format_config(mount_json&.dig(:path) || "") - return default unless format_json + default = default_filesystem_config(filesystem_json&.dig(:path) || "") # @todo Check whether the given filesystem can be used for the mount point. # @todo Check whether snapshots can be configured and restore to default if needed. - Format::FromJSON.new(format_json).convert(default) - end - - # @return [Configs::Mount, nil] - def convert_mount - mount_json = blk_device_json[:mount] - return unless mount_json - - Mount::FromJSON.new(mount_json).convert + Filesystem::FromJSON.new(filesystem_json).convert(default) end # @todo Recover values from ProductDefinition instead of ProposalSettings. # # Default encryption config from the product definition. # - # @return [Configs::Encrypt] + # @return [Configs::Encryption] def default_encrypt_config - Configs::Encrypt.new.tap do |config| + Configs::Encryption.new.tap do |config| config.key = settings.encryption.password config.method = settings.encryption.method config.pbkd_function = settings.encryption.pbkd_function @@ -114,10 +102,10 @@ def default_encrypt_config # Default format config from the product definition. # # @param mount_path [String] - # @return [Configs::Format] - def default_format_config(mount_path) - Configs::Format.new.tap do |config| - config.filesystem = default_filesystem_config(mount_path) + # @return [Configs::Filesystem] + def default_filesystem_config(mount_path) + Configs::Filesystem.new.tap do |config| + config.type = default_fstype_config(mount_path) end end @@ -126,12 +114,12 @@ def default_format_config(mount_path) # Default filesystem config from the product definition. # # @param mount_path [String] - # @return [Configs::Filesystem] - def default_filesystem_config(mount_path) + # @return [Configs::FilesystemType] + def default_fstype_config(mount_path) volume = volume_builder.for(mount_path) - Configs::Filesystem.new.tap do |config| - config.type = volume.fs_type + Configs::FilesystemType.new.tap do |config| + config.fstype = volume.fs_type config.btrfs = volume.btrfs end end diff --git a/service/lib/agama/storage/config_conversions/encrypt.rb b/service/lib/agama/storage/config_conversions/encryption.rb similarity index 87% rename from service/lib/agama/storage/config_conversions/encrypt.rb rename to service/lib/agama/storage/config_conversions/encryption.rb index 185894e084..288818bb96 100644 --- a/service/lib/agama/storage/config_conversions/encrypt.rb +++ b/service/lib/agama/storage/config_conversions/encryption.rb @@ -19,13 +19,13 @@ # 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_conversions/encrypt/from_json" +require "agama/storage/config_conversions/encryption/from_json" module Agama module Storage module ConfigConversions - # Conversions for encrypt. - module Encrypt + # Conversions for encryption. + module Encryption end end end diff --git a/service/lib/agama/storage/config_conversions/encrypt/from_json.rb b/service/lib/agama/storage/config_conversions/encryption/from_json.rb similarity index 75% rename from service/lib/agama/storage/config_conversions/encrypt/from_json.rb rename to service/lib/agama/storage/config_conversions/encryption/from_json.rb index a01304a20d..0dd7af467c 100644 --- a/service/lib/agama/storage/config_conversions/encrypt/from_json.rb +++ b/service/lib/agama/storage/config_conversions/encryption/from_json.rb @@ -19,26 +19,26 @@ # 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/encrypt" +require "agama/storage/configs/encryption" require "y2storage/encryption_method" require "y2storage/pbkd_function" module Agama module Storage module ConfigConversions - module Encrypt - # Encrypt conversion from JSON hash according to schema. + module Encryption + # Encryption conversion from JSON hash according to schema. class FromJSON - # @param encrypt_json [Hash] + # @param encryption_json [Hash] # @param default [Configs::Encrypt] - def initialize(encrypt_json, default: nil) - @encrypt_json = encrypt_json - @default_config = default || Configs::Encrypt.new + def initialize(encryption_json, default: nil) + @encryption_json = encryption_json + @default_config = default || Configs::Encryption.new end # Performs the conversion from Hash according to the JSON schema. # - # @return [Configs::Encrypt] + # @return [Configs::Encryption] def convert default_config.dup.tap do |config| key = convert_key @@ -54,19 +54,19 @@ def convert private # @return [Hash] - attr_reader :encrypt_json + attr_reader :encryption_json - # @return [Configs::Encrypt] + # @return [Configs::Encryption] attr_reader :default_config # @return [String, nil] def convert_key - encrypt_json[:password] + encryption_json[:password] end # @return [Y2Storage::EncryptionMethod, nil] def convert_method - value = encrypt_json[:method] + value = encryption_json[:method] return unless value Y2Storage::EncryptionMethod.find(value.to_sym) @@ -74,7 +74,7 @@ def convert_method # @return [Y2Storage::PbkdFunction, nil] def convert_pbkd_function - Y2Storage::PbkdFunction.find(encrypt_json[:pbkdFunction]) + Y2Storage::PbkdFunction.find(encryption_json[:pbkdFunction]) end end end diff --git a/service/lib/agama/storage/config_conversions/filesystem/from_json.rb b/service/lib/agama/storage/config_conversions/filesystem/from_json.rb index 2045e39bc8..ac637f2434 100644 --- a/service/lib/agama/storage/config_conversions/filesystem/from_json.rb +++ b/service/lib/agama/storage/config_conversions/filesystem/from_json.rb @@ -19,9 +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/btrfs" +require "agama/storage/config_conversions/filesystem_type/from_json" require "agama/storage/configs/filesystem" -require "y2storage/filesystems/type" module Agama module Storage @@ -29,7 +28,7 @@ module ConfigConversions module Filesystem # Filesystem conversion from JSON hash according to schema. class FromJSON - # @param filesystem_json [Hash, String] + # @param filesystem_json [Hash] def initialize(filesystem_json) @filesystem_json = filesystem_json end @@ -37,15 +36,19 @@ def initialize(filesystem_json) # Performs the conversion from Hash according to the JSON schema. # # @param default [Configs::Filesystem, nil] - # @return [Configs::Filesystem] + # @return [Configs::Format] def convert(default = nil) default_config = default.dup || Configs::Filesystem.new default_config.tap do |config| - btrfs = convert_btrfs(config.btrfs) + label = filesystem_json[:label] + mkfs_options = filesystem_json[:mkfsOptions] - config.type = convert_type - config.btrfs = btrfs if btrfs + config.path = filesystem_json[:path] + config.mount_options = filesystem_json[:mountOptions] || [] + config.type = convert_type(config.type) + config.label = label if label + config.mkfs_options = mkfs_options if mkfs_options end end @@ -54,25 +57,10 @@ def convert(default = nil) # @return [Hash] attr_reader :filesystem_json - # @return [Y2Storage::Filesystems::Type] - def convert_type - value = filesystem_json.is_a?(String) ? filesystem_json : "btrfs" - Y2Storage::Filesystems::Type.find(value.to_sym) - end - - # @param default [Configs::Btrfs] - # @return [Configs::Btrfs, nil] - def convert_btrfs(default = nil) - return nil if filesystem_json.is_a?(String) - - btrfs_json = filesystem_json[:btrfs] - default_config = default.dup || Configs::Btrfs.new - - default_config.tap do |config| - snapshots = btrfs_json[:snapshots] - - config.snapshots = snapshots if snapshots - end + # @param default [Configs::Filesystem, nil] + # @return [Configs::FilesystemType] + def convert_type(default = nil) + FilesystemType::FromJSON.new(filesystem_json[:type]).convert(default) end end end diff --git a/service/lib/agama/storage/config_conversions/mount.rb b/service/lib/agama/storage/config_conversions/filesystem_type.rb similarity index 86% rename from service/lib/agama/storage/config_conversions/mount.rb rename to service/lib/agama/storage/config_conversions/filesystem_type.rb index cd5ff6dec8..ace22105b4 100644 --- a/service/lib/agama/storage/config_conversions/mount.rb +++ b/service/lib/agama/storage/config_conversions/filesystem_type.rb @@ -19,13 +19,13 @@ # 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_conversions/mount/from_json" +require "agama/storage/config_conversions/filesystem_type/from_json" module Agama module Storage module ConfigConversions - # Conversions for mount. - module Mount + # Conversions for filesystem types + module FilesystemType end end end diff --git a/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb b/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb new file mode 100644 index 0000000000..c3406a9972 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] 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/configs/btrfs" +require "agama/storage/configs/filesystem_type" +require "y2storage/filesystems/type" + +module Agama + module Storage + module ConfigConversions + module FilesystemType + # Filesystem conversion from JSON hash according to schema. + class FromJSON + # @param filesystem_json [Hash, String] + def initialize(filesystem_json) + @filesystem_json = filesystem_json + end + + # Performs the conversion from Hash according to the JSON schema. + # + # @param default [Configs::Filesystem, nil] + # @return [Configs::Filesystem] + def convert(default = nil) + default_config = default.dup || Configs::FilesystemType.new + + default_config.tap do |config| + btrfs = convert_btrfs(config.btrfs) + + config.fstype = convert_type + config.btrfs = btrfs if btrfs + end + end + + private + + # @return [Hash] + attr_reader :filesystem_json + + # @return [Y2Storage::Filesystems::Type] + def convert_type + value = filesystem_json.is_a?(String) ? filesystem_json : "btrfs" + Y2Storage::Filesystems::Type.find(value.to_sym) + end + + # @param default [Configs::Btrfs] + # @return [Configs::Btrfs, nil] + def convert_btrfs(default = nil) + return nil if filesystem_json.is_a?(String) + + btrfs_json = filesystem_json[:btrfs] + default_config = default.dup || Configs::Btrfs.new + + default_config.tap do |config| + snapshots = btrfs_json[:snapshots] + + config.snapshots = snapshots if snapshots + end + end + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/format.rb b/service/lib/agama/storage/config_conversions/format.rb deleted file mode 100644 index f65f978a5b..0000000000 --- a/service/lib/agama/storage/config_conversions/format.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -# Copyright (c) [2024] 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_conversions/format/from_json" - -module Agama - module Storage - module ConfigConversions - # Conversions for format. - module Format - end - end - end -end diff --git a/service/lib/agama/storage/config_conversions/format/from_json.rb b/service/lib/agama/storage/config_conversions/format/from_json.rb deleted file mode 100644 index 2f49d8ec82..0000000000 --- a/service/lib/agama/storage/config_conversions/format/from_json.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -# Copyright (c) [2024] 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_conversions/filesystem/from_json" -require "agama/storage/configs/format" - -module Agama - module Storage - module ConfigConversions - module Format - # Format conversion from JSON hash according to schema. - class FromJSON - # @param format_json [Hash] - def initialize(format_json) - @format_json = format_json - end - - # Performs the conversion from Hash according to the JSON schema. - # - # @param default [Configs::Format, nil] - # @return [Configs::Format] - def convert(default = nil) - default_config = default.dup || Configs::Format.new - - default_config.tap do |config| - label = format_json[:label] - mkfs_options = format_json[:mkfsOptions] - - config.filesystem = convert_filesystem(config.filesystem) - config.label = label if label - config.mkfs_options = mkfs_options if mkfs_options - end - end - - private - - # @return [Hash] - attr_reader :format_json - - # @param default [Configs::Filesystem, nil] - # @return [Configs::Filesystem] - def convert_filesystem(default = nil) - Filesystem::FromJSON.new(format_json[:filesystem]).convert(default) - end - end - end - end - end -end diff --git a/service/lib/agama/storage/config_conversions/mount/from_json.rb b/service/lib/agama/storage/config_conversions/mount/from_json.rb deleted file mode 100644 index a095b6bed3..0000000000 --- a/service/lib/agama/storage/config_conversions/mount/from_json.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -# Copyright (c) [2024] 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/configs/mount" - -module Agama - module Storage - module ConfigConversions - module Mount - # Mount conversion from JSON hash according to schema. - class FromJSON - # @param mount_json [Hash] - def initialize(mount_json) - @mount_json = mount_json - end - - # Performs the conversion from Hash according to the JSON schema. - # - # @return [Configs::Mount] - def convert - Configs::Mount.new.tap do |config| - config.path = mount_json[:path] - config.options = mount_json[:options] || [] - end - end - - private - - # @return [Hash] - attr_reader :mount_json - end - end - end - end -end diff --git a/service/lib/agama/storage/configs.rb b/service/lib/agama/storage/configs.rb index 8b6ed6755a..7f276374c0 100644 --- a/service/lib/agama/storage/configs.rb +++ b/service/lib/agama/storage/configs.rb @@ -30,10 +30,9 @@ module Configs require "agama/storage/configs/boot" require "agama/storage/configs/btrfs" require "agama/storage/configs/drive" -require "agama/storage/configs/encrypt" +require "agama/storage/configs/encryption" require "agama/storage/configs/filesystem" -require "agama/storage/configs/format" -require "agama/storage/configs/mount" +require "agama/storage/configs/filesystem_type" require "agama/storage/configs/partition" require "agama/storage/configs/search" require "agama/storage/configs/size" diff --git a/service/lib/agama/storage/configs/drive.rb b/service/lib/agama/storage/configs/drive.rb index 214bc7791e..4946d24fd2 100644 --- a/service/lib/agama/storage/configs/drive.rb +++ b/service/lib/agama/storage/configs/drive.rb @@ -27,15 +27,15 @@ module Configs class Drive attr_accessor :search - # @return [Encrypt] - attr_accessor :encrypt + # @return [Encryption] + attr_accessor :encryption - # @return [Format] - attr_accessor :format + # @return [Filesystem] + attr_accessor :filesystem - # @return [Mount] - attr_accessor :mount attr_accessor :ptable_type + + # @return [Array] attr_accessor :partitions # @param mount_path [String] diff --git a/service/lib/agama/storage/configs/encrypt.rb b/service/lib/agama/storage/configs/encryption.rb similarity index 97% rename from service/lib/agama/storage/configs/encrypt.rb rename to service/lib/agama/storage/configs/encryption.rb index 2062eda0d3..68ae84c5b5 100644 --- a/service/lib/agama/storage/configs/encrypt.rb +++ b/service/lib/agama/storage/configs/encryption.rb @@ -22,7 +22,7 @@ module Agama module Storage module Configs - class Encrypt + class Encryption attr_accessor :method attr_accessor :key attr_accessor :pbkd_function diff --git a/service/lib/agama/storage/configs/filesystem.rb b/service/lib/agama/storage/configs/filesystem.rb index 7843e239b4..ced9ad1a43 100644 --- a/service/lib/agama/storage/configs/filesystem.rb +++ b/service/lib/agama/storage/configs/filesystem.rb @@ -23,11 +23,18 @@ module Agama module Storage module Configs class Filesystem - # @return [Y2Storage::Filesystems::Type, nil] + attr_accessor :path + # @return [Configs::FilesystemType] attr_accessor :type + attr_accessor :label + attr_accessor :mkfs_options + attr_accessor :mount_options + attr_accessor :mount_by - # @return [Configs::Btrfs, nil] - attr_accessor :btrfs + def initialize + @mount_options = [] + @mkfs = [] + end end end end diff --git a/service/lib/agama/storage/configs/mount.rb b/service/lib/agama/storage/configs/filesystem_type.rb similarity index 83% rename from service/lib/agama/storage/configs/mount.rb rename to service/lib/agama/storage/configs/filesystem_type.rb index 9b3be11224..c82efff4b4 100644 --- a/service/lib/agama/storage/configs/mount.rb +++ b/service/lib/agama/storage/configs/filesystem_type.rb @@ -22,14 +22,12 @@ module Agama module Storage module Configs - class Mount - attr_accessor :path - attr_accessor :options - attr_accessor :mount_by + class FilesystemType + # @return [Y2Storage::Filesystems::Type] + attr_accessor :fstype - def initialize - @options = [] - end + # @return [Configs::Btrfs, nil] + attr_accessor :btrfs end end end diff --git a/service/lib/agama/storage/configs/format.rb b/service/lib/agama/storage/configs/format.rb deleted file mode 100644 index 4927ee70ee..0000000000 --- a/service/lib/agama/storage/configs/format.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -# Copyright (c) [2024] 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 - class Format - # @return [Configs::Filesystem] - attr_accessor :filesystem - attr_accessor :label - attr_accessor :mkfs_options - end - end - end -end diff --git a/service/lib/agama/storage/configs/partition.rb b/service/lib/agama/storage/configs/partition.rb index 42fb8b7c7d..1d58d4b3d6 100644 --- a/service/lib/agama/storage/configs/partition.rb +++ b/service/lib/agama/storage/configs/partition.rb @@ -26,9 +26,8 @@ class Partition attr_accessor :search attr_accessor :id attr_accessor :size - attr_accessor :encrypt - attr_accessor :format - attr_accessor :mount + attr_accessor :encryption + attr_accessor :filesystem def search_device(devicegraph, parent_sid, used_sids) @search ||= default_search diff --git a/service/test/agama/storage/config_conversions/from_json_test.rb b/service/test/agama/storage/config_conversions/from_json_test.rb index c45c1e8ccc..66a6e3f007 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -42,12 +42,22 @@ "volumes" => ["/", "swap"], "volume_templates" => [ { - "mount_path" => "/", - "outline" => { "required" => true } + "mount_path" => "/", "filesystem" => "btrfs", "size" => { "auto" => true }, + "outline" => { + "required" => true, "snapshots_configurable" => true, + "auto_size" => { + "base_min" => "5 GiB", "base_max" => "10 GiB", + "min_fallback_for" => ["/home"], "max_fallback_for" => ["/home"], + "snapshots_increment" => "300%" + } + } }, { "mount_path" => "/home", - "outline" => { "required" => false } + "outline" => { + "required" => false, "filesystem" => "xfs", + "size" => { "auto" => false, "min" => "5 GiB" } + } }, { "mount_path" => "swap", @@ -69,10 +79,7 @@ { ptableType: "gpt", partitions: [ - { - format: { filesystem: "ext4" }, - mount: { path: "/" } - } + { filesystem: { path: "/", type: "ext4" } } ] } ] From b63a0fbb2ed54b0dbb2c655bdab8130bbc87987e Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 30 Jul 2024 16:09:07 +0200 Subject: [PATCH 08/21] Calculate default size --- service/lib/agama/storage/config.rb | 71 +++++++++++++++++++ .../filesystem_type/from_json.rb | 7 +- .../storage/config_conversions/from_json.rb | 1 + .../config_conversions/partition/from_json.rb | 21 +----- .../config_conversions/size/from_json.rb | 8 ++- .../lib/agama/storage/configs/filesystem.rb | 6 ++ service/lib/agama/storage/configs/size.rb | 9 +++ 7 files changed, 101 insertions(+), 22 deletions(-) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index 4f1a83ffc8..cf1b6df89c 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -77,6 +77,77 @@ def implicit_boot_device root_drive&.found_device.name end + + def calculate_default_sizes(volume_builder) + default_size_devices.each do |dev| + dev.size.min = default_size(dev, :min, volume_builder) + dev.size.max = default_size(dev, :max, volume_builder) + end + end + + private + + def filesystems + (drives + partitions).map(&:filesystem).compact + end + + def partitions + drives.flat_map(&:partitions) + end + + def default_size_devices + partitions.select { |p| p.size&.default? } + end + + def default_size(device, attr, builder) + # TODO: what to do if path is nil or empty? + path = device.filesystem&.path + # TODO: what to do if there is no default volume? + vol = builder.for(path) + + return vol.send(:"#{attr}_size") unless vol.auto_size? + + outline = vol.outline + size = size_with_fallbacks(device, outline, attr, builder) + size = size_with_ram(size, outline) + size_with_snapshots(size, device, outline) + end + + def size_with_fallbacks(device, outline, attr, builder) + proposed_paths = filesystems.map(&:path) + + size = outline.send(:"base_#{attr}_size") + + fallback_paths = outline.send(:"#{attr}_size_fallback_for") + # TODO: we need to normalize all the paths (or use Path for comparison or whatever) + missing_paths = fallback_paths - proposed_paths + missing_paths.inject(size) { |total, p| total + builder.for(p).send(:"#{attr}_size") } + end + + def size_with_ram(initial_size, outline) + return initial_size unless outline.adjust_by_ram? + + [initial_size, ram_size].max + end + + def size_with_snapshots(initial_size, device, outline) + return initial_size unless device.filesystem.btrfs_snapshots? + return initial_size unless outline.snapshots_affect_sizes? + + if outline.snapshots_size && outline.snapshots_size > DiskSize.zero + initial_size + outline.snapshots_size + else + multiplicator = 1.0 + (outline.snapshots_percentage / 100.0) + initial_size * multiplicator + end + end + + # Return the total amount of RAM as DiskSize + # + # @return [DiskSize] current RAM size + def ram_size + @ram_size ||= Y2Storage::DiskSize.new(Y2Storage::StorageManager.instance.arch.ram_size) + end end end end diff --git a/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb b/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb index c3406a9972..8d0d4e7cc0 100644 --- a/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb +++ b/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb @@ -29,7 +29,7 @@ module ConfigConversions module FilesystemType # Filesystem conversion from JSON hash according to schema. class FromJSON - # @param filesystem_json [Hash, String] + # @param filesystem_json [Hash, String, nil] def initialize(filesystem_json) @filesystem_json = filesystem_json end @@ -43,8 +43,9 @@ def convert(default = nil) default_config.tap do |config| btrfs = convert_btrfs(config.btrfs) + type = convert_type - config.fstype = convert_type + config.fstype = type if type config.btrfs = btrfs if btrfs end end @@ -63,7 +64,7 @@ def convert_type # @param default [Configs::Btrfs] # @return [Configs::Btrfs, nil] def convert_btrfs(default = nil) - return nil if filesystem_json.is_a?(String) + return nil if filesystem_json.nil? || filesystem_json.is_a?(String) btrfs_json = filesystem_json[:btrfs] default_config = default.dup || Configs::Btrfs.new diff --git a/service/lib/agama/storage/config_conversions/from_json.rb b/service/lib/agama/storage/config_conversions/from_json.rb index 9625798616..8fe8b0abfa 100644 --- a/service/lib/agama/storage/config_conversions/from_json.rb +++ b/service/lib/agama/storage/config_conversions/from_json.rb @@ -70,6 +70,7 @@ def convert config.boot = boot if boot config.drives = drives if drives + config.calculate_default_sizes(volume_builder) end end diff --git a/service/lib/agama/storage/config_conversions/partition/from_json.rb b/service/lib/agama/storage/config_conversions/partition/from_json.rb index 748701fed4..ec92e3de63 100644 --- a/service/lib/agama/storage/config_conversions/partition/from_json.rb +++ b/service/lib/agama/storage/config_conversions/partition/from_json.rb @@ -65,8 +65,7 @@ def convert # @return [Y2Storage::PartitionId, nil] def convert_id - # @todo Decide whether to use "create" in JSON schema. - value = partition_json.dig(:create, :id) + value = partition_json.dig(:id) return unless value Y2Storage::PartitionId.find(value) @@ -74,9 +73,8 @@ def convert_id # @return [Configs::Size] def convert_size - # @todo Decide whether to use "create" in JSON schema. - size_json = partition_json.dig(:create, :size) - return default_size_config unless size_json + size_json = partition_json.dig(:size) + return Configs::Size.new unless size_json Size::FromJSON.new(size_json).convert end @@ -88,19 +86,6 @@ def convert_block_device(config) converter.convert(config) end - - # @todo Auto size? - # - # @return [Configs::Size] - def default_size_config - mount_path = partition_json.dig(:mount, :path) - volume = volume_builder.for(mount_path || "") - - Configs::Size.new.tap do |config| - config.min = volume.min_size - config.max = volume.max_size - end - end end end end diff --git a/service/lib/agama/storage/config_conversions/size/from_json.rb b/service/lib/agama/storage/config_conversions/size/from_json.rb index 4172341401..c41a79bc3a 100644 --- a/service/lib/agama/storage/config_conversions/size/from_json.rb +++ b/service/lib/agama/storage/config_conversions/size/from_json.rb @@ -33,7 +33,6 @@ def initialize(size_json) @size_json = size_json end - # @todo Add support for auto. # @todo For now only {min: number, max: number} schema is supported. Add support for a # direct value (e.g., 1024, "2 GiB"), and array format ([min, max]). # @@ -41,7 +40,10 @@ def initialize(size_json) # # @return [Configs::Size] def convert + return default_size if size_json.is_a?(String) && size_json.casecmp?("default") + Configs::Size.new.tap do |config| + config.default = false config.min = convert_min config.max = convert_max || Y2Storage::DiskSize.unlimited end @@ -64,6 +66,10 @@ def convert_max Y2Storage::DiskSize.new(value) end + + def default_size + Configs::Size.new.tap { |c| c.default = true } + end end end end diff --git a/service/lib/agama/storage/configs/filesystem.rb b/service/lib/agama/storage/configs/filesystem.rb index ced9ad1a43..3455eef8b2 100644 --- a/service/lib/agama/storage/configs/filesystem.rb +++ b/service/lib/agama/storage/configs/filesystem.rb @@ -35,6 +35,12 @@ def initialize @mount_options = [] @mkfs = [] end + + def btrfs_snapshots? + return false unless type&.fstype&.is?(:btrfs) + + type.btrfs&.snapshots? + end end end end diff --git a/service/lib/agama/storage/configs/size.rb b/service/lib/agama/storage/configs/size.rb index 49e2a5507b..82399c90c6 100644 --- a/service/lib/agama/storage/configs/size.rb +++ b/service/lib/agama/storage/configs/size.rb @@ -23,8 +23,17 @@ module Agama module Storage module Configs class Size + attr_accessor :default attr_accessor :min attr_accessor :max + + def initialize + @default = true + end + + def default? + !!@default + end end end end From 00578e8a8eb97a0930fccf34ebdf5ce176878566 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 30 Jul 2024 17:17:33 +0200 Subject: [PATCH 09/21] More tests and corresponding fixes --- service/lib/agama/storage/config.rb | 17 +- .../block_device/from_json.rb | 2 +- .../encryption/from_json.rb | 18 +- .../filesystem_type/from_json.rb | 8 +- .../lib/agama/storage/configs/filesystem.rb | 2 +- .../agama/storage/configs/filesystem_type.rb | 2 +- .../proposal/agama_device_planner.rb | 28 +- .../config_conversions/from_json_test.rb | 363 ++++++++++++++++-- service/test/y2storage/agama_proposal_test.rb | 19 +- 9 files changed, 402 insertions(+), 57 deletions(-) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index cf1b6df89c..9ccd3c6fdf 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -72,7 +72,7 @@ def explicit_boot_device def implicit_boot_device # TODO: preliminary implementation with very simplistic checks root_drive = drives.find do |drive| - drive.partitions.any? { |p| p.mount&.path == "/" } + drive.partitions.any? { |p| p.filesystem&.path == "/" } end root_drive&.found_device.name @@ -100,11 +100,12 @@ def default_size_devices end def default_size(device, attr, builder) - # TODO: what to do if path is nil or empty? - path = device.filesystem&.path - # TODO: what to do if there is no default volume? + path = device.filesystem&.path || "" vol = builder.for(path) + return fallback_size(attr) unless vol + # Theoretically, neither Volume#min_size or Volume#max_size can be nil + # At most they will be zero or unlimited, respectively return vol.send(:"#{attr}_size") unless vol.auto_size? outline = vol.outline @@ -113,6 +114,14 @@ def default_size(device, attr, builder) size_with_snapshots(size, device, outline) end + # TODO: these are the fallbacks used when constructing volumes, not sure if repeating them + # here is right + def fallback_size(attr) + return Y2Storage::DiskSize.zero if attr == :min + + Y2Storage::DiskSize.unlimited + end + def size_with_fallbacks(device, outline, attr, builder) proposed_paths = filesystems.map(&:path) diff --git a/service/lib/agama/storage/config_conversions/block_device/from_json.rb b/service/lib/agama/storage/config_conversions/block_device/from_json.rb index 2a40ec59ea..efd287b36a 100644 --- a/service/lib/agama/storage/config_conversions/block_device/from_json.rb +++ b/service/lib/agama/storage/config_conversions/block_device/from_json.rb @@ -119,7 +119,7 @@ def default_fstype_config(mount_path) volume = volume_builder.for(mount_path) Configs::FilesystemType.new.tap do |config| - config.fstype = volume.fs_type + config.fs_type = volume.fs_type config.btrfs = volume.btrfs end end diff --git a/service/lib/agama/storage/config_conversions/encryption/from_json.rb b/service/lib/agama/storage/config_conversions/encryption/from_json.rb index 0dd7af467c..cc62fa5713 100644 --- a/service/lib/agama/storage/config_conversions/encryption/from_json.rb +++ b/service/lib/agama/storage/config_conversions/encryption/from_json.rb @@ -44,10 +44,14 @@ def convert key = convert_key method = convert_method pbkdf = convert_pbkd_function + key_size = convert_key_size + cipher = convert_cipher config.key = key if key config.method = method if method config.pbkd_function = pbkdf if pbkdf + config.key_size = key_size if key_size + config.cipher = cipher if cipher end end @@ -61,7 +65,8 @@ def convert # @return [String, nil] def convert_key - encryption_json[:password] + # TODO: this is set as "key" in the schema, but it looks like a bad name + encryption_json[:key] end # @return [Y2Storage::EncryptionMethod, nil] @@ -76,6 +81,17 @@ def convert_method def convert_pbkd_function Y2Storage::PbkdFunction.find(encryption_json[:pbkdFunction]) end + + # @return [Integer, nil] + def convert_key_size + # FIXME: this is using camel_case in the schema definition, looks wrong + encryption_json[:keySize] + end + + # @return [String, nil] + def convert_cipher + encryption_json[:cipher] + end end end end diff --git a/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb b/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb index 8d0d4e7cc0..5df77eb2d9 100644 --- a/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb +++ b/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb @@ -45,7 +45,7 @@ def convert(default = nil) btrfs = convert_btrfs(config.btrfs) type = convert_type - config.fstype = type if type + config.fs_type = type if type config.btrfs = btrfs if btrfs end end @@ -57,6 +57,8 @@ def convert(default = nil) # @return [Y2Storage::Filesystems::Type] def convert_type + return if filesystem_json.nil? + value = filesystem_json.is_a?(String) ? filesystem_json : "btrfs" Y2Storage::Filesystems::Type.find(value.to_sym) end @@ -64,7 +66,7 @@ def convert_type # @param default [Configs::Btrfs] # @return [Configs::Btrfs, nil] def convert_btrfs(default = nil) - return nil if filesystem_json.nil? || filesystem_json.is_a?(String) + return if filesystem_json.nil? || filesystem_json.is_a?(String) btrfs_json = filesystem_json[:btrfs] default_config = default.dup || Configs::Btrfs.new @@ -72,7 +74,7 @@ def convert_btrfs(default = nil) default_config.tap do |config| snapshots = btrfs_json[:snapshots] - config.snapshots = snapshots if snapshots + config.snapshots = snapshots unless snapshots.nil? end end end diff --git a/service/lib/agama/storage/configs/filesystem.rb b/service/lib/agama/storage/configs/filesystem.rb index 3455eef8b2..3f5dec5fae 100644 --- a/service/lib/agama/storage/configs/filesystem.rb +++ b/service/lib/agama/storage/configs/filesystem.rb @@ -37,7 +37,7 @@ def initialize end def btrfs_snapshots? - return false unless type&.fstype&.is?(:btrfs) + return false unless type&.fs_type&.is?(:btrfs) type.btrfs&.snapshots? end diff --git a/service/lib/agama/storage/configs/filesystem_type.rb b/service/lib/agama/storage/configs/filesystem_type.rb index c82efff4b4..6895f4e26e 100644 --- a/service/lib/agama/storage/configs/filesystem_type.rb +++ b/service/lib/agama/storage/configs/filesystem_type.rb @@ -24,7 +24,7 @@ module Storage module Configs class FilesystemType # @return [Y2Storage::Filesystems::Type] - attr_accessor :fstype + attr_accessor :fs_type # @return [Configs::Btrfs, nil] attr_accessor :btrfs diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index b96c800fdd..39dfa13dce 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -50,22 +50,26 @@ def planned_devices(_setting) # @param settings [#format, #mount] def configure_device(planned, settings) # TODO configure_encrypt - configure_format(planned, settings.format) if settings.format - configure_mount(planned, settings.mount) if settings.mount + configure_filesystem(planned, settings.filesystem) if settings.filesystem end # @param planned [Planned::Disk, Planned::Partition] # @param settings [Agama::Storage::Settings::Format] - def configure_format(planned, settings) - planned.label = settings.label + def configure_filesystem(planned, settings) + planned.mount_point = settings.path + planned.mount_by = settings.mount_by + planned.fstab_options = settings.mount_options planned.mkfs_options = settings.mkfs_options - configure_filesystem(planned, settings.filesystem) if settings.filesystem + # FIXME: Is this needed? Or #mount_options is enough? + # planned.read_only = settings.read_only? + planned.label = settings.label + configure_filesystem_type(planned, settings.type) if settings.type end # @param planned [Planned::Disk, Planned::Partition] # @param settings [Agama::Storage::Settings::Filesystem] - def configure_filesystem(planned, settings) - planned.filesystem_type = settings.type + def configure_filesystem_type(planned, settings) + planned.filesystem_type = settings.fs_type configure_btrfs(planned, settings.btrfs) if settings.btrfs end @@ -77,16 +81,6 @@ def configure_btrfs(planned, settings) planned.subvolumes = settings.subvolumes end - # @param planned [Planned::Disk, Planned::Partition] - # @param settings [Agama::Storage::Settings::Mount] - def configure_mount(planned, settings) - planned.mount_point = settings.path - planned.mount_by = settings.mount_by - planned.fstab_options = settings.options - # FIXME: Is this needed? Or #options is enough? - # planned.read_only = settings.read_only? - end - # @param planned [Planned::Partition] # @param settings [Agama::Storage::Settings::Size] def configure_size(planned, settings) diff --git a/service/test/agama/storage/config_conversions/from_json_test.rb b/service/test/agama/storage/config_conversions/from_json_test.rb index 66a6e3f007..1af4f637dd 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -43,6 +43,10 @@ "volume_templates" => [ { "mount_path" => "/", "filesystem" => "btrfs", "size" => { "auto" => true }, + "btrfs" => { + "snapshots" => true, "default_subvolume" => "@", + "subvolumes" => ["home", "opt", "root", "srv"] + }, "outline" => { "required" => true, "snapshots_configurable" => true, "auto_size" => { @@ -53,43 +57,358 @@ } }, { - "mount_path" => "/home", - "outline" => { - "required" => false, "filesystem" => "xfs", - "size" => { "auto" => false, "min" => "5 GiB" } - } + "mount_path" => "/home", "size" => { "auto" => false, "min" => "5 GiB" }, + "filesystem" => "xfs", "outline" => { "required" => false} }, { - "mount_path" => "swap", + "mount_path" => "swap", "filesystem" => "swap", "outline" => { "required" => false } - } + }, + { "mount_path" => "", "filesystem" => "ext4", + "size" => { "min" => "100 MiB" } } ] } } end describe "#convert" do - let(:config_json) do - { - boot: { - configure: true, - device: "/dev/sdb" - }, - drives: [ + using Y2Storage::Refinements::SizeCasts + + context "with an empty JSON configuration" do + let(:config_json) { {} } + + it "generates a storage configuration" do + config = subject.convert + expect(config).to be_a(Agama::Storage::Config) + end + + it "calculates default boot settings" do + config = subject.convert + expect(config.boot).to be_a(Agama::Storage::Configs::Boot) + expect(config.boot.configure).to eq true + expect(config.boot.device).to eq nil + end + + # FIXME: Is this correct? + it "does not include any device in the configuration" do + config = subject.convert + expect(config.drives).to be_empty + end + end + + context "with some drives and boot configuration at JSON" do + let(:config_json) do + { + boot: { configure: true, device: "/dev/sdb" }, + drives: [ + { + ptableType: "gpt", + partitions: [{ filesystem: { path: "/" } }] + } + ] + } + end + + it "generates a storage configuration" do + config = subject.convert + expect(config).to be_a(Agama::Storage::Config) + end + + it "calculates the corresponding boot settings" do + config = subject.convert + expect(config.boot).to be_a(Agama::Storage::Configs::Boot) + expect(config.boot.configure).to eq true + expect(config.boot.device).to eq "/dev/sdb" + end + + it "includes the corresponding drives" do + config = subject.convert + expect(config.drives.size).to eq 1 + drive = config.drives.first + expect(drive).to be_a(Agama::Storage::Configs::Drive) + expect(drive.ptable_type).to eq Y2Storage::PartitionTables::Type::GPT + expect(drive.partitions.size).to eq 1 + partition = drive.partitions.first + expect(partition.filesystem.path).to eq "/" + end + end + + context "omitting sizes for the partitions" do + let(:config_json) do + { + drives: [ + { + partitions: [ + { + filesystem: { path: "/", type: { btrfs: { snapshots: false } } } + }, + { + filesystem: { path: "/home" } + }, + { + filesystem: { path: "/opt" } + }, + { + filesystem: { path: "swap" } + } + ] + } + ] + } + end + + it "uses default sizes" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes(path: "/"), + size: have_attributes(default: true, min: 5.GiB, max: 10.GiB) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "/home"), + size: have_attributes(default: true, min: 5.GiB, max: Y2Storage::DiskSize.unlimited) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "/opt"), + size: have_attributes(default: true, min: 100.MiB, max: Y2Storage::DiskSize.unlimited) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "swap"), + size: have_attributes( + default: true, min: Y2Storage::DiskSize.zero, max: Y2Storage::DiskSize.unlimited + ) + ) + ) + end + end + + # Note the min is mandatory + context "specifying size limits for the partitions" do + let(:config_json) do + { + drives: [ + { + partitions: [ + { + filesystem: { path: "/", type: { btrfs: { snapshots: false } } }, + size: { min: "3 GiB" } + }, + { + filesystem: { path: "/home" }, + size: { min: "6 GiB", max: "9 GiB" } + } + ] + } + ] + } + end + + it "sets both min and max limits as requested" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to include( + an_object_having_attributes( + filesystem: have_attributes(path: "/home"), + size: have_attributes(default: false, min: 6.GiB, max: 9.GiB) + ) + ) + end + + it "uses unlimited for the omitted max sizes" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to include( + an_object_having_attributes( + filesystem: have_attributes(path: "/"), + size: have_attributes(default: false, min: 3.GiB, max: Y2Storage::DiskSize.unlimited) + ) + ) + end + end + + context "using 'default' as size for some partitions and size limit for others" do + let(:config_json) do + { + drives: [ + { + partitions: [ + { + filesystem: { path: "/", size: "default" } + }, + { + filesystem: { path: "/opt" }, + size: { min: "6 GiB", max: "22 GiB" } + } + ] + } + ] + } + end + + it "uses the appropriate sizes for each partition" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes(path: "/"), + size: have_attributes(default: true, min: 40.GiB, max: Y2Storage::DiskSize.unlimited) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "/opt"), + size: have_attributes(default: false, min: 6.GiB, max: 22.GiB) + ) + ) + end + end + + context "specifying a filesystem for a drive" do + let(:config_json) do + { + drives: [{ filesystem: filesystem }] + } + end + + context "if the filesystem specification only contains a path" do + let(:filesystem) { { path: "/" } } + + it "uses the default type and btrfs attributes for that path" do + config = subject.convert + filesystem = config.drives.first.filesystem + expect(filesystem.type.fs_type).to eq Y2Storage::Filesystems::Type::BTRFS + expect(filesystem.type.btrfs.snapshots).to eq true + expect(filesystem.type.btrfs.default_subvolume).to eq "@" + expect(filesystem.type.btrfs.subvolumes.map(&:path)).to eq ["home", "opt", "root", "srv"] + end + end + + context "if the filesystem specification contains some btrfs settings" do + let(:filesystem) do + { path: "/", + type: { btrfs: { snapshots: false, default_subvolume: "", subvolumes: ["tmp"] } } + } + end + + it "uses the specified btrfs attributes" do + config = subject.convert + filesystem = config.drives.first.filesystem + expect(filesystem.type.fs_type).to eq Y2Storage::Filesystems::Type::BTRFS + expect(filesystem.type.btrfs.snapshots).to eq false + # TODO: none of the following attributes are specified at the schema. Intentional? + # expect(filesystem.type.btrfs.default_subvolume).to eq "" + # expect(filesystem.type.btrfs.subvolumes.map(&:path)).to eq ["tmp"] + end + end + end + + context "configuring partial information for several mount points" do + let(:config_json) { { drives: [{ partitions: partitions }] } } + let(:partitions) do + [ + { "filesystem": { "path": "/" } }, + { "filesystem": { "path": "swap" } }, + { "filesystem": { "path": "/opt" } } + ] + end + + it "configures the filesystem types according to the product configuration" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes( + path: "/", type: have_attributes(fs_type: Y2Storage::Filesystems::Type::BTRFS) + ) + ), + an_object_having_attributes( + filesystem: have_attributes( + path: "swap", type: have_attributes(fs_type: Y2Storage::Filesystems::Type::SWAP) + ) + ), + an_object_having_attributes( + filesystem: have_attributes( + path: "/opt", type: have_attributes(fs_type: Y2Storage::Filesystems::Type::EXT4) + ) + ) + ) + end + end + + context "when some partition is configured to be encrypted" do + let(:config_json) do + { + drives: [{ partitions: partitions }] + } + end + + let(:partitions) do + [ + { + "id": "linux", "size": { "min": "10 GiB" }, + # FIXME: The schema specified "key_size" instead of keySize + # FIXME: Not sure if "key" is a good name for the password/passphrase + "encryption": { "key": "notsecret", "method": "luks2", "keySize": 256 }, + "filesystem": { "type": "xfs", "path": "/home" } + }, { - ptableType: "gpt", - partitions: [ - { filesystem: { path: "/", type: "ext4" } } - ] + "size": { "min": "2 GiB" }, + "filesystem": { "type": "swap", "path": "swap" } } ] - } + end + + it "sets the encryption settings for the corresponding partition" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes(path: "/home"), + encryption: have_attributes( + key: "notsecret", method: Y2Storage::EncryptionMethod::LUKS2, key_size: 256 + ) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "swap"), + encryption: nil + ) + ) + end end - it "generates settings with the values provided from JSON" do - config = subject.convert + context "when the id of some partition is specified" do + let(:config_json) do + { + drives: [{ partitions: partitions }] + } + end + + let(:partitions) do + [ + { + "id": "Esp", "size": { "min": "10 GiB" }, + "filesystem": { "type": "xfs", "path": "/home" } + }, + { + "size": { "min": "2 GiB" }, + "filesystem": { "type": "swap", "path": "swap" } + } + ] + end - expect(config).to be_a(Agama::Storage::Config) + it "configures the corresponding id" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes(path: "/home"), + id: Y2Storage::PartitionId::ESP + ), + an_object_having_attributes( + filesystem: have_attributes(path: "swap"), + id: nil + ) + ) + end end end end diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index a9396447b8..c266e155f1 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -43,7 +43,12 @@ Agama::Storage::Configs::Drive.new.tap do |drive| drive.partitions = [ Agama::Storage::Configs::Partition.new.tap do |part| - part.mount = Agama::Storage::Configs::Mount.new.tap { |m| m.path = "/" } + part.filesystem = Agama::Storage::Configs::Filesystem.new.tap do |fs| + fs.path = "/" + fs.type = Agama::Storage::Configs::FilesystemType.new.tap do |type| + type.fs_type = Y2Storage::Filesystems::Type::BTRFS + end + end part.size = Agama::Storage::Configs::Size.new.tap do |size| size.min = Y2Storage::DiskSize.GiB(8.5) size.max = Y2Storage::DiskSize.unlimited @@ -64,9 +69,9 @@ expect(partitions.first.id).to eq Y2Storage::PartitionId::BIOS_BOOT root_part = partitions.last expect(root_part.size).to be > Y2Storage::DiskSize.GiB(49) - # root_fs = root_part.filesystem - # expect(root_fs.root?).to eq true - # expect(root_fs.type.is?(:btrfs)).to eq true + root_fs = root_part.filesystem + expect(root_fs.root?).to eq true + expect(root_fs.type.is?(:btrfs)).to eq true end end @@ -82,9 +87,9 @@ root_part = partitions.first expect(root_part.id).to eq Y2Storage::PartitionId::LINUX expect(root_part.size).to be > Y2Storage::DiskSize.GiB(49) - # root_fs = root_part.filesystem - # expect(root_fs.root?).to eq true - # expect(root_fs.type.is?(:btrfs)).to eq true + root_fs = root_part.filesystem + expect(root_fs.root?).to eq true + expect(root_fs.type.is?(:btrfs)).to eq true end end end From 9e180ccc0e1fc2f959575dd667b37d8deb635e31 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 5 Aug 2024 17:25:36 +0200 Subject: [PATCH 10/21] Improve path comparisons --- service/lib/agama/storage/config.rb | 13 +++++----- .../lib/agama/storage/configs/filesystem.rb | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index 9ccd3c6fdf..09e8ec6c72 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -72,7 +72,7 @@ def explicit_boot_device def implicit_boot_device # TODO: preliminary implementation with very simplistic checks root_drive = drives.find do |drive| - drive.partitions.any? { |p| p.filesystem&.path == "/" } + drive.partitions.any? { |p| p.filesystem.root? } end root_drive&.found_device.name @@ -123,16 +123,17 @@ def fallback_size(attr) end def size_with_fallbacks(device, outline, attr, builder) - proposed_paths = filesystems.map(&:path) + fallback_paths = outline.send(:"#{attr}_size_fallback_for") + missing_paths = fallback_paths.reject { |p| proposed_path?(p) } size = outline.send(:"base_#{attr}_size") - - fallback_paths = outline.send(:"#{attr}_size_fallback_for") - # TODO: we need to normalize all the paths (or use Path for comparison or whatever) - missing_paths = fallback_paths - proposed_paths missing_paths.inject(size) { |total, p| total + builder.for(p).send(:"#{attr}_size") } end + def proposed_path?(path) + filesystems.any? { |fs| fs.path?(path) } + end + def size_with_ram(initial_size, outline) return initial_size unless outline.adjust_by_ram? diff --git a/service/lib/agama/storage/configs/filesystem.rb b/service/lib/agama/storage/configs/filesystem.rb index 3f5dec5fae..762116a8c6 100644 --- a/service/lib/agama/storage/configs/filesystem.rb +++ b/service/lib/agama/storage/configs/filesystem.rb @@ -19,10 +19,15 @@ # To contact SUSE LLC about this file by physical or electronic mail, you may # find current contact information at www.suse.com. +require "pathname" + module Agama module Storage module Configs class Filesystem + # @return [Pathname] Object that represents the root path + ROOT_PATH = Pathname.new("/").freeze + attr_accessor :path # @return [Configs::FilesystemType] attr_accessor :type @@ -36,6 +41,25 @@ def initialize @mkfs = [] end + # Whether the given path is equivalent to {#path} + # + # This method is more robust than a simple string comparison, since it takes + # into account trailing slashes and similar potential problems. + # + # @param other_path [String, Pathname] + # @return [Boolean] + def path?(other_path) + return false unless path + + Pathname.new(other_path).cleanpath == Pathname.new(path).cleanpath + end + + # Whether the mount point is root + # @return [Boolean] + def root? + path?(ROOT_PATH) + end + def btrfs_snapshots? return false unless type&.fs_type&.is?(:btrfs) From 0bdc20bccd833c08646ebd635677599ed0c16d44 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 6 Aug 2024 16:03:40 +0200 Subject: [PATCH 11/21] Manage different size formats --- .../config_conversions/size/from_json.rb | 30 ++- .../config_conversions/from_json_test.rb | 236 ++++++++++++++++-- 2 files changed, 226 insertions(+), 40 deletions(-) diff --git a/service/lib/agama/storage/config_conversions/size/from_json.rb b/service/lib/agama/storage/config_conversions/size/from_json.rb index c41a79bc3a..719e5751a4 100644 --- a/service/lib/agama/storage/config_conversions/size/from_json.rb +++ b/service/lib/agama/storage/config_conversions/size/from_json.rb @@ -33,9 +33,6 @@ def initialize(size_json) @size_json = size_json end - # @todo For now only {min: number, max: number} schema is supported. Add support for a - # direct value (e.g., 1024, "2 GiB"), and array format ([min, max]). - # # Performs the conversion from Hash according to the JSON schema. # # @return [Configs::Size] @@ -44,8 +41,8 @@ def convert Configs::Size.new.tap do |config| config.default = false - config.min = convert_min - config.max = convert_max || Y2Storage::DiskSize.unlimited + config.min = convert_size(:min) + config.max = convert_size(:max) || Y2Storage::DiskSize.unlimited end end @@ -54,17 +51,24 @@ def convert # @return [Hash] attr_reader :size_json - # @return [Y2Storage::DiskSize] - def convert_min - Y2Storage::DiskSize.new(size_json[:min]) - end - # @return [Y2Storage::DiskSize, nil] - def convert_max - value = size_json[:max] + def convert_size(field) + value = + if size_json.is_a?(Hash) + size_json[field] + elsif size_json.is_a?(Array) + field == :max ? size_json[1] : size_json[0] + else + size_json + end return unless value - Y2Storage::DiskSize.new(value) + begin + # This parses without legacy_units, ie. "1 GiB" != "1 GB" + Y2Storage::DiskSize.new(value) + rescue TypeError + # JSON schema validations should prevent this from happening + end end def default_size diff --git a/service/test/agama/storage/config_conversions/from_json_test.rb b/service/test/agama/storage/config_conversions/from_json_test.rb index 1af4f637dd..12f6a412e1 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -139,18 +139,10 @@ drives: [ { partitions: [ - { - filesystem: { path: "/", type: { btrfs: { snapshots: false } } } - }, - { - filesystem: { path: "/home" } - }, - { - filesystem: { path: "/opt" } - }, - { - filesystem: { path: "swap" } - } + { filesystem: { path: "/", type: { btrfs: { snapshots: false } } } }, + { filesystem: { path: "/home" } }, + { filesystem: { path: "/opt" } }, + { filesystem: { path: "swap" } } ] } ] @@ -183,45 +175,197 @@ end end - # Note the min is mandatory - context "specifying size limits for the partitions" do + context "setting fixed sizes for the partitions" do let(:config_json) do { drives: [ { partitions: [ - { - filesystem: { path: "/", type: { btrfs: { snapshots: false } } }, - size: { min: "3 GiB" } - }, - { - filesystem: { path: "/home" }, - size: { min: "6 GiB", max: "9 GiB" } - } + { filesystem: { path: "/"}, size: "10 GiB" }, + { filesystem: { path: "/home" }, size: "6Gb" }, + { filesystem: { path: "/opt" }, size: 3221225472 }, + { filesystem: { path: "swap" }, size: "6 Gib" } ] } ] } end - it "sets both min and max limits as requested" do + it "sets both min and max to the same value if a string is used" do config = subject.convert partitions = config.drives.first.partitions expect(partitions).to include( an_object_having_attributes( - filesystem: have_attributes(path: "/home"), - size: have_attributes(default: false, min: 6.GiB, max: 9.GiB) + filesystem: have_attributes(path: "/"), + size: have_attributes(default: false, min: 10.GiB, max: 10.GiB) ) ) end - it "uses unlimited for the omitted max sizes" do + it "sets both min and max to the same value if an integer is used" do config = subject.convert partitions = config.drives.first.partitions expect(partitions).to include( + an_object_having_attributes( + filesystem: have_attributes(path: "/opt"), + size: have_attributes(default: false, min: 3.GiB, max: 3.GiB) + ) + ) + end + + it "makes a difference between SI units and binary units" do + config = subject.convert + partitions = config.drives.first.partitions + home_size = partitions.find { |p| p.filesystem.path == "/home" }.size + swap_size = partitions.find { |p| p.filesystem.path == "swap" }.size + expect(swap_size.min.to_i).to eq 6*1024*1024*1024 + expect(home_size.max.to_i).to eq 6*1000*1000*1000 + end + end + + # Note the min is mandatory + context "specifying size limits for the partitions" do + RSpec.shared_examples "size limits" do + it "sets both min and max limits as requested if strings are used" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to include( + an_object_having_attributes( + filesystem: have_attributes(path: "/home"), + size: have_attributes(default: false, min: 6.GiB, max: 9.GiB) + ) + ) + end + + it "makes a difference between SI units and binary units" do + config = subject.convert + partitions = config.drives.first.partitions + home_size = partitions.find { |p| p.filesystem.path == "/home" }.size + swap_size = partitions.find { |p| p.filesystem.path == "swap" }.size + expect(home_size.min.to_i).to eq 6*1024*1024*1024 + expect(swap_size.max.to_i).to eq 6*1000*1000*1000 + end + + it "sets both min and max limits as requested if numbers are used" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to include( + an_object_having_attributes( + filesystem: have_attributes(path: "swap"), + size: have_attributes(default: false, min: 1.GiB) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "/opt"), + size: have_attributes(default: false, min: 1.GiB, max: 3.GiB) + ) + ) + end + + it "uses unlimited for the omitted max sizes" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to include( + an_object_having_attributes( + filesystem: have_attributes(path: "/"), + size: have_attributes(default: false, min: 3.GiB, max: Y2Storage::DiskSize.unlimited) + ) + ) + end + end + + context "using a hash" do + let(:config_json) do + { + drives: [ + { + partitions: [ + { + filesystem: { path: "/", type: { btrfs: { snapshots: false } } }, + size: { min: "3 GiB" } + }, + { + filesystem: { path: "/home" }, + size: { min: "6 GiB", max: "9 GiB" } + }, + { + filesystem: { path: "swap" }, + size: { min: 1073741824, max: "6 GB" } + }, + { + filesystem: { path: "/opt" }, + size: { min: "1073741824", max: 3221225472 } + } + ] + } + ] + } + end + + include_examples "size limits" + end + + context "using an array" do + let(:config_json) do + { + drives: [ + { + partitions: [ + { + filesystem: { path: "/", type: { btrfs: { snapshots: false } } }, + size: ["3 GiB"] + }, + { + filesystem: { path: "/home" }, + size: ["6 GiB", "9 GiB"] + }, + { + filesystem: { path: "swap" }, + size: [ 1073741824, "6 GB"] + }, + { + filesystem: { path: "/opt" }, + size: ["1073741824", 3221225472] + } + ] + } + ] + } + end + + include_examples "size limits" + end + end + + context "using 'default' as size for some partitions and size limit for others" do + let(:config_json) do + { + drives: [ + { + partitions: [ + { + filesystem: { path: "/", size: "default" } + }, + { + filesystem: { path: "/opt" }, + size: { min: "6 GiB", max: "22 GiB" } + } + ] + } + ] + } + end + + it "uses the appropriate sizes for each partition" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( an_object_having_attributes( filesystem: have_attributes(path: "/"), - size: have_attributes(default: false, min: 3.GiB, max: Y2Storage::DiskSize.unlimited) + size: have_attributes(default: true, min: 40.GiB, max: Y2Storage::DiskSize.unlimited) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "/opt"), + size: have_attributes(default: false, min: 6.GiB, max: 22.GiB) ) ) end @@ -262,6 +406,44 @@ end end + context "using 'default' for a partition that is fallback for others" do + let(:config_json) { { drives: [{ partitions: partitions }] } } + let(:root) do + { "filesystem": { "path": "/", type: { btrfs: { snapshots: false } } }, size: "default" } + end + let(:partitions) { [root] + other } + + context "if the other partitions are ommitted" do + let(:other) { [] } + + it "sums all the fallback sizes" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes(path: "/"), + size: have_attributes(default: true, min: 10.GiB, max: Y2Storage::DiskSize.unlimited) + ) + ) + end + end + + context "if the other partitions are included (even with non-exact name)" do + let(:other) { [ { "filesystem": { "path": "/home/"} } ] } + + it "ignores the fallback sizes" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to include( + an_object_having_attributes( + filesystem: have_attributes(path: "/"), + size: have_attributes(default: true, min: 5.GiB, max: 10.GiB) + ) + ) + end + end + end + context "specifying a filesystem for a drive" do let(:config_json) do { From fcec441831731ff65233c38a4bb4f4ed09ec66e4 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 7 Aug 2024 17:48:42 +0200 Subject: [PATCH 12/21] WIP: partial encryption support --- service/lib/y2storage/agama_proposal.rb | 12 +- .../proposal/agama_device_planner.rb | 74 ++++++++- .../lib/y2storage/proposal/agama_searcher.rb | 4 +- .../config_conversions/from_json_test.rb | 117 +++++++++++--- service/test/y2storage/agama_proposal_test.rb | 153 ++++++++++++++++-- 5 files changed, 320 insertions(+), 40 deletions(-) diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index e2178c507d..50c6f4d77f 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -72,12 +72,16 @@ def initialize(initial_settings, devicegraph: nil, disk_analyzer: nil, issues_li # @return [Proposal::SpaceMaker] attr_reader :space_maker + def fatal_error? + issues_list.any?(&:error?) + end + # Calculates the proposal # # @raise [NoDiskSpaceError] if there is no enough space to perform the installation def calculate_proposal Proposal::AgamaSearcher.new.search(initial_devicegraph, settings, issues_list) - if issues_list.any?(:error?) + if fatal_error? # This means some IfNotFound is set to "error" and we failed to find a match @devices = nil return @devices @@ -90,13 +94,17 @@ def calculate_proposal # Proposes a devicegraph based on given configuration # # @param devicegraph [Devicegraph] Starting point - # @return [Devicegraph] Devicegraph containing the planned devices + # @return [Devicegraph, nil] Devicegraph containing the planned devices, nil if the proposal + # failed def propose_devicegraph devicegraph = initial_devicegraph.dup calculate_initial_planned(devicegraph) + return if fatal_error? + clean_graph(devicegraph) complete_planned(devicegraph) + return if fatal_error? result = create_devices(devicegraph) result.devicegraph diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index 39dfa13dce..17b6da46d9 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -18,11 +18,14 @@ # find current contact information at www.suse.com. require "y2storage/planned" +require "agama/issue" module Y2Storage module Proposal # Base class used by Agama planners. class AgamaDevicePlanner + include Yast::I18n + # @!attribute [r] devicegraph # @return [Devicegraph] attr_reader :devicegraph @@ -33,6 +36,8 @@ class AgamaDevicePlanner # @param devicegraph [Devicegraph] Devicegraph to be used as starting point. # @param issues_list [AutoinstIssues::List] List of issues to register them. def initialize(devicegraph, issues_list) + textdomain "agama" + @devicegraph = devicegraph @issues_list = issues_list end @@ -49,7 +54,7 @@ def planned_devices(_setting) # @param planned [Planned::Disk, Planned::Partition] # @param settings [#format, #mount] def configure_device(planned, settings) - # TODO configure_encrypt + configure_encryption(planned, settings.encryption) if settings.encryption configure_filesystem(planned, settings.filesystem) if settings.filesystem end @@ -81,6 +86,73 @@ def configure_btrfs(planned, settings) planned.subvolumes = settings.subvolumes end + # @param planned [Planned::Disk, Planned::Partition] + # @param settings [Agama::Storage::Configs::Encryption] + def configure_encryption(planned, settings) + planned.encryption_password = settings.key + planned.encryption_method = settings.method + planned.encryption_pbkdf = settings.pbkd_function + planned.encryption_label = settings.label + planned.encryption_cipher = settings.cipher + planned.encryption_key_size = settings.key_size + + check_encryption(planned) + end + + def check_encryption(dev) + issues_list << issue_missing_enc_password(dev) if missing_enc_password?(dev) + issues_list << issue_available_enc_method(dev) unless dev.encryption_method.available? + issues_list << issue_wrong_enc_method(dev) unless supported_enc_method?(dev) + end + + def missing_enc_password?(planned) + return false unless planned.encryption_method&.password_required? + + planned.encryption_password.nil? || planned.encryption_password.empty? + end + + def supported_enc_method?(planned) + planned.supported_encryption_method?(planned.encryption_method) + end + + def issue_missing_enc_password(planned) + msg = format( + # TRANSLATORS: 'crypt_method' is the identifier of the method to encrypt the device (like + # 'luks1' or 'random_swap'). + _("No passphrase provided (required for using the method '%{crypt_method}')."), + crypt_method: planned.encryption_method.id.to_s + ) + encryption_issue(msg) + end + + def issue_available_enc_method(planned) + msg = format( + # TRANSLATORS: 'crypt_method' is the identifier of the method to encrypt the device (like + # 'luks1' or 'random_swap'). + _("Encryption method '%{crypt_method}' is not available in this system."), + crypt_method: planned.encryption_method.id.to_s + ) + encryption_issue(msg) + end + + def issue_wrong_enc_method(planned) + msg = format( + # TRANSLATORS: 'crypt_method' is the name of the method to encrypt the device (like + # 'luks1' or 'random_swap'). + _("'%{crypt_method}' is not a suitable method to encrypt the device."), + crypt_method: planned.encryption_method.id.to_s + ) + encryption_issue(msg) + end + + def encryption_issue(message) + Agama::Issue.new( + message, + source: Agama::Issue::Source::CONFIG, + severity: Agama::Issue::Severity::ERROR + ) + end + # @param planned [Planned::Partition] # @param settings [Agama::Storage::Settings::Size] def configure_size(planned, settings) diff --git a/service/lib/y2storage/proposal/agama_searcher.rb b/service/lib/y2storage/proposal/agama_searcher.rb index fc103ee988..b0ea8f84ca 100644 --- a/service/lib/y2storage/proposal/agama_searcher.rb +++ b/service/lib/y2storage/proposal/agama_searcher.rb @@ -67,8 +67,8 @@ def search(devicegraph, settings, issues_list) def issue_missing_drive(drive) Agama::Issue.new( _("No device found for a given drive"), - source: Issue::Source::CONFIG, - severity: Issue::Severity::ERROR + source: Agama::Issue::Source::CONFIG, + severity: Agama::Issue::Severity::ERROR ) end end diff --git a/service/test/agama/storage/config_conversions/from_json_test.rb b/service/test/agama/storage/config_conversions/from_json_test.rb index 12f6a412e1..b994d84597 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -527,33 +527,114 @@ [ { "id": "linux", "size": { "min": "10 GiB" }, - # FIXME: The schema specified "key_size" instead of keySize - # FIXME: Not sure if "key" is a good name for the password/passphrase - "encryption": { "key": "notsecret", "method": "luks2", "keySize": 256 }, - "filesystem": { "type": "xfs", "path": "/home" } + "filesystem": { "type": "xfs", "path": "/home" }, + "encryption": encryption_home }, { "size": { "min": "2 GiB" }, - "filesystem": { "type": "swap", "path": "swap" } + "filesystem": { "type": "swap", "path": "swap" }, + "encryption": encryption_swap } ] end - it "sets the encryption settings for the corresponding partition" do - config = subject.convert - partitions = config.drives.first.partitions - expect(partitions).to contain_exactly( - an_object_having_attributes( - filesystem: have_attributes(path: "/home"), - encryption: have_attributes( - key: "notsecret", method: Y2Storage::EncryptionMethod::LUKS2, key_size: 256 + context "if the method and the mandatory attributes are specified" do + let(:encryption_home) do + # FIXME: The schema specified "key_size" instead of keySize + # FIXME: Not sure if "key" is a good name for the password/passphrase + { "key": "notsecret", "method": "luks2", "keySize": 256 } + end + let(:encryption_swap) { nil } + + it "sets the encryption settings for the corresponding partition" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes(path: "/home"), + encryption: have_attributes( + key: "notsecret", method: Y2Storage::EncryptionMethod::LUKS2, key_size: 256 + ) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "swap"), + encryption: nil ) - ), - an_object_having_attributes( - filesystem: have_attributes(path: "swap"), - encryption: nil ) - ) + end + end + + context "if only the password is provided" do + let(:encryption_home) { { "key": "notsecret" } } + let(:encryption_swap) { nil } + + it "uses the default method and derivation function" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes(path: "/home"), + encryption: have_attributes( + key: "notsecret", + method: Y2Storage::EncryptionMethod::LUKS2, + pbkd_function: Y2Storage::PbkdFunction::ARGON2ID + ) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "swap"), + encryption: nil + ) + ) + end + end + + context "if random encryption is configured for swap" do + let(:encryption_home) { nil } + let(:encryption_swap) { { "method": "random_swap" } } + + it "sets the corresponding configuration" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes(path: "/home"), + encryption: nil + ), + an_object_having_attributes( + filesystem: have_attributes(path: "swap"), + encryption: have_attributes( + key: nil, + label: nil, + cipher: nil, + method: Y2Storage::EncryptionMethod::RANDOM_SWAP + ) + ) + ) + end + end + + context "if an unknown encryption method is specified" do + let(:encryption_home) { { "key": "notsecret", method: "foo" } } + let(:encryption_swap) { nil } + + # FIXME: shouldn't the problem (and the applied 'fix') be reported as an issue? + it "uses the default method" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes(path: "/home"), + encryption: have_attributes( + key: "notsecret", + method: Y2Storage::EncryptionMethod::LUKS2 + ) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "swap"), + encryption: nil + ) + ) + end end end diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index c266e155f1..0213fd7551 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -36,28 +36,27 @@ end let(:initial_settings) do Agama::Storage::Config.new.tap do |settings| - settings.drives = [root_drive] + settings.drives = drives end end - let(:root_drive) do - Agama::Storage::Configs::Drive.new.tap do |drive| - drive.partitions = [ - Agama::Storage::Configs::Partition.new.tap do |part| - part.filesystem = Agama::Storage::Configs::Filesystem.new.tap do |fs| - fs.path = "/" - fs.type = Agama::Storage::Configs::FilesystemType.new.tap do |type| - type.fs_type = Y2Storage::Filesystems::Type::BTRFS - end - end - part.size = Agama::Storage::Configs::Size.new.tap do |size| - size.min = Y2Storage::DiskSize.GiB(8.5) - size.max = Y2Storage::DiskSize.unlimited - end + let(:issues_list) { [] } + let(:drives) { [drive0] } + let(:drive0) { Agama::Storage::Configs::Drive.new.tap { |d| d.partitions = partitions0 } } + let(:partitions0) { [root_partition ] } + let(:root_partition) do + Agama::Storage::Configs::Partition.new.tap do |part| + part.filesystem = Agama::Storage::Configs::Filesystem.new.tap do |fs| + fs.path = "/" + fs.type = Agama::Storage::Configs::FilesystemType.new.tap do |type| + type.fs_type = Y2Storage::Filesystems::Type::BTRFS end - ] + end + part.size = Agama::Storage::Configs::Size.new.tap do |size| + size.min = Y2Storage::DiskSize.GiB(8.5) + size.max = Y2Storage::DiskSize.unlimited + end end end - let(:issues_list) { [] } describe "#propose" do context "when only the root partition is specified" do @@ -93,5 +92,125 @@ end end end + + context "when encrypting some devices" do + let(:partitions0) { [root_partition, home_partition ] } + + let(:home_partition) do + Agama::Storage::Configs::Partition.new.tap do |part| + part.filesystem = Agama::Storage::Configs::Filesystem.new.tap do |fs| + fs.path = "/home" + fs.type = Agama::Storage::Configs::FilesystemType.new.tap do |type| + type.fs_type = Y2Storage::Filesystems::Type::EXT4 + end + end + part.size = Agama::Storage::Configs::Size.new.tap do |size| + size.min = Y2Storage::DiskSize.GiB(10) + size.max = Y2Storage::DiskSize.unlimited + end + part.encryption = home_encryption + end + end + + let(:home_encryption) do + Agama::Storage::Configs::Encryption.new.tap do |enc| + enc.key = "notSecreT" + enc.method = encryption_method + end + end + + let(:encryption_method) { Y2Storage::EncryptionMethod::LUKS2 } + let(:available?) { true } + + before do + allow(encryption_method).to receive(:available?).and_return(available?) if encryption_method + end + + context "if the encryption settings contain all the detailed information" do + let(:home_encryption) do + Agama::Storage::Configs::Encryption.new.tap do |enc| + enc.key = "notSecreT" + enc.method = encryption_method + enc.pbkd_function = Y2Storage::PbkdFunction::ARGON2I + enc.label = "luks_label" + enc.cipher = "aes-xts-plain64" + enc.key_size = 512 + end + end + + it "proposes the right encryption layer" do + proposal.propose + partition = proposal.devices.partitions.find do |part| + part.blk_filesystem&.mount_path == "/home" + end + expect(partition.encrypted?).to eq true + expect(partition.encryption).to have_attributes( + method: Y2Storage::EncryptionMethod::LUKS2, + password: "notSecreT", + pbkdf: Y2Storage::PbkdFunction::ARGON2I, + label: "luks_label", + cipher: "aes-xts-plain64", + # libstorage-ng uses bytes instead of bits to represent the key size, contrary to + # all LUKS documentation and to cryptsetup + key_size: 64 + ) + end + end + + context "if the encryption method is not available for this system" do + let(:available?) { false } + + it "aborts the proposal process" do + proposal.propose + expect(proposal.failed?).to eq true + end + + it "reports the corresponding error" do + proposal.propose + expect(proposal.issues_list).to include an_object_having_attributes( + description: /method 'luks2' is not available/, + severity: Agama::Issue::Severity::ERROR + ) + end + end + + context "if the encryption method is not available for this system" do + let(:encryption_method) { Y2Storage::EncryptionMethod::RANDOM_SWAP } + + it "aborts the proposal process" do + proposal.propose + expect(proposal.failed?).to eq true + end + + it "reports the corresponding error" do + proposal.propose + expect(proposal.issues_list).to include an_object_having_attributes( + description: /'random_swap' is not a suitable method/, + severity: Agama::Issue::Severity::ERROR + ) + end + end + + context "if the method requires a password but none is provided" do + let(:home_encryption) do + Agama::Storage::Configs::Encryption.new.tap do |enc| + enc.method = encryption_method + end + end + + it "aborts the proposal process" do + proposal.propose + expect(proposal.failed?).to eq true + end + + it "reports the corresponding error" do + proposal.propose + expect(proposal.issues_list).to include an_object_having_attributes( + description: /No passphrase provided/, + severity: Agama::Issue::Severity::ERROR + ) + end + end + end end end From 63977cc0eb362e2fd5cc8e8f59e4af3b65e67113 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 13 Aug 2024 15:47:38 +0200 Subject: [PATCH 13/21] Honor ptable_type for disks --- service/lib/y2storage/agama_proposal.rb | 30 ++++++++++--------- service/test/y2storage/agama_proposal_test.rb | 21 +++++++++++++ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index 50c6f4d77f..742d45151b 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -102,6 +102,7 @@ def propose_devicegraph calculate_initial_planned(devicegraph) return if fatal_error? + configure_ptable_types(devicegraph) clean_graph(devicegraph) complete_planned(devicegraph) return if fatal_error? @@ -124,6 +125,16 @@ def clean_graph(devicegraph) space_maker.prepare_devicegraph(devicegraph, partitions_for_clean) end + def configure_ptable_types(devicegraph) + configured = settings.drives.select(&:ptable_type) + configured.each do |drive| + dev = device_for(drive, devicegraph) + next unless dev + + dev.forced_ptable_type = drive.ptable_type + end + end + # Modifies the given list of planned devices, removing shadowed subvolumes and # adding any planned partition needed for booting the new target system # @@ -165,7 +176,7 @@ def remove_empty_partition_tables(devicegraph) # @param devicegraph [Y2Storage::Devicegraph] # @return [Array] def drives_with_empty_partition_table(devicegraph) - devices = settings.drives.map(&:found_device).compact + devices = settings.drives.map { |d| device_for(d, devicegraph) }.compact devices.select { |d| d.partition_table && d.partitions.empty? } end @@ -190,25 +201,16 @@ def protect_sids # Creates planned devices on a given devicegraph # def create_devices(devicegraph) - # Almost for sure, this should happen as part of the creation of devices below - add_partition_tables(devicegraph) - devices_creator = Proposal::AgamaDevicesCreator.new(devicegraph, issues_list) names = settings.drives.map(&:found_device).compact.map(&:name) protect_sids result = devices_creator.populated_devicegraph(planned_devices, names, space_maker) end - # Add partition tables - # - # This method create/change partitions tables according to information - # specified in the profile. Disks containing any partition will be ignored. - # - # The devicegraph which is passed as first argument will be modified. - # - # @param devicegraph [Devicegraph] Starting point - def add_partition_tables(devicegraph) - # TODO: if needed, will very likely be moved to AgamaDevicesCreator + def device_for(drive, devicegraph) + return unless drive.found_device + + devicegraph.find_device(drive.found_device.sid) end end end diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index 0213fd7551..07f098711c 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -93,6 +93,27 @@ end end + context "when a partition table type is specified for a drive" do + let(:drive0) do + Agama::Storage::Configs::Drive.new.tap do |drive| + drive.partitions = partitions0 + drive.ptable_type = Y2Storage::PartitionTables::Type::MSDOS + end + end + + it "tries to propose a partition table of the requested type" do + proposal.propose + ptable = proposal.devices.disks.first.partition_table + expect(ptable.type).to eq Y2Storage::PartitionTables::Type::MSDOS + end + + it "honors the partition table type if possible when calculating the boot partitions" do + proposal.propose + partitions = proposal.devices.partitions + expect(partitions.map(&:id)).to_not include Y2Storage::PartitionId::BIOS_BOOT + end + end + context "when encrypting some devices" do let(:partitions0) { [root_partition, home_partition ] } From fb33390df5a5b911ee82ed32d9958fee2bb6fd58 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 13 Aug 2024 15:54:24 +0200 Subject: [PATCH 14/21] Adjust TODOs, NOTEs and FIXMEs --- service/lib/agama/storage/config.rb | 2 +- service/lib/y2storage/agama_proposal.rb | 2 +- service/lib/y2storage/proposal/agama_device_planner.rb | 5 +++-- service/lib/y2storage/proposal/agama_devices_creator.rb | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index 09e8ec6c72..f6c52d4603 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -70,7 +70,7 @@ def explicit_boot_device end def implicit_boot_device - # TODO: preliminary implementation with very simplistic checks + # NOTE: preliminary implementation with very simplistic checks root_drive = drives.find do |drive| drive.partitions.any? { |p| p.filesystem.root? } end diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index 742d45151b..5c3bf026cf 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -182,7 +182,7 @@ def drives_with_empty_partition_table(devicegraph) # Planned partitions that will hold the given planned devices # - # TODO: + # NOTE: # Extracted to a separate method because it's something that may need some extra logic # in the future. See the equivalent method at DevicegraphGenerator. # diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index 17b6da46d9..14dc418327 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -65,8 +65,6 @@ def configure_filesystem(planned, settings) planned.mount_by = settings.mount_by planned.fstab_options = settings.mount_options planned.mkfs_options = settings.mkfs_options - # FIXME: Is this needed? Or #mount_options is enough? - # planned.read_only = settings.read_only? planned.label = settings.label configure_filesystem_type(planned, settings.type) if settings.type end @@ -81,6 +79,9 @@ def configure_filesystem_type(planned, settings) # @param planned [Planned::Disk, Planned::Partition] # @param settings [Agama::Storage::Settings::Btrfs] def configure_btrfs(planned, settings) + # TODO: we need to discuss what to do with transactional systems and the read_only + # property. We are not sure whether those things should be configurable by the user. + # planned.read_only = settings.read_only? planned.snapshots = settings.snapshots? planned.default_subvolume = settings.default_subvolume planned.subvolumes = settings.subvolumes diff --git a/service/lib/y2storage/proposal/agama_devices_creator.rb b/service/lib/y2storage/proposal/agama_devices_creator.rb index af1bbef378..5dafdae5c3 100644 --- a/service/lib/y2storage/proposal/agama_devices_creator.rb +++ b/service/lib/y2storage/proposal/agama_devices_creator.rb @@ -155,7 +155,7 @@ def provide_space(planned_partitions, devicegraph, lvm_helper) def partitions_for_existing(planned_devices) # Maybe in the future this can include partitions on top of existing MDs - # TODO: simplistic implementation + # NOTE: simplistic implementation planned_devices.partitions.reject(&:reuse?) end From 9fdb49f7839004091028056ec4a6744f79bacbda Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 19 Aug 2024 12:44:20 +0200 Subject: [PATCH 15/21] Adapt to schema names --- .../config_conversions/block_device/from_json.rb | 2 +- .../config_conversions/encryption/from_json.rb | 10 ++++------ service/lib/agama/storage/configs/encryption.rb | 2 +- .../y2storage/proposal/agama_device_planner.rb | 2 +- .../storage/config_conversions/from_json_test.rb | 16 +++++++--------- service/test/y2storage/agama_proposal_test.rb | 4 ++-- 6 files changed, 16 insertions(+), 20 deletions(-) diff --git a/service/lib/agama/storage/config_conversions/block_device/from_json.rb b/service/lib/agama/storage/config_conversions/block_device/from_json.rb index efd287b36a..c6c5e6fc32 100644 --- a/service/lib/agama/storage/config_conversions/block_device/from_json.rb +++ b/service/lib/agama/storage/config_conversions/block_device/from_json.rb @@ -93,7 +93,7 @@ def convert_filesystem # @return [Configs::Encryption] def default_encrypt_config Configs::Encryption.new.tap do |config| - config.key = settings.encryption.password + config.password = settings.encryption.password config.method = settings.encryption.method config.pbkd_function = settings.encryption.pbkd_function end diff --git a/service/lib/agama/storage/config_conversions/encryption/from_json.rb b/service/lib/agama/storage/config_conversions/encryption/from_json.rb index cc62fa5713..07591ecf08 100644 --- a/service/lib/agama/storage/config_conversions/encryption/from_json.rb +++ b/service/lib/agama/storage/config_conversions/encryption/from_json.rb @@ -41,13 +41,13 @@ def initialize(encryption_json, default: nil) # @return [Configs::Encryption] def convert default_config.dup.tap do |config| - key = convert_key + password = convert_password method = convert_method pbkdf = convert_pbkd_function key_size = convert_key_size cipher = convert_cipher - config.key = key if key + config.password = password if password config.method = method if method config.pbkd_function = pbkdf if pbkdf config.key_size = key_size if key_size @@ -64,9 +64,8 @@ def convert attr_reader :default_config # @return [String, nil] - def convert_key - # TODO: this is set as "key" in the schema, but it looks like a bad name - encryption_json[:key] + def convert_password + encryption_json[:password] end # @return [Y2Storage::EncryptionMethod, nil] @@ -84,7 +83,6 @@ def convert_pbkd_function # @return [Integer, nil] def convert_key_size - # FIXME: this is using camel_case in the schema definition, looks wrong encryption_json[:keySize] end diff --git a/service/lib/agama/storage/configs/encryption.rb b/service/lib/agama/storage/configs/encryption.rb index 68ae84c5b5..aeb186578e 100644 --- a/service/lib/agama/storage/configs/encryption.rb +++ b/service/lib/agama/storage/configs/encryption.rb @@ -24,7 +24,7 @@ module Storage module Configs class Encryption attr_accessor :method - attr_accessor :key + attr_accessor :password attr_accessor :pbkd_function attr_accessor :label attr_accessor :cipher diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index 14dc418327..0992dbcdf6 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -90,7 +90,7 @@ def configure_btrfs(planned, settings) # @param planned [Planned::Disk, Planned::Partition] # @param settings [Agama::Storage::Configs::Encryption] def configure_encryption(planned, settings) - planned.encryption_password = settings.key + planned.encryption_password = settings.password planned.encryption_method = settings.method planned.encryption_pbkdf = settings.pbkd_function planned.encryption_label = settings.label diff --git a/service/test/agama/storage/config_conversions/from_json_test.rb b/service/test/agama/storage/config_conversions/from_json_test.rb index b994d84597..c763dbcf98 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -540,9 +540,7 @@ context "if the method and the mandatory attributes are specified" do let(:encryption_home) do - # FIXME: The schema specified "key_size" instead of keySize - # FIXME: Not sure if "key" is a good name for the password/passphrase - { "key": "notsecret", "method": "luks2", "keySize": 256 } + { "password": "notsecret", "method": "luks2", "keySize": 256 } end let(:encryption_swap) { nil } @@ -553,7 +551,7 @@ an_object_having_attributes( filesystem: have_attributes(path: "/home"), encryption: have_attributes( - key: "notsecret", method: Y2Storage::EncryptionMethod::LUKS2, key_size: 256 + password: "notsecret", method: Y2Storage::EncryptionMethod::LUKS2, key_size: 256 ) ), an_object_having_attributes( @@ -565,7 +563,7 @@ end context "if only the password is provided" do - let(:encryption_home) { { "key": "notsecret" } } + let(:encryption_home) { { "password": "notsecret" } } let(:encryption_swap) { nil } it "uses the default method and derivation function" do @@ -575,7 +573,7 @@ an_object_having_attributes( filesystem: have_attributes(path: "/home"), encryption: have_attributes( - key: "notsecret", + password: "notsecret", method: Y2Storage::EncryptionMethod::LUKS2, pbkd_function: Y2Storage::PbkdFunction::ARGON2ID ) @@ -603,7 +601,7 @@ an_object_having_attributes( filesystem: have_attributes(path: "swap"), encryption: have_attributes( - key: nil, + password: nil, label: nil, cipher: nil, method: Y2Storage::EncryptionMethod::RANDOM_SWAP @@ -614,7 +612,7 @@ end context "if an unknown encryption method is specified" do - let(:encryption_home) { { "key": "notsecret", method: "foo" } } + let(:encryption_home) { { "password": "notsecret", method: "foo" } } let(:encryption_swap) { nil } # FIXME: shouldn't the problem (and the applied 'fix') be reported as an issue? @@ -625,7 +623,7 @@ an_object_having_attributes( filesystem: have_attributes(path: "/home"), encryption: have_attributes( - key: "notsecret", + password: "notsecret", method: Y2Storage::EncryptionMethod::LUKS2 ) ), diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index 07f098711c..0461aa0935 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -135,7 +135,7 @@ let(:home_encryption) do Agama::Storage::Configs::Encryption.new.tap do |enc| - enc.key = "notSecreT" + enc.password = "notSecreT" enc.method = encryption_method end end @@ -150,7 +150,7 @@ context "if the encryption settings contain all the detailed information" do let(:home_encryption) do Agama::Storage::Configs::Encryption.new.tap do |enc| - enc.key = "notSecreT" + enc.password = "notSecreT" enc.method = encryption_method enc.pbkd_function = Y2Storage::PbkdFunction::ARGON2I enc.label = "luks_label" From bef2c028ba5ede0237daf2ad095eb419049be77e Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 19 Aug 2024 16:32:37 +0200 Subject: [PATCH 16/21] Improve AgamaSearcher --- service/lib/agama/storage/config.rb | 1 - service/lib/agama/storage/configs/drive.rb | 3 +- .../lib/agama/storage/configs/partition.rb | 4 +- service/lib/agama/storage/configs/search.rb | 27 +++--- .../lib/y2storage/proposal/agama_searcher.rb | 67 +++++++++----- service/test/y2storage/agama_proposal_test.rb | 90 +++++++++++++++++++ 6 files changed, 152 insertions(+), 40 deletions(-) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index f6c52d4603..1e4cf0eaf9 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -35,7 +35,6 @@ class Config attr_accessor :md_raids attr_accessor :btrfs_raids attr_accessor :nfs_mounts - attr_accessor :original_graph def initialize @boot = Configs::Boot.new diff --git a/service/lib/agama/storage/configs/drive.rb b/service/lib/agama/storage/configs/drive.rb index 4946d24fd2..0b063a3bf8 100644 --- a/service/lib/agama/storage/configs/drive.rb +++ b/service/lib/agama/storage/configs/drive.rb @@ -45,7 +45,8 @@ def initialize def search_device(devicegraph, used_sids) @search ||= default_search - search.find(self, devicegraph, used_sids) + devs = devicegraph.blk_devices.select { |d| d.is?(:disk_device, :stray_blk_device) } + search.find(self, devs, used_sids) end def default_search diff --git a/service/lib/agama/storage/configs/partition.rb b/service/lib/agama/storage/configs/partition.rb index 1d58d4b3d6..ebcd3042d3 100644 --- a/service/lib/agama/storage/configs/partition.rb +++ b/service/lib/agama/storage/configs/partition.rb @@ -29,9 +29,9 @@ class Partition attr_accessor :encryption attr_accessor :filesystem - def search_device(devicegraph, parent_sid, used_sids) + def search_device(partitionable, used_sids) @search ||= default_search - search.find(self, devicegraph, used_sids, parent: parent_sid) + search.find(self, partitionable.partitions, used_sids) end def default_search diff --git a/service/lib/agama/storage/configs/search.rb b/service/lib/agama/storage/configs/search.rb index 15ae284a5c..5113e291e6 100644 --- a/service/lib/agama/storage/configs/search.rb +++ b/service/lib/agama/storage/configs/search.rb @@ -24,21 +24,24 @@ module Storage module Configs class Search attr_reader :device + attr_accessor :if_not_found - def find(setting, devicegraph, used_sids, parent: nil) - devices = candidate_devices(setting, devicegraph, parent) - devices.reject! { |d| used_sids.include?(d.sid) } - @device = devices.sort_by(&:name).first + def initialize + @if_not_found = :skip + end + + def resolved? + !!@resolved end - def candidate_devices(setting, devicegraph, parent) - if setting.kind_of?(Drive) - devicegraph.blk_devices.select do |dev| - dev.is?(:disk_device, :stray_blk_device) - end - else - devicegraph.find_device(parent).partitions - end + def skip_device? + resolved? && device.nil? && if_not_found == :skip + end + + def find(setting, candidate_devs, used_sids) + devices = candidate_devs.reject { |d| used_sids.include?(d.sid) } + @resolved = true + @device = devices.sort_by(&:name).first end end end diff --git a/service/lib/y2storage/proposal/agama_searcher.rb b/service/lib/y2storage/proposal/agama_searcher.rb index b0ea8f84ca..de7a431586 100644 --- a/service/lib/y2storage/proposal/agama_searcher.rb +++ b/service/lib/y2storage/proposal/agama_searcher.rb @@ -33,44 +33,63 @@ def initialize # The last two arguments get modified def search(devicegraph, settings, issues_list) - settings.original_graph = devicegraph - - sids = [] + @sids = [] settings.drives.each do |drive| - drive.search_device(devicegraph, sids) - - found = drive.found_device - if found.nil? - # TODO: If IfNotFound is 'skip' => - # invalidate somehow the device definition (registering issue?) - # - # Let's assume IfNotFound is 'error' - issues_list << issue_missing_drive(drive) - return false - end + drive.search_device(devicegraph, @sids) + process_element(drive, settings.drives, issues_list) - sids << found.sid - next unless drive.partitions? + next unless drive.found_device && drive.partitions? drive.partitions.each do |part| - part.search_device(devicegraph, found.sid, sids) - part_sid = part.found_device&.sid - sids << part_sid if part_sid + next unless part.search + + part.search_device(drive.found_device, @sids) + process_element(part, drive.partitions, issues_list) end end - - true end private - def issue_missing_drive(drive) + def process_element(element, collection, issues_list) + found = element.found_device + if found + @sids << found.sid + else + issues_list << not_found_issue(element) + collection.delete(element) if element.search.skip_device? + end + end + + def not_found_issue(element) Agama::Issue.new( - _("No device found for a given drive"), + issue_message(element), source: Agama::Issue::Source::CONFIG, - severity: Agama::Issue::Severity::ERROR + severity: issue_severity(element.search) ) end + + def issue_message(element) + if element.kind_of?(Agama::Storage::Configs::Drive) + if element.search.skip_device? + _("No device found for an optional drive") + else + _("No device found for a mandatory drive") + end + else + if element.search.skip_device? + _("No device found for an optional partition") + else + _("No device found for a mandatory partition") + end + end + end + + def issue_severity(search) + return Agama::Issue::Severity::WARN if search.skip_device? + + Agama::Issue::Severity::ERROR + end end end end diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index 0461aa0935..0d13f138eb 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -233,5 +233,95 @@ end end end + + context "when there are more drives than disks in the system" do + let(:drives) { [drive0, drive1] } + let(:drive1) do + Agama::Storage::Configs::Drive.new.tap do |drive| + drive.search = Agama::Storage::Configs::Search.new.tap do |search| + search.if_not_found = if_not_found + end + end + end + + context "if if_not_found is set to :skip for the surplus drive" do + let(:if_not_found) { :skip } + + it "calculates a proposal if possible" do + proposal.propose + expect(proposal.failed?).to eq false + end + + it "registers a non-critical issue" do + proposal.propose + expect(proposal.issues_list).to include an_object_having_attributes( + description: /optional drive/, + severity: Agama::Issue::Severity::WARN + ) + end + end + + context "if if_not_found is set to :error for the surplus drive" do + let(:if_not_found) { :error } + + it "aborts the proposal" do + proposal.propose + expect(proposal.failed?).to eq true + end + + it "registers a critical issue" do + proposal.propose + expect(proposal.issues_list).to include an_object_having_attributes( + description: /mandatory drive/, + severity: Agama::Issue::Severity::ERROR + ) + end + end + end + + context "when searching for a non-existent partition" do + let(:partitions0) { [root_partition, existing_partition ] } + let(:existing_partition) do + Agama::Storage::Configs::Partition.new.tap do |part| + part.search = Agama::Storage::Configs::Search.new.tap do |search| + search.if_not_found = if_not_found + end + end + end + + context "if if_not_found is set to :skip" do + let(:if_not_found) { :skip } + + it "calculates a proposal if possible" do + proposal.propose + expect(proposal.failed?).to eq false + end + + it "registers a non-critical issue" do + proposal.propose + expect(proposal.issues_list).to include an_object_having_attributes( + description: /optional partition/, + severity: Agama::Issue::Severity::WARN + ) + end + end + + context "if if_not_found is set to :error" do + let(:if_not_found) { :error } + + it "aborts the proposal" do + proposal.propose + expect(proposal.failed?).to eq true + end + + it "registers a critical issue" do + proposal.propose + expect(proposal.issues_list).to include an_object_having_attributes( + description: /mandatory partition/, + severity: Agama::Issue::Severity::ERROR + ) + end + end + end end end From 156df2da077ab730e7737318f999fb178f4adada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Tue, 20 Aug 2024 12:58:30 +0100 Subject: [PATCH 17/21] storage: encryption config from JSON --- .../block_device/from_json.rb | 2 - .../encryption/from_json.rb | 105 +++++++++++++----- .../config_conversions/from_json_test.rb | 74 +++++------- 3 files changed, 104 insertions(+), 77 deletions(-) diff --git a/service/lib/agama/storage/config_conversions/block_device/from_json.rb b/service/lib/agama/storage/config_conversions/block_device/from_json.rb index c6c5e6fc32..feb8e99793 100644 --- a/service/lib/agama/storage/config_conversions/block_device/from_json.rb +++ b/service/lib/agama/storage/config_conversions/block_device/from_json.rb @@ -74,8 +74,6 @@ def convert_encrypt # @return [Configs::Filesystem, nil] def convert_filesystem filesystem_json = blk_device_json[:filesystem] - - return if filesystem_json == false # "filesystem": false return if filesystem_json.nil? default = default_filesystem_config(filesystem_json&.dig(:path) || "") diff --git a/service/lib/agama/storage/config_conversions/encryption/from_json.rb b/service/lib/agama/storage/config_conversions/encryption/from_json.rb index 07591ecf08..d3d0be7fed 100644 --- a/service/lib/agama/storage/config_conversions/encryption/from_json.rb +++ b/service/lib/agama/storage/config_conversions/encryption/from_json.rb @@ -29,7 +29,7 @@ module ConfigConversions module Encryption # Encryption conversion from JSON hash according to schema. class FromJSON - # @param encryption_json [Hash] + # @param encryption_json [Hash, String] # @param default [Configs::Encrypt] def initialize(encryption_json, default: nil) @encryption_json = encryption_json @@ -41,54 +41,103 @@ def initialize(encryption_json, default: nil) # @return [Configs::Encryption] def convert default_config.dup.tap do |config| - password = convert_password - method = convert_method - pbkdf = convert_pbkd_function - key_size = convert_key_size - cipher = convert_cipher - - config.password = password if password - config.method = method if method - config.pbkd_function = pbkdf if pbkdf - config.key_size = key_size if key_size - config.cipher = cipher if cipher + convert_luks1(config) || + convert_luks2(config) || + convert_pervasive_luks2(config) || + convert_swap_encryption(config) end end private - # @return [Hash] + # @return [Hash, String] attr_reader :encryption_json # @return [Configs::Encryption] attr_reader :default_config - # @return [String, nil] - def convert_password - encryption_json[:password] + # @param config [Configs::Encryption] + # @return [Configs::Encryption, nil] nil if JSON does not match LUKS1 schema. + def convert_luks1(config) + luks1_json = encryption_json.is_a?(Hash) && encryption_json[:luks1] + return unless luks1_json + + key_size = convert_key_size(luks1_json) + cipher = convert_cipher(luks1_json) + + config.method = Y2Storage::EncryptionMethod::LUKS1 + config.password = convert_password(luks1_json) + config.key_size = key_size if key_size + config.cipher = cipher if cipher end - # @return [Y2Storage::EncryptionMethod, nil] - def convert_method - value = encryption_json[:method] - return unless value + # @param config [Configs::Encryption] + # @return [Configs::Encryption, nil] nil if JSON does not match LUKS2 schema. + def convert_luks2(config) + luks2_json = encryption_json.is_a?(Hash) && encryption_json[:luks2] + return unless luks2_json - Y2Storage::EncryptionMethod.find(value.to_sym) + key_size = convert_key_size(luks2_json) + cipher = convert_cipher(luks2_json) + label = convert_label + pbkdf = convert_pbkd_function + + config.method = Y2Storage::EncryptionMethod::LUKS2 + config.password = convert_password(luks2_json) + config.key_size = key_size if key_size + config.cipher = cipher if cipher + config.label = label if label + config.pbkd_function = pbkdf if pbkdf end - # @return [Y2Storage::PbkdFunction, nil] - def convert_pbkd_function - Y2Storage::PbkdFunction.find(encryption_json[:pbkdFunction]) + # @param config [Configs::Encryption] + # @return [Configs::Encryption, nil] nil if JSON does not match pervasive LUKS2 schema. + def convert_pervasive_luks2(config) + pervasive_json = encryption_json.is_a?(Hash) && encryption_json[:pervasive_luks2] + return unless pervasive_json + + config.method = Y2Storage::EncryptionMethod::PERVASIVE_LUKS2 + config.password = convert_password(pervasive_json) + end + + # @param config [Configs::Encryption] + # @return [Configs::Encryption, nil] nil if JSON does not match a swap encryption schema. + def convert_swap_encryption(config) + return unless encryption_json.is_a?(String) + + # @todo Report issue if the schema admits an unknown method. + method = Y2Storage::EncryptionMethod.find(encryption_json.to_sym) + return unless method + + config.method = method end + # @param method_json [Hash] + # @return [String, nil] + def convert_password(method_json) + method_json[:password] + end + + # @param method_json [Hash] # @return [Integer, nil] - def convert_key_size - encryption_json[:keySize] + def convert_key_size(method_json) + method_json[:keySize] + end + + # @param method_json [Hash] + # @return [String, nil] + def convert_cipher(method_json) + method_json[:cipher] end # @return [String, nil] - def convert_cipher - encryption_json[:cipher] + def convert_label + encryption_json.dig(:luks2, :label) + end + + # @return [Y2Storage::PbkdFunction, nil] + def convert_pbkd_function + Y2Storage::PbkdFunction.find(encryption_json.dig(:luks2, :pbkdFunction)) end end end diff --git a/service/test/agama/storage/config_conversions/from_json_test.rb b/service/test/agama/storage/config_conversions/from_json_test.rb index c763dbcf98..c0e73bcae3 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -71,6 +71,11 @@ } end + before do + # Speed up tests by avoding real check of TPM presence. + allow(Y2Storage::EncryptionMethod::TPM_FDE).to receive(:possible?).and_return(true) + end + describe "#convert" do using Y2Storage::Refinements::SizeCasts @@ -89,7 +94,7 @@ expect(config.boot.device).to eq nil end - # FIXME: Is this correct? + # @todo Generate default drive/LVM from product descripton. it "does not include any device in the configuration" do config = subject.convert expect(config.drives).to be_empty @@ -538,35 +543,34 @@ ] end - context "if the method and the mandatory attributes are specified" do - let(:encryption_home) do - { "password": "notsecret", "method": "luks2", "keySize": 256 } - end - let(:encryption_swap) { nil } + let(:encryption_home) do + { "luks2": { "password": "notsecret", "keySize": 256 } } + end - it "sets the encryption settings for the corresponding partition" do - config = subject.convert - partitions = config.drives.first.partitions - expect(partitions).to contain_exactly( - an_object_having_attributes( - filesystem: have_attributes(path: "/home"), - encryption: have_attributes( - password: "notsecret", method: Y2Storage::EncryptionMethod::LUKS2, key_size: 256 - ) - ), - an_object_having_attributes( - filesystem: have_attributes(path: "swap"), - encryption: nil + let(:encryption_swap) { nil } + + it "sets the encryption settings for the corresponding partition" do + config = subject.convert + partitions = config.drives.first.partitions + expect(partitions).to contain_exactly( + an_object_having_attributes( + filesystem: have_attributes(path: "/home"), + encryption: have_attributes( + password: "notsecret", method: Y2Storage::EncryptionMethod::LUKS2, key_size: 256 ) + ), + an_object_having_attributes( + filesystem: have_attributes(path: "swap"), + encryption: nil ) - end + ) end context "if only the password is provided" do - let(:encryption_home) { { "password": "notsecret" } } + let(:encryption_home) { { "luks2": { "password": "notsecret" } } } let(:encryption_swap) { nil } - it "uses the default method and derivation function" do + it "uses the default derivation function" do config = subject.convert partitions = config.drives.first.partitions expect(partitions).to contain_exactly( @@ -588,7 +592,7 @@ context "if random encryption is configured for swap" do let(:encryption_home) { nil } - let(:encryption_swap) { { "method": "random_swap" } } + let(:encryption_swap) { "random_swap" } it "sets the corresponding configuration" do config = subject.convert @@ -610,30 +614,6 @@ ) end end - - context "if an unknown encryption method is specified" do - let(:encryption_home) { { "password": "notsecret", method: "foo" } } - let(:encryption_swap) { nil } - - # FIXME: shouldn't the problem (and the applied 'fix') be reported as an issue? - it "uses the default method" do - config = subject.convert - partitions = config.drives.first.partitions - expect(partitions).to contain_exactly( - an_object_having_attributes( - filesystem: have_attributes(path: "/home"), - encryption: have_attributes( - password: "notsecret", - method: Y2Storage::EncryptionMethod::LUKS2 - ) - ), - an_object_having_attributes( - filesystem: have_attributes(path: "swap"), - encryption: nil - ) - ) - end - end end context "when the id of some partition is specified" do From f5bab4ee66cafaf6243e91dc233e85530dd65e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Tue, 20 Aug 2024 13:01:43 +0100 Subject: [PATCH 18/21] storage: rubocop auto-corrections --- service/lib/agama/storage/config.rb | 4 +- .../encryption/from_json.rb | 6 +- service/lib/agama/storage/configs/search.rb | 2 +- service/lib/y2storage/agama_proposal.rb | 4 +- .../proposal/agama_device_planner.rb | 4 +- .../proposal/agama_devices_creator.rb | 10 +- .../proposal/agama_devices_planner.rb | 2 +- .../y2storage/proposal/agama_drive_planner.rb | 2 +- .../lib/y2storage/proposal/agama_searcher.rb | 12 +- .../config_conversions/from_json_test.rb | 123 +++++++++--------- service/test/y2storage/agama_proposal_test.rb | 46 +++---- 11 files changed, 109 insertions(+), 106 deletions(-) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index 1e4cf0eaf9..bd2287c5cc 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -84,7 +84,7 @@ def calculate_default_sizes(volume_builder) end end - private + private def filesystems (drives + partitions).map(&:filesystem).compact @@ -121,7 +121,7 @@ def fallback_size(attr) Y2Storage::DiskSize.unlimited end - def size_with_fallbacks(device, outline, attr, builder) + def size_with_fallbacks(_device, outline, attr, builder) fallback_paths = outline.send(:"#{attr}_size_fallback_for") missing_paths = fallback_paths.reject { |p| proposed_path?(p) } diff --git a/service/lib/agama/storage/config_conversions/encryption/from_json.rb b/service/lib/agama/storage/config_conversions/encryption/from_json.rb index d3d0be7fed..c9e193d694 100644 --- a/service/lib/agama/storage/config_conversions/encryption/from_json.rb +++ b/service/lib/agama/storage/config_conversions/encryption/from_json.rb @@ -42,9 +42,9 @@ def initialize(encryption_json, default: nil) def convert default_config.dup.tap do |config| convert_luks1(config) || - convert_luks2(config) || - convert_pervasive_luks2(config) || - convert_swap_encryption(config) + convert_luks2(config) || + convert_pervasive_luks2(config) || + convert_swap_encryption(config) end end diff --git a/service/lib/agama/storage/configs/search.rb b/service/lib/agama/storage/configs/search.rb index 5113e291e6..c55f195ae8 100644 --- a/service/lib/agama/storage/configs/search.rb +++ b/service/lib/agama/storage/configs/search.rb @@ -38,7 +38,7 @@ def skip_device? resolved? && device.nil? && if_not_found == :skip end - def find(setting, candidate_devs, used_sids) + def find(_setting, candidate_devs, used_sids) devices = candidate_devs.reject { |d| used_sids.include?(d.sid) } @resolved = true @device = devices.sort_by(&:name).first diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index 5c3bf026cf..1509ed365f 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -63,7 +63,7 @@ def initialize(initial_settings, devicegraph: nil, disk_analyzer: nil, issues_li @settings = initial_settings end - private + private # Not sure if needed in the final version # @return [ProposalSettings] @@ -151,7 +151,7 @@ def boot_partitions(devicegraph) checker = BootRequirementsChecker.new( devicegraph, planned_devices: planned_devices.mountable_devices, - boot_disk_name: settings.boot_device + boot_disk_name: settings.boot_device ) # NOTE: Should we try with :desired first? checker.needed_partitions(:min) diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index 0992dbcdf6..300d790976 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -49,7 +49,7 @@ def planned_devices(_setting) raise NotImplementedError end - private + private # @param planned [Planned::Disk, Planned::Partition] # @param settings [#format, #mount] @@ -149,7 +149,7 @@ def issue_wrong_enc_method(planned) def encryption_issue(message) Agama::Issue.new( message, - source: Agama::Issue::Source::CONFIG, + source: Agama::Issue::Source::CONFIG, severity: Agama::Issue::Severity::ERROR ) end diff --git a/service/lib/y2storage/proposal/agama_devices_creator.rb b/service/lib/y2storage/proposal/agama_devices_creator.rb index 5dafdae5c3..26c82e4428 100644 --- a/service/lib/y2storage/proposal/agama_devices_creator.rb +++ b/service/lib/y2storage/proposal/agama_devices_creator.rb @@ -60,7 +60,7 @@ def populated_devicegraph(planned_devices, disk_names, space_maker) process_devices end - protected + protected # @return [Devicegraph] Original devicegraph attr_reader :original_graph @@ -79,7 +79,7 @@ def populated_devicegraph(planned_devices, disk_names, space_maker) # @return [Devicegraph] Current devicegraph attr_reader :devicegraph - private + private # Sets the current creator result # @@ -125,7 +125,7 @@ def process_existing_partitionables # This may be here or before create_partitions. # # What about resizing if needed? - # Likely shrinking is fine and should be always handled at the SpaceMaker. + # Likely shrinking is fine and should be always handled at the SpaceMaker. # But I'm not so sure if growing is so fine (we may need to make some space first). # I don't think we have the growing case covered by SpaceMaker, the distribution # calculator, etc. @@ -141,7 +141,7 @@ def process_existing_partitionables # if settings.use_lvm # new_pvs = new_physical_volumes(space_result[:devicegraph], graph) # graph = lvm_helper.create_volumes(graph, new_pvs) - #end + # end # Needed or already part of other components? # graph.mount_points.each(&:adjust_mount_options) @@ -164,7 +164,7 @@ def partitions_for_existing(planned_devices) # Add planned disk like devices to reuse list so they can be considered for lvm and raids # later on. def process_disk_like_devs - # Do we do something about SpaceMaker here? I assume it was already done as mandatory + # Do we do something about SpaceMaker here? I assume it was already done as mandatory planned_devs = planned_devices.select do |dev| dev.is_a?(Planned::StrayBlkDevice) || dev.is_a?(Planned::Disk) end diff --git a/service/lib/y2storage/proposal/agama_devices_planner.rb b/service/lib/y2storage/proposal/agama_devices_planner.rb index efffaeeea9..0e42a4e7d7 100644 --- a/service/lib/y2storage/proposal/agama_devices_planner.rb +++ b/service/lib/y2storage/proposal/agama_devices_planner.rb @@ -57,7 +57,7 @@ def initial_planned_devices(devicegraph) Planned::DevicesCollection.new(devs) end - protected + protected # @return [Array] List to register any found issue attr_reader :issues_list diff --git a/service/lib/y2storage/proposal/agama_drive_planner.rb b/service/lib/y2storage/proposal/agama_drive_planner.rb index 59fd0eed2d..f2d585c2be 100644 --- a/service/lib/y2storage/proposal/agama_drive_planner.rb +++ b/service/lib/y2storage/proposal/agama_drive_planner.rb @@ -28,7 +28,7 @@ def planned_devices(settings) [planned_drive(settings)] end - private + private # @param settings [Agama::Storage::Settings::Drive] # @return [Planned::Disk] diff --git a/service/lib/y2storage/proposal/agama_searcher.rb b/service/lib/y2storage/proposal/agama_searcher.rb index de7a431586..42925ee08d 100644 --- a/service/lib/y2storage/proposal/agama_searcher.rb +++ b/service/lib/y2storage/proposal/agama_searcher.rb @@ -49,7 +49,7 @@ def search(devicegraph, settings, issues_list) end end - private + private def process_element(element, collection, issues_list) found = element.found_device @@ -70,18 +70,16 @@ def not_found_issue(element) end def issue_message(element) - if element.kind_of?(Agama::Storage::Configs::Drive) + if element.is_a?(Agama::Storage::Configs::Drive) if element.search.skip_device? _("No device found for an optional drive") else _("No device found for a mandatory drive") end + elsif element.search.skip_device? + _("No device found for an optional partition") else - if element.search.skip_device? - _("No device found for an optional partition") - else - _("No device found for a mandatory partition") - end + _("No device found for a mandatory partition") end end diff --git a/service/test/agama/storage/config_conversions/from_json_test.rb b/service/test/agama/storage/config_conversions/from_json_test.rb index c0e73bcae3..c648874d6d 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -58,7 +58,7 @@ }, { "mount_path" => "/home", "size" => { "auto" => false, "min" => "5 GiB" }, - "filesystem" => "xfs", "outline" => { "required" => false} + "filesystem" => "xfs", "outline" => { "required" => false } }, { "mount_path" => "swap", "filesystem" => "swap", @@ -104,7 +104,7 @@ context "with some drives and boot configuration at JSON" do let(:config_json) do { - boot: { configure: true, device: "/dev/sdb" }, + boot: { configure: true, device: "/dev/sdb" }, drives: [ { ptableType: "gpt", @@ -160,19 +160,21 @@ expect(partitions).to contain_exactly( an_object_having_attributes( filesystem: have_attributes(path: "/"), - size: have_attributes(default: true, min: 5.GiB, max: 10.GiB) + size: have_attributes(default: true, min: 5.GiB, max: 10.GiB) ), an_object_having_attributes( filesystem: have_attributes(path: "/home"), - size: have_attributes(default: true, min: 5.GiB, max: Y2Storage::DiskSize.unlimited) + size: have_attributes(default: true, min: 5.GiB, + max: Y2Storage::DiskSize.unlimited) ), an_object_having_attributes( filesystem: have_attributes(path: "/opt"), - size: have_attributes(default: true, min: 100.MiB, max: Y2Storage::DiskSize.unlimited) + size: have_attributes(default: true, min: 100.MiB, + max: Y2Storage::DiskSize.unlimited) ), an_object_having_attributes( filesystem: have_attributes(path: "swap"), - size: have_attributes( + size: have_attributes( default: true, min: Y2Storage::DiskSize.zero, max: Y2Storage::DiskSize.unlimited ) ) @@ -186,7 +188,7 @@ drives: [ { partitions: [ - { filesystem: { path: "/"}, size: "10 GiB" }, + { filesystem: { path: "/" }, size: "10 GiB" }, { filesystem: { path: "/home" }, size: "6Gb" }, { filesystem: { path: "/opt" }, size: 3221225472 }, { filesystem: { path: "swap" }, size: "6 Gib" } @@ -202,7 +204,7 @@ expect(partitions).to include( an_object_having_attributes( filesystem: have_attributes(path: "/"), - size: have_attributes(default: false, min: 10.GiB, max: 10.GiB) + size: have_attributes(default: false, min: 10.GiB, max: 10.GiB) ) ) end @@ -213,7 +215,7 @@ expect(partitions).to include( an_object_having_attributes( filesystem: have_attributes(path: "/opt"), - size: have_attributes(default: false, min: 3.GiB, max: 3.GiB) + size: have_attributes(default: false, min: 3.GiB, max: 3.GiB) ) ) end @@ -223,8 +225,8 @@ partitions = config.drives.first.partitions home_size = partitions.find { |p| p.filesystem.path == "/home" }.size swap_size = partitions.find { |p| p.filesystem.path == "swap" }.size - expect(swap_size.min.to_i).to eq 6*1024*1024*1024 - expect(home_size.max.to_i).to eq 6*1000*1000*1000 + expect(swap_size.min.to_i).to eq 6 * 1024 * 1024 * 1024 + expect(home_size.max.to_i).to eq 6 * 1000 * 1000 * 1000 end end @@ -237,7 +239,7 @@ expect(partitions).to include( an_object_having_attributes( filesystem: have_attributes(path: "/home"), - size: have_attributes(default: false, min: 6.GiB, max: 9.GiB) + size: have_attributes(default: false, min: 6.GiB, max: 9.GiB) ) ) end @@ -247,8 +249,8 @@ partitions = config.drives.first.partitions home_size = partitions.find { |p| p.filesystem.path == "/home" }.size swap_size = partitions.find { |p| p.filesystem.path == "swap" }.size - expect(home_size.min.to_i).to eq 6*1024*1024*1024 - expect(swap_size.max.to_i).to eq 6*1000*1000*1000 + expect(home_size.min.to_i).to eq 6 * 1024 * 1024 * 1024 + expect(swap_size.max.to_i).to eq 6 * 1000 * 1000 * 1000 end it "sets both min and max limits as requested if numbers are used" do @@ -257,11 +259,11 @@ expect(partitions).to include( an_object_having_attributes( filesystem: have_attributes(path: "swap"), - size: have_attributes(default: false, min: 1.GiB) + size: have_attributes(default: false, min: 1.GiB) ), an_object_having_attributes( filesystem: have_attributes(path: "/opt"), - size: have_attributes(default: false, min: 1.GiB, max: 3.GiB) + size: have_attributes(default: false, min: 1.GiB, max: 3.GiB) ) ) end @@ -272,7 +274,8 @@ expect(partitions).to include( an_object_having_attributes( filesystem: have_attributes(path: "/"), - size: have_attributes(default: false, min: 3.GiB, max: Y2Storage::DiskSize.unlimited) + size: have_attributes(default: false, min: 3.GiB, + max: Y2Storage::DiskSize.unlimited) ) ) end @@ -286,19 +289,19 @@ partitions: [ { filesystem: { path: "/", type: { btrfs: { snapshots: false } } }, - size: { min: "3 GiB" } + size: { min: "3 GiB" } }, { filesystem: { path: "/home" }, - size: { min: "6 GiB", max: "9 GiB" } + size: { min: "6 GiB", max: "9 GiB" } }, { filesystem: { path: "swap" }, - size: { min: 1073741824, max: "6 GB" } + size: { min: 1073741824, max: "6 GB" } }, { filesystem: { path: "/opt" }, - size: { min: "1073741824", max: 3221225472 } + size: { min: "1073741824", max: 3221225472 } } ] } @@ -317,19 +320,19 @@ partitions: [ { filesystem: { path: "/", type: { btrfs: { snapshots: false } } }, - size: ["3 GiB"] + size: ["3 GiB"] }, { filesystem: { path: "/home" }, - size: ["6 GiB", "9 GiB"] + size: ["6 GiB", "9 GiB"] }, { filesystem: { path: "swap" }, - size: [ 1073741824, "6 GB"] + size: [1073741824, "6 GB"] }, { filesystem: { path: "/opt" }, - size: ["1073741824", 3221225472] + size: ["1073741824", 3221225472] } ] } @@ -352,7 +355,7 @@ }, { filesystem: { path: "/opt" }, - size: { min: "6 GiB", max: "22 GiB" } + size: { min: "6 GiB", max: "22 GiB" } } ] } @@ -366,11 +369,12 @@ expect(partitions).to contain_exactly( an_object_having_attributes( filesystem: have_attributes(path: "/"), - size: have_attributes(default: true, min: 40.GiB, max: Y2Storage::DiskSize.unlimited) + size: have_attributes(default: true, min: 40.GiB, + max: Y2Storage::DiskSize.unlimited) ), an_object_having_attributes( filesystem: have_attributes(path: "/opt"), - size: have_attributes(default: false, min: 6.GiB, max: 22.GiB) + size: have_attributes(default: false, min: 6.GiB, max: 22.GiB) ) ) end @@ -387,7 +391,7 @@ }, { filesystem: { path: "/opt" }, - size: { min: "6 GiB", max: "22 GiB" } + size: { min: "6 GiB", max: "22 GiB" } } ] } @@ -401,11 +405,12 @@ expect(partitions).to contain_exactly( an_object_having_attributes( filesystem: have_attributes(path: "/"), - size: have_attributes(default: true, min: 40.GiB, max: Y2Storage::DiskSize.unlimited) + size: have_attributes(default: true, min: 40.GiB, + max: Y2Storage::DiskSize.unlimited) ), an_object_having_attributes( filesystem: have_attributes(path: "/opt"), - size: have_attributes(default: false, min: 6.GiB, max: 22.GiB) + size: have_attributes(default: false, min: 6.GiB, max: 22.GiB) ) ) end @@ -414,7 +419,7 @@ context "using 'default' for a partition that is fallback for others" do let(:config_json) { { drives: [{ partitions: partitions }] } } let(:root) do - { "filesystem": { "path": "/", type: { btrfs: { snapshots: false } } }, size: "default" } + { filesystem: { path: "/", type: { btrfs: { snapshots: false } } }, size: "default" } end let(:partitions) { [root] + other } @@ -427,14 +432,15 @@ expect(partitions).to contain_exactly( an_object_having_attributes( filesystem: have_attributes(path: "/"), - size: have_attributes(default: true, min: 10.GiB, max: Y2Storage::DiskSize.unlimited) + size: have_attributes(default: true, min: 10.GiB, + max: Y2Storage::DiskSize.unlimited) ) ) end end context "if the other partitions are included (even with non-exact name)" do - let(:other) { [ { "filesystem": { "path": "/home/"} } ] } + let(:other) { [{ filesystem: { path: "/home/" } }] } it "ignores the fallback sizes" do config = subject.convert @@ -442,7 +448,7 @@ expect(partitions).to include( an_object_having_attributes( filesystem: have_attributes(path: "/"), - size: have_attributes(default: true, min: 5.GiB, max: 10.GiB) + size: have_attributes(default: true, min: 5.GiB, max: 10.GiB) ) ) end @@ -472,8 +478,7 @@ context "if the filesystem specification contains some btrfs settings" do let(:filesystem) do { path: "/", - type: { btrfs: { snapshots: false, default_subvolume: "", subvolumes: ["tmp"] } } - } + type: { btrfs: { snapshots: false, default_subvolume: "", subvolumes: ["tmp"] } } } end it "uses the specified btrfs attributes" do @@ -492,9 +497,9 @@ let(:config_json) { { drives: [{ partitions: partitions }] } } let(:partitions) do [ - { "filesystem": { "path": "/" } }, - { "filesystem": { "path": "swap" } }, - { "filesystem": { "path": "/opt" } } + { filesystem: { path: "/" } }, + { filesystem: { path: "swap" } }, + { filesystem: { path: "/opt" } } ] end @@ -531,20 +536,20 @@ let(:partitions) do [ { - "id": "linux", "size": { "min": "10 GiB" }, - "filesystem": { "type": "xfs", "path": "/home" }, - "encryption": encryption_home + id: "linux", size: { min: "10 GiB" }, + filesystem: { type: "xfs", path: "/home" }, + encryption: encryption_home }, { - "size": { "min": "2 GiB" }, - "filesystem": { "type": "swap", "path": "swap" }, - "encryption": encryption_swap + size: { min: "2 GiB" }, + filesystem: { type: "swap", path: "swap" }, + encryption: encryption_swap } ] end let(:encryption_home) do - { "luks2": { "password": "notsecret", "keySize": 256 } } + { luks2: { password: "notsecret", keySize: 256 } } end let(:encryption_swap) { nil } @@ -567,7 +572,7 @@ end context "if only the password is provided" do - let(:encryption_home) { { "luks2": { "password": "notsecret" } } } + let(:encryption_home) { { luks2: { password: "notsecret" } } } let(:encryption_swap) { nil } it "uses the default derivation function" do @@ -577,8 +582,8 @@ an_object_having_attributes( filesystem: have_attributes(path: "/home"), encryption: have_attributes( - password: "notsecret", - method: Y2Storage::EncryptionMethod::LUKS2, + password: "notsecret", + method: Y2Storage::EncryptionMethod::LUKS2, pbkd_function: Y2Storage::PbkdFunction::ARGON2ID ) ), @@ -606,9 +611,9 @@ filesystem: have_attributes(path: "swap"), encryption: have_attributes( password: nil, - label: nil, - cipher: nil, - method: Y2Storage::EncryptionMethod::RANDOM_SWAP + label: nil, + cipher: nil, + method: Y2Storage::EncryptionMethod::RANDOM_SWAP ) ) ) @@ -626,12 +631,12 @@ let(:partitions) do [ { - "id": "Esp", "size": { "min": "10 GiB" }, - "filesystem": { "type": "xfs", "path": "/home" } + id: "Esp", size: { min: "10 GiB" }, + filesystem: { type: "xfs", path: "/home" } }, { - "size": { "min": "2 GiB" }, - "filesystem": { "type": "swap", "path": "swap" } + size: { min: "2 GiB" }, + filesystem: { type: "swap", path: "swap" } } ] end @@ -642,11 +647,11 @@ expect(partitions).to contain_exactly( an_object_having_attributes( filesystem: have_attributes(path: "/home"), - id: Y2Storage::PartitionId::ESP + id: Y2Storage::PartitionId::ESP ), an_object_having_attributes( filesystem: have_attributes(path: "swap"), - id: nil + id: nil ) ) end diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index 0d13f138eb..5af850cdac 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -42,7 +42,7 @@ let(:issues_list) { [] } let(:drives) { [drive0] } let(:drive0) { Agama::Storage::Configs::Drive.new.tap { |d| d.partitions = partitions0 } } - let(:partitions0) { [root_partition ] } + let(:partitions0) { [root_partition] } let(:root_partition) do Agama::Storage::Configs::Partition.new.tap do |part| part.filesystem = Agama::Storage::Configs::Filesystem.new.tap do |fs| @@ -115,7 +115,7 @@ end context "when encrypting some devices" do - let(:partitions0) { [root_partition, home_partition ] } + let(:partitions0) { [root_partition, home_partition] } let(:home_partition) do Agama::Storage::Configs::Partition.new.tap do |part| @@ -135,8 +135,8 @@ let(:home_encryption) do Agama::Storage::Configs::Encryption.new.tap do |enc| - enc.password = "notSecreT" - enc.method = encryption_method + enc.password = "notSecreT" + enc.method = encryption_method end end @@ -150,12 +150,12 @@ context "if the encryption settings contain all the detailed information" do let(:home_encryption) do Agama::Storage::Configs::Encryption.new.tap do |enc| - enc.password = "notSecreT" - enc.method = encryption_method - enc.pbkd_function = Y2Storage::PbkdFunction::ARGON2I - enc.label = "luks_label" - enc.cipher = "aes-xts-plain64" - enc.key_size = 512 + enc.password = "notSecreT" + enc.method = encryption_method + enc.pbkd_function = Y2Storage::PbkdFunction::ARGON2I + enc.label = "luks_label" + enc.cipher = "aes-xts-plain64" + enc.key_size = 512 end end @@ -166,11 +166,11 @@ end expect(partition.encrypted?).to eq true expect(partition.encryption).to have_attributes( - method: Y2Storage::EncryptionMethod::LUKS2, + method: Y2Storage::EncryptionMethod::LUKS2, password: "notSecreT", - pbkdf: Y2Storage::PbkdFunction::ARGON2I, - label: "luks_label", - cipher: "aes-xts-plain64", + pbkdf: Y2Storage::PbkdFunction::ARGON2I, + label: "luks_label", + cipher: "aes-xts-plain64", # libstorage-ng uses bytes instead of bits to represent the key size, contrary to # all LUKS documentation and to cryptsetup key_size: 64 @@ -190,7 +190,7 @@ proposal.propose expect(proposal.issues_list).to include an_object_having_attributes( description: /method 'luks2' is not available/, - severity: Agama::Issue::Severity::ERROR + severity: Agama::Issue::Severity::ERROR ) end end @@ -207,7 +207,7 @@ proposal.propose expect(proposal.issues_list).to include an_object_having_attributes( description: /'random_swap' is not a suitable method/, - severity: Agama::Issue::Severity::ERROR + severity: Agama::Issue::Severity::ERROR ) end end @@ -215,7 +215,7 @@ context "if the method requires a password but none is provided" do let(:home_encryption) do Agama::Storage::Configs::Encryption.new.tap do |enc| - enc.method = encryption_method + enc.method = encryption_method end end @@ -228,7 +228,7 @@ proposal.propose expect(proposal.issues_list).to include an_object_having_attributes( description: /No passphrase provided/, - severity: Agama::Issue::Severity::ERROR + severity: Agama::Issue::Severity::ERROR ) end end @@ -256,7 +256,7 @@ proposal.propose expect(proposal.issues_list).to include an_object_having_attributes( description: /optional drive/, - severity: Agama::Issue::Severity::WARN + severity: Agama::Issue::Severity::WARN ) end end @@ -273,14 +273,14 @@ proposal.propose expect(proposal.issues_list).to include an_object_having_attributes( description: /mandatory drive/, - severity: Agama::Issue::Severity::ERROR + severity: Agama::Issue::Severity::ERROR ) end end end context "when searching for a non-existent partition" do - let(:partitions0) { [root_partition, existing_partition ] } + let(:partitions0) { [root_partition, existing_partition] } let(:existing_partition) do Agama::Storage::Configs::Partition.new.tap do |part| part.search = Agama::Storage::Configs::Search.new.tap do |search| @@ -301,7 +301,7 @@ proposal.propose expect(proposal.issues_list).to include an_object_having_attributes( description: /optional partition/, - severity: Agama::Issue::Severity::WARN + severity: Agama::Issue::Severity::WARN ) end end @@ -318,7 +318,7 @@ proposal.propose expect(proposal.issues_list).to include an_object_having_attributes( description: /mandatory partition/, - severity: Agama::Issue::Severity::ERROR + severity: Agama::Issue::Severity::ERROR ) end end From bcc9cf757d9bae85bb707b06c53999f2ab072330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Iv=C3=A1n=20L=C3=B3pez=20Gonz=C3=A1lez?= Date: Tue, 20 Aug 2024 13:32:48 +0100 Subject: [PATCH 19/21] storage: Documentation andd rubocop fixes --- service/lib/agama/storage/config.rb | 2 +- .../config_conversions/partition/from_json.rb | 4 ++-- .../config_conversions/size/from_json.rb | 17 +++++++++-------- service/lib/agama/storage/configs/btrfs.rb | 4 ++-- service/lib/agama/storage/configs/drive.rb | 4 +++- service/lib/agama/storage/configs/filesystem.rb | 17 ++++++++++++++--- service/lib/agama/storage/configs/partition.rb | 10 ++++++++++ service/lib/agama/storage/configs/search.rb | 3 ++- service/lib/agama/storage/configs/size.rb | 6 ++++++ service/lib/y2storage/agama_proposal.rb | 2 ++ .../y2storage/proposal/agama_device_planner.rb | 2 ++ .../y2storage/proposal/agama_devices_creator.rb | 8 ++++++-- .../y2storage/proposal/agama_devices_planner.rb | 3 +++ .../y2storage/proposal/agama_drive_planner.rb | 3 +++ .../lib/y2storage/proposal/agama_lvm_helper.rb | 5 ++++- .../lib/y2storage/proposal/agama_searcher.rb | 4 ++-- .../lib/y2storage/proposal/agama_space_maker.rb | 3 +++ 17 files changed, 74 insertions(+), 23 deletions(-) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index bd2287c5cc..757b477c1f 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -74,7 +74,7 @@ def implicit_boot_device drive.partitions.any? { |p| p.filesystem.root? } end - root_drive&.found_device.name + root_drive&.found_device&.name end def calculate_default_sizes(volume_builder) diff --git a/service/lib/agama/storage/config_conversions/partition/from_json.rb b/service/lib/agama/storage/config_conversions/partition/from_json.rb index ec92e3de63..47e7736d4e 100644 --- a/service/lib/agama/storage/config_conversions/partition/from_json.rb +++ b/service/lib/agama/storage/config_conversions/partition/from_json.rb @@ -65,7 +65,7 @@ def convert # @return [Y2Storage::PartitionId, nil] def convert_id - value = partition_json.dig(:id) + value = partition_json[:id] return unless value Y2Storage::PartitionId.find(value) @@ -73,7 +73,7 @@ def convert_id # @return [Configs::Size] def convert_size - size_json = partition_json.dig(:size) + size_json = partition_json[:size] return Configs::Size.new unless size_json Size::FromJSON.new(size_json).convert diff --git a/service/lib/agama/storage/config_conversions/size/from_json.rb b/service/lib/agama/storage/config_conversions/size/from_json.rb index 719e5751a4..f140ff9ed4 100644 --- a/service/lib/agama/storage/config_conversions/size/from_json.rb +++ b/service/lib/agama/storage/config_conversions/size/from_json.rb @@ -53,14 +53,15 @@ def convert # @return [Y2Storage::DiskSize, nil] def convert_size(field) - value = - if size_json.is_a?(Hash) - size_json[field] - elsif size_json.is_a?(Array) - field == :max ? size_json[1] : size_json[0] - else - size_json - end + value = case size_json + when Hash + size_json[field] + when Array + field == :max ? size_json[1] : size_json[0] + else + size_json + end + return unless value begin diff --git a/service/lib/agama/storage/configs/btrfs.rb b/service/lib/agama/storage/configs/btrfs.rb index db2627decb..6b4ed490f0 100644 --- a/service/lib/agama/storage/configs/btrfs.rb +++ b/service/lib/agama/storage/configs/btrfs.rb @@ -34,8 +34,8 @@ class Btrfs attr_accessor :read_only alias_method :read_only?, :read_only - # @return [Array, nil] if nil, a historical fallback list may - # be applied depending on the mount path of the volume + # @return [Array, nil] if nil, a historical fallback list + # may be applied depending on the mount path of the volume attr_accessor :subvolumes # @return [String] diff --git a/service/lib/agama/storage/configs/drive.rb b/service/lib/agama/storage/configs/drive.rb index 0b063a3bf8..60e207ee05 100644 --- a/service/lib/agama/storage/configs/drive.rb +++ b/service/lib/agama/storage/configs/drive.rb @@ -24,7 +24,9 @@ module Agama module Storage module Configs + # Drive configuration. class Drive + # @return [Search] attr_accessor :search # @return [Encryption] @@ -33,12 +35,12 @@ class Drive # @return [Filesystem] attr_accessor :filesystem + # @return [Y2Storage::PartitionTables::Type] attr_accessor :ptable_type # @return [Array] attr_accessor :partitions - # @param mount_path [String] def initialize @partitions = [] end diff --git a/service/lib/agama/storage/configs/filesystem.rb b/service/lib/agama/storage/configs/filesystem.rb index 762116a8c6..940f9cab30 100644 --- a/service/lib/agama/storage/configs/filesystem.rb +++ b/service/lib/agama/storage/configs/filesystem.rb @@ -24,21 +24,32 @@ module Agama module Storage module Configs + # File system configuration. class Filesystem - # @return [Pathname] Object that represents the root path + # @return [Pathname] Object that represents the root path. ROOT_PATH = Pathname.new("/").freeze + # @return [String, nil] attr_accessor :path - # @return [Configs::FilesystemType] + + # @return [Configs::FilesystemType, nil] attr_accessor :type + + # @return [String, nil] attr_accessor :label + + # @return [Array] attr_accessor :mkfs_options + + # @return [Array] attr_accessor :mount_options + + # @return [Y2Storage::Filesystems::MountByType, nil] attr_accessor :mount_by def initialize @mount_options = [] - @mkfs = [] + @mkfs_options = [] end # Whether the given path is equivalent to {#path} diff --git a/service/lib/agama/storage/configs/partition.rb b/service/lib/agama/storage/configs/partition.rb index ebcd3042d3..5f1c54b8d5 100644 --- a/service/lib/agama/storage/configs/partition.rb +++ b/service/lib/agama/storage/configs/partition.rb @@ -22,11 +22,21 @@ module Agama module Storage module Configs + # Partition configuration. class Partition + # @return [Search] attr_accessor :search + + # @return [Y2Storage::PartitionId, nil] attr_accessor :id + + # @return [Size, nil] attr_accessor :size + + # @return [Encryption, nil] attr_accessor :encryption + + # @return [Filesystem, nil] attr_accessor :filesystem def search_device(partitionable, used_sids) diff --git a/service/lib/agama/storage/configs/search.rb b/service/lib/agama/storage/configs/search.rb index c55f195ae8..bb7e9e1a17 100644 --- a/service/lib/agama/storage/configs/search.rb +++ b/service/lib/agama/storage/configs/search.rb @@ -22,6 +22,7 @@ module Agama module Storage module Configs + # Search configuration. class Search attr_reader :device attr_accessor :if_not_found @@ -41,7 +42,7 @@ def skip_device? def find(_setting, candidate_devs, used_sids) devices = candidate_devs.reject { |d| used_sids.include?(d.sid) } @resolved = true - @device = devices.sort_by(&:name).first + @device = devices.min_by(&:name) end end end diff --git a/service/lib/agama/storage/configs/size.rb b/service/lib/agama/storage/configs/size.rb index 82399c90c6..5d46a12586 100644 --- a/service/lib/agama/storage/configs/size.rb +++ b/service/lib/agama/storage/configs/size.rb @@ -22,9 +22,15 @@ module Agama module Storage module Configs + # Size configuration. class Size + # @return [Boolean] attr_accessor :default + + # @return [Y2Storage::DiskSize, nil] attr_accessor :min + + # @return [Y2Storage::DiskSize, nil] attr_accessor :max def initialize diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index 1509ed365f..f59e7885c0 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright (c) [2024] SUSE LLC # # All Rights Reserved. diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index 300d790976..b068b17423 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright (c) [2024] SUSE LLC # # All Rights Reserved. diff --git a/service/lib/y2storage/proposal/agama_devices_creator.rb b/service/lib/y2storage/proposal/agama_devices_creator.rb index 26c82e4428..a2a475c42e 100644 --- a/service/lib/y2storage/proposal/agama_devices_creator.rb +++ b/service/lib/y2storage/proposal/agama_devices_creator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright (c) [2017-2020] SUSE LLC # # All Rights Reserved. @@ -73,7 +75,8 @@ def populated_devicegraph(planned_devices, disk_names, space_maker) attr_reader :space_maker - # @return [Proposal::CreatorResult] Current result containing the devices that have been created + # @return [Proposal::CreatorResult] Current result containing the devices that have been + # created attr_reader :creator_result # @return [Devicegraph] Current devicegraph @@ -120,7 +123,8 @@ def process_existing_partitionables space_result = provide_space(partitions, original_graph, lvm_helper) partition_creator = PartitionCreator.new(space_result[:devicegraph]) - self.creator_result = partition_creator.create_partitions(space_result[:partitions_distribution]) + self.creator_result = + partition_creator.create_partitions(space_result[:partitions_distribution]) # This may be here or before create_partitions. # diff --git a/service/lib/y2storage/proposal/agama_devices_planner.rb b/service/lib/y2storage/proposal/agama_devices_planner.rb index 0e42a4e7d7..e26f1ba6f0 100644 --- a/service/lib/y2storage/proposal/agama_devices_planner.rb +++ b/service/lib/y2storage/proposal/agama_devices_planner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright (c) [2024] SUSE LLC # # All Rights Reserved. @@ -22,6 +24,7 @@ module Y2Storage module Proposal + # Devices planner for Agama. class AgamaDevicesPlanner include Yast::Logger diff --git a/service/lib/y2storage/proposal/agama_drive_planner.rb b/service/lib/y2storage/proposal/agama_drive_planner.rb index f2d585c2be..873bacf142 100644 --- a/service/lib/y2storage/proposal/agama_drive_planner.rb +++ b/service/lib/y2storage/proposal/agama_drive_planner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright (c) [2024] SUSE LLC # # All Rights Reserved. @@ -21,6 +23,7 @@ module Y2Storage module Proposal + # Drive planner for Agama. class AgamaDrivePlanner < AgamaDevicePlanner # @param settings [Agama::Storage::Settings::Drive] # @return [Array] diff --git a/service/lib/y2storage/proposal/agama_lvm_helper.rb b/service/lib/y2storage/proposal/agama_lvm_helper.rb index 78518b3dd4..c4ffd74933 100644 --- a/service/lib/y2storage/proposal/agama_lvm_helper.rb +++ b/service/lib/y2storage/proposal/agama_lvm_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright (c) [2024] SUSE LLC # # All Rights Reserved. @@ -22,6 +24,7 @@ module Y2Storage module Proposal + # LVM helper for Agama. class AgamaLvmHelper < LvmHelper # Initialize. def initialize(lvm_lvs) @@ -35,7 +38,7 @@ def guided_settings Y2Storage::ProposalSettings.new_for_current_product.tap do |target| target.lvm_vg_strategy = :use_needed target.lvm_vg_reuse = false - # TODO: + # TODO: Add encryption options. target.encryption_password = nil # target.encryption_pbkdf # target.encryption_method diff --git a/service/lib/y2storage/proposal/agama_searcher.rb b/service/lib/y2storage/proposal/agama_searcher.rb index 42925ee08d..f747ab212b 100644 --- a/service/lib/y2storage/proposal/agama_searcher.rb +++ b/service/lib/y2storage/proposal/agama_searcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright (c) [2024] SUSE LLC # # All Rights Reserved. @@ -25,8 +27,6 @@ class AgamaSearcher include Yast::Logger include Yast::I18n - # Constructor - # def initialize textdomain "agama" end diff --git a/service/lib/y2storage/proposal/agama_space_maker.rb b/service/lib/y2storage/proposal/agama_space_maker.rb index baf92aa017..18c56d5c0b 100644 --- a/service/lib/y2storage/proposal/agama_space_maker.rb +++ b/service/lib/y2storage/proposal/agama_space_maker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Copyright (c) [2024] SUSE LLC # # All Rights Reserved. @@ -22,6 +24,7 @@ module Y2Storage module Proposal + # Space maker for Agama. class AgamaSpaceMaker < SpaceMaker # Initialize. def initialize(disk_analyzer, settings) From 6a8e048acba276cd09b00e8b27a1d58412ac67ef Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 20 Aug 2024 17:02:26 +0200 Subject: [PATCH 20/21] Yardoc improvements and small code fixes --- service/lib/agama/storage/config.rb | 50 ++++++++++++++--- .../block_device/from_json.rb | 2 +- service/lib/agama/storage/configs/boot.rb | 1 + service/lib/agama/storage/configs/btrfs.rb | 1 + service/lib/agama/storage/configs/drive.rb | 30 ++++++++-- .../lib/agama/storage/configs/encryption.rb | 29 +++++++++- .../lib/agama/storage/configs/filesystem.rb | 1 + .../lib/agama/storage/configs/partition.rb | 22 +++++--- service/lib/agama/storage/configs/search.rb | 21 ++++++- service/lib/agama/storage/configs/size.rb | 2 + .../lib/agama/storage/encryption_settings.rb | 2 +- service/lib/y2storage/agama_proposal.rb | 55 +++++++++++-------- .../proposal/agama_device_planner.rb | 32 +++++++---- .../proposal/agama_devices_creator.rb | 36 ++++++------ .../proposal/agama_devices_planner.rb | 22 ++++---- .../y2storage/proposal/agama_drive_planner.rb | 13 +++-- .../y2storage/proposal/agama_lvm_helper.rb | 5 +- .../lib/y2storage/proposal/agama_searcher.rb | 26 ++++++++- .../y2storage/proposal/agama_space_maker.rb | 5 +- 19 files changed, 255 insertions(+), 100 deletions(-) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index 757b477c1f..1deeba2a6e 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -30,10 +30,19 @@ class Config # @return [Configs::Boot] attr_accessor :boot + # @return [Array] attr_accessor :drives + + # @return [Array] attr_accessor :volume_groups + + # @return [Array] attr_accessor :md_raids + + # @return [Array] attr_accessor :btrfs_raids + + # @return [Array] attr_accessor :nfs_mounts def initialize @@ -55,19 +64,25 @@ def self.new_from_json(config_json, product_config:) ConfigConversions::FromJSON.new(config_json, product_config: product_config).convert end + # Name of the device that will presumably be used to boot the target system + # + # @return [String, nil] nil if there is no enough information to infer a possible boot disk def boot_device explicit_boot_device || implicit_boot_device end - # Device used for booting. + # Device used for booting the target system # - # @return [String, nil] + # @return [String, nil] nil if no disk is explicitly chosen def explicit_boot_device return nil unless boot.configure? boot.device end + # Device that seems to be expected to be used for booting, according to the drive definitions + # + # @return [String, nil] nil if the information cannot be inferred from the list of drives def implicit_boot_device # NOTE: preliminary implementation with very simplistic checks root_drive = drives.find do |drive| @@ -77,6 +92,10 @@ def implicit_boot_device root_drive&.found_device&.name end + # Sets min and max sizes for all partitions and logical volumes with default size + # + # @param volume_builder [VolumeTemplatesBuilder] used to check the configuration of the + # product volume templates def calculate_default_sizes(volume_builder) default_size_devices.each do |dev| dev.size.min = default_size(dev, :min, volume_builder) @@ -86,18 +105,26 @@ def calculate_default_sizes(volume_builder) private + # return [Array] def filesystems (drives + partitions).map(&:filesystem).compact end + # return [Array] def partitions drives.flat_map(&:partitions) end + # return [Array] def default_size_devices partitions.select { |p| p.size&.default? } end + # Min or max size that should be used for the given partition or logical volume + # + # @param device [Configs::Partition] device configured to have a default size + # @param attr [Symbol] :min or :max + # @param builder [VolumeTemplatesBuilder] see {#calculate_default_sizes} def default_size(device, attr, builder) path = device.filesystem&.path || "" vol = builder.for(path) @@ -108,7 +135,7 @@ def default_size(device, attr, builder) return vol.send(:"#{attr}_size") unless vol.auto_size? outline = vol.outline - size = size_with_fallbacks(device, outline, attr, builder) + size = size_with_fallbacks(outline, attr, builder) size = size_with_ram(size, outline) size_with_snapshots(size, device, outline) end @@ -121,7 +148,8 @@ def fallback_size(attr) Y2Storage::DiskSize.unlimited end - def size_with_fallbacks(_device, outline, attr, builder) + # @see #default_size + def size_with_fallbacks(outline, attr, builder) fallback_paths = outline.send(:"#{attr}_size_fallback_for") missing_paths = fallback_paths.reject { |p| proposed_path?(p) } @@ -129,16 +157,14 @@ def size_with_fallbacks(_device, outline, attr, builder) missing_paths.inject(size) { |total, p| total + builder.for(p).send(:"#{attr}_size") } end - def proposed_path?(path) - filesystems.any? { |fs| fs.path?(path) } - end - + # @see #default_size def size_with_ram(initial_size, outline) return initial_size unless outline.adjust_by_ram? [initial_size, ram_size].max end + # @see #default_size def size_with_snapshots(initial_size, device, outline) return initial_size unless device.filesystem.btrfs_snapshots? return initial_size unless outline.snapshots_affect_sizes? @@ -151,6 +177,14 @@ def size_with_snapshots(initial_size, device, outline) end end + # Whether there is a separate filesystem configured for the given path + # + # @param path [String, Pathname] + # @return [Boolean] + def proposed_path?(path) + filesystems.any? { |fs| fs.path?(path) } + end + # Return the total amount of RAM as DiskSize # # @return [DiskSize] current RAM size diff --git a/service/lib/agama/storage/config_conversions/block_device/from_json.rb b/service/lib/agama/storage/config_conversions/block_device/from_json.rb index feb8e99793..768c2ba358 100644 --- a/service/lib/agama/storage/config_conversions/block_device/from_json.rb +++ b/service/lib/agama/storage/config_conversions/block_device/from_json.rb @@ -34,7 +34,7 @@ module BlockDevice class FromJSON # @todo Replace settings and volume_builder params by a ProductDefinition. # - # @param drive_json [Hash] + # @param blk_device_json [Hash] # @param settings [ProposalSettings] # @param volume_builder [VolumeTemplatesBuilder] def initialize(blk_device_json, settings:, volume_builder:) diff --git a/service/lib/agama/storage/configs/boot.rb b/service/lib/agama/storage/configs/boot.rb index a5c76657b5..88071b086d 100644 --- a/service/lib/agama/storage/configs/boot.rb +++ b/service/lib/agama/storage/configs/boot.rb @@ -36,6 +36,7 @@ class Boot # device for allocating root. attr_accessor :device + # Constructor def initialize @configure = true end diff --git a/service/lib/agama/storage/configs/btrfs.rb b/service/lib/agama/storage/configs/btrfs.rb index 6b4ed490f0..1dd3cf4dec 100644 --- a/service/lib/agama/storage/configs/btrfs.rb +++ b/service/lib/agama/storage/configs/btrfs.rb @@ -41,6 +41,7 @@ class Btrfs # @return [String] attr_accessor :default_subvolume + # Constructor def initialize @snapshots = false @read_only = false diff --git a/service/lib/agama/storage/configs/drive.rb b/service/lib/agama/storage/configs/drive.rb index 60e207ee05..36ddff9351 100644 --- a/service/lib/agama/storage/configs/drive.rb +++ b/service/lib/agama/storage/configs/drive.rb @@ -24,41 +24,59 @@ module Agama module Storage module Configs - # Drive configuration. + # Section of the configuration representing a device that is expected to exist in the target + # system and that can be used as a regular disk. class Drive - # @return [Search] + # @return [Search, nil] attr_accessor :search - # @return [Encryption] + # @return [Encryption, nil] attr_accessor :encryption - # @return [Filesystem] + # @return [Filesystem, nil] attr_accessor :filesystem - # @return [Y2Storage::PartitionTables::Type] + # @return [Y2Storage::PartitionTables::Type, nil] attr_accessor :ptable_type # @return [Array] attr_accessor :partitions + # Constructor def initialize @partitions = [] end + # Resolves the search, so a devices of the given devicegraph is associated to the drive if + # possible + # + # Since all drives are expected to match a real device in the system, this creates a default + # search if that was ommited. + # + # @param devicegraph [Y2Storage::Devicegraph] source of the search + # @param used_sids [Array] SIDs of the devices that are already associated to + # another drive, so they cannot be associated to this def search_device(devicegraph, used_sids) @search ||= default_search devs = devicegraph.blk_devices.select { |d| d.is?(:disk_device, :stray_blk_device) } - search.find(self, devs, used_sids) + search.find(devs, used_sids) end + # @return [Search] def default_search Search.new end + # Device resulting from a previous call to {#search_device} + # + # @return [Y2Storage::Device, nil] def found_device search&.device end + # Whether the drive definition contains partition definitions + # + # @return [Boolean] def partitions? partitions.any? end diff --git a/service/lib/agama/storage/configs/encryption.rb b/service/lib/agama/storage/configs/encryption.rb index aeb186578e..5b28ed4ea6 100644 --- a/service/lib/agama/storage/configs/encryption.rb +++ b/service/lib/agama/storage/configs/encryption.rb @@ -19,15 +19,42 @@ # To contact SUSE LLC about this file by physical or electronic mail, you may # find current contact information at www.suse.com. +require "y2storage/secret_attributes" + module Agama module Storage module Configs + # Configuration setting describing the desired encryption for a device class Encryption + include Y2Storage::SecretAttributes + + # @return [Y2Storage::EncryptionMethod::Base] attr_accessor :method - attr_accessor :password + + # @!attribute password + # Password to use if the encryption method requires one + # @return [String, nil] nil if undetermined or not needed + secret_attr :password + + # PBKD function to use for LUKS2 + # + # @return [Y2Storage::PbkdFunction, nil] Can be nil for methods that are not LUKS2 attr_accessor :pbkd_function + + # Optional LUKS2 label + # + # @return [String, nil] attr_accessor :label + + # Optional cipher if LUKS is going to be used + # + # @return [String, nil] attr_accessor :cipher + + # Specific key size (in bits) if LUKS is going to be used + # + # @return [Integer,nil] If nil, the default key size will be used. If an integer + # value is used, it has to be a multiple of 8 attr_accessor :key_size end end diff --git a/service/lib/agama/storage/configs/filesystem.rb b/service/lib/agama/storage/configs/filesystem.rb index 940f9cab30..4b126ddd26 100644 --- a/service/lib/agama/storage/configs/filesystem.rb +++ b/service/lib/agama/storage/configs/filesystem.rb @@ -71,6 +71,7 @@ def root? path?(ROOT_PATH) end + # @return [Boolean] def btrfs_snapshots? return false unless type&.fs_type&.is?(:btrfs) diff --git a/service/lib/agama/storage/configs/partition.rb b/service/lib/agama/storage/configs/partition.rb index 5f1c54b8d5..fbe38f6335 100644 --- a/service/lib/agama/storage/configs/partition.rb +++ b/service/lib/agama/storage/configs/partition.rb @@ -22,15 +22,15 @@ module Agama module Storage module Configs - # Partition configuration. + # Section of the configuration representing a partition class Partition - # @return [Search] + # @return [Search, nil] attr_accessor :search # @return [Y2Storage::PartitionId, nil] attr_accessor :id - # @return [Size, nil] + # @return [Size, nil] can be nil for reused partitions attr_accessor :size # @return [Encryption, nil] @@ -39,15 +39,21 @@ class Partition # @return [Filesystem, nil] attr_accessor :filesystem + # Resolves the search if the partition specification contains any, associating a partition + # of the given device if possible + # + # @param partitionable [Y2Storage::Partitionable] scope for the search + # @param used_sids [Array] 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) - @search ||= default_search - search.find(self, partitionable.partitions, used_sids) - end + return unless search - def default_search - Search.new + search.find(partitionable.partitions, used_sids) end + # Device resulting from a previous call to {#search_device} + # + # @return [Y2Storage::Device, nil] def found_device search&.device end diff --git a/service/lib/agama/storage/configs/search.rb b/service/lib/agama/storage/configs/search.rb index bb7e9e1a17..2c99bf7f77 100644 --- a/service/lib/agama/storage/configs/search.rb +++ b/service/lib/agama/storage/configs/search.rb @@ -22,24 +22,41 @@ module Agama module Storage module Configs - # Search configuration. + # Configuration used to match drives, partitions and other device definition with devices + # from the initial devicegraph class Search + # Found device, if any + # @return [Y2Storage::Device, nil] attr_reader :device + + # What to do if the search does not match with the expected number of devices + # @return [Symbol] :create, :skip or :error attr_accessor :if_not_found + # Constructor def initialize @if_not_found = :skip end + # Whether {#find} was already called + # + # @return [Boolean] def resolved? !!@resolved end + # Whether the section containing the search should be skipped + # + # @return [Boolean] def skip_device? resolved? && device.nil? && if_not_found == :skip end - def find(_setting, candidate_devs, used_sids) + # Resolve the search, associating the corresponding device to {#device} + # + # @param candidate_devs [Array] candidate devices + # @param used_sids [Array] 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) diff --git a/service/lib/agama/storage/configs/size.rb b/service/lib/agama/storage/configs/size.rb index 5d46a12586..5c3d408d84 100644 --- a/service/lib/agama/storage/configs/size.rb +++ b/service/lib/agama/storage/configs/size.rb @@ -33,10 +33,12 @@ class Size # @return [Y2Storage::DiskSize, nil] attr_accessor :max + # Constructor def initialize @default = true end + # @return [Boolean] def default? !!@default end diff --git a/service/lib/agama/storage/encryption_settings.rb b/service/lib/agama/storage/encryption_settings.rb index eb755d5b62..2d6e2748e9 100644 --- a/service/lib/agama/storage/encryption_settings.rb +++ b/service/lib/agama/storage/encryption_settings.rb @@ -35,7 +35,7 @@ class EncryptionSettings ].freeze private_constant :METHODS - # @!attribute encryption_password + # @!attribute password # Password to use when creating new encryption devices # @return [String, nil] nil if undetermined secret_attr :password diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index f59e7885c0..6628e41dce 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -31,9 +31,9 @@ module Y2Storage # Class to calculate a storage proposal for autoinstallation using Agama # - # @example Creating a proposal from the current AutoYaST profile - # partitioning = Yast::Profile.current["partitioning"] - # proposal = Y2Storage::AutoinstProposal.new(partitioning: partitioning) + # @example Creating a proposal from the current Agama configuration + # config = Agama::Storage::Config.new_from_json(config_json) + # proposal = Y2Storage::AgamaProposal.new(config) # proposal.proposed? # => false # proposal.devices # => nil # proposal.planned_devices # => nil @@ -52,11 +52,11 @@ class AgamaProposal < Proposal::Base # Constructor # - # @param settings [Agama::Storage::Settings] proposal settings + # @param initial_settings [Agama::Storage::Config] Agama storage settings # @param devicegraph [Devicegraph] starting point. If nil, then probed devicegraph # will be used # @param disk_analyzer [DiskAnalyzer] by default, the method will create a new one - # based on the initial devicegraph or will use the one in {StorageManager} if + # based on the initial devicegraph or will use the one from the StorageManager if # starting from probed (i.e. 'devicegraph' argument is also missing) # @param issues_list [Array] sid of devices where partition table was deleted from def remove_empty_partition_tables(devicegraph) devices = drives_with_empty_partition_table(devicegraph) @@ -184,14 +188,11 @@ def drives_with_empty_partition_table(devicegraph) # Planned partitions that will hold the given planned devices # - # NOTE: - # Extracted to a separate method because it's something that may need some extra logic - # in the future. See the equivalent method at DevicegraphGenerator. - # - # @param planned_devices [Array] list of planned devices # @return [Array] def partitions_for_clean - # NOTE: take into account (partitions on) pre-existing RAIDs? + # The current logic is quite trivial, but this is implemented as a separate method because + # some extra logic is expected in the future (eg. considering partitions on pre-existing + # RAIDs and more stuff). See the equivalent method at DevicegraphGenerator. planned_devices.partitions end @@ -200,8 +201,9 @@ def protect_sids space_maker.protected_sids = planned_devices.all.select(&:reuse?).map(&:reuse_sid) end - # Creates planned devices on a given devicegraph + # Creates the planned devices on a given devicegraph # + # @param devicegraph [Devicegraph] the graph gets modified def create_devices(devicegraph) devices_creator = Proposal::AgamaDevicesCreator.new(devicegraph, issues_list) names = settings.drives.map(&:found_device).compact.map(&:name) @@ -209,6 +211,11 @@ def create_devices(devicegraph) result = devices_creator.populated_devicegraph(planned_devices, names, space_maker) end + # Equivalent device at the given devicegraph for the given configuration setting (eg. drive) + # + # @param drive [Agama::Storage::Configs::Drive] + # @param devicegraph [Devicegraph] + # @return [Device] def device_for(drive, devicegraph) return unless drive.found_device diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index b068b17423..e696a7d968 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -29,14 +29,19 @@ class AgamaDevicePlanner include Yast::I18n # @!attribute [r] devicegraph + # Devicegraph to be used as starting point. # @return [Devicegraph] attr_reader :devicegraph # @!attribute [r] issues_list + # List of issues to register any found problem + # @return [Array] attr_reader :issues_list - # @param devicegraph [Devicegraph] Devicegraph to be used as starting point. - # @param issues_list [AutoinstIssues::List] List of issues to register them. + # Constructor + # + # @param devicegraph [Devicegraph] see {#devicegraph} + # @param issues_list [Array] see {#issues_list} def initialize(devicegraph, issues_list) textdomain "agama" @@ -54,32 +59,32 @@ def planned_devices(_setting) private # @param planned [Planned::Disk, Planned::Partition] - # @param settings [#format, #mount] + # @param settings [#encryption, #filesystem] def configure_device(planned, settings) configure_encryption(planned, settings.encryption) if settings.encryption configure_filesystem(planned, settings.filesystem) if settings.filesystem end # @param planned [Planned::Disk, Planned::Partition] - # @param settings [Agama::Storage::Settings::Format] + # @param settings [Agama::Storage::Configs::Filesystem] def configure_filesystem(planned, settings) planned.mount_point = settings.path planned.mount_by = settings.mount_by planned.fstab_options = settings.mount_options - planned.mkfs_options = settings.mkfs_options + planned.mkfs_options = settings.mkfs_options.join(",") planned.label = settings.label configure_filesystem_type(planned, settings.type) if settings.type end # @param planned [Planned::Disk, Planned::Partition] - # @param settings [Agama::Storage::Settings::Filesystem] + # @param settings [Agama::Storage::Configs::FilesystemType] def configure_filesystem_type(planned, settings) planned.filesystem_type = settings.fs_type configure_btrfs(planned, settings.btrfs) if settings.btrfs end # @param planned [Planned::Disk, Planned::Partition] - # @param settings [Agama::Storage::Settings::Btrfs] + # @param settings [Agama::Storage::Configs::Btrfs] def configure_btrfs(planned, settings) # TODO: we need to discuss what to do with transactional systems and the read_only # property. We are not sure whether those things should be configurable by the user. @@ -102,22 +107,26 @@ def configure_encryption(planned, settings) check_encryption(planned) end + # @see #configure_encryption def check_encryption(dev) issues_list << issue_missing_enc_password(dev) if missing_enc_password?(dev) issues_list << issue_available_enc_method(dev) unless dev.encryption_method.available? issues_list << issue_wrong_enc_method(dev) unless supported_enc_method?(dev) end + # @see #check_encryption def missing_enc_password?(planned) return false unless planned.encryption_method&.password_required? planned.encryption_password.nil? || planned.encryption_password.empty? end + # @see #check_encryption def supported_enc_method?(planned) planned.supported_encryption_method?(planned.encryption_method) end + # @see #check_encryption def issue_missing_enc_password(planned) msg = format( # TRANSLATORS: 'crypt_method' is the identifier of the method to encrypt the device (like @@ -128,6 +137,7 @@ def issue_missing_enc_password(planned) encryption_issue(msg) end + # @see #check_encryption def issue_available_enc_method(planned) msg = format( # TRANSLATORS: 'crypt_method' is the identifier of the method to encrypt the device (like @@ -138,6 +148,7 @@ def issue_available_enc_method(planned) encryption_issue(msg) end + # @see #check_encryption def issue_wrong_enc_method(planned) msg = format( # TRANSLATORS: 'crypt_method' is the name of the method to encrypt the device (like @@ -148,6 +159,7 @@ def issue_wrong_enc_method(planned) encryption_issue(msg) end + # @see #check_encryption def encryption_issue(message) Agama::Issue.new( message, @@ -157,7 +169,7 @@ def encryption_issue(message) end # @param planned [Planned::Partition] - # @param settings [Agama::Storage::Settings::Size] + # @param settings [Agama::Storage::Configs::Size] def configure_size(planned, settings) planned.min_size = settings.min planned.max_size = settings.max @@ -165,14 +177,14 @@ def configure_size(planned, settings) end # @param planned [Planned::Disk] - # @param settings [Agama::Storage::Settings::Drive] + # @param settings [Agama::Storage::Configs::Drive] def configure_partitions(planned, settings) planned.partitions = settings.partitions.map do |partition_settings| planned_partition(partition_settings).tap { |p| p.disk = settings.found_device.name } end end - # @param settings [Agama::Storage::Settings::Partition] + # @param settings [Agama::Storage::Configs::Partition] # @return [Planned::Partition] def planned_partition(settings) Planned::Partition.new(nil, nil).tap do |planned| diff --git a/service/lib/y2storage/proposal/agama_devices_creator.rb b/service/lib/y2storage/proposal/agama_devices_creator.rb index a2a475c42e..1c273535a1 100644 --- a/service/lib/y2storage/proposal/agama_devices_creator.rb +++ b/service/lib/y2storage/proposal/agama_devices_creator.rb @@ -28,7 +28,7 @@ module Proposal class AgamaDevicesCreator include Yast::Logger - # @return [AutoinstIssues::List] List of found AutoYaST issues + # @return [Array] List of found issues attr_reader :issues_list # Constructor @@ -45,8 +45,9 @@ def initialize(original_graph, issues_list) # # @param planned_devices [Planned::DevicesCollection] Devices to create/reuse # @param disk_names [Array] Disks to consider + # @param space_maker [SpaceMaker] # - # @return [AutoinstCreatorResult] Result with new devicegraph in which all the + # @return [CreatorResult] Result with new devicegraph in which all the # planned devices have been allocated def populated_devicegraph(planned_devices, disk_names, space_maker) # Process planned partitions @@ -73,6 +74,7 @@ def populated_devicegraph(planned_devices, disk_names, space_maker) # @return [Array] Disks to consider attr_reader :disk_names + # @return [SpaceMaker] space maker to use during operation attr_reader :space_maker # @return [Proposal::CreatorResult] Current result containing the devices that have been @@ -104,13 +106,14 @@ def reset # Reuses and creates planned devices # - # @return [AutoinstCreatorResult] Result with new devicegraph in which all the + # @return [CreatorResult] Result with new devicegraph in which all the # planned devices have been allocated def process_devices process_existing_partitionables creator_result end + # @see #process_devices def process_existing_partitionables partitions = partitions_for_existing(planned_devices) @@ -140,39 +143,34 @@ def process_existing_partitionables planned.reuse!(devicegraph) end - # graph = create_separate_vgs(planned_devices, creator_result).devicegraph - - # if settings.use_lvm - # new_pvs = new_physical_volumes(space_result[:devicegraph], graph) - # graph = lvm_helper.create_volumes(graph, new_pvs) - # end - - # Needed or already part of other components? - # graph.mount_points.each(&:adjust_mount_options) + # This may be unexpected if the storage configuration provided by the user includes + # carefully crafted mount options but may be needed in weird situations for more automated + # proposals. Let's re-evaluate over time. + devicegraph.mount_points.each(&:adjust_mount_options) end + # @see #process_existing_partitionables def provide_space(planned_partitions, devicegraph, lvm_helper) result = space_maker.provide_space(devicegraph, planned_partitions, lvm_helper) log.info "Found enough space" result end + # @see #process_existing_partitionables def partitions_for_existing(planned_devices) # Maybe in the future this can include partitions on top of existing MDs # NOTE: simplistic implementation planned_devices.partitions.reject(&:reuse?) end - # Formats and/or mounts the disk like block devices (Xen virtual partitions and full disks) + # Formats and/or mounts the disk-like block devices + # + # XEN partitions (StrayBlkDevice) are intentionally left out for now # - # Add planned disk like devices to reuse list so they can be considered for lvm and raids - # later on. + # Add planned disks to reuse list so they can be considered for lvm and raids later on. def process_disk_like_devs # Do we do something about SpaceMaker here? I assume it was already done as mandatory - planned_devs = planned_devices.select do |dev| - dev.is_a?(Planned::StrayBlkDevice) || dev.is_a?(Planned::Disk) - end - + planned_devs = planned_devices.select { |d| d.is_a?(Planned::Disk) } planned_devs.each { |d| d.reuse!(devicegraph) } end end diff --git a/service/lib/y2storage/proposal/agama_devices_planner.rb b/service/lib/y2storage/proposal/agama_devices_planner.rb index e26f1ba6f0..4476f66cc1 100644 --- a/service/lib/y2storage/proposal/agama_devices_planner.rb +++ b/service/lib/y2storage/proposal/agama_devices_planner.rb @@ -30,10 +30,10 @@ class AgamaDevicesPlanner # Settings used to calculate the planned devices. # - # @return [Agama::Storage::Profile] + # @return [Agama::Storage::Config] attr_reader :settings - # @param settings [Agama::Storage::Profile] + # @param settings [Agama::Storage::Config] # @param issues_list [Array] def initialize(settings, issues_list) @settings = settings @@ -46,16 +46,15 @@ def initialize(settings, issues_list) # For the time being, this implements only stuff coming from partitition elements within # drive elements. # - # In the future this will also include planned devices that are a direct translations of - # those typically generated by the Guided Proposal. For those, note that: - # - For dedicated VGs it creates a Planned VG containing a Planned LV, but no PVs - # - For LVM volumes it create a Planned LV but associated to no planned VG - # - For partition volumes, it creates a planned partition, of course - # - # @param target [Symbol] see #planned_devices # @param devicegraph [Devicegraph] - # @return [Array] + # @return [Planned::DevicesCollection] def initial_planned_devices(devicegraph) + # In the future this will also include planned devices that are equivalent to + # those typically generated by the Guided Proposal. For those, note that: + # - For dedicated VGs it creates a Planned VG containing a Planned LV, but no PVs + # - For LVM volumes it create a Planned LV but associated to no planned VG + # - For partition volumes, it creates a planned partition, of course + devs = settings.drives.flat_map { |d| planned_for_drive(d, devicegraph) }.compact Planned::DevicesCollection.new(devs) end @@ -65,8 +64,7 @@ def initial_planned_devices(devicegraph) # @return [Array] List to register any found issue attr_reader :issues_list - # I'm leaving out intentionally support for StrayBlkDevice. As far as I know, - # the plan for SLE/Leap 16 is to drop XEN support + # @see #initial_planned_devices def planned_for_drive(drive, devicegraph) planner = AgamaDrivePlanner.new(devicegraph, issues_list) planner.planned_devices(drive) diff --git a/service/lib/y2storage/proposal/agama_drive_planner.rb b/service/lib/y2storage/proposal/agama_drive_planner.rb index 873bacf142..c8f0be0203 100644 --- a/service/lib/y2storage/proposal/agama_drive_planner.rb +++ b/service/lib/y2storage/proposal/agama_drive_planner.rb @@ -25,7 +25,7 @@ module Y2Storage module Proposal # Drive planner for Agama. class AgamaDrivePlanner < AgamaDevicePlanner - # @param settings [Agama::Storage::Settings::Drive] + # @param settings [Agama::Storage::Configs::Drive] # @return [Array] def planned_devices(settings) [planned_drive(settings)] @@ -33,7 +33,10 @@ def planned_devices(settings) private - # @param settings [Agama::Storage::Settings::Drive] + # Support for StrayBlkDevice is intentionally left out. As far as we know, the plan + # for SLE/Leap 16 is to drop XEN support + # + # @param settings [Agama::Storage::Configs::Drive] # @return [Planned::Disk] def planned_drive(settings) return planned_full_drive(settings) unless settings.partitions? @@ -41,7 +44,7 @@ def planned_drive(settings) planned_partitioned_drive(settings) end - # @param settings [Agama::Storage::Settings::Drive] + # @param settings [Agama::Storage::Configs::Drive] # @return [Planned::Disk] def planned_full_drive(settings) Planned::Disk.new.tap do |planned| @@ -50,7 +53,7 @@ def planned_full_drive(settings) end end - # @param settings [Agama::Storage::Settings::Drive] + # @param settings [Agama::Storage::Configs::Drive] # @return [Planned::Disk] def planned_partitioned_drive(settings) Planned::Disk.new.tap do |planned| @@ -60,7 +63,7 @@ def planned_partitioned_drive(settings) end # @param planned [Planned::Disk] - # @param settings [Agama::Storage::Settings::Drive] + # @param settings [Agama::Storage::Configs::Drive] def configure_drive(planned, settings) planned.assign_reuse(settings.found_device) end diff --git a/service/lib/y2storage/proposal/agama_lvm_helper.rb b/service/lib/y2storage/proposal/agama_lvm_helper.rb index c4ffd74933..80784a13b4 100644 --- a/service/lib/y2storage/proposal/agama_lvm_helper.rb +++ b/service/lib/y2storage/proposal/agama_lvm_helper.rb @@ -26,11 +26,14 @@ module Y2Storage module Proposal # LVM helper for Agama. class AgamaLvmHelper < LvmHelper - # Initialize. + # Constructor def initialize(lvm_lvs) super(lvm_lvs, guided_settings) end + private + + # Method used by the constructor to somehow simulate a typical Guided Proposal def guided_settings # Despite the "current_product" part in the name of the constructor, it only applies # generic default values that are independent of the product (there is no YaST diff --git a/service/lib/y2storage/proposal/agama_searcher.rb b/service/lib/y2storage/proposal/agama_searcher.rb index f747ab212b..614f57d38e 100644 --- a/service/lib/y2storage/proposal/agama_searcher.rb +++ b/service/lib/y2storage/proposal/agama_searcher.rb @@ -23,15 +23,32 @@ module Y2Storage module Proposal + # Auxiliary class to handle the 'search' elements within a storage configuration class AgamaSearcher include Yast::Logger include Yast::I18n + # Constructor def initialize textdomain "agama" end - # The last two arguments get modified + # Resolve all the 'search' elements within a given configuration + # + # The second 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. + # + # @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 + # like drives, volume groups, etc. + # @param issues_list [Array] def search(devicegraph, settings, issues_list) @sids = [] settings.drives.each do |drive| @@ -51,6 +68,7 @@ def search(devicegraph, settings, issues_list) private + # @see #search def process_element(element, collection, issues_list) found = element.found_device if found @@ -61,6 +79,10 @@ def process_element(element, collection, issues_list) end end + # Issue generated if a corresponding device is not found for the given element + # + # @param element [Agama::Storage::Configs::Drive, Agama::Storage::Configs::Partition] + # @return [Agama::Issue] def not_found_issue(element) Agama::Issue.new( issue_message(element), @@ -69,6 +91,7 @@ def not_found_issue(element) ) end + # @see #not_found_issue def issue_message(element) if element.is_a?(Agama::Storage::Configs::Drive) if element.search.skip_device? @@ -83,6 +106,7 @@ def issue_message(element) end end + # @see #not_found_issue def issue_severity(search) return Agama::Issue::Severity::WARN if search.skip_device? diff --git a/service/lib/y2storage/proposal/agama_space_maker.rb b/service/lib/y2storage/proposal/agama_space_maker.rb index 18c56d5c0b..bb14a399d3 100644 --- a/service/lib/y2storage/proposal/agama_space_maker.rb +++ b/service/lib/y2storage/proposal/agama_space_maker.rb @@ -26,11 +26,14 @@ module Y2Storage module Proposal # Space maker for Agama. class AgamaSpaceMaker < SpaceMaker - # Initialize. + # Constructor def initialize(disk_analyzer, settings) super(disk_analyzer, guided_settings(settings)) end + private + + # Method used by the constructor to somehow simulate a typical Guided Proposal def guided_settings(settings) # Despite the "current_product" part in the name of the constructor, it only applies # generic default values that are independent of the product (there is no YaST From 421143514b0f7ff840e9031f45a76c01fa61ec1d Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 21 Aug 2024 15:52:29 +0200 Subject: [PATCH 21/21] Adapt to recent changes at yast2-storage-ng --- service/lib/y2storage/agama_proposal.rb | 5 ++++- service/package/gem2rpm.yml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index 6628e41dce..1590074104 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -25,6 +25,7 @@ require "y2storage/proposal/agama_space_maker" require "y2storage/proposal/agama_devices_planner" require "y2storage/proposal/agama_devices_creator" +require "y2storage/proposal/planned_devices_handler" require "y2storage/exceptions" require "y2storage/planned" @@ -44,6 +45,8 @@ module Y2Storage # proposal.devices # => Proposed layout # class AgamaProposal < Proposal::Base + include Proposal::PlannedDevicesHandler + # @return [Agama::Storage::Config] attr_reader :settings @@ -151,7 +154,7 @@ def complete_planned(devicegraph) @planned_devices = planned_devices.prepend(boot_partitions(devicegraph)) end - planned_devices.remove_shadowed_subvols + remove_shadowed_subvols(planned_devices) end # @see #complete_planned diff --git a/service/package/gem2rpm.yml b/service/package/gem2rpm.yml index aab4071193..6acbef4133 100644 --- a/service/package/gem2rpm.yml +++ b/service/package/gem2rpm.yml @@ -38,7 +38,7 @@ Requires: yast2-iscsi-client >= 4.5.7 Requires: yast2-network Requires: yast2-proxy - Requires: yast2-storage-ng >= 5.0.13 + Requires: yast2-storage-ng >= 5.0.16 Requires: yast2-users %ifarch s390 s390x Requires: yast2-s390 >= 4.6.4