-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New Y2Storage proposal for Agama (#1552)
In order to implement the unattended behavior for storage described at [auto_storage.md](https://github.com/openSUSE/agama/blob/master/doc/auto_storage.md), we decided we need a new kind of proposal since we want to achieve things that would be impossible with `Y2Storage::GuidedProposal` or `Y2Storage::AutoinstProposal`. This introduces a new `Y2Storage::AgamaProposal` that combines components from the mentioned existing proposals. Replaces #1448
- Loading branch information
Showing
46 changed files
with
3,883 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
32
service/lib/agama/storage/config_conversions/block_device.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
128
service/lib/agama/storage/config_conversions/block_device/from_json.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.