diff --git a/service/lib/y2storage/proposal/agama_device_planner.rb b/service/lib/y2storage/proposal/agama_device_planner.rb index c7ce0d8ddf..c45284b4cb 100644 --- a/service/lib/y2storage/proposal/agama_device_planner.rb +++ b/service/lib/y2storage/proposal/agama_device_planner.rb @@ -200,27 +200,38 @@ def configure_size(planned, config) end # @param planned [Planned::Disk] - # @param config [Agama::Storage::Configs::Drive] - def configure_partitions(planned, config) - partition_configs = config.partitions + # @param device_config [Agama::Storage::Configs::Drive] + def configure_partitions(planned, device_config, config) + partition_configs = device_config.partitions .reject(&:delete?) .reject(&:delete_if_needed?) planned.partitions = partition_configs.map do |partition_config| - planned_partition(partition_config).tap { |p| p.disk = config.found_device.name } + planned_partition(partition_config, device_config, config) end end # @param config [Agama::Storage::Configs::Partition] # @return [Planned::Partition] - def planned_partition(config) + def planned_partition(partition_config, device_config, config) Planned::Partition.new(nil, nil).tap do |planned| - planned.partition_id = config.id - configure_reuse(planned, config) - configure_block_device(planned, config) - configure_size(planned, config.size) + 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) end end + + 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.name + end end end end diff --git a/service/lib/y2storage/proposal/agama_devices_planner.rb b/service/lib/y2storage/proposal/agama_devices_planner.rb index 9ebf638066..f268a4fb75 100644 --- a/service/lib/y2storage/proposal/agama_devices_planner.rb +++ b/service/lib/y2storage/proposal/agama_devices_planner.rb @@ -19,8 +19,9 @@ # 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/devices_collection" require "y2storage/proposal/agama_drive_planner" -require "y2storage/planned" +require "y2storage/proposal/agama_vg_planner" module Y2Storage module Proposal @@ -49,7 +50,8 @@ def planned_devices(config) # - 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 - planned = planned_for_drives(config) + planned = planned_drives(config) + planned_vgs(config) + require "byebug"; byebug Planned::DevicesCollection.new(planned) end @@ -63,10 +65,20 @@ def planned_devices(config) # @param config [Agama::Storage::Config] # @return [Array] - def planned_for_drives(config) + def planned_drives(config) config.drives.flat_map do |drive| planner = AgamaDrivePlanner.new(devicegraph, issues_list) - planner.planned_devices(drive) + planner.planned_devices(drive, config) + end + end + + # @param config [Agama::Storage::Config] + # @return [Array] + def planned_vgs(config) + # TODO: Generate issues for volume groups with unknown physical volume aliases. + config.volume_groups.flat_map do |vg| + planner = AgamaVgPlanner.new(devicegraph, issues_list) + planner.planned_devices(vg) end end end diff --git a/service/lib/y2storage/proposal/agama_drive_planner.rb b/service/lib/y2storage/proposal/agama_drive_planner.rb index 541fb51540..ec4b40b7c4 100644 --- a/service/lib/y2storage/proposal/agama_drive_planner.rb +++ b/service/lib/y2storage/proposal/agama_drive_planner.rb @@ -26,10 +26,12 @@ module Y2Storage module Proposal # Drive planner for Agama. class AgamaDrivePlanner < AgamaDevicePlanner - # @param settings [Agama::Storage::Configs::Drive] + # @param drive_config [Agama::Storage::Configs::Drive] + # @param drive_config [Agama::Storage::Config] + # # @return [Array] - def planned_devices(settings) - [planned_drive(settings)] + def planned_devices(drive_config, config) + [planned_drive(drive_config, config)] end private @@ -37,29 +39,30 @@ def planned_devices(settings) # 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] + # @param drive_config [Agama::Storage::Configs::Drive] # @return [Planned::Disk] - def planned_drive(settings) - return planned_full_drive(settings) unless settings.partitions? + def planned_drive(drive_config, config) + return planned_full_drive(drive_config, config) unless drive_config.partitions? - planned_partitioned_drive(settings) + planned_partitioned_drive(drive_config, config) end - # @param settings [Agama::Storage::Configs::Drive] + # @param drive_config [Agama::Storage::Configs::Drive] # @return [Planned::Disk] - def planned_full_drive(settings) + def planned_full_drive(drive_config, config) Planned::Disk.new.tap do |planned| - configure_reuse(planned, settings) - configure_block_device(planned, settings) + configure_reuse(planned, drive_config) + configure_block_device(planned, drive_config) + configure_pv(planned, drive_config, config) end end - # @param settings [Agama::Storage::Configs::Drive] + # @param drive_config [Agama::Storage::Configs::Drive] # @return [Planned::Disk] - def planned_partitioned_drive(settings) + def planned_partitioned_drive(drive_config, config) Planned::Disk.new.tap do |planned| - configure_reuse(planned, settings) - configure_partitions(planned, settings) + configure_reuse(planned, drive_config) + configure_partitions(planned, drive_config, config) end end end diff --git a/service/lib/y2storage/proposal/agama_vg_planner.rb b/service/lib/y2storage/proposal/agama_vg_planner.rb new file mode 100644 index 0000000000..5e92e7d7c2 --- /dev/null +++ b/service/lib/y2storage/proposal/agama_vg_planner.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 "y2storage/lv_type" +require "y2storage/proposal/agama_device_planner" + +module Y2Storage + module Proposal + # Volume group planner for Agama. + class AgamaVgPlanner < AgamaDevicePlanner + # @param config [Agama::Storage::Configs::VolumeGroup] + # @return [Array] + def planned_devices(config) + [planned_vg(config)] + end + + private + + def planned_vg(config) + Y2Storage::Planned::LvmVg.new(volume_group_name: config.name).tap do |planned| + planned.extent_size = config.extent_size + planned.lvs = planned_lvs(config) + end + end + + def planned_lvs(config) + # TODO: Generate issues for thin configs with unknown pool alias. + + normal_lvs = planned_normal_lvs(config) + thin_pool_lvs = planned_thin_pool_lvs(config) + + normal_lvs + thin_pool_lvs + end + + def planned_normal_lvs(config) + configs = config.logical_volumes.reject(&:pool?).reject(&:thin_volume?) + configs.map { |c| planned_lv(c, LvType::NORMAL) } + end + + def planned_thin_pool_lvs(config) + pool_configs = config.logical_volumes.select(&:pool?) + thin_configs = config.logical_volumes.select(&:thin_volume?) + + pool_configs.map { |c| planned_thin_pool_logical_volume(c, thin_configs) } + end + + def planned_thin_pool_logical_volume(config, thin_configs) + thin_configs = thin_configs.select { |c| c.used_pool == config.alias } + + planned_pool = planned_lv(config, LvType::THIN_POOL) + planned_thin_volumes = thin_configs.map { |c| planned_lv(c, LvType::THIN) } + + planned_thin_volumes.each { |v| planned_pool.add_thin_lv(v) } + planned_pool + end + + def planned_lv(config, type) + Planned::LvmLv.new(nil, nil).tap do |planned| + planned.logical_volume_name = config.name + planned.lv_type = type + planned.stripes = config.stripes + planned.stripe_size = config.stripe_size + configure_block_device(planned, config) + configure_size(planned, config.size) + end + end + + def configure_size(planned, config) + # TODO: Generate issue if there is no size config. + return unless config + + super + end + end + end +end diff --git a/service/test/y2storage/agama_proposal_test.rb b/service/test/y2storage/agama_proposal_test.rb index ccb133968b..8891e840fa 100644 --- a/service/test/y2storage/agama_proposal_test.rb +++ b/service/test/y2storage/agama_proposal_test.rb @@ -22,6 +22,7 @@ require_relative "../agama/storage/storage_helpers" require "agama/config" require "agama/storage/config" +require "agama/storage/config_conversions/from_json" require "y2storage/agama_proposal" # @param config [Agama::Storage::Configs::Drive, Agama::Storage::Configs::Partition] @@ -760,5 +761,70 @@ def partition_config(name: nil, filesystem: nil, size: nil) end end end + + context "lvm" do + let(:scenario) { "empty-hd-50GiB.yaml" } + + let(:initial_settings) do + Agama::Storage::ConfigConversions::FromJSON + .new(config_json, product_config: product_config) + .convert + end + + let(:product_config) { Agama::Config.new({}) } + + let(:config_json) do + { + drives: [ + { + partitions: [ + { + alias: "pv1", + size: "40 GiB" + } + ] + } + ], + volumeGroups: [ + { + name: "vg0", + extentSize: "2 MiB", + physicalVolumes: ["pv1"], + logicalVolumes: [ + { + name: "root", + size: "10 GiB", + filesystem: { + path: "/", + type: "btrfs" + }, + encryption: { + luks2: { password: "12345" } + } + }, + { + alias: "thin-pool", + name: "pool", + pool: true, + size: "20 GiB", + stripes: 10, + stripeSize: "4 KiB" + }, + { + name: "data", + size: "50 GiB", + usedPool: "thin-pool", + filesystem: { type: "xfs" } + } + ] + } + ] + } + end + + it "works" do + proposal.propose + end + end end end