# frozen_string_literal: true # Copyright (c) [2024-2026] SUSE LLC # # All Rights Reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms of version 2 of the GNU General Public License as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, contact SUSE LLC. # # To contact SUSE LLC about this file by physical or electronic mail, you may # find current contact information at www.suse.com. require "y2storage/planned" require "y2storage/proposal/agama_md_name" module Y2Storage module Proposal # Base class used by Agama planners. class AgamaDevicePlanner include AgamaMdName # @!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 # @!attribute [r] partitionables # Map to track the planned devices that can contain partitions, indexed by alias # @return [Hash{String => Array}] attr_reader :partitionables # Constructor # # @param devicegraph [Devicegraph] see {#devicegraph} # @param issues_list [Array] see {#issues_list} # @param partitionables [Hash{String => Array}] see {#partitionables} def initialize(devicegraph, issues_list, partitionables) @devicegraph = devicegraph @issues_list = issues_list @partitionables = partitionables end # Planned devices according to the given config. # # @return [Array] def planned_devices raise NotImplementedError end private # @param planned [Planned::Disk, Planned::Partition] # @param config [Agama::Storage::Configs::Drive, Agama::Storage::Configs::Partition] def configure_reuse(planned, config) device = config.found_device return unless device planned.assign_reuse(device) planned.reformat = reformat?(device, config) if planned.respond_to?(:reformat=) planned.resize = grow?(device, config) if planned.respond_to?(:resize=) end # Whether to reformat the device. # # @param device [Y2Storage::BlkDevice] # @param config [Agama::Storage::Configs::Drive, Agama::Storage::Configs::Partition] # @return [Boolean] def reformat?(device, config) return true if device.filesystem.nil? # TODO: reformat if the encryption has to be created. !config.filesystem&.reuse? end # Whether the device is a candidate to be resized (grown) # # @param device [Y2Storage::BlkDevice] # @param config [Agama::Storage::Configs::Partition] # @return [Boolean] def grow?(device, config) return false unless config.size config.size.max.unlimited? || config.size.max > device.size end # @param planned [Planned::Disk, Planned::Partition, Planned::LvmLv] # @param config [#encryption, #filesystem] def configure_block_device(planned, config) configure_encryption(planned, config.encryption) if config.encryption configure_filesystem(planned, config.filesystem) if config.filesystem end # @param planned [Planned::Disk, Planned::Partition, Planned::LvmLv] # @param config [Agama::Storage::Configs::Filesystem] def configure_filesystem(planned, config) planned.mount_point = config.path planned.mount_by = config.mount_by planned.fstab_options = config.mount_options planned.mkfs_options = config.mkfs_args planned.label = config.label configure_filesystem_type(planned, config.type) if config.type end # @param planned [Planned::Disk, Planned::Partition, Planned::LvmLv] # @param config [Agama::Storage::Configs::FilesystemType] def configure_filesystem_type(planned, config) planned.filesystem_type = config.fs_type configure_btrfs(planned, config.btrfs) if config.btrfs end # @param planned [Planned::Disk, Planned::Partition, Planned::LvmLv] # @param config [Agama::Storage::Configs::Btrfs] def configure_btrfs(planned, config) # The read_only flag can only be set by the product configuration. # See Agama::Storage::Configs::Btrfs#read_only planned.read_only = config.read_only? planned.snapshots = config.snapshots? planned.default_subvolume = config.default_subvolume planned.subvolumes = config.subvolumes end # @param planned [Planned::Disk, Planned::Partition, Planned::LvmLv] # @param config [Agama::Storage::Configs::Encryption] def configure_encryption(planned, config) planned.encryption_password = config.password planned.encryption_method = config.method planned.encryption_pbkdf = config.pbkd_function planned.encryption_label = config.label planned.encryption_cipher = config.cipher planned.encryption_key_size = config.key_size configure_pervasive_encryption(planned, config) end # @param planned [Planned::Disk, Planned::Partition, Planned::LvmLv] # @param config [Agama::Storage::Configs::Encryption] def configure_pervasive_encryption(planned, config) planned.encryption_pervasive_key_type = config.pervasive_key_type # TODO: report APQN issues (e.g., not found, incompatible types, different master key, # offline). apqns = config.apqns || [] planned.encryption_pervasive_apqns = Y2Storage::EncryptionProcesses::Apqn .all .select { |a| apqns.include?(a.name) } end # @param planned [Planned::Partition, Planned::LvmLv] # @param config [Agama::Storage::Configs::Size] def configure_size(planned, config) planned.min_size = config.min planned.max_size = config.max planned.weight = 100 end # @param planned [Planned::Disk] # @param device_config [Agama::Storage::Configs::Drive] # @param config [Agama::Storage::Config] def configure_partitions(planned, device_config, config) planned.ptable_type = device_config.ptable_type if planned.respond_to?(:ptable_type=) partition_configs = device_config.partitions .reject(&:delete?) .reject(&:delete_if_needed?) .reject { |c| c.search&.skip_device? } planned.partitions = partition_configs.map do |partition_config| planned_partition(partition_config, device_config, config) end end # @param partition_config [Agama::Storage::Configs::Partition] # @param device_config [Agama::Storage::Configs::Drive] # @param config [Agama::Storage::Config] # # @return [Planned::Partition] def planned_partition(partition_config, device_config, config) Planned::Partition.new(nil, nil).tap do |planned| planned.disk = device_config.found_device&.name planned.partition_id = partition_config.id configure_reuse(planned, partition_config) configure_block_device(planned, partition_config) configure_size(planned, partition_config.size) configure_pv(planned, partition_config, config) configure_md_member(planned, partition_config, config) end end # @param planned [Planned::Disk, Planned::Partition] # @param device_config [Agama::Storage::Configs::Drive, Agama::Storage::Configs::Partition] # @param config [Agama::Storage::Config] def configure_pv(planned, device_config, config) return unless planned.respond_to?(:lvm_volume_group_name) && device_config.alias vg = config.volume_groups.find { |v| v.physical_volumes.include?(device_config.alias) } return unless vg planned.lvm_volume_group_name = vg.vg_name end # @param planned [Planned::Disk, Planned::Partition] # @param device_config [Agama::Storage::Configs::Drive, Agama::Storage::Configs::Partition] # @param config [Agama::Storage::Config] def configure_md_member(planned, device_config, config) return unless planned.respond_to?(:raid_name) && device_config.alias md = config.md_raids.find { |r| r.devices.include?(device_config.alias) } return unless md planned.raid_name = member_md_name(md, device_config, config) end # Stores the given device at the map of partitionables # # @param device [Planned::Device] # @param config [Agama::Storage::Configs::Drive, Agama::Storage::Configs::Md] def register_partitionable(device, config) return unless config.alias partitionables[config.alias] ||= [] partitionables[config.alias] << device end end end end