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
62 changes: 62 additions & 0 deletions rust/agama-lib/share/examples/storage_sizes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"storage": {
"drives": [
{
"partitions": [
{
"size": 2048
},
{
"size": "10 GiB"
},
{
"size": ["1 GiB"]
},
{
"size": [1024, "50 GiB"]
},
{
"size": {
"min": "1 GiB"
}
},
{
"size": {
"min": 1024,
"max": "50 GiB"
}
},
{
"search": {},
"size": ["current"]
},
{
"search": {},
"size": [0, "current"]
},
{
"search": {},
"size": ["current", "10 GiB"]
},
{
"size": {
"min": "current"
}
},
{
"size": {
"min": 0,
"max": "current"
}
},
{
"size": {
"min": "current",
"max": "10 GiB"
}
}
]
}
]
}
}
23 changes: 14 additions & 9 deletions rust/agama-lib/share/profile.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -920,14 +920,19 @@
{ "$ref": "#/$defs/sizeInteger" }
]
},
"sizeValueWithCurrent": {
"anyOf": [
{ "$ref": "#/$defs/sizeValue" },
{
"title": "Current size",
"description": "The current size of the device.",
"const": "current"
}
]
},
"size": {
"title": "Size options",
"anyOf": [
{
"title": "Automatic size",
"description": "The size is auto calculated according to the product.",
"const": "auto"
},
{
"$ref": "#/$defs/sizeValue"
},
Expand All @@ -936,11 +941,11 @@
"description": "Lower size limit and optionally upper size limit.",
"type": "array",
"items": {
"$ref": "#/$defs/sizeValue"
"$ref": "#/$defs/sizeValueWithCurrent"
},
"minItems": 1,
"maxItems": 2,
"examples": [[1024, 2048], ["1 GiB", "5 GiB"], [1024, "2 GiB"], ["2 GiB"]]
"examples": [[1024, "current"], ["1 GiB", "5 GiB"], [1024, "2 GiB"], ["2 GiB"]]
},
{
"title": "Size range",
Expand All @@ -950,11 +955,11 @@
"properties": {
"min": {
"title": "Mandatory lower size limit",
"$ref": "#/$defs/sizeValue"
"$ref": "#/$defs/sizeValueWithCurrent"
},
"max": {
"title": "Optional upper size limit",
"$ref": "#/$defs/sizeValue"
"$ref": "#/$defs/sizeValueWithCurrent"
}
}
}
Expand Down
98 changes: 2 additions & 96 deletions service/lib/agama/storage/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
# 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"
require "agama/storage/configs/boot"
require "agama/storage/config_conversions/from_json"

module Agama
module Storage
Expand Down Expand Up @@ -92,24 +93,6 @@ def implicit_boot_device
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 + logical_volumes).map(&:filesystem).compact
end

# return [Array<Configs::Partition>]
def partitions
drives.flat_map(&:partitions)
Expand All @@ -119,83 +102,6 @@ def partitions
def logical_volumes
volume_groups.flat_map(&:logical_volumes)
end

# return [Array<Configs::Partition, Configs::LogicalVolume>]
def default_size_devices
(partitions + logical_volumes).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
116 changes: 116 additions & 0 deletions service/lib/agama/storage/config_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
require "agama/storage/configs"
require "agama/storage/proposal_settings_reader"
require "agama/storage/volume_templates_builder"
require "pathname"
require "y2storage/disk_size"
require "y2storage/storage_manager"

module Agama
module Storage
Expand Down Expand Up @@ -55,6 +58,37 @@ def default_filesystem(path = nil)
end
end

# Default size config from the product definition.
#
# The size defined by the product depends on the mount path of the device. That size can be
# increased because some reasons:
# * Fallback sizes: the size of other path is added to the volume. For example, if /home is
# not present, then the root volume increases its min and max limits by adding the min and
# max limits from the missing /home. The having_paths parameter is used to indicate what
# paths are present. The product defines the fallback paths.
# * Snapshots size: a device can be configured to use snapshots. The default size limits
# could be increased if snapshots are used. The with_snapshots parameter indicates whether
# to add the snapshots size. The product defines the snapshots size.
# * RAM size: the product defines whether the volume for a specific path should be as big as
# the RAM size.
#
# @param path [String, nil] Mount path of the device.
# @param having_paths [Array<String>] Paths where other devices are mounted.
# @param with_snapshots [Boolean] Whether to add the Btrfs snapshots size.
# @return [Configs::Size]
def default_size(path = nil, having_paths: [], with_snapshots: true)
volume = volume_builder.for(path || "")

return unlimited_size unless volume

return auto_size(volume.outline, having_paths, with_snapshots) if volume.auto_size?

Configs::Size.new.tap do |config|
config.min = volume.min_size
config.max = volume.max_size
end
end

private

# @return [Agama::Config]
Expand All @@ -73,6 +107,88 @@ def default_fstype(path = nil)
end
end

# @return [Configs::Size]
def unlimited_size
Configs::Size.new.tap do |config|
config.min = Y2Storage::DiskSize.zero
config.max = Y2Storage::DiskSize.unlimited
end
end

# @see #default_size
#
# @param outline [VolumeOutline]
# @param paths [Array<String>]
# @param snapshots [Boolean]
#
# @return [Configs::Size]
def auto_size(outline, paths, snapshots)
min_fallbacks = remove_paths(outline.min_size_fallback_for, paths)
min_size_fallbacks = min_fallbacks.map { |p| volume_builder.for(p).min_size }
min = min_size_fallbacks.reduce(outline.base_min_size, &:+)

max_fallbacks = remove_paths(outline.max_size_fallback_for, paths)
max_size_fallbacks = max_fallbacks.map { |p| volume_builder.for(p).max_size }
max = max_size_fallbacks.reduce(outline.base_max_size, &:+)

if outline.adjust_by_ram?
min = size_with_ram(min)
max = size_with_ram(max)
end

if snapshots
min = size_with_snapshots(min, outline)
max = size_with_snapshots(max, outline)
end

Configs::Size.new.tap do |config|
config.min = min
config.max = max
end
end

# @see #default_size
#
# @param size [Y2Storage::DiskSize]
# @return [Y2Storage::DiskSize]
def size_with_ram(size)
[size, ram_size].max
end

# @see #default_size
#
# @param size [Y2Storage::DiskSize]
# @param outline [VolumeOutline]
#
# @return [Y2Storage::DiskSize]
def size_with_snapshots(size, outline)
return size unless outline.snapshots_affect_sizes?

if outline.snapshots_size && outline.snapshots_size > Y2Storage::DiskSize.zero
size + outline.snapshots_size
else
multiplicator = 1.0 + (outline.snapshots_percentage / 100.0)
size * multiplicator
end
end

# @param paths [Array<String>]
# @param paths_to_remove [Array<String>]
#
# @return [Array<String>]
def remove_paths(paths, paths_to_remove)
paths.reject do |path|
paths_to_remove.any? { |p| Pathname.new(p).cleanpath == Pathname.new(path).cleanpath }
end
end

# Total amount of RAM.
#
# @return [DiskSize]
def ram_size
@ram_size ||= Y2Storage::DiskSize.new(Y2Storage::StorageManager.instance.arch.ram_size)
end

# @return [ProposalSettings]
def settings
@settings ||= ProposalSettingsReader.new(product_config).read
Expand Down
Loading