From 0b18f205089d7a546298d1b6b2c8015d61b6828d Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 8 Jul 2024 15:15:15 +0200 Subject: [PATCH 01/51] WIP: some ideas to discuss --- doc/auto_storage.md | 62 +++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/doc/auto_storage.md b/doc/auto_storage.md index e7cd92ebea..e060a309e4 100644 --- a/doc/auto_storage.md +++ b/doc/auto_storage.md @@ -37,8 +37,8 @@ Storage volumeGroups mdRaids btrfsRaids - bcacheDevices nfsMounts + boot [BootSettings] guided ``` @@ -67,6 +67,7 @@ Drive format [] mount [] ptableType [] + space <'delete'|'resize'|'keep'> partitions [] VolumeGroup @@ -89,6 +90,7 @@ MdRaid format [] mount [] ptableType [] + space <'delete'|'resize'|'keep'> partitions [] delete [] @@ -162,6 +164,10 @@ Size <'default'|string|SizeRange> SizeRange min max + +BootSettings + configure + device ``` To illustrate how all that fits together, let's see the following example in which the first disk of @@ -420,6 +426,41 @@ above, it would be possible to use the key as name of the property, resulting in } ``` +## Making Space and Specifying what to do with Existing Partitions (under discussion) + +The `space` subsection of each drive or RAID can be used to specify what to do with existing +partitions, if any. That can also be combined with more specific actions indicated by "searching" a +given partition within the `partitions` subsection. + +Theoretically, we could do the following actions for each given partition during the algorithm +execution. We just need to decide how to define all that using `space` and `partitions`. + +- Delete the partition (mandatory action executed as soon as we process the disk) +- Shrink the partition to a given size (same than above, note we need to clearly define the way to + specify a partition must be resized or grown and likely both things will happen at different + stages of the algorithm). +- Shrink the partition if needed (optional action done if needed and with a calculated target size). +- Delete the partition if needed (optional action done if needed). +- Shrink or delete if needed (first an optional resize will be attemped, deleting if it's not + enough). + +Note also we may need to consider which resize actions are possible depending on the content of the +partition, the filesystem type, etc. + +Maybe a `space` action is not needed since the same behavior can be specified only using something +like this: + +```json +"storage": { + "drives": [ + { + "partitions": + { "search": {}, "delete": true } + } + ] +} +``` + ## Referencing Other Devices Sometimes is necessary to reference other devices as part of the specification of an LVM volume @@ -518,28 +559,21 @@ system (so the same conditions can be matched by a disk, a partition, an LVM dev ## Partitions needed for Booting -When relying on the Agama proposal (see below), there are some options to configure whether (and -where) Agama should calculate and create the extra partitions needed for booting. - -If the proposal is not used, Agama will always try to calculate and create those partitions taking -the location of the root file system as a reference. That's the same approach that AutoYaST has -followed for years. +The `boot` section can be used to configure whether (and where) Agama should calculate and create +the extra partitions needed for booting. If the device is not specified, Agama will take the +location of the root file system as a reference. ## Using the Automatic Proposal Agama can rely on the process known as Guided Proposal to calculate all the needed partitions, LVM -devices and file systems based on some general product settings and some user preferences. That -mechanism can also be used as part of the profile and will be executed as a last step, after -processing all the explicit sections that describe devices. +devices and file systems based on some general product settings and some user preferences. The `guided` section conforms to the following specification. ``` Guided device [TargetDevice] - boot [BootSettings] encryption [EncryptionSettings] - space <'delete'|'resize'|'keep'> volumes [Volume[]] TargetDevice @@ -553,10 +587,6 @@ TargetNewLvm TargetReusedLvm reusedLvmVg -BootSettings - configure - device - EncryptionSettings password method From 2c4f7603eccd663c87614d64a8dadb63b490437d Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 17 Jul 2024 10:05:47 +0200 Subject: [PATCH 02/51] WIP: some starting point for AgamaProposal --- service/lib/agama/storage/profile.rb | 77 ++++++ service/lib/agama/storage/settings/drive.rb | 55 +++++ service/lib/agama/storage/settings/encrypt.rb | 35 +++ service/lib/agama/storage/settings/format.rb | 32 +++ service/lib/agama/storage/settings/mount.rb | 36 +++ .../lib/agama/storage/settings/partition.rb | 41 +++ service/lib/agama/storage/settings/search.rb | 46 ++++ .../lib/agama/storage/settings/size_range.rb | 31 +++ service/lib/y2storage/agama_proposal.rb | 233 ++++++++++++++++++ .../proposal/agama_device_planner.rb | 223 +++++++++++++++++ .../proposal/agama_devices_creator.rb | 179 ++++++++++++++ .../proposal/agama_devices_planner.rb | 129 ++++++++++ .../y2storage/proposal/agama_drive_planner.rb | 50 ++++ 13 files changed, 1167 insertions(+) create mode 100644 service/lib/agama/storage/profile.rb create mode 100644 service/lib/agama/storage/settings/drive.rb create mode 100644 service/lib/agama/storage/settings/encrypt.rb create mode 100644 service/lib/agama/storage/settings/format.rb create mode 100644 service/lib/agama/storage/settings/mount.rb create mode 100644 service/lib/agama/storage/settings/partition.rb create mode 100644 service/lib/agama/storage/settings/search.rb create mode 100644 service/lib/agama/storage/settings/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 diff --git a/service/lib/agama/storage/profile.rb b/service/lib/agama/storage/profile.rb new file mode 100644 index 0000000000..eb7067ae82 --- /dev/null +++ b/service/lib/agama/storage/profile.rb @@ -0,0 +1,77 @@ +# 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/boot_settings" + +module Agama + module Storage + # Settings used to calculate an storage proposal. + class Profile + # Boot settings. + # + # @return [BootSettings] + attr_accessor :boot + + attr_accessor :drives + attr_accessor :volume_groups + attr_accessor :md_raids + attr_accessor :btrfs_raids + attr_accessor :nfs_mounts + + def initialize + @boot = BootSettings.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 + + private + + # Device used for booting. + # + # @return [String, nil] + def boot_device + return nil unless boot.configure? + + boot.device + end + end + end +end diff --git a/service/lib/agama/storage/settings/drive.rb b/service/lib/agama/storage/settings/drive.rb new file mode 100644 index 0000000000..8bfde9d524 --- /dev/null +++ b/service/lib/agama/storage/settings/drive.rb @@ -0,0 +1,55 @@ +# 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/settings/search" + +module Agama + module Storage + module Settings + class Drive + attr_accessor :encrypt + attr_accessor :format + attr_accessor :mount + attr_accessor :ptable_type + attr_accessor :partitions + + # @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_sid + search&.sid + end + end + end + end +end diff --git a/service/lib/agama/storage/settings/encrypt.rb b/service/lib/agama/storage/settings/encrypt.rb new file mode 100644 index 0000000000..d331513343 --- /dev/null +++ b/service/lib/agama/storage/settings/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 Settings + 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/settings/format.rb b/service/lib/agama/storage/settings/format.rb new file mode 100644 index 0000000000..d5d0ff955f --- /dev/null +++ b/service/lib/agama/storage/settings/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 Settings + class Format + attr_accessor :filesystem + attr_accessor :label + attr_accessor :mkfs_options + end + end + end +end diff --git a/service/lib/agama/storage/settings/mount.rb b/service/lib/agama/storage/settings/mount.rb new file mode 100644 index 0000000000..f805990482 --- /dev/null +++ b/service/lib/agama/storage/settings/mount.rb @@ -0,0 +1,36 @@ +# 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 Settings + class Mount + attr_accessor :path + attr_accessor :options + attr_accessor :mount_by + + def initialize + @options = [] + end + end + end + end +end diff --git a/service/lib/agama/storage/settings/partition.rb b/service/lib/agama/storage/settings/partition.rb new file mode 100644 index 0000000000..6c8abd8980 --- /dev/null +++ b/service/lib/agama/storage/settings/partition.rb @@ -0,0 +1,41 @@ +# 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 Settings + class Partition + attr_accessor :id + attr_accessor :type + attr_accessor :size + attr_accessor :resize + attr_accessor :delete + attr_accessor :encrypt + attr_accessor :format + attr_accessor :mount + + def search_device(devicegraph, parent_sid, used_sids) + search.find(self, devicegraph, used_sids, parent: parent_sid) + end + end + end + end +end diff --git a/service/lib/agama/storage/settings/search.rb b/service/lib/agama/storage/settings/search.rb new file mode 100644 index 0000000000..e8e69ee890 --- /dev/null +++ b/service/lib/agama/storage/settings/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 Settings + class Search + attr_reader :sid + + def find(setting, devicegraph, used_sids, parent:) + devices = candidate_devices(setting, devicegraph, parent) + devices.reject! { |d| used_sids.include?(d.sid) } + @sid = devices.sort_by(&:name).first&.sid + 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/settings/size_range.rb b/service/lib/agama/storage/settings/size_range.rb new file mode 100644 index 0000000000..ac4802e769 --- /dev/null +++ b/service/lib/agama/storage/settings/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 Settings + class SizeRange + attr_accessor :min + attr_accessor :max + end + end + end +end diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb new file mode 100644 index 0000000000..b8590c236c --- /dev/null +++ b/service/lib/y2storage/agama_proposal.rb @@ -0,0 +1,233 @@ +# 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_settings" +require "y2storage/exceptions" +require "y2storage/planned" +require "y2storage/proposal" +require "y2storage/guided_proposal" + +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::Profile] + attr_reader :settings + + # @return [Agama::Config] + attr_reader :config + + # @return [Array] List of found issues + attr_reader :issues_list + + # Constructor + # + # @param settings [Agama::Storage::Settings] proposal settings + # @param config [Agama::Config] + # @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 invalidate somehow the device definition (registering issue?) + # TODO: If IfNotFound is 'error' => register error + sids = [] + settings.drives.each do |drive| + drive.search_device(devicegraph, sids) + sids << drive.sid + next unless drive.sid && drive.partitions? + + drive.partitions.each do |part| + part.search_device(devicegraph, drive.sid, sids) + sids << part.sid + end + end + end + + def create_space_maker + # This is a Y2Storage::ProposalSettings. Only relevant parts: strategy and list of actions + guided_settings = Agama::Storage::SettingsConversions::ForSpaceMaker(settings).new.convert + Proposal::SpaceMaker.new(disk_analyzer, guided_settings) + end + + # Proposes a devicegraph based on given configuration + # + # @param devicegraph [Devicegraph] Starting point + # @return [Devicegraph] Devicegraph containing the planned devices + def propose_devicegraph(devicegraph) + @planned_devices = initial_planned_devices(devicegraph) + + # This is from Guided + raise Error if useless_volumes_sets? + + clean_devicegraph = clean_graph(devicegraph, @planned_devices) + + planner = Proposal::AgamaDevicesPlanner.new(settings) + planner.add_boot_devices(@planned_devices, clean_devicegraph) + + # Almost for sure, this should happen as part of the creation of devices below + add_partition_tables(devicegraph) + + result = create_devices(devicegraph, @planned_devices) + result.devicegraph + 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 + + # Calculates list of planned devices + # + # @param devicegraph [Devicegraph] Starting point + # @return [Planned::DevicesCollection] Devices to add + def initial_planned_devices(devicegraph) + planner = Proposal::AgamaDevicesPlanner.new(settings, config) + planner.initial_planned_devices(devicegraph) + end + + # Clean a devicegraph + # + # @return [Y2Storage::Devicegraph] + def clean_graph(devicegraph, planned_devices) + new_devicegraph = devicegraph.dup + + # TODO: remember the list of affected devices so we can restore their partition tables at + # the end of the process for those devices that were not used (as soon as libstorage-ng + # allows us to copy sub-graphs). + remove_empty_partition_tables(new_devicegraph) + + protect_sids(planned_devices) + # NOTE: take into account (partitions on) pre-existing RAIDs? + partitions = partitions_for(planned_devices) + space_maker.prepare_devicegraph(new_devicegraph, partitions) + 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) + drive_sids = settings.drives.map(&:found_sid) + devices = drive_sids.map { |n| devicegraph.find_device(n) }.compact + devices.select { |d| d.partition_table && d.partitions.empty? } + end + + # Planned partitions that will hold the given planned devices + # + # 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(planned_devices) + planned_devices.select { |d| device.is_a?(Planned::Partition) } + end + + # Configures SpaceMaker#protected_sids according to the given list of planned devices + # + # @param devices [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 + + # @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 + # Process planned disk like devices (Xen virtual partitions and full disks) + process_disk_like_devs + # TODO: + # process_mds + # process_vgs + # process_btrfs_filesystems + # process_nfs_filesystems + end + + def process_existing_partitionables + partitions = partitions_for_existing(planned_devices) + + lvm_lvs = system_lvm_over_existing? ? system_lvs(planned_devices) : [] + lvm_helper = LvmHelper.new(lvm_lvs, settings) + + # Check whether there is any chance of getting an unwanted order for the planned partitions + # within a disk + space_result = provide_space(partitions, initial_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!(graph) + 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 partitions_for_existing(planned_devices) + # TODO: + # Exclude reused partitions + # Maybe in the future this can include partitions on top of existing MDs + 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..c9d4ef764d --- /dev/null +++ b/service/lib/y2storage/proposal/agama_devices_planner.rb @@ -0,0 +1,129 @@ +# Copyright (c) [2016-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/storage_manager" +require "y2storage/planned" +require "y2storage/disk_size" +require "y2storage/boot_requirements_checker" +require "y2storage/exceptions" + +module Y2Storage + module Proposal + class AgamaDevicesPlanner + include Yast::Logger + + # Settings used to calculate the planned devices + # @return [ProposalSettings] + attr_reader :settings + + # Constructor + # + # @param settings [ProposalSettings] + # @param config [Agama::Config] + def initialize(settings, config, issues_list) + @settings = settings + @config = config + @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) + # This will need to share a lot of logic with the DevicesPlanner of the guided proposal, + # especially everything related to sizes and Btfs stuff. Maybe extracting the logic to + # shared classes, maybe making this a descendant... to be decided during implementation + # + # This will also include a lot of logic that nowadays lives at + # Agama::ProposalSettingsConversions and Agama::VolumesConversions + + devices = settings.drives.each_with_object([]) do |drive, memo| + planned_devs = planned_for_drive(drive) + memo.concat(planned_devs) if planned_devs + end + + collection = Planned::DevicesCollection.new(devices) + remove_shadowed_subvols(collection.mountable_devices) + collection + end + + # Modifies the given list of planned devices, adding any planned partition needed for booting + # the new target system + # + # @param devices [Array] + # @param target [Symbol] see #planned_devices + # @param devicegraph [Devicegraph] + # @return [Array] + def add_boot_devices(devices, devicegraph) + return unless settings.boot.configure? + + devices.unshift(*planned_boot_devices(devices, devicegraph)) + remove_shadowed_subvolumes(devices) + end + + protected + + # Agama configuration, needed to read the list of volumes of the product + # @return [Agama::Config] + attr_reader :config + + # @return [Array] List to register any found issue + attr_reader :issues_list + + # This method is 99% copied from the guided DevicesPlanner + def planned_boot_devices(planned_devices, devicegraph) + # This line is basically guessing + boot_disk_name = settings.boot.device || settings.default_boot_device + + flat = planned_devices.flat_map do |dev| + dev.respond_to?(:lvs) ? dev.lvs : dev + end + checker = BootRequirementsChecker.new( + devicegraph, planned_devices: flat, boot_disk_name: boot_disk_name + ) + checker.needed_partitions(:min) + rescue BootRequirementsChecker::Error => e + # As documented, {BootRequirementsChecker#needed_partition} raises this + # exception if it's impossible to get a bootable system, even adding + # more partitions. + raise NotBootableError, e.message + end + + # 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) + planner = AgamaDrivePlanner.new(devicegraph, config, 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..3d500e3edd --- /dev/null +++ b/service/lib/y2storage/proposal/agama_drive_planner.rb @@ -0,0 +1,50 @@ +# 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 Y2Storage + module Proposal + class AgamaDrivePlanner < AgamaDevicePlanner + def planned_devices(drive) + result = + if drive.partitions? + planned_for_partitioned_drive(drive) + else + planned_for_full_drive(drive) + end + Array(result) + end + + private + + def planned_for_partitioned_drive(drive) + # TODO: register error if this contain some kind of specification for full disk like + # "format", "mounts", etc. + + planned_disk = Y2Storage::Planned::Disk.new + + planned_disk.partitions = drive.partitions.each_with_object([]).each do |partition, memo| + planned_partition = plan_partition(disk, drive, section) + memo << planned_partition if planned_partition + end + + planned_disk + end + end + end +end From 777af0afe7ac3164cca9abe0ebe118a9f6d4426f Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 17 Jul 2024 16:35:56 +0200 Subject: [PATCH 03/51] WIP: some code reorganization --- service/lib/y2storage/agama_proposal.rb | 31 +------- .../proposal/agama_devices_creator.rb | 19 ++--- .../y2storage/proposal/agama_lvm_helper.rb | 46 +++++++++++ .../lib/y2storage/proposal/agama_searcher.rb | 77 +++++++++++++++++++ .../y2storage/proposal/agama_space_maker.rb | 42 ++++++++++ 5 files changed, 179 insertions(+), 36 deletions(-) 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 diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index b8590c236c..afd723579d 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -18,11 +18,10 @@ # find current contact information at www.suse.com. require "yast" -require "y2storage/proposal_settings" +require "y2storage/proposal" +require "y2storage/agama_searcher" require "y2storage/exceptions" require "y2storage/planned" -require "y2storage/proposal" -require "y2storage/guided_proposal" module Y2Storage # Class to calculate a storage proposal for autoinstallation using Agama @@ -79,39 +78,17 @@ def initialize(initial_settings, config, devicegraph: nil, disk_analyzer: nil, i # # @raise [NoDiskSpaceError] if there is no enough space to perform the installation def calculate_proposal - search_devices + Proposal::AgamaSearcher.new(initial_devicegraph).search(settings, issues_list) if issues_list.any?(:error?) # This means some IfNotFound is set to "error" and we failed to find a match @devices = nil return @devices end - @space_maker = create_space_maker + @space_maker = Proposal::AgamaSpaceMaker.new(disk_analyzer, settings, config) @devices = propose_devicegraph(initial_devicegraph) end - def search_devices - # TODO: If IfNotFound is 'skip' => invalidate somehow the device definition (registering issue?) - # TODO: If IfNotFound is 'error' => register error - sids = [] - settings.drives.each do |drive| - drive.search_device(devicegraph, sids) - sids << drive.sid - next unless drive.sid && drive.partitions? - - drive.partitions.each do |part| - part.search_device(devicegraph, drive.sid, sids) - sids << part.sid - end - end - end - - def create_space_maker - # This is a Y2Storage::ProposalSettings. Only relevant parts: strategy and list of actions - guided_settings = Agama::Storage::SettingsConversions::ForSpaceMaker(settings).new.convert - Proposal::SpaceMaker.new(disk_analyzer, guided_settings) - end - # Proposes a devicegraph based on given configuration # # @param devicegraph [Devicegraph] Starting point diff --git a/service/lib/y2storage/proposal/agama_devices_creator.rb b/service/lib/y2storage/proposal/agama_devices_creator.rb index 90783145c3..3f02013816 100644 --- a/service/lib/y2storage/proposal/agama_devices_creator.rb +++ b/service/lib/y2storage/proposal/agama_devices_creator.rb @@ -17,13 +17,7 @@ # 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_partitioner" -require "y2storage/proposal/autoinst_md_creator" -require "y2storage/proposal/lvm_creator" -require "y2storage/proposal/btrfs_creator" -require "y2storage/proposal/nfs_creator" -require "y2storage/proposal/tmpfs_creator" -require "y2storage/proposal/autoinst_creator_result" +require "y2storage/proposal/agama_lvm_helper" require "y2storage/exceptions" module Y2Storage @@ -121,8 +115,9 @@ def process_devices def process_existing_partitionables partitions = partitions_for_existing(planned_devices) - lvm_lvs = system_lvm_over_existing? ? system_lvs(planned_devices) : [] - lvm_helper = LvmHelper.new(lvm_lvs, settings) + # 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 @@ -156,6 +151,12 @@ def process_existing_partitionables # 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) # TODO: # Exclude reused partitions 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..b36a01cc73 --- /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 AgamaSpaceMaker < SpaceMaker + # 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..21f88bd52e --- /dev/null +++ b/service/lib/y2storage/proposal/agama_searcher.rb @@ -0,0 +1,77 @@ +# 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 + # + # @param settings [ProposalSettings] + # @param config [Agama::Config] + def initialize(devicegraph) + textdomain "agama" + + @devicegraph = devicegraph + end + + # Both arguments get modified + def search(settings, issues_list) + # TODO: If IfNotFound is 'error' => register error + sids = [] + settings.drives.each do |drive| + drive.search_device(devicegraph, sids) + + if drive.sid.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 << drive.sid + next unless drive.sid && drive.partitions? + + drive.partitions.each do |part| + part.search_device(devicegraph, drive.sid, sids) + sids << 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..b2d31910f8 --- /dev/null +++ b/service/lib/y2storage/proposal/agama_space_maker.rb @@ -0,0 +1,42 @@ +# 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, config) + super(disk_analyzer, guided_settings(settings, config)) + end + + def guided_settings(_settings, _config) + # 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 = [] + end + end + end + end +end From f835de4f5f5ffccc1f8e1a12ab83b9c8ab55b717 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Thu, 18 Jul 2024 14:13:46 +0200 Subject: [PATCH 04/51] WIP: putting some pieces together --- service/lib/agama/storage/profile.rb | 4 +- service/lib/y2storage/agama_proposal.rb | 34 +++++------ .../proposal/agama_devices_creator.rb | 10 +++- .../y2storage/proposal/agama_lvm_helper.rb | 2 +- .../lib/y2storage/proposal/agama_searcher.rb | 2 +- .../y2storage/proposal/agama_space_maker.rb | 24 ++++++-- service/test/agama/storage/storage_helpers.rb | 24 ++++++++ service/test/y2storage/agama_proposal_test.rb | 60 +++++++++++++++++++ 8 files changed, 129 insertions(+), 31 deletions(-) create mode 100644 service/test/y2storage/agama_proposal_test.rb diff --git a/service/lib/agama/storage/profile.rb b/service/lib/agama/storage/profile.rb index eb7067ae82..12551c5339 100644 --- a/service/lib/agama/storage/profile.rb +++ b/service/lib/agama/storage/profile.rb @@ -62,12 +62,10 @@ def to_json_settings Storage::ProposalSettingsConversions::ToJSON.new(self).convert end - private - # Device used for booting. # # @return [String, nil] - def boot_device + def explicit_boot_device return nil unless boot.configure? boot.device diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index afd723579d..9f2af9a414 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -19,7 +19,10 @@ require "yast" require "y2storage/proposal" -require "y2storage/agama_searcher" +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" @@ -96,9 +99,6 @@ def calculate_proposal def propose_devicegraph(devicegraph) @planned_devices = initial_planned_devices(devicegraph) - # This is from Guided - raise Error if useless_volumes_sets? - clean_devicegraph = clean_graph(devicegraph, @planned_devices) planner = Proposal::AgamaDevicesPlanner.new(settings) @@ -128,7 +128,7 @@ def add_partition_tables(devicegraph) # @param devicegraph [Devicegraph] Starting point # @return [Planned::DevicesCollection] Devices to add def initial_planned_devices(devicegraph) - planner = Proposal::AgamaDevicesPlanner.new(settings, config) + planner = Proposal::AgamaDevicesPlanner.new(settings, config, issues_list) planner.initial_planned_devices(devicegraph) end @@ -169,20 +169,20 @@ def drives_with_empty_partition_table(devicegraph) drive_sids = settings.drives.map(&:found_sid) devices = drive_sids.map { |n| devicegraph.find_device(n) }.compact devices.select { |d| d.partition_table && d.partitions.empty? } - end + end - # Planned partitions that will hold the given planned devices - # - # Extracted to a separate method because it's something that may need some extra logic + # Planned partitions that will hold the given planned devices + # + # 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(planned_devices) - planned_devices.select { |d| device.is_a?(Planned::Partition) } - end - - # Configures SpaceMaker#protected_sids according to the given list of planned devices + # + # @param planned_devices [Array] list of planned devices + # @return [Array] + def partitions_for(planned_devices) + planned_devices.select { |d| d.is_a?(Planned::Partition) } + end + + # Configures SpaceMaker#protected_sids according to the given list of planned devices # # @param devices [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 @@ -110,6 +112,8 @@ def process_devices # process_vgs # process_btrfs_filesystems # process_nfs_filesystems + + creator_result end def process_existing_partitionables @@ -121,7 +125,7 @@ def process_existing_partitionables # Check whether there is any chance of getting an unwanted order for the planned partitions # within a disk - space_result = provide_space(partitions, initial_graph, lvm_helper) + 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]) @@ -158,9 +162,9 @@ def provide_space(planned_partitions, devicegraph, lvm_helper) end def partitions_for_existing(planned_devices) - # TODO: - # Exclude reused partitions # Maybe in the future this can include partitions on top of existing MDs + # TODO: simplistic implementation + planned_devices.select { |d| d.is_a?(Planned::Partition) && !d.reuse? } end # Formats and/or mounts the disk like block devices (Xen virtual partitions and full disks) diff --git a/service/lib/y2storage/proposal/agama_lvm_helper.rb b/service/lib/y2storage/proposal/agama_lvm_helper.rb index b36a01cc73..78518b3dd4 100644 --- a/service/lib/y2storage/proposal/agama_lvm_helper.rb +++ b/service/lib/y2storage/proposal/agama_lvm_helper.rb @@ -22,7 +22,7 @@ module Y2Storage module Proposal - class AgamaSpaceMaker < SpaceMaker + class AgamaLvmHelper < LvmHelper # Initialize. def initialize(lvm_lvs) super(lvm_lvs, guided_settings) diff --git a/service/lib/y2storage/proposal/agama_searcher.rb b/service/lib/y2storage/proposal/agama_searcher.rb index 21f88bd52e..c0052b9983 100644 --- a/service/lib/y2storage/proposal/agama_searcher.rb +++ b/service/lib/y2storage/proposal/agama_searcher.rb @@ -67,7 +67,7 @@ def search(settings, issues_list) def issue_missing_drive(drive) Agama::Issue.new( - _("No device found for a given drive") + _("No device found for a given drive"), source: Issue::Source::CONFIG, severity: Issue::Severity::ERROR ) diff --git a/service/lib/y2storage/proposal/agama_space_maker.rb b/service/lib/y2storage/proposal/agama_space_maker.rb index b2d31910f8..9de09c10c2 100644 --- a/service/lib/y2storage/proposal/agama_space_maker.rb +++ b/service/lib/y2storage/proposal/agama_space_maker.rb @@ -28,14 +28,26 @@ def initialize(disk_analyzer, settings, config) super(disk_analyzer, guided_settings(settings, config)) end - def guided_settings(_settings, _config) - # 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| + def guided_settings(settings, _config) + # 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 = [] - end + + boot_device = settings.explicit_boot_device || implicit_boot_device(settings) + + target.root_device = boot_device + target.candidate_devices = [boot_device].compact + end + end + + private + + def implicit_boot_device(settings) + # TODO + "/dev/sda" end end end diff --git a/service/test/agama/storage/storage_helpers.rb b/service/test/agama/storage/storage_helpers.rb index 94baff35ff..29c84c9f2e 100644 --- a/service/test/agama/storage/storage_helpers.rb +++ b/service/test/agama/storage/storage_helpers.rb @@ -54,6 +54,30 @@ def mock_storage_probing(devicegraph_file) def mock_hwinfo(hwinfo) allow_any_instance_of(Y2Storage::HWInfoReader).to receive(:for_device).and_return(hwinfo) end + + def planned_partition(attrs = {}) + part = Y2Storage::Planned::Partition.new(nil) + add_planned_attributes(part, attrs) + end + + def add_planned_attributes(device, attrs) + attrs = attrs.dup + + if device.respond_to?(:filesystem_type) + type = attrs.delete(:type) + device.filesystem_type = + if type.is_a?(::String) || type.is_a?(Symbol) + Y2Storage::Filesystems::Type.const_get(type.to_s.upcase) + else + type + end + end + + attrs.each_pair do |key, value| + device.send(:"#{key}=", value) + end + device + 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..ee295574e7 --- /dev/null +++ b/service/test/y2storage/agama_proposal_test.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_relative "../agama/storage/storage_helpers" +require "agama/config" +require "agama/storage/profile" +require "y2storage/agama_proposal" + +describe Y2Storage::AgamaProposal do + include Agama::RSpec::StorageHelpers + + before do + mock_storage(devicegraph: "empty-hd-50GiB.yaml") + allow(Y2Storage::Proposal::AgamaDevicesPlanner).to receive(:new).and_return dev_generator + allow(dev_generator).to receive(:add_boot_devices) + end + + subject(:proposal) do + described_class.new(initial_settings, config, issues_list: issues_list) + end + let(:config) { Agama::Config.new } + let(:initial_settings) { Agama::Storage::Profile.new } + let(:issues_list) { [] } + let(:dev_generator) do + instance_double( + "Y2Storage::Proposal::AgamaDevicesPlanner", initial_planned_devices: planned_devices + ) + end + let(:planned_devices) do + [ + planned_partition(mount_point: "/", type: :ext4, min: Y2Storage::DiskSize.GiB(8.5)), + planned_partition(mount_point: "swap", type: :swap, min: Y2Storage::DiskSize.GiB(1)) + ] + end + + describe "#propose" do + it "does something" do + proposal.propose + expect(proposal.devices.partitions.size).to eq 2 + end + end +end From 09420eb9ae3abde8197b6afb4b2ca740a1b73965 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Thu, 18 Jul 2024 17:11:52 +0200 Subject: [PATCH 05/51] WIP: cache devigraph used for searches --- service/lib/agama/storage/profile.rb | 2 ++ service/lib/agama/storage/settings.rb | 36 +++++++++++++++++++ service/lib/agama/storage/settings/drive.rb | 10 ++++-- .../lib/agama/storage/settings/partition.rb | 10 ++++++ service/lib/agama/storage/settings/search.rb | 6 ++-- service/lib/y2storage/agama_proposal.rb | 12 ++----- .../lib/y2storage/proposal/agama_searcher.rb | 24 ++++++------- .../y2storage/proposal/agama_space_maker.rb | 8 +++-- service/test/y2storage/agama_proposal_test.rb | 22 ++++++++---- 9 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 service/lib/agama/storage/settings.rb diff --git a/service/lib/agama/storage/profile.rb b/service/lib/agama/storage/profile.rb index 12551c5339..e0fc8b0d1e 100644 --- a/service/lib/agama/storage/profile.rb +++ b/service/lib/agama/storage/profile.rb @@ -20,6 +20,7 @@ # find current contact information at www.suse.com. require "agama/storage/boot_settings" +require "agama/storage/settings" module Agama module Storage @@ -35,6 +36,7 @@ class Profile attr_accessor :md_raids attr_accessor :btrfs_raids attr_accessor :nfs_mounts + attr_accessor :original_graph def initialize @boot = BootSettings.new diff --git a/service/lib/agama/storage/settings.rb b/service/lib/agama/storage/settings.rb new file mode 100644 index 0000000000..00ed64e97a --- /dev/null +++ b/service/lib/agama/storage/settings.rb @@ -0,0 +1,36 @@ +# 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 Settings + end + end +end + +require "agama/storage/settings/drive" +require "agama/storage/settings/encrypt" +require "agama/storage/settings/format" +require "agama/storage/settings/mount" +require "agama/storage/settings/partition" +require "agama/storage/settings/search" +require "agama/storage/settings/size_range" diff --git a/service/lib/agama/storage/settings/drive.rb b/service/lib/agama/storage/settings/drive.rb index 8bfde9d524..fa358bab04 100644 --- a/service/lib/agama/storage/settings/drive.rb +++ b/service/lib/agama/storage/settings/drive.rb @@ -30,6 +30,7 @@ class Drive attr_accessor :mount attr_accessor :ptable_type attr_accessor :partitions + attr_accessor :search # @param mount_path [String] def initialize @@ -38,7 +39,6 @@ def initialize def search_device(devicegraph, used_sids) @search ||= default_search - search.find(self, devicegraph, used_sids) end @@ -46,8 +46,12 @@ def default_search Search.new end - def found_sid - search&.sid + def found_device + search&.device + end + + def partitions? + partitions.any? end end end diff --git a/service/lib/agama/storage/settings/partition.rb b/service/lib/agama/storage/settings/partition.rb index 6c8abd8980..029242abf9 100644 --- a/service/lib/agama/storage/settings/partition.rb +++ b/service/lib/agama/storage/settings/partition.rb @@ -31,10 +31,20 @@ class Partition 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 diff --git a/service/lib/agama/storage/settings/search.rb b/service/lib/agama/storage/settings/search.rb index e8e69ee890..64374e4b8a 100644 --- a/service/lib/agama/storage/settings/search.rb +++ b/service/lib/agama/storage/settings/search.rb @@ -23,12 +23,12 @@ module Agama module Storage module Settings class Search - attr_reader :sid + attr_reader :device - def find(setting, devicegraph, used_sids, parent:) + def find(setting, devicegraph, used_sids, parent: nil) devices = candidate_devices(setting, devicegraph, parent) devices.reject! { |d| used_sids.include?(d.sid) } - @sid = devices.sort_by(&:name).first&.sid + @device = devices.sort_by(&:name).first end def candidate_devices(setting, devicegraph, parent) diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index 9f2af9a414..62cf5b6aee 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -81,7 +81,7 @@ def initialize(initial_settings, config, devicegraph: nil, disk_analyzer: nil, i # # @raise [NoDiskSpaceError] if there is no enough space to perform the installation def calculate_proposal - Proposal::AgamaSearcher.new(initial_devicegraph).search(settings, issues_list) + Proposal::AgamaSearcher.new.search(initial_devicegraph, settings, issues_list) if issues_list.any?(:error?) # This means some IfNotFound is set to "error" and we failed to find a match @devices = nil @@ -166,8 +166,7 @@ def remove_empty_partition_tables(devicegraph) # @param devicegraph [Y2Storage::Devicegraph] # @return [Array] def drives_with_empty_partition_table(devicegraph) - drive_sids = settings.drives.map(&:found_sid) - devices = drive_sids.map { |n| devicegraph.find_device(n) }.compact + devices = settings.drives.map(&:found_device).compact devices.select { |d| d.partition_table && d.partitions.empty? } end @@ -197,14 +196,9 @@ def create_devices(devicegraph, planned_devices) planned_devices = planned_devices.map(&:dup) devices_creator = Proposal::AgamaDevicesCreator.new(devicegraph, issues_list) - names = disk_names(devicegraph) + names = settings.drives.map(&:found_device).compact.map(&:name) protect_sids(planned_devices) result = devices_creator.populated_devicegraph(planned_devices, names, space_maker) end - - def disk_names(devicegraph) - disk_sids = settings.drives.map(&:found_sid).compact - disk_sids.map {|s| devicegraph.find_device(s) } - end end end diff --git a/service/lib/y2storage/proposal/agama_searcher.rb b/service/lib/y2storage/proposal/agama_searcher.rb index c0052b9983..68ae3eed37 100644 --- a/service/lib/y2storage/proposal/agama_searcher.rb +++ b/service/lib/y2storage/proposal/agama_searcher.rb @@ -27,22 +27,21 @@ class AgamaSearcher # Constructor # - # @param settings [ProposalSettings] - # @param config [Agama::Config] - def initialize(devicegraph) + def initialize textdomain "agama" - - @devicegraph = devicegraph end - # Both arguments get modified - def search(settings, issues_list) + # The last two arguments get modified + def search(devicegraph, settings, issues_list) + settings.original_graph = devicegraph + # TODO: If IfNotFound is 'error' => register error sids = [] settings.drives.each do |drive| drive.search_device(devicegraph, sids) - if drive.sid.nil? + found = drive.found_device + if found.nil? # TODO: If IfNotFound is 'skip' => # invalidate somehow the device definition (registering issue?) # @@ -51,12 +50,13 @@ def search(settings, issues_list) return false end - sids << drive.sid - next unless drive.sid && drive.partitions? + sids << found.sid + next unless drive.partitions? drive.partitions.each do |part| - part.search_device(devicegraph, drive.sid, sids) - sids << part.sid + part.search_device(devicegraph, found.sid, sids) + part_sid = part.found_device&.sid + sids << part_sid if part_sid end end diff --git a/service/lib/y2storage/proposal/agama_space_maker.rb b/service/lib/y2storage/proposal/agama_space_maker.rb index 9de09c10c2..0deb49da40 100644 --- a/service/lib/y2storage/proposal/agama_space_maker.rb +++ b/service/lib/y2storage/proposal/agama_space_maker.rb @@ -46,8 +46,12 @@ def guided_settings(settings, _config) private def implicit_boot_device(settings) - # TODO - "/dev/sda" + # TODO: preliminary implementation with very simplistic checks + root_drive = settings.drives.find do |drive| + drive.partitions.any? { |p| p.mount&.path == "/" } + end + + root_drive&.found_device.name end end end diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index ee295574e7..615b287595 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -37,7 +37,20 @@ described_class.new(initial_settings, config, issues_list: issues_list) end let(:config) { Agama::Config.new } - let(:initial_settings) { Agama::Storage::Profile.new } + let(:initial_settings) do + Agama::Storage::Profile.new.tap do |settings| + settings.drives = [drive] + end + end + let(:drive) do + Agama::Storage::Settings::Drive.new.tap do |drive| + drive.partitions = [ + Agama::Storage::Settings::Partition.new.tap do |part| + part.mount = Agama::Storage::Settings::Mount.new.tap { |m| m.path = "/" } + end + ] + end + end let(:issues_list) { [] } let(:dev_generator) do instance_double( @@ -45,16 +58,13 @@ ) end let(:planned_devices) do - [ - planned_partition(mount_point: "/", type: :ext4, min: Y2Storage::DiskSize.GiB(8.5)), - planned_partition(mount_point: "swap", type: :swap, min: Y2Storage::DiskSize.GiB(1)) - ] + [planned_partition(mount_point: "/", type: :ext4, min: Y2Storage::DiskSize.GiB(8.5))] end describe "#propose" do it "does something" do proposal.propose - expect(proposal.devices.partitions.size).to eq 2 + expect(proposal.devices.partitions.size).to eq 1 end end end From 7ecffc0769f7691aaa984aad94eafbabf6c570b8 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, 18 Jul 2024 16:23:53 +0100 Subject: [PATCH 06/51] WIP: planned devices --- service/lib/agama/storage/settings/drive.rb | 1 + .../lib/agama/storage/settings/partition.rb | 1 + service/lib/y2storage/agama_proposal.rb | 4 +- .../proposal/agama_device_planner.rb | 225 +++++------------- .../proposal/agama_devices_planner.rb | 29 +-- .../y2storage/proposal/agama_drive_planner.rb | 46 ++-- 6 files changed, 111 insertions(+), 195 deletions(-) diff --git a/service/lib/agama/storage/settings/drive.rb b/service/lib/agama/storage/settings/drive.rb index fa358bab04..d92ca3a8cd 100644 --- a/service/lib/agama/storage/settings/drive.rb +++ b/service/lib/agama/storage/settings/drive.rb @@ -25,6 +25,7 @@ module Agama module Storage module Settings class Drive + attr_accessor :search attr_accessor :encrypt attr_accessor :format attr_accessor :mount diff --git a/service/lib/agama/storage/settings/partition.rb b/service/lib/agama/storage/settings/partition.rb index 029242abf9..d3c97638b0 100644 --- a/service/lib/agama/storage/settings/partition.rb +++ b/service/lib/agama/storage/settings/partition.rb @@ -23,6 +23,7 @@ module Agama module Storage module Settings class Partition + attr_accessor :search attr_accessor :id attr_accessor :type attr_accessor :size diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index 62cf5b6aee..f79ba309f8 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -125,14 +125,14 @@ def add_partition_tables(devicegraph) # Calculates list of planned devices # - # @param devicegraph [Devicegraph] Starting point + # @param devicegraph [Devicegraph] Starting point # @return [Planned::DevicesCollection] Devices to add def initial_planned_devices(devicegraph) planner = Proposal::AgamaDevicesPlanner.new(settings, config, issues_list) planner.initial_planned_devices(devicegraph) end - # Clean a devicegraph + # Clean a devicegraph # # @return [Y2Storage::Devicegraph] def clean_graph(devicegraph, planned_devices) diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index c99d637525..2c0a39c35f 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -17,206 +17,105 @@ # 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_settings" -require "y2storage/proposal/autoinst_size_parser" -require "y2storage/volume_specification" +require "y2storage/planned" module Y2Storage module Proposal - # This module offers a set of common methods that are used by Agama planners. + # Base class used by Agama planners. class AgamaDevicePlanner # @!attribute [r] devicegraph # @return [Devicegraph] + attr_reader :devicegraph + + # @!attribute [r] config + # @return [Agama::Config] + attr_reader :config + # @!attribute [r] issues_list - # - attr_reader :devicegraph, :config, :issues_list + attr_reader :issues_list - # Constructor - # - # @param devicegraph [Devicegraph] Devicegraph to be used as starting point - # @param issues_list [AutoinstIssues::List] List of AutoYaST issues to register them + # @param devicegraph [Devicegraph] Devicegraph to be used as starting point. + # @param issues_list [AutoinstIssues::List] List of issues to register them. def initialize(devicegraph, config, issues_list) @devicegraph = devicegraph @config = config @issues_list = issues_list end - # Returns a planned volume group according to an AutoYaST specification + # Planned devices according to the given settings. # - # @param _drive [AutoinstProfile::DriveSection] drive section - # @return [Array] Array of planned devices + # @return [Array] Array of planned devices. def planned_devices(_setting) raise NotImplementedError end private - # @param device [Planned::Device] Planned device - def configure_device(device, specification) - configure_mount(device, specification) - configure_format(device, specification) + # @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 device [Planned::Device] - def configure_format(device, spec) - format = spec.format - return unless format - - device.label = format.label - device.mkfs_options = format.mkfs_options - device.filesystem_type = filesystem_for(spec) - - btrfs = format.btrfs_info? ? format.filesystem : nil - configure_btrfs(device, btrfs) + # @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 device [Planned::Device] - def configure_mount(device, spec) - mount = spec.mount - return unless mount - - device.mount_point = mount.path - device.fstab_options = mount.mount_options - device.mount_by = mount.type_for_mount_by + # @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 - # TODO: support the case in which btrfs is nil - def configure_btrfs(device, btrfs) - configure_snapshots(device, btrfs) - configure_subvolumes(device, btrfs) + # @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 - # Sets device attributes related to snapshots - # - # This method modifies the first argument - # - # @param device [Planned::Device] Planned device - def configure_snapshots(device, btrfs) - return unless device.respond_to?(:root?) && device.root? - device.snapshots = btrfs.snapshots? + # @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 + # Is this needed? Or #options is enough? + planned.read_only = settings.read_only? end - # Sets devices attributes related to Btrfs subvolumes - # - # This method modifies the first argument setting default_subvolume and - # subvolumes. - # - # @param device [Planned::Device] Planned device - def configure_subvolumes(device, btrfs) - defaults = subvolume_attrs_for(device.mount_point) - - device.default_subvolume = btrfs.subvolumes_prefix || defaults[:subvolumes_prefix] - - device.subvolumes = section.subvolumes || defaults[:subvolumes] || [] - configure_btrfs_quotas(device, btrfs) + # @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 end - # Sets the Btrfs quotas according to the section and the subvolumes - # - # If `section.quotas` is nil, it inspect whether quotas are needed for any - # of the subvolumes. In that case, it sets `device.quota` to true. - # - # @param device [Planned::Device] Planned device - def configure_btrfs_quotas(device, btrfs) - if !section.quotas.nil? - device.quota = section.quotas - return - end - - subvols_with_quotas = device.subvolumes.select do |subvol| - subvol.referenced_limit && !subvol.referenced_limit.unlimited? + # @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.device.name } end - return if subvols_with_quotas.empty? - - device.quota = true - issues_list.add( - Y2Storage::AutoinstIssues::MissingBtrfsQuotas, section, subvols_with_quotas - ) end - # Return the default subvolume attributes for a given mount point - # - # @param mount [String] Mount point - # @return [Hash] - def subvolume_attrs_for(mount) - return {} if mount.nil? - - spec = VolumeSpecification.for(mount) - return {} if spec.nil? - - { subvolumes_prefix: spec.btrfs_default_subvolume, subvolumes: spec.subvolumes } - end - - # Return the filesystem type for a given section - # - # @param partition_section [AutoinstProfile::PartitionSection] AutoYaST specification - # @return [Filesystems::Type] Filesystem type - def filesystem_for(partition_section) - return partition_section.type_for_filesystem if partition_section.type_for_filesystem - return nil unless partition_section.mount - - default_filesystem_for(partition_section) - end - - # Return the default filesystem type for a given section - # - # @param section [AutoinstProfile::PartitionSection] - # @return [Filesystems::Type] Filesystem type - def default_filesystem_for(section) - spec = VolumeSpecification.for(section.mount) - return spec.fs_type if spec&.fs_type - - (section.mount == "swap") ? Filesystems::Type::SWAP : Filesystems::Type::BTRFS - end - - # Determine whether the filesystem for the given mount point should be read-only - # - # @param mount_point [String] Filesystem mount point - # @return [Boolean] true if it should be read-only; false otherwise. - def read_only?(mount_point) - return false unless mount_point - - spec = VolumeSpecification.for(mount_point) - !!spec && spec.btrfs_read_only? - end - - # @return [DiskSize] Minimal partition size - PARTITION_MIN_SIZE = DiskSize.B(1).freeze - - # @param container [Planned::Disk,Planned::Dasd,Planned::Md] Device to place the partitions on - # @param drive [AutoinstProfile::DriveSection] - # @param section [AutoinstProfile::PartitionSection] - # @return [Planned::Partition,nil] - def plan_partition(container, drive, section) - partition = Y2Storage::Planned::Partition.new(nil, nil) - - return unless assign_size_to_partition(partition, section) - - partition.disk = container.name - partition.partition_id = section.id_for_partition - partition.primary = section.partition_type == "primary" if section.partition_type - device_config(partition, section, drive) - add_partition_reuse(partition, section) if section.create == false - partition - end - - # Assign disk size according to AutoYaSt section - # - # @param partition [Planned::Partition] Partition to assign the size to - # @param part_section [AutoinstProfile::PartitionSection] Partition specification from AutoYaST - def assign_size_to_partition(partition, part_section) - size_info = parse_size(part_section, PARTITION_MIN_SIZE, DiskSize.unlimited) - - if size_info.nil? - issues_list.add(Y2Storage::AutoinstIssues::InvalidValue, part_section, :size) - return false + # @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 + planned.primary = settings.primary? + configure_device(planned, settings) + configure_size(planned, settings.size) end - - partition.percent_size = size_info.percentage - partition.min_size = size_info.min - partition.max_size = size_info.max - partition.weight = 1 if size_info.unlimited? - true end end end diff --git a/service/lib/y2storage/proposal/agama_devices_planner.rb b/service/lib/y2storage/proposal/agama_devices_planner.rb index c9d4ef764d..22ae41e4bd 100644 --- a/service/lib/y2storage/proposal/agama_devices_planner.rb +++ b/service/lib/y2storage/proposal/agama_devices_planner.rb @@ -1,4 +1,4 @@ -# Copyright (c) [2016-2024] SUSE LLC +# Copyright (c) [2024] SUSE LLC # # All Rights Reserved. # @@ -28,21 +28,22 @@ module Proposal class AgamaDevicesPlanner include Yast::Logger - # Settings used to calculate the planned devices - # @return [ProposalSettings] + # Settings used to calculate the planned devices. + # + # @return [Agama::Storage::Profile] attr_reader :settings - # Constructor - # - # @param settings [ProposalSettings] + # @param settings [Agama::Storage::Profile] # @param config [Agama::Config] + # @param issues_list [Array] + # + # TODO Check which params are mandatory def initialize(settings, config, issues_list) @settings = settings @config = config @issues_list = issues_list end - # List of devices that need to be created to satisfy the settings. Does not include # devices needed for booting. # @@ -66,14 +67,14 @@ def initial_planned_devices(devicegraph) # This will also include a lot of logic that nowadays lives at # Agama::ProposalSettingsConversions and Agama::VolumesConversions - devices = settings.drives.each_with_object([]) do |drive, memo| - planned_devs = planned_for_drive(drive) - memo.concat(planned_devs) if planned_devs - end + planned = settings.drives + .flat_map { |d| planned_for_drive(d) } + .compact - collection = Planned::DevicesCollection.new(devices) - remove_shadowed_subvols(collection.mountable_devices) - collection + Planned::DevicesCollection.new(planned).tap do |collection| + # TODO + remove_shadowed_subvols(collection.mountable_devices) + end end # Modifies the given list of planned devices, adding any planned partition needed for booting diff --git a/service/lib/y2storage/proposal/agama_drive_planner.rb b/service/lib/y2storage/proposal/agama_drive_planner.rb index 3d500e3edd..f8589b8bd4 100644 --- a/service/lib/y2storage/proposal/agama_drive_planner.rb +++ b/service/lib/y2storage/proposal/agama_drive_planner.rb @@ -20,30 +20,44 @@ module Y2Storage module Proposal class AgamaDrivePlanner < AgamaDevicePlanner - def planned_devices(drive) - result = - if drive.partitions? - planned_for_partitioned_drive(drive) - else - planned_for_full_drive(drive) - end - Array(result) + # @param settings [Agama::Storage::Settings::Drive] + # @return [Array] + def planned_devices(settings) + [planned_drive(settings)] end private - def planned_for_partitioned_drive(drive) - # TODO: register error if this contain some kind of specification for full disk like - # "format", "mounts", etc. + # @param settings [Agama::Storage::Settings::Drive] + # @return [Planned::Disk] + def planned_drive(settings) + return planned_full_drive(settings) unless settings.partitions? - planned_disk = Y2Storage::Planned::Disk.new + planned_partitioned_drive(settings) + end - planned_disk.partitions = drive.partitions.each_with_object([]).each do |partition, memo| - planned_partition = plan_partition(disk, drive, section) - memo << planned_partition if planned_partition + # @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 - planned_disk + # @param planned [Planned::Disk] + # @param settings [Agama::Storage::Settings::Drive] + def configure_drive(planned, settings) + planned.reuse_name = settings.device.name end end end From 7f68df2c3e5e7eb64fb80c8b39e595ad5662e1f2 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 22 Jul 2024 13:25:30 +0200 Subject: [PATCH 07/51] WIP: remove mocking of DevicesPlanner --- service/lib/agama/storage/profile.rb | 13 ++++++++++ service/lib/y2storage/agama_proposal.rb | 2 +- .../proposal/agama_device_planner.rb | 10 ++++---- .../proposal/agama_devices_creator.rb | 2 +- .../proposal/agama_devices_planner.rb | 15 ++++++------ .../y2storage/proposal/agama_drive_planner.rb | 4 +++- .../y2storage/proposal/agama_space_maker.rb | 13 +--------- service/test/agama/storage/storage_helpers.rb | 24 ------------------- service/test/y2storage/agama_proposal_test.rb | 14 ++++------- 9 files changed, 37 insertions(+), 60 deletions(-) diff --git a/service/lib/agama/storage/profile.rb b/service/lib/agama/storage/profile.rb index e0fc8b0d1e..895de1a40c 100644 --- a/service/lib/agama/storage/profile.rb +++ b/service/lib/agama/storage/profile.rb @@ -64,6 +64,10 @@ 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] @@ -72,6 +76,15 @@ def explicit_boot_device 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/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index f79ba309f8..98103b5ba7 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -101,7 +101,7 @@ def propose_devicegraph(devicegraph) clean_devicegraph = clean_graph(devicegraph, @planned_devices) - planner = Proposal::AgamaDevicesPlanner.new(settings) + planner = Proposal::AgamaDevicesPlanner.new(settings, config, issues_list) planner.add_boot_devices(@planned_devices, clean_devicegraph) # Almost for sure, this should happen as part of the creation of devices below diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index 2c0a39c35f..d11902ecc7 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -88,8 +88,8 @@ def configure_mount(planned, settings) planned.mount_point = settings.path planned.mount_by = settings.mount_by planned.fstab_options = settings.options - # Is this needed? Or #options is enough? - planned.read_only = settings.read_only? + # FIXME: Is this needed? Or #options is enough? + # planned.read_only = settings.read_only? end # @param planned [Planned::Partition] @@ -103,7 +103,7 @@ def configure_size(planned, settings) # @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.device.name } + planned_partition(partition_settings).tap { |p| p.disk = settings.found_device.name } end end @@ -112,7 +112,9 @@ def configure_partitions(planned, settings) def planned_partition(settings) Planned::Partition.new(nil, nil).tap do |planned| planned.partition_id = settings.id - planned.primary = settings.primary? + # FIXME: do we want to allow to force primary partitions? That was not really + # well solved at AutoYaST + # planned.primary = settings.primary? configure_device(planned, settings) configure_size(planned, settings.size) end diff --git a/service/lib/y2storage/proposal/agama_devices_creator.rb b/service/lib/y2storage/proposal/agama_devices_creator.rb index 153735009a..09501e8d06 100644 --- a/service/lib/y2storage/proposal/agama_devices_creator.rb +++ b/service/lib/y2storage/proposal/agama_devices_creator.rb @@ -141,7 +141,7 @@ def process_existing_partitionables planned_devices.each do |planned| next unless planned.reuse? - planned.reuse!(graph) + planned.reuse!(devicegraph) end # graph = create_separate_vgs(planned_devices, creator_result).devicegraph diff --git a/service/lib/y2storage/proposal/agama_devices_planner.rb b/service/lib/y2storage/proposal/agama_devices_planner.rb index 22ae41e4bd..a8b09147de 100644 --- a/service/lib/y2storage/proposal/agama_devices_planner.rb +++ b/service/lib/y2storage/proposal/agama_devices_planner.rb @@ -22,6 +22,7 @@ require "y2storage/disk_size" require "y2storage/boot_requirements_checker" require "y2storage/exceptions" +require "y2storage/proposal/agama_drive_planner" module Y2Storage module Proposal @@ -68,12 +69,12 @@ def initial_planned_devices(devicegraph) # Agama::ProposalSettingsConversions and Agama::VolumesConversions planned = settings.drives - .flat_map { |d| planned_for_drive(d) } + .flat_map { |d| planned_for_drive(d, devicegraph) } .compact Planned::DevicesCollection.new(planned).tap do |collection| # TODO - remove_shadowed_subvols(collection.mountable_devices) + # remove_shadowed_subvols(collection.mountable_devices) end end @@ -87,8 +88,9 @@ def initial_planned_devices(devicegraph) def add_boot_devices(devices, devicegraph) return unless settings.boot.configure? - devices.unshift(*planned_boot_devices(devices, devicegraph)) - remove_shadowed_subvolumes(devices) + devices.prepend(planned_boot_devices(devices, devicegraph)) + # TODO + # remove_shadowed_subvolumes(devices) end protected @@ -102,8 +104,7 @@ def add_boot_devices(devices, devicegraph) # This method is 99% copied from the guided DevicesPlanner def planned_boot_devices(planned_devices, devicegraph) - # This line is basically guessing - boot_disk_name = settings.boot.device || settings.default_boot_device + boot_disk_name = settings.boot_device flat = planned_devices.flat_map do |dev| dev.respond_to?(:lvs) ? dev.lvs : dev @@ -121,7 +122,7 @@ def planned_boot_devices(planned_devices, devicegraph) # 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) + def planned_for_drive(drive, devicegraph) planner = AgamaDrivePlanner.new(devicegraph, config, issues_list) planner.planned_devices(drive) end diff --git a/service/lib/y2storage/proposal/agama_drive_planner.rb b/service/lib/y2storage/proposal/agama_drive_planner.rb index f8589b8bd4..59fd0eed2d 100644 --- a/service/lib/y2storage/proposal/agama_drive_planner.rb +++ b/service/lib/y2storage/proposal/agama_drive_planner.rb @@ -17,6 +17,8 @@ # 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 @@ -57,7 +59,7 @@ def planned_partitioned_drive(settings) # @param planned [Planned::Disk] # @param settings [Agama::Storage::Settings::Drive] def configure_drive(planned, settings) - planned.reuse_name = settings.device.name + planned.assign_reuse(settings.found_device) end end end diff --git a/service/lib/y2storage/proposal/agama_space_maker.rb b/service/lib/y2storage/proposal/agama_space_maker.rb index 0deb49da40..0bbd9fed3a 100644 --- a/service/lib/y2storage/proposal/agama_space_maker.rb +++ b/service/lib/y2storage/proposal/agama_space_maker.rb @@ -36,23 +36,12 @@ def guided_settings(settings, _config) target.space_settings.strategy = :bigger_resize target.space_settings.actions = [] - boot_device = settings.explicit_boot_device || implicit_boot_device(settings) + boot_device = settings.boot_device target.root_device = boot_device target.candidate_devices = [boot_device].compact end end - - private - - def implicit_boot_device(settings) - # TODO: preliminary implementation with very simplistic checks - root_drive = settings.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/test/agama/storage/storage_helpers.rb b/service/test/agama/storage/storage_helpers.rb index 29c84c9f2e..94baff35ff 100644 --- a/service/test/agama/storage/storage_helpers.rb +++ b/service/test/agama/storage/storage_helpers.rb @@ -54,30 +54,6 @@ def mock_storage_probing(devicegraph_file) def mock_hwinfo(hwinfo) allow_any_instance_of(Y2Storage::HWInfoReader).to receive(:for_device).and_return(hwinfo) end - - def planned_partition(attrs = {}) - part = Y2Storage::Planned::Partition.new(nil) - add_planned_attributes(part, attrs) - end - - def add_planned_attributes(device, attrs) - attrs = attrs.dup - - if device.respond_to?(:filesystem_type) - type = attrs.delete(:type) - device.filesystem_type = - if type.is_a?(::String) || type.is_a?(Symbol) - Y2Storage::Filesystems::Type.const_get(type.to_s.upcase) - else - type - end - end - - attrs.each_pair do |key, value| - device.send(:"#{key}=", value) - end - device - end end end end diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index 615b287595..d5b8fe15e9 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -29,8 +29,6 @@ before do mock_storage(devicegraph: "empty-hd-50GiB.yaml") - allow(Y2Storage::Proposal::AgamaDevicesPlanner).to receive(:new).and_return dev_generator - allow(dev_generator).to receive(:add_boot_devices) end subject(:proposal) do @@ -47,19 +45,15 @@ drive.partitions = [ Agama::Storage::Settings::Partition.new.tap do |part| part.mount = Agama::Storage::Settings::Mount.new.tap { |m| m.path = "/" } + part.size = Agama::Storage::Settings::SizeRange.new.tap do |size| + size.min = Y2Storage::DiskSize.GiB(8.5) + size.max = Y2Storage::DiskSize.unlimited + end end ] end end let(:issues_list) { [] } - let(:dev_generator) do - instance_double( - "Y2Storage::Proposal::AgamaDevicesPlanner", initial_planned_devices: planned_devices - ) - end - let(:planned_devices) do - [planned_partition(mount_point: "/", type: :ext4, min: Y2Storage::DiskSize.GiB(8.5))] - end describe "#propose" do it "does something" do From 9cbc50e7fcc9e9b7012da8100ee172371d30757c Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 23 Jul 2024 15:48:20 +0200 Subject: [PATCH 08/51] WIP: use shared logic from y2storage --- service/lib/y2storage/agama_proposal.rb | 51 ++++++------------ .../proposal/agama_device_planner.rb | 7 +-- .../proposal/agama_devices_planner.rb | 54 +++---------------- .../y2storage/proposal/agama_space_maker.rb | 6 +-- service/test/y2storage/agama_proposal_test.rb | 3 +- 5 files changed, 27 insertions(+), 94 deletions(-) diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index 98103b5ba7..edcb3e7fd9 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -45,27 +45,22 @@ class AgamaProposal < Proposal::Base # @return [Agama::Storage::Profile] attr_reader :settings - # @return [Agama::Config] - attr_reader :config - # @return [Array] List of found issues attr_reader :issues_list # Constructor # # @param settings [Agama::Storage::Settings] proposal settings - # @param config [Agama::Config] # @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] - # - # TODO Check which params are mandatory - def initialize(settings, config, issues_list) + def initialize(settings, issues_list) @settings = settings - @config = config @issues_list = issues_list end @@ -61,21 +56,7 @@ def initialize(settings, config, issues_list) # @param devicegraph [Devicegraph] # @return [Array] def initial_planned_devices(devicegraph) - # This will need to share a lot of logic with the DevicesPlanner of the guided proposal, - # especially everything related to sizes and Btfs stuff. Maybe extracting the logic to - # shared classes, maybe making this a descendant... to be decided during implementation - # - # This will also include a lot of logic that nowadays lives at - # Agama::ProposalSettingsConversions and Agama::VolumesConversions - - planned = settings.drives - .flat_map { |d| planned_for_drive(d, devicegraph) } - .compact - - Planned::DevicesCollection.new(planned).tap do |collection| - # TODO - # remove_shadowed_subvols(collection.mountable_devices) - end + settings.drives.flat_map { |d| planned_for_drive(d, devicegraph) }.compact end # Modifies the given list of planned devices, adding any planned partition needed for booting @@ -88,42 +69,19 @@ def initial_planned_devices(devicegraph) def add_boot_devices(devices, devicegraph) return unless settings.boot.configure? - devices.prepend(planned_boot_devices(devices, devicegraph)) - # TODO - # remove_shadowed_subvolumes(devices) + boot = PlannedProcessor.new(devices).boot_devices(:min, devicegraph, settings.boot_device) + devices.unshift(*boot) end protected - # Agama configuration, needed to read the list of volumes of the product - # @return [Agama::Config] - attr_reader :config - # @return [Array] List to register any found issue attr_reader :issues_list - # This method is 99% copied from the guided DevicesPlanner - def planned_boot_devices(planned_devices, devicegraph) - boot_disk_name = settings.boot_device - - flat = planned_devices.flat_map do |dev| - dev.respond_to?(:lvs) ? dev.lvs : dev - end - checker = BootRequirementsChecker.new( - devicegraph, planned_devices: flat, boot_disk_name: boot_disk_name - ) - checker.needed_partitions(:min) - rescue BootRequirementsChecker::Error => e - # As documented, {BootRequirementsChecker#needed_partition} raises this - # exception if it's impossible to get a bootable system, even adding - # more partitions. - raise NotBootableError, e.message - end - # 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, config, issues_list) + planner = AgamaDrivePlanner.new(devicegraph, issues_list) planner.planned_devices(drive) end end diff --git a/service/lib/y2storage/proposal/agama_space_maker.rb b/service/lib/y2storage/proposal/agama_space_maker.rb index 0bbd9fed3a..baf92aa017 100644 --- a/service/lib/y2storage/proposal/agama_space_maker.rb +++ b/service/lib/y2storage/proposal/agama_space_maker.rb @@ -24,11 +24,11 @@ module Y2Storage module Proposal class AgamaSpaceMaker < SpaceMaker # Initialize. - def initialize(disk_analyzer, settings, config) - super(disk_analyzer, guided_settings(settings, config)) + def initialize(disk_analyzer, settings) + super(disk_analyzer, guided_settings(settings)) end - def guided_settings(settings, _config) + 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). diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index d5b8fe15e9..b59c742f13 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -32,9 +32,8 @@ end subject(:proposal) do - described_class.new(initial_settings, config, issues_list: issues_list) + described_class.new(initial_settings, issues_list: issues_list) end - let(:config) { Agama::Config.new } let(:initial_settings) do Agama::Storage::Profile.new.tap do |settings| settings.drives = [drive] From 5271abfc722dfa2984818ec9f5c612702a1bfe9f Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 23 Jul 2024 16:28:04 +0200 Subject: [PATCH 09/51] WIP: remove broken support for partition type --- service/lib/y2storage/proposal/agama_device_planner.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index f65f196b97..15b7443139 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -107,9 +107,6 @@ def configure_partitions(planned, settings) def planned_partition(settings) Planned::Partition.new(nil, nil).tap do |planned| planned.partition_id = settings.id - # FIXME: do we want to allow to force primary partitions? That was not really - # well solved at AutoYaST - # planned.primary = settings.primary? configure_device(planned, settings) configure_size(planned, settings.size) end From ba2898dc90665c36debf51402552b5a9c509231e Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 23 Jul 2024 16:43:12 +0200 Subject: [PATCH 10/51] WIP: renaming settings namespace - step 1 --- service/lib/agama/storage/{settings.rb => configs.rb} | 0 service/lib/agama/storage/{settings => configs}/drive.rb | 0 service/lib/agama/storage/{settings => configs}/encrypt.rb | 0 service/lib/agama/storage/{settings => configs}/format.rb | 0 service/lib/agama/storage/{settings => configs}/mount.rb | 0 service/lib/agama/storage/{settings => configs}/partition.rb | 0 service/lib/agama/storage/{settings => configs}/search.rb | 0 service/lib/agama/storage/{settings => configs}/size_range.rb | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename service/lib/agama/storage/{settings.rb => configs.rb} (100%) rename service/lib/agama/storage/{settings => configs}/drive.rb (100%) rename service/lib/agama/storage/{settings => configs}/encrypt.rb (100%) rename service/lib/agama/storage/{settings => configs}/format.rb (100%) rename service/lib/agama/storage/{settings => configs}/mount.rb (100%) rename service/lib/agama/storage/{settings => configs}/partition.rb (100%) rename service/lib/agama/storage/{settings => configs}/search.rb (100%) rename service/lib/agama/storage/{settings => configs}/size_range.rb (100%) diff --git a/service/lib/agama/storage/settings.rb b/service/lib/agama/storage/configs.rb similarity index 100% rename from service/lib/agama/storage/settings.rb rename to service/lib/agama/storage/configs.rb diff --git a/service/lib/agama/storage/settings/drive.rb b/service/lib/agama/storage/configs/drive.rb similarity index 100% rename from service/lib/agama/storage/settings/drive.rb rename to service/lib/agama/storage/configs/drive.rb diff --git a/service/lib/agama/storage/settings/encrypt.rb b/service/lib/agama/storage/configs/encrypt.rb similarity index 100% rename from service/lib/agama/storage/settings/encrypt.rb rename to service/lib/agama/storage/configs/encrypt.rb diff --git a/service/lib/agama/storage/settings/format.rb b/service/lib/agama/storage/configs/format.rb similarity index 100% rename from service/lib/agama/storage/settings/format.rb rename to service/lib/agama/storage/configs/format.rb diff --git a/service/lib/agama/storage/settings/mount.rb b/service/lib/agama/storage/configs/mount.rb similarity index 100% rename from service/lib/agama/storage/settings/mount.rb rename to service/lib/agama/storage/configs/mount.rb diff --git a/service/lib/agama/storage/settings/partition.rb b/service/lib/agama/storage/configs/partition.rb similarity index 100% rename from service/lib/agama/storage/settings/partition.rb rename to service/lib/agama/storage/configs/partition.rb diff --git a/service/lib/agama/storage/settings/search.rb b/service/lib/agama/storage/configs/search.rb similarity index 100% rename from service/lib/agama/storage/settings/search.rb rename to service/lib/agama/storage/configs/search.rb diff --git a/service/lib/agama/storage/settings/size_range.rb b/service/lib/agama/storage/configs/size_range.rb similarity index 100% rename from service/lib/agama/storage/settings/size_range.rb rename to service/lib/agama/storage/configs/size_range.rb From 97441b1e13cb23939c3f1ab2e41145b93df6bc1a Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 23 Jul 2024 16:48:08 +0200 Subject: [PATCH 11/51] WIP: renaming Settings namespace - step 2 --- service/lib/agama/storage/configs.rb | 16 ++++++++-------- service/lib/agama/storage/configs/drive.rb | 4 ++-- service/lib/agama/storage/configs/encrypt.rb | 2 +- service/lib/agama/storage/configs/format.rb | 2 +- service/lib/agama/storage/configs/mount.rb | 2 +- service/lib/agama/storage/configs/partition.rb | 2 +- service/lib/agama/storage/configs/search.rb | 2 +- service/lib/agama/storage/configs/size_range.rb | 2 +- service/lib/agama/storage/profile.rb | 2 +- service/test/y2storage/agama_proposal_test.rb | 8 ++++---- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/service/lib/agama/storage/configs.rb b/service/lib/agama/storage/configs.rb index 00ed64e97a..561f8dac77 100644 --- a/service/lib/agama/storage/configs.rb +++ b/service/lib/agama/storage/configs.rb @@ -22,15 +22,15 @@ module Agama module Storage # Namespace for all the supported settings to configure storage - module Settings + module Configs end end end -require "agama/storage/settings/drive" -require "agama/storage/settings/encrypt" -require "agama/storage/settings/format" -require "agama/storage/settings/mount" -require "agama/storage/settings/partition" -require "agama/storage/settings/search" -require "agama/storage/settings/size_range" +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/drive.rb b/service/lib/agama/storage/configs/drive.rb index d92ca3a8cd..a0a3ac4001 100644 --- a/service/lib/agama/storage/configs/drive.rb +++ b/service/lib/agama/storage/configs/drive.rb @@ -19,11 +19,11 @@ # 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/settings/search" +require "agama/storage/configs/search" module Agama module Storage - module Settings + module Configs class Drive attr_accessor :search attr_accessor :encrypt diff --git a/service/lib/agama/storage/configs/encrypt.rb b/service/lib/agama/storage/configs/encrypt.rb index d331513343..f129373f77 100644 --- a/service/lib/agama/storage/configs/encrypt.rb +++ b/service/lib/agama/storage/configs/encrypt.rb @@ -21,7 +21,7 @@ module Agama module Storage - module Settings + module Configs class Mount attr_accessor :method attr_accessor :key diff --git a/service/lib/agama/storage/configs/format.rb b/service/lib/agama/storage/configs/format.rb index d5d0ff955f..abf0faebff 100644 --- a/service/lib/agama/storage/configs/format.rb +++ b/service/lib/agama/storage/configs/format.rb @@ -21,7 +21,7 @@ module Agama module Storage - module Settings + module Configs class Format attr_accessor :filesystem attr_accessor :label diff --git a/service/lib/agama/storage/configs/mount.rb b/service/lib/agama/storage/configs/mount.rb index f805990482..9b3be11224 100644 --- a/service/lib/agama/storage/configs/mount.rb +++ b/service/lib/agama/storage/configs/mount.rb @@ -21,7 +21,7 @@ module Agama module Storage - module Settings + module Configs class Mount attr_accessor :path attr_accessor :options diff --git a/service/lib/agama/storage/configs/partition.rb b/service/lib/agama/storage/configs/partition.rb index d3c97638b0..2133327b34 100644 --- a/service/lib/agama/storage/configs/partition.rb +++ b/service/lib/agama/storage/configs/partition.rb @@ -21,7 +21,7 @@ module Agama module Storage - module Settings + module Configs class Partition attr_accessor :search attr_accessor :id diff --git a/service/lib/agama/storage/configs/search.rb b/service/lib/agama/storage/configs/search.rb index 64374e4b8a..15ae284a5c 100644 --- a/service/lib/agama/storage/configs/search.rb +++ b/service/lib/agama/storage/configs/search.rb @@ -21,7 +21,7 @@ module Agama module Storage - module Settings + module Configs class Search attr_reader :device diff --git a/service/lib/agama/storage/configs/size_range.rb b/service/lib/agama/storage/configs/size_range.rb index ac4802e769..164816d23d 100644 --- a/service/lib/agama/storage/configs/size_range.rb +++ b/service/lib/agama/storage/configs/size_range.rb @@ -21,7 +21,7 @@ module Agama module Storage - module Settings + module Configs class SizeRange attr_accessor :min attr_accessor :max diff --git a/service/lib/agama/storage/profile.rb b/service/lib/agama/storage/profile.rb index 895de1a40c..87de61be9b 100644 --- a/service/lib/agama/storage/profile.rb +++ b/service/lib/agama/storage/profile.rb @@ -20,7 +20,7 @@ # find current contact information at www.suse.com. require "agama/storage/boot_settings" -require "agama/storage/settings" +require "agama/storage/configs" module Agama module Storage diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index b59c742f13..a7c29c2694 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -40,11 +40,11 @@ end end let(:drive) do - Agama::Storage::Settings::Drive.new.tap do |drive| + Agama::Storage::Configs::Drive.new.tap do |drive| drive.partitions = [ - Agama::Storage::Settings::Partition.new.tap do |part| - part.mount = Agama::Storage::Settings::Mount.new.tap { |m| m.path = "/" } - part.size = Agama::Storage::Settings::SizeRange.new.tap do |size| + 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 From be3066601e8bc2c5e3492d7399bed1065f201d27 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 23 Jul 2024 16:50:35 +0200 Subject: [PATCH 12/51] WIP: rename Storage::Profile to Storage::Config --- service/lib/agama/storage/{profile.rb => config.rb} | 2 +- service/test/y2storage/agama_proposal_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename service/lib/agama/storage/{profile.rb => config.rb} (99%) diff --git a/service/lib/agama/storage/profile.rb b/service/lib/agama/storage/config.rb similarity index 99% rename from service/lib/agama/storage/profile.rb rename to service/lib/agama/storage/config.rb index 87de61be9b..8344e1b9ba 100644 --- a/service/lib/agama/storage/profile.rb +++ b/service/lib/agama/storage/config.rb @@ -25,7 +25,7 @@ module Agama module Storage # Settings used to calculate an storage proposal. - class Profile + class Config # Boot settings. # # @return [BootSettings] diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index a7c29c2694..7346e2669b 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -21,7 +21,7 @@ require_relative "../agama/storage/storage_helpers" require "agama/config" -require "agama/storage/profile" +require "agama/storage/config" require "y2storage/agama_proposal" describe Y2Storage::AgamaProposal do @@ -35,7 +35,7 @@ described_class.new(initial_settings, issues_list: issues_list) end let(:initial_settings) do - Agama::Storage::Profile.new.tap do |settings| + Agama::Storage::Config.new.tap do |settings| settings.drives = [drive] end end From 6ea054e5892c54fa96e214ecb82e2bf10449e5ad Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 23 Jul 2024 16:52:22 +0200 Subject: [PATCH 13/51] WIP: remove obsolete comment --- service/lib/y2storage/proposal/agama_searcher.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/service/lib/y2storage/proposal/agama_searcher.rb b/service/lib/y2storage/proposal/agama_searcher.rb index 68ae3eed37..fc103ee988 100644 --- a/service/lib/y2storage/proposal/agama_searcher.rb +++ b/service/lib/y2storage/proposal/agama_searcher.rb @@ -35,7 +35,6 @@ def initialize def search(devicegraph, settings, issues_list) settings.original_graph = devicegraph - # TODO: If IfNotFound is 'error' => register error sids = [] settings.drives.each do |drive| drive.search_device(devicegraph, sids) From c9b790cd020e2beeb9b04fc093292798a370421c Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 24 Jul 2024 14:28:47 +0200 Subject: [PATCH 14/51] WIP: Use Planned::DevicesCollection where appropriate --- service/lib/y2storage/agama_proposal.rb | 87 ++++++++++++------- .../proposal/agama_device_planner.rb | 1 + .../proposal/agama_devices_creator.rb | 10 +-- .../proposal/agama_devices_planner.rb | 22 +---- service/test/y2storage/agama_proposal_test.rb | 6 +- 5 files changed, 62 insertions(+), 64 deletions(-) diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index edcb3e7fd9..e2178c507d 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -42,7 +42,7 @@ module Y2Storage # proposal.devices # => Proposed layout # class AgamaProposal < Proposal::Base - # @return [Agama::Storage::Profile] + # @return [Agama::Storage::Config] attr_reader :settings # @return [Array] List of found issues @@ -94,26 +94,17 @@ def calculate_proposal def propose_devicegraph devicegraph = initial_devicegraph.dup - planner = Proposal::AgamaDevicesPlanner.new(settings, issues_list) - @planned_devices = planner.initial_planned_devices(devicegraph) + calculate_initial_planned(devicegraph) clean_graph(devicegraph) - planner.add_boot_devices(@planned_devices, devicegraph) - Proposal::PlannedProcessor.new(@planned_devices).remove_shadowed_subvolumes + complete_planned(devicegraph) - result = create_devices(devicegraph, @planned_devices) + result = create_devices(devicegraph) result.devicegraph 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 calculate_initial_planned(devicegraph) + planner = Proposal::AgamaDevicesPlanner.new(settings, issues_list) + @planned_devices = planner.initial_planned_devices(devicegraph) end # Clean a devicegraph @@ -121,13 +112,35 @@ def add_partition_tables(devicegraph) # @return [Y2Storage::Devicegraph] def clean_graph(devicegraph) remove_empty_partition_tables(devicegraph) - protect_sids(planned_devices) - # NOTE: take into account (partitions on) pre-existing RAIDs? - partitions = partitions_for(planned_devices) - space_maker.prepare_devicegraph(devicegraph, partitions) + protect_sids + space_maker.prepare_devicegraph(devicegraph, partitions_for_clean) end - # Removes partition tables from candidate devices with empty partition table + # Modifies the given list of planned devices, removing shadowed subvolumes and + # adding any planned partition needed for booting the new target system + # + # @param devicegraph [Devicegraph] + def complete_planned(devicegraph) + if settings.boot.configure? + @planned_devices = planned_devices.prepend(boot_partitions(devicegraph)) + end + + planned_devices.remove_shadowed_subvols + end + + def boot_partitions(devicegraph) + checker = BootRequirementsChecker.new( + devicegraph, + planned_devices: planned_devices.mountable_devices, + boot_disk_name: settings.boot_device + ) + # NOTE: Should we try with :desired first? + checker.needed_partitions(:min) + rescue BootRequirementsChecker::Error => e + raise NotBootableError, e.message + end + + # Removes partition tables from candidate devices with empty partition table # # @note The devicegraph is modified. # @@ -150,36 +163,44 @@ def drives_with_empty_partition_table(devicegraph) # 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(planned_devices) - planned_devices.select { |d| d.is_a?(Planned::Partition) } + 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 - # - # @param devices [Array] def initial_planned_devices(devicegraph) - settings.drives.flat_map { |d| planned_for_drive(d, devicegraph) }.compact - end - - # Modifies the given list of planned devices, adding any planned partition needed for booting - # the new target system - # - # @param devices [Array] - # @param target [Symbol] see #planned_devices - # @param devicegraph [Devicegraph] - # @return [Array] - def add_boot_devices(devices, devicegraph) - return unless settings.boot.configure? - - boot = PlannedProcessor.new(devices).boot_devices(:min, devicegraph, settings.boot_device) - devices.unshift(*boot) + devs = settings.drives.flat_map { |d| planned_for_drive(d, devicegraph) }.compact + Planned::DevicesCollection.new(devs) end protected diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index 7346e2669b..474cd7ae38 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -36,10 +36,10 @@ end let(:initial_settings) do Agama::Storage::Config.new.tap do |settings| - settings.drives = [drive] + settings.drives = [root_drive] end end - let(:drive) do + let(:root_drive) do Agama::Storage::Configs::Drive.new.tap do |drive| drive.partitions = [ Agama::Storage::Configs::Partition.new.tap do |part| @@ -57,7 +57,7 @@ describe "#propose" do it "does something" do proposal.propose - expect(proposal.devices.partitions.size).to eq 1 + expect(proposal.devices.partitions.size).to eq 2 end end end From d539833e6ac7dc912baed1b9c14a5642a1801c45 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 15/51] WIP: Initial conversion from JSON --- service/lib/agama/storage/boot_settings.rb | 1 + 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 | 94 +++++++++++++ .../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/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 - 24 files changed, 1077 insertions(+), 20 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 diff --git a/service/lib/agama/storage/boot_settings.rb b/service/lib/agama/storage/boot_settings.rb index 228668280e..a6a73c3126 100644 --- a/service/lib/agama/storage/boot_settings.rb +++ b/service/lib/agama/storage/boot_settings.rb @@ -22,6 +22,7 @@ module Agama module Storage # Class for configuring the boot settings of the Agama storage proposal. + # @todo Move to Settings::Boot ? class BootSettings # Whether to configure partitions for booting. # diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index 8344e1b9ba..adc785a069 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. # @@ -47,21 +47,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..3fe7ffbc88 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/from_json.rb @@ -0,0 +1,94 @@ +# 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" + +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. + # boot_settings = boot_conversion + Agama::Storage::Config.new.tap do |config| + config.drives = convert_drives + end + end + + private + + # @return [Hash] + attr_reader :config_json + + # @return [Agama::Config] + attr_reader :product_config + + # def boot_conversion + # boot_json = config_json[:boot] + # return unless boot_json + + # Agama::Storage::BootSettings.new.tap do |boot_settings| + # boot_settings.configure = boot_json[:configure] + # boot_settings.device = boot_json[:device] + # end + # end + + # @return [Array] + 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..ed5d88ad6f --- /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 = BlkDevice::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/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 From 441557ab397e4a6cb8cf65207450f9fadaf66e41 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 24 Jul 2024 16:08:47 +0200 Subject: [PATCH 16/51] WIP: extend preliminary unit tests --- service/test/y2storage/agama_proposal_test.rb | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index 474cd7ae38..6b7224b80e 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -55,9 +55,38 @@ let(:issues_list) { [] } describe "#propose" do - it "does something" do - proposal.propose - expect(proposal.devices.partitions.size).to eq 2 + 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 c1fd88f65a7860a5ec29b462fbfecafa922f45f4 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 15:31:07 +0100 Subject: [PATCH 17/51] WIP: Move boot settings to Configs namespace --- service/lib/agama/storage/config.rb | 5 ++-- service/lib/agama/storage/configs.rb | 1 + .../{boot_settings.rb => configs/boot.rb} | 30 ++++++++++--------- .../lib/agama/storage/proposal_settings.rb | 8 ++--- .../from_json.rb | 4 +-- 5 files changed, 25 insertions(+), 23 deletions(-) rename service/lib/agama/storage/{boot_settings.rb => configs/boot.rb} (61%) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index adc785a069..4f1a83ffc8 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -19,7 +19,6 @@ # 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" module Agama @@ -28,7 +27,7 @@ module Storage class Config # Boot settings. # - # @return [BootSettings] + # @return [Configs::Boot] attr_accessor :boot attr_accessor :drives @@ -39,7 +38,7 @@ class Config attr_accessor :original_graph def initialize - @boot = BootSettings.new + @boot = Configs::Boot.new @drives = [] @volume_groups = [] @md_raids = [] diff --git a/service/lib/agama/storage/configs.rb b/service/lib/agama/storage/configs.rb index 561f8dac77..abb1359440 100644 --- a/service/lib/agama/storage/configs.rb +++ b/service/lib/agama/storage/configs.rb @@ -27,6 +27,7 @@ module Configs end end +require "agama/storage/configs/boot" require "agama/storage/configs/drive" require "agama/storage/configs/encrypt" require "agama/storage/configs/format" diff --git a/service/lib/agama/storage/boot_settings.rb b/service/lib/agama/storage/configs/boot.rb similarity index 61% rename from service/lib/agama/storage/boot_settings.rb rename to service/lib/agama/storage/configs/boot.rb index a6a73c3126..a5c76657b5 100644 --- a/service/lib/agama/storage/boot_settings.rb +++ b/service/lib/agama/storage/configs/boot.rb @@ -21,22 +21,24 @@ module Agama module Storage - # Class for configuring the boot settings of the Agama storage proposal. - # @todo Move to Settings::Boot ? - class BootSettings - # Whether to configure partitions for booting. - # - # @return [Boolean] - attr_accessor :configure - alias_method :configure?, :configure + 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] nil means use installation device. - attr_accessor :device + # 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 + def initialize + @configure = true + 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 From ebca7b08804bac0241150ef4b8da376f0215a7dc 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 15:46:33 +0100 Subject: [PATCH 18/51] WIP: Convert boot config from JSON --- .../storage/config_conversions/from_json.rb | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/service/lib/agama/storage/config_conversions/from_json.rb b/service/lib/agama/storage/config_conversions/from_json.rb index 3fe7ffbc88..4228cfd48d 100644 --- a/service/lib/agama/storage/config_conversions/from_json.rb +++ b/service/lib/agama/storage/config_conversions/from_json.rb @@ -21,6 +21,7 @@ require "agama/storage/config" require "agama/storage/config_conversions/drive/from_json" +require "agama/storage/configs/boot" module Agama module Storage @@ -41,9 +42,12 @@ def initialize(config_json, product_config:) # @return [Storage::Config] def convert # @todo Raise error if config_json does not match the JSON schema. - # boot_settings = boot_conversion - Agama::Storage::Config.new.tap do |config| - config.drives = convert_drives + Storage::Config.new.tap do |config| + boot = convert_boot + drives = convert_drives + + config.boot = boot if boot + config.drives = drives if drives end end @@ -55,20 +59,21 @@ def convert # @return [Agama::Config] attr_reader :product_config - # def boot_conversion - # boot_json = config_json[:boot] - # return unless boot_json + # @return [Configs::Boot, nil] + def convert_boot + boot_json = config_json[:boot] + return unless boot_json - # Agama::Storage::BootSettings.new.tap do |boot_settings| - # boot_settings.configure = boot_json[:configure] - # boot_settings.device = boot_json[:device] - # end - # end + Configs::Boot.new.tap do |config| + config.configure = boot_json[:configure] + config.device = boot_json[:device] + end + end - # @return [Array] + # @return [Array, nil] def convert_drives drives_json = config_json[:drives] - return [] unless drives_json + return unless drives_json drives_json.map { |d| convert_drive(d) } end From c9b84336d2cd5397f82ecabf1492a6b8d406142e Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 24 Jul 2024 17:25:27 +0200 Subject: [PATCH 19/51] WIP: initial version of a unit test with associated fixes --- .../storage/config_conversions/from_json.rb | 1 + .../config_conversions/partition/from_json.rb | 2 +- service/lib/agama/storage/configs.rb | 2 +- .../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 | 2 +- 7 files changed, 94 insertions(+), 4 deletions(-) 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_conversions/from_json.rb b/service/lib/agama/storage/config_conversions/from_json.rb index 3fe7ffbc88..15d48bcdc4 100644 --- a/service/lib/agama/storage/config_conversions/from_json.rb +++ b/service/lib/agama/storage/config_conversions/from_json.rb @@ -21,6 +21,7 @@ require "agama/storage/config" require "agama/storage/config_conversions/drive/from_json" +require "agama/storage/proposal_settings_reader" module Agama module Storage 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 ed5d88ad6f..748701fed4 100644 --- a/service/lib/agama/storage/config_conversions/partition/from_json.rb +++ b/service/lib/agama/storage/config_conversions/partition/from_json.rb @@ -83,7 +83,7 @@ def convert_size # @param config [Configs::Partition] def convert_block_device(config) - converter = BlkDevice::FromJSON.new(partition_json, + converter = BlockDevice::FromJSON.new(partition_json, settings: settings, volume_builder: volume_builder) converter.convert(config) diff --git a/service/lib/agama/storage/configs.rb b/service/lib/agama/storage/configs.rb index 561f8dac77..090c618f20 100644 --- a/service/lib/agama/storage/configs.rb +++ b/service/lib/agama/storage/configs.rb @@ -33,4 +33,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/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..398657eecb 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 From 04573949ed17554cd60ce18101f4737b2b094956 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 24 Jul 2024 17:28:53 +0200 Subject: [PATCH 20/51] WIP: fix after latest merge --- service/test/y2storage/agama_proposal_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index 398657eecb..a9396447b8 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -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 778bf2de7f238cfbd2e4e4f33b007d01867fb009 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 21/51] WIP: 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 d888009eca83697abf0db98752a63ea26e22051b 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 22/51] 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 42bd23c3da5777d09621a076db6d0b768dbf6337 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 23/51] WIP: 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 93409cd4d0c702f992390ee58b42b5306643b01d 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 24/51] WIP: 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 8b01b82c5e..79fd1f8d13 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 008e2f9732c45456b5395e9553ce304ac4581908 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 29 Jul 2024 15:43:31 +0200 Subject: [PATCH 25/51] WIP: some renamings --- 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 ++- 6 files changed, 24 insertions(+), 53 deletions(-) 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/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 From 8ede47fb0c9e579e2170c3879f989f55bb6fc815 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 29 Jul 2024 15:44:55 +0200 Subject: [PATCH 26/51] WIP: more renaming --- .../config_conversions/filesystem_type.rb | 32 +++++++++++++++++++ .../from_json.rb | 8 ++--- 2 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 service/lib/agama/storage/config_conversions/filesystem_type.rb rename service/lib/agama/storage/config_conversions/{filesystem => filesystem_type}/from_json.rb (92%) diff --git a/service/lib/agama/storage/config_conversions/filesystem_type.rb b/service/lib/agama/storage/config_conversions/filesystem_type.rb new file mode 100644 index 0000000000..ace22105b4 --- /dev/null +++ b/service/lib/agama/storage/config_conversions/filesystem_type.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_type/from_json" + +module Agama + module Storage + module ConfigConversions + # Conversions for filesystem types + module FilesystemType + 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_type/from_json.rb similarity index 92% rename from service/lib/agama/storage/config_conversions/filesystem/from_json.rb rename to service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb index 2045e39bc8..c3406a9972 100644 --- a/service/lib/agama/storage/config_conversions/filesystem/from_json.rb +++ b/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb @@ -20,13 +20,13 @@ # find current contact information at www.suse.com. require "agama/storage/configs/btrfs" -require "agama/storage/configs/filesystem" +require "agama/storage/configs/filesystem_type" require "y2storage/filesystems/type" module Agama module Storage module ConfigConversions - module Filesystem + module FilesystemType # Filesystem conversion from JSON hash according to schema. class FromJSON # @param filesystem_json [Hash, String] @@ -39,12 +39,12 @@ def initialize(filesystem_json) # @param default [Configs::Filesystem, nil] # @return [Configs::Filesystem] def convert(default = nil) - default_config = default.dup || Configs::Filesystem.new + default_config = default.dup || Configs::FilesystemType.new default_config.tap do |config| btrfs = convert_btrfs(config.btrfs) - config.type = convert_type + config.fstype = convert_type config.btrfs = btrfs if btrfs end end From 0c0f79f205c6e94de7e9819f8234376f9b30cf5e Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 29 Jul 2024 16:06:13 +0200 Subject: [PATCH 27/51] WIP: more renaming --- .../{format => filesystem}/from_json.rb | 34 ++++++------ .../storage/config_conversions/format.rb | 32 ----------- .../agama/storage/config_conversions/mount.rb | 32 ----------- .../config_conversions/mount/from_json.rb | 53 ------------------- 4 files changed, 18 insertions(+), 133 deletions(-) rename service/lib/agama/storage/config_conversions/{format => filesystem}/from_json.rb (59%) delete mode 100644 service/lib/agama/storage/config_conversions/format.rb delete mode 100644 service/lib/agama/storage/config_conversions/mount.rb delete mode 100644 service/lib/agama/storage/config_conversions/mount/from_json.rb diff --git a/service/lib/agama/storage/config_conversions/format/from_json.rb b/service/lib/agama/storage/config_conversions/filesystem/from_json.rb similarity index 59% rename from service/lib/agama/storage/config_conversions/format/from_json.rb rename to service/lib/agama/storage/config_conversions/filesystem/from_json.rb index 2f49d8ec82..1cbd6558b2 100644 --- a/service/lib/agama/storage/config_conversions/format/from_json.rb +++ b/service/lib/agama/storage/config_conversions/filesystem/from_json.rb @@ -19,32 +19,34 @@ # 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 "agama/storage/config_conversions/filesystem_type/from_json" +require "agama/storage/configs/filesystem" module Agama module Storage module ConfigConversions - module Format - # Format conversion from JSON hash according to schema. + module Filesystem + # Filesystem conversion from JSON hash according to schema. class FromJSON - # @param format_json [Hash] - def initialize(format_json) - @format_json = format_json + # @param filesystem_json [Hash] + def initialize(filesystem_json) + @filesystem_json = filesystem_json end # Performs the conversion from Hash according to the JSON schema. # - # @param default [Configs::Format, nil] + # @param default [Configs::Filesystem, nil] # @return [Configs::Format] def convert(default = nil) - default_config = default.dup || Configs::Format.new + default_config = default.dup || Configs::Filesystem.new default_config.tap do |config| - label = format_json[:label] - mkfs_options = format_json[:mkfsOptions] + label = filesystem_json[:label] + mkfs_options = filesystem_json[:mkfsOptions] - config.filesystem = convert_filesystem(config.filesystem) + 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 @@ -53,12 +55,12 @@ def convert(default = nil) private # @return [Hash] - attr_reader :format_json + attr_reader :filesystem_json # @param default [Configs::Filesystem, nil] - # @return [Configs::Filesystem] - def convert_filesystem(default = nil) - Filesystem::FromJSON.new(format_json[:filesystem]).convert(default) + # @return [Configs::FilesystemType] + def convert_type(default = nil) + FilesystemType::FromJSON.new(format_json[:type]).convert(default) 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/mount.rb b/service/lib/agama/storage/config_conversions/mount.rb deleted file mode 100644 index cd5ff6dec8..0000000000 --- a/service/lib/agama/storage/config_conversions/mount.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/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 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 From 5e00b952bcf9b00bc0b413b5ffb5bda688ab5944 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 29 Jul 2024 16:09:42 +0200 Subject: [PATCH 28/51] WIP: more renaming --- .../{encrypt.rb => encryption.rb} | 6 ++--- .../{encrypt => encryption}/from_json.rb | 26 +++++++++---------- 2 files changed, 16 insertions(+), 16 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%) 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 From bc1464cf34e9b2c0ffcb2dec71fe5b065850fa1a Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 29 Jul 2024 17:18:41 +0200 Subject: [PATCH 29/51] [WIP] Adapt code to new class names --- .../block_device/from_json.rb | 64 ++++++++----------- .../filesystem/from_json.rb | 2 +- service/lib/agama/storage/configs.rb | 5 +- .../config_conversions/from_json_test.rb | 21 ++++-- 4 files changed, 43 insertions(+), 49 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 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/filesystem/from_json.rb b/service/lib/agama/storage/config_conversions/filesystem/from_json.rb index 1cbd6558b2..ac637f2434 100644 --- a/service/lib/agama/storage/config_conversions/filesystem/from_json.rb +++ b/service/lib/agama/storage/config_conversions/filesystem/from_json.rb @@ -60,7 +60,7 @@ def convert(default = nil) # @param default [Configs::Filesystem, nil] # @return [Configs::FilesystemType] def convert_type(default = nil) - FilesystemType::FromJSON.new(format_json[:type]).convert(default) + FilesystemType::FromJSON.new(filesystem_json[:type]).convert(default) 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/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 1caea31d7fbbae82037b01e38a372802ea360bbf Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 30 Jul 2024 16:09:07 +0200 Subject: [PATCH 30/51] WIP: 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 79fd1f8d13..6c1cace51d 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 f9dae900eca147a8ff15de8ccb4a59af1bda45a7 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 30 Jul 2024 17:17:33 +0200 Subject: [PATCH 31/51] WIP: a bit more of testing --- .../config_conversions/from_json_test.rb | 68 ++++++++++++++----- 1 file changed, 50 insertions(+), 18 deletions(-) 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..48bb205f25 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -69,27 +69,59 @@ end describe "#convert" do - let(:config_json) do - { - boot: { - configure: true, - device: "/dev/sdb" - }, - drives: [ - { - ptableType: "gpt", - partitions: [ - { filesystem: { path: "/", type: "ext4" } } - ] - } - ] - } + 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 - it "generates settings with the values provided from JSON" do - config = subject.convert + 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: "/", type: { btrfs: { snapshots: false } } } + } + ] + } + ] + } + end + + it "generates a storage configuration" do + config = subject.convert + expect(config).to be_a(Agama::Storage::Config) + end - expect(config).to be_a(Agama::Storage::Config) + 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 end end end From 2eecbfb7dfdedbbec06f59f939dd52bc675bac75 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 31 Jul 2024 16:08:47 +0200 Subject: [PATCH 32/51] WIP: tests and fixes for size calculation --- service/lib/agama/storage/config.rb | 15 +- .../filesystem_type/from_json.rb | 2 +- .../config_conversions/from_json_test.rb | 162 ++++++++++++++++-- 3 files changed, 159 insertions(+), 20 deletions(-) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index cf1b6df89c..7f41be1bf9 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -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/filesystem_type/from_json.rb b/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb index 8d0d4e7cc0..f1a97e9c2f 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 @@ -72,7 +72,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/test/agama/storage/config_conversions/from_json_test.rb b/service/test/agama/storage/config_conversions/from_json_test.rb index 48bb205f25..386cae557c 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,22 +57,29 @@ } }, { - "mount_path" => "/home", - "outline" => { - "required" => false, "filesystem" => "xfs", - "size" => { "auto" => false, "min" => "5 GiB" } - } + "mount_path" => "/home", "size" => { "auto" => false, "min" => "5 GiB" }, + "outline" => { "required" => false, "filesystem" => "xfs" } }, { "mount_path" => "swap", - "outline" => { "required" => false } - } + "outline" => { "required" => false, "filesystem" => "swap" } + }, + { "mount_path" => "", "size" => { "min" => "100 MiB" } } ] } } end describe "#convert" do + using Y2Storage::Refinements::SizeCasts + + # TODO: + # Encryption + # Filesystem type (btrfs, etc) + # Filesystem at disk (including default types based on config, etc.) + # Filesystem at partition + # Partition id + context "with an empty JSON configuration" do let(:config_json) { {} } @@ -94,18 +105,11 @@ 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", - partitions: [ - { - filesystem: { path: "/", type: { btrfs: { snapshots: false } } } - } - ] + partitions: [{ filesystem: { path: "/" } }] } ] } @@ -122,6 +126,132 @@ 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 "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.fstype).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 end end end From 30a7f405594ab05bdbf68f09cb596958929ee408 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Thu, 1 Aug 2024 14:11:21 +0200 Subject: [PATCH 33/51] WIP: more tests and some fixes reading encryption from JSON --- .../encryption/from_json.rb | 18 ++- .../config_conversions/from_json_test.rb | 137 +++++++++++++++++- 2 files changed, 147 insertions(+), 8 deletions(-) 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/test/agama/storage/config_conversions/from_json_test.rb b/service/test/agama/storage/config_conversions/from_json_test.rb index 386cae557c..8abb72f524 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -73,13 +73,6 @@ describe "#convert" do using Y2Storage::Refinements::SizeCasts - # TODO: - # Encryption - # Filesystem type (btrfs, etc) - # Filesystem at disk (including default types based on config, etc.) - # Filesystem at partition - # Partition id - context "with an empty JSON configuration" do let(:config_json) { {} } @@ -233,6 +226,41 @@ 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 { @@ -252,6 +280,101 @@ 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.fstype).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 "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" } + }, + { + "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 + + 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 + + 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 From d64ddb44278172c6dc57c5ff0034429156d34c0f Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Thu, 1 Aug 2024 16:02:04 +0200 Subject: [PATCH 34/51] WIP: more tests and corresponding fixes --- service/lib/agama/storage/config.rb | 2 +- .../block_device/from_json.rb | 2 +- .../filesystem_type/from_json.rb | 6 ++- .../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 | 46 ++++++++++++++++--- service/test/y2storage/agama_proposal_test.rb | 19 +++++--- 8 files changed, 71 insertions(+), 36 deletions(-) diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index 7f41be1bf9..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 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/filesystem_type/from_json.rb b/service/lib/agama/storage/config_conversions/filesystem_type/from_json.rb index f1a97e9c2f..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 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 8abb72f524..1af4f637dd 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -58,13 +58,14 @@ }, { "mount_path" => "/home", "size" => { "auto" => false, "min" => "5 GiB" }, - "outline" => { "required" => false, "filesystem" => "xfs" } + "filesystem" => "xfs", "outline" => { "required" => false} }, { - "mount_path" => "swap", - "outline" => { "required" => false, "filesystem" => "swap" } + "mount_path" => "swap", "filesystem" => "swap", + "outline" => { "required" => false } }, - { "mount_path" => "", "size" => { "min" => "100 MiB" } } + { "mount_path" => "", "filesystem" => "ext4", + "size" => { "min" => "100 MiB" } } ] } } @@ -274,7 +275,7 @@ it "uses the default type and btrfs attributes for that path" do config = subject.convert filesystem = config.drives.first.filesystem - expect(filesystem.type.fstype).to eq Y2Storage::Filesystems::Type::BTRFS + 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"] @@ -291,7 +292,7 @@ it "uses the specified btrfs attributes" do config = subject.convert filesystem = config.drives.first.filesystem - expect(filesystem.type.fstype).to eq Y2Storage::Filesystems::Type::BTRFS + 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 "" @@ -300,6 +301,39 @@ 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 { 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 5d4bb3878fe7a028a61ac144bc8f9e4457bf830c Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 5 Aug 2024 17:25:36 +0200 Subject: [PATCH 35/51] WIP: 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 c42c110e570d5fc43fa16d722c4b658cb3417d15 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 6 Aug 2024 16:03:40 +0200 Subject: [PATCH 36/51] WIP: 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 21039946ebb072ea967772966ec55b4656755a4d Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 7 Aug 2024 17:48:42 +0200 Subject: [PATCH 37/51] 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 c5e8a5ab3e32d1af4659f7a50c202649068f3de1 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 13 Aug 2024 15:47:38 +0200 Subject: [PATCH 38/51] WIP: 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 a72c6b48aa22c31268c0720bb419117abf5d9244 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 13 Aug 2024 15:54:24 +0200 Subject: [PATCH 39/51] WIP: turn some TODOs into NOTEs --- service/lib/agama/storage/config.rb | 2 +- service/lib/y2storage/agama_proposal.rb | 2 +- service/lib/y2storage/proposal/agama_devices_creator.rb | 2 +- 3 files changed, 3 insertions(+), 3 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_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 ebf0081768feaa451180d03adf8832726e9b5d29 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 19 Aug 2024 12:44:20 +0200 Subject: [PATCH 40/51] WIP: 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 17b6da46d9..aa012e05e0 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -89,7 +89,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 782196e2e142011047957e9b11defd3e49a6425b Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 19 Aug 2024 16:32:37 +0200 Subject: [PATCH 41/51] WIP: 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 588d778d4528122ca1fb5f6af525620597357a93 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 20 Aug 2024 11:36:38 +0200 Subject: [PATCH 42/51] WIP: adjust a FIXME --- service/lib/y2storage/proposal/agama_device_planner.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index aa012e05e0..0992dbcdf6 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 From 98d88a65cf31129fe8d86adb9889b8aa11d5fd44 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 43/51] 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 c597edeabacaeeb542aa56bc8f55f8feef3c3164 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 44/51] 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 53f8ac68a9e75d9473ccb706cb983ac76dadb7a2 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 45/51] 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 d31e6a8ad1b211204e2d6d723877e26a47e313e4 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 20 Aug 2024 17:01:39 +0200 Subject: [PATCH 46/51] WIP: minimal (probably incorrect) fix --- service/lib/y2storage/proposal/agama_device_planner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index b068b17423..cc34791a60 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -66,7 +66,7 @@ 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 From 9583ae807dbbfbb4bc709da2b24148ac2417a974 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Tue, 20 Aug 2024 17:02:26 +0200 Subject: [PATCH 47/51] WIP: Yardoc and fixes for Storage::Config --- service/lib/agama/storage/config.rb | 50 ++++++++++++++++--- 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 +- 10 files changed, 133 insertions(+), 26 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/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 From b930884a7043c665ba82147db7712b4d849905eb Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 21 Aug 2024 13:45:21 +0200 Subject: [PATCH 48/51] WIP: yardoc for Y2Storage::AgamaProposal --- service/lib/y2storage/agama_proposal.rb | 53 +++++++++++-------- .../proposal/agama_device_planner.rb | 30 +++++++---- .../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 +- 8 files changed, 119 insertions(+), 71 deletions(-) diff --git a/service/lib/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index f59e7885c0..3399954683 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,7 +52,7 @@ 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 @@ -67,13 +67,12 @@ def initialize(initial_settings, devicegraph: nil, disk_analyzer: nil, issues_li private - # Not sure if needed in the final version - # @return [ProposalSettings] - # attr_reader :guided_settings - # @return [Proposal::SpaceMaker] attr_reader :space_maker + # Whether the list of issues generated so far already includes any serious error + # + # @return [Boolean] def fatal_error? issues_list.any?(&:error?) end @@ -95,7 +94,6 @@ def calculate_proposal # Proposes a devicegraph based on given configuration # - # @param devicegraph [Devicegraph] Starting point # @return [Devicegraph, nil] Devicegraph containing the planned devices, nil if the proposal # failed def propose_devicegraph @@ -113,20 +111,27 @@ def propose_devicegraph result.devicegraph end + # Fills the list of planned devices, excluding partitions from the boot requirements checker + # + # @return [Planned::DevicesCollection] def calculate_initial_planned(devicegraph) planner = Proposal::AgamaDevicesPlanner.new(settings, issues_list) @planned_devices = planner.initial_planned_devices(devicegraph) end - # Clean a devicegraph + # Performs the mandatory space-making actions on the given devicegraph # - # @return [Y2Storage::Devicegraph] + # @param devicegraph [Devicegraph] the graph gets modified def clean_graph(devicegraph) remove_empty_partition_tables(devicegraph) protect_sids space_maker.prepare_devicegraph(devicegraph, partitions_for_clean) end + # Configures the disk devices on the given devicegraph to prefer the appropriate partition table + # types, if any partition table needs to be created later during the proposal + # + # @param devicegraph [Devicegraph] the graph gets modified def configure_ptable_types(devicegraph) configured = settings.drives.select(&:ptable_type) configured.each do |drive| @@ -137,8 +142,8 @@ def configure_ptable_types(devicegraph) end end - # Modifies the given list of planned devices, removing shadowed subvolumes and - # adding any planned partition needed for booting the new target system + # Modifies the list of planned devices, removing shadowed subvolumes and adding any planned + # partition needed for booting the new target system # # @param devicegraph [Devicegraph] def complete_planned(devicegraph) @@ -149,6 +154,7 @@ def complete_planned(devicegraph) planned_devices.remove_shadowed_subvols end + # @see #complete_planned def boot_partitions(devicegraph) checker = BootRequirementsChecker.new( devicegraph, @@ -163,9 +169,7 @@ def boot_partitions(devicegraph) # Removes partition tables from candidate devices with empty partition table # - # @note The devicegraph is modified. - # - # @param devicegraph [Y2Storage::Devicegraph] + # @param devicegraph [Devicegraph] the graph gets modified # @return [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 cc34791a60..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,14 +59,14 @@ 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 @@ -72,14 +77,14 @@ def configure_filesystem(planned, settings) 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 9a690aae30eac603d9c89767167c5bc258d1321b Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 21 Aug 2024 13:53:31 +0200 Subject: [PATCH 49/51] WIP: two minor yardoc fixes --- .../agama/storage/config_conversions/block_device/from_json.rb | 2 +- service/lib/y2storage/agama_proposal.rb | 2 +- 2 files changed, 2 insertions(+), 2 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 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/y2storage/agama_proposal.rb b/service/lib/y2storage/agama_proposal.rb index 3399954683..6628e41dce 100644 --- a/service/lib/y2storage/agama_proposal.rb +++ b/service/lib/y2storage/agama_proposal.rb @@ -56,7 +56,7 @@ class AgamaProposal < Proposal::Base # @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 Date: Wed, 21 Aug 2024 15:52:29 +0200 Subject: [PATCH 50/51] WIP: adapt to recent changes at yast2-storage-ng --- service/lib/y2storage/agama_proposal.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From 5711b4ee94841efd3c69f6607c0aaae2add90ffb Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Wed, 21 Aug 2024 16:01:38 +0200 Subject: [PATCH 51/51] Update dependency on yast2-storage-ng --- service/package/gem2rpm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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