From ed5805c5768d598d13918707d420d41b9eced05a 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, 11 Sep 2024 15:02:45 +0100 Subject: [PATCH] storage: create LVM config from JSON --- service/lib/agama/storage/config.rb | 11 +- .../from_json_conversions.rb | 2 + .../from_json_conversions/config.rb | 22 +++- .../from_json_conversions/logical_volume.rb | 88 +++++++++++++ .../from_json_conversions/volume_group.rb | 92 +++++++++++++ .../volume_group/from_json.rb | 105 --------------- .../config_conversions/from_json_test.rb | 124 ++++++++++++++++++ 7 files changed, 334 insertions(+), 110 deletions(-) create mode 100644 service/lib/agama/storage/config_conversions/from_json_conversions/logical_volume.rb create mode 100644 service/lib/agama/storage/config_conversions/from_json_conversions/volume_group.rb delete mode 100644 service/lib/agama/storage/config_conversions/volume_group/from_json.rb diff --git a/service/lib/agama/storage/config.rb b/service/lib/agama/storage/config.rb index ae9fc8c6b2..7384c447c8 100644 --- a/service/lib/agama/storage/config.rb +++ b/service/lib/agama/storage/config.rb @@ -107,7 +107,7 @@ def calculate_default_sizes(volume_builder) # return [Array] def filesystems - (drives + partitions).map(&:filesystem).compact + (drives + partitions + logical_volumes).map(&:filesystem).compact end # return [Array] @@ -115,9 +115,14 @@ def partitions drives.flat_map(&:partitions) end - # return [Array] + # return [Array] + def logical_volumes + volume_groups.flat_map(&:logical_volumes) + end + + # return [Array] def default_size_devices - partitions.select { |p| p.size&.default? } + (partitions + logical_volumes).select { |p| p.size&.default? } end # Min or max size that should be used for the given partition or logical volume diff --git a/service/lib/agama/storage/config_conversions/from_json_conversions.rb b/service/lib/agama/storage/config_conversions/from_json_conversions.rb index cc892b8801..e463c51003 100644 --- a/service/lib/agama/storage/config_conversions/from_json_conversions.rb +++ b/service/lib/agama/storage/config_conversions/from_json_conversions.rb @@ -26,9 +26,11 @@ require "agama/storage/config_conversions/from_json_conversions/encryption" require "agama/storage/config_conversions/from_json_conversions/filesystem" require "agama/storage/config_conversions/from_json_conversions/filesystem_type" +require "agama/storage/config_conversions/from_json_conversions/logical_volume" require "agama/storage/config_conversions/from_json_conversions/partition" require "agama/storage/config_conversions/from_json_conversions/search" require "agama/storage/config_conversions/from_json_conversions/size" +require "agama/storage/config_conversions/from_json_conversions/volume_group" module Agama module Storage diff --git a/service/lib/agama/storage/config_conversions/from_json_conversions/config.rb b/service/lib/agama/storage/config_conversions/from_json_conversions/config.rb index 3582f92192..fbb4c6fd3a 100644 --- a/service/lib/agama/storage/config_conversions/from_json_conversions/config.rb +++ b/service/lib/agama/storage/config_conversions/from_json_conversions/config.rb @@ -22,6 +22,7 @@ require "agama/storage/config_conversions/from_json_conversions/base" require "agama/storage/config_conversions/from_json_conversions/boot" require "agama/storage/config_conversions/from_json_conversions/drive" +require "agama/storage/config_conversions/from_json_conversions/volume_group" require "agama/storage/config" module Agama @@ -56,8 +57,9 @@ def convert(default = nil) # @return [Hash] def conversions(default) { - boot: convert_boot(default.boot), - drives: convert_drives + boot: convert_boot(default.boot), + drives: convert_drives, + volume_groups: convert_volume_groups } end @@ -83,6 +85,22 @@ def convert_drives def convert_drive(drive_json) FromJSONConversions::Drive.new(drive_json, config_builder: config_builder).convert end + + # @return [Array, nil] + def convert_volume_groups + volume_groups_json = config_json[:volumeGroups] + return unless volume_groups_json + + volume_groups_json.map { |v| convert_volume_group(v) } + end + + # @param volume_group_json [Hash] + # @return [Configs::VolumeGroup] + def convert_volume_group(volume_group_json) + FromJSONConversions::VolumeGroup + .new(volume_group_json, config_builder: config_builder) + .convert + end end end end diff --git a/service/lib/agama/storage/config_conversions/from_json_conversions/logical_volume.rb b/service/lib/agama/storage/config_conversions/from_json_conversions/logical_volume.rb new file mode 100644 index 0000000000..cd48f8de1f --- /dev/null +++ b/service/lib/agama/storage/config_conversions/from_json_conversions/logical_volume.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/from_json_conversions/base" +require "agama/storage/config_conversions/from_json_conversions/with_encryption" +require "agama/storage/config_conversions/from_json_conversions/with_filesystem" +require "agama/storage/config_conversions/from_json_conversions/with_size" +require "agama/storage/configs/logical_volume" +require "y2storage/disk_size" + +module Agama + module Storage + module ConfigConversions + module FromJSONConversions + # Logical volume conversion from JSON hash according to schema. + class LogicalVolume < Base + include WithEncryption + include WithFilesystem + include WithSize + + # @param logical_volume_json [Hash] + # @param config_builder [ConfigBuilder, nil] + def initialize(logical_volume_json, config_builder: nil) + super(config_builder) + @logical_volume_json = logical_volume_json + end + + # @see Base#convert + # + # @param default [Configs::LogicalVolume, nil] + # @return [Configs::LogicalVolume] + def convert(default = nil) + super(default || Configs::LogicalVolume.new) + end + + private + + # @return [Hash] + attr_reader :logical_volume_json + + # @see Base#conversions + # + # @param default [Configs::LogicalVolume] + # @return [Hash] + def conversions(default) + { + alias: logical_volume_json[:alias], + encryption: convert_encryption(logical_volume_json, default: default.encryption), + filesystem: convert_filesystem(logical_volume_json, default: default.filesystem), + size: convert_size(logical_volume_json, default: default.size), + name: logical_volume_json[:name], + stripes: logical_volume_json[:stripes], + stripe_size: convert_stripe_size, + pool: logical_volume_json[:pool], + used_pool: logical_volume_json[:usedPool] + } + end + + # @return [Y2Storage::DiskSize, nil] + def convert_stripe_size + value = logical_volume_json[:stripeSize] + return unless value + + Y2Storage::DiskSize.new(value) + end + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/from_json_conversions/volume_group.rb b/service/lib/agama/storage/config_conversions/from_json_conversions/volume_group.rb new file mode 100644 index 0000000000..1a6ab92f4c --- /dev/null +++ b/service/lib/agama/storage/config_conversions/from_json_conversions/volume_group.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +# Copyright (c) [2024] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "agama/storage/config_conversions/from_json_conversions/base" +require "agama/storage/config_conversions/from_json_conversions/logical_volume" +require "agama/storage/configs/drive" + +module Agama + module Storage + module ConfigConversions + module FromJSONConversions + # Volume group conversion from JSON hash according to schema. + class VolumeGroup < Base + # @param volume_group_json [Hash] + # @param config_builder [ConfigBuilder, nil] + def initialize(volume_group_json, config_builder: nil) + super(config_builder) + @volume_group_json = volume_group_json + end + + # @see Base#convert + # + # @param default [Configs::VolumeGroup, nil] + # @return [Configs::VolumeGroup] + def convert(default = nil) + super(default || Configs::VolumeGroup.new) + end + + private + + # @return [Hash] + attr_reader :volume_group_json + + # @see Base#conversions + # + # @param _default [Configs::VolumeGroup] + # @return [Hash] + def conversions(_default) + { + name: volume_group_json[:name], + extend_size: convert_extend_size, + physical_volumes: volume_group_json[:physicalVolumes], + logical_volumes: convert_logical_volumes + } + end + + # @return [Y2Storage::DiskSize, nil] + def convert_extend_size + value = volume_group_json[:extendSize] + return unless value + + Y2Storage::DiskSize.new(value) + end + + # @return [Array, nil] + def convert_logical_volumes + logical_volumes_json = volume_group_json[:logicalVolumes] + return unless logical_volumes_json + + logical_volumes_json.map { |l| convert_logical_volume(l) } + end + + # @param logical_volume_json [Hash] + # @return [Configs::LogicalVolume] + def convert_logical_volume(logical_volume_json) + FromJSONConversions::LogicalVolume + .new(logical_volume_json, config_builder: config_builder) + .convert + end + end + end + end + end +end diff --git a/service/lib/agama/storage/config_conversions/volume_group/from_json.rb b/service/lib/agama/storage/config_conversions/volume_group/from_json.rb deleted file mode 100644 index 94dbfafbac..0000000000 --- a/service/lib/agama/storage/config_conversions/volume_group/from_json.rb +++ /dev/null @@ -1,105 +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/block_device/from_json" -require "agama/storage/config_conversions/search/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. - # - # @param default [Configs::Drive, nil] - # @return [Configs::Drive] - def convert(default = nil) - default_config = default.dup || Configs::Drive.new - - convert_drive(default_config).tap do |config| - search = convert_search(config.search) - config.search = search if search - end - end - - private - - # @return [Hash] - attr_reader :drive_json - - # @return [ProposalSettings] - attr_reader :settings - - # @return [VolumeTemplatesBuilder] - attr_reader :volume_builder - - # @param config [Configs::Drive] - # @return [Configs::Drive] - def convert_drive(config) - convert_block_device( - convert_partitionable(config) - ) - end - - # @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 - - # @param config [Configs::Search] - # @return [Configs::Search, nil] - def convert_search(config) - search_json = drive_json[:search] - return unless search_json - - converter = Search::FromJSON.new(search_json) - converter.convert(config) - end - end - 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 bd023f5f42..3a5e99e3a1 100644 --- a/service/test/agama/storage/config_conversions/from_json_test.rb +++ b/service/test/agama/storage/config_conversions/from_json_test.rb @@ -880,5 +880,129 @@ ) end end + + context "with some LVM volume groups" do + let(:config_json) do + { + volumeGroups: [ + { + name: "vg0", + extendSize: "2 MiB", + physicalVolumes: ["alias1", "alias2"], + logicalVolumes: [ + { + name: "root", + filesystem: { path: "/" }, + encryption: { + luks2: { password: "12345" } + } + }, + { + alias: "thin-pool", + name: "pool", + pool: true, + size: "100 GiB", + stripes: 10, + stripeSize: "4 KiB" + }, + { + name: "data", + size: "50 GiB", + usedPool: "thin-pool", + filesystem: { type: "xfs" } + } + ] + }, + { + name: "vg1" + } + ] + } + end + + it "generates the corresponding volume groups and logical volumes" do + config = subject.convert + + expect(config.volume_groups).to contain_exactly( + an_object_having_attributes( + name: "vg0", + extend_size: 2.MiB, + physical_volumes: ["alias1", "alias2"] + ), + an_object_having_attributes( + name: "vg1", + extend_size: be_nil, + physical_volumes: be_empty, + logical_volumes: be_empty + ) + ) + + logical_volumes = config.volume_groups + .find { |v| v.name == "vg0" } + .logical_volumes + + expect(logical_volumes).to include( + an_object_having_attributes( + alias: be_nil, + name: "root", + encryption: have_attributes( + password: "12345", + method: Y2Storage::EncryptionMethod::LUKS2, + pbkd_function: Y2Storage::PbkdFunction::ARGON2ID + ), + filesystem: have_attributes( + path: "/", + type: have_attributes( + fs_type: Y2Storage::Filesystems::Type::BTRFS + ) + ), + size: have_attributes( + default: true, + min: 40.GiB, + max: Y2Storage::DiskSize.unlimited + ), + stripes: be_nil, + stripe_size: be_nil, + pool: false, + used_pool: be_nil + ), + an_object_having_attributes( + alias: "thin-pool", + name: "pool", + encryption: be_nil, + filesystem: be_nil, + size: have_attributes( + default: false, + min: 100.GiB, + max: 100.GiB + ), + stripes: 10, + stripe_size: 4.KiB, + pool: true, + used_pool: be_nil + ), + an_object_having_attributes( + alias: be_nil, + name: "data", + encryption: be_nil, + filesystem: have_attributes( + path: be_nil, + type: have_attributes( + fs_type: Y2Storage::Filesystems::Type::XFS + ) + ), + size: have_attributes( + default: false, + min: 50.GiB, + max: 50.GiB + ), + stripes: be_nil, + stripe_size: be_nil, + pool: false, + used_pool: "thin-pool" + ) + ) + end + end end end