Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions service/lib/agama/storage/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# 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"

module Agama
module Storage
# Settings used to calculate an storage proposal.
class Config
# Boot settings.
#
# @return [Configs::Boot]
attr_accessor :boot

# @return [Array<Configs::Drive>]
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
@boot = Configs::Boot.new
@drives = []
@volume_groups = []
@md_raids = []
@btrfs_raids = []
@nfs_mounts = []
end

# Creates a config from JSON hash according to schema.
#
# @param config_json [Hash]
# @param product_config [Agama::Config]
#
# @return [Storage::Config]
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 the target system
#
# @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|
drive.partitions.any? { |p| p.filesystem.root? }
end

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)
dev.size.max = default_size(dev, :max, volume_builder)
end
end

private

# return [Array<Configs::Filesystem>]
def filesystems
(drives + partitions).map(&:filesystem).compact
end

# return [Array<Configs::Partition>]
def partitions
drives.flat_map(&:partitions)
end

# return [Array<Configs::Partitions>]
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)
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
size = size_with_fallbacks(outline, attr, builder)
size = size_with_ram(size, outline)
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

# @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) }

size = outline.send(:"base_#{attr}_size")
missing_paths.inject(size) { |total, p| total + builder.for(p).send(:"#{attr}_size") }
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?

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

# 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
def ram_size
@ram_size ||= Y2Storage::DiskSize.new(Y2Storage::StorageManager.instance.arch.ram_size)
end
end
end
end
39 changes: 39 additions & 0 deletions service/lib/agama/storage/config_conversions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 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"
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
# Conversions for the storage config.
module ConfigConversions
end
end
end
32 changes: 32 additions & 0 deletions service/lib/agama/storage/config_conversions/block_device.rb
Original file line number Diff line number Diff line change
@@ -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
128 changes: 128 additions & 0 deletions service/lib/agama/storage/config_conversions/block_device/from_json.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# 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/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/filesystem_type"

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 blk_device_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.encryption = convert_encrypt
config.filesystem = convert_filesystem
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[:encryption]
return unless encrypt_json

Encryption::FromJSON.new(encrypt_json, default: default_encrypt_config).convert
end

# @return [Configs::Filesystem, nil]
def convert_filesystem
filesystem_json = blk_device_json[:filesystem]
return if filesystem_json.nil?

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.

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::Encryption]
def default_encrypt_config
Configs::Encryption.new.tap do |config|
config.password = settings.encryption.password
config.method = settings.encryption.method
config.pbkd_function = settings.encryption.pbkd_function
end
end

# Default format config from the product definition.
#
# @param mount_path [String]
# @return [Configs::Filesystem]
def default_filesystem_config(mount_path)
Configs::Filesystem.new.tap do |config|
config.type = default_fstype_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::FilesystemType]
def default_fstype_config(mount_path)
volume = volume_builder.for(mount_path)

Configs::FilesystemType.new.tap do |config|
config.fs_type = volume.fs_type
config.btrfs = volume.btrfs
end
end
end
end
end
end
end
Loading