Skip to content

Commit

Permalink
WIP: draft for getting the volume template for a path
Browse files Browse the repository at this point in the history
  • Loading branch information
ancorgs committed Jul 19, 2023
1 parent 93cd4b1 commit c3ac9e8
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 100 deletions.
9 changes: 3 additions & 6 deletions service/lib/agama/storage/btrfs_settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class BtrfsSettings
# @return [Boolean]
attr_accessor :read_only

# @return [Array<String>]
# @return [Array<String>, nil] if nil, a historical fallback list may be applied depending
# on the mount path of the volume
attr_accessor :subvolumes

# @return [String]
Expand All @@ -40,11 +41,7 @@ class BtrfsSettings
def initialize
@snapshots = false
@read_only = false
@subvolumes = []
@default_subvolume = "@"
end

def load_features(values)
@default_subvolume = ""
end
end
end
Expand Down
17 changes: 11 additions & 6 deletions service/lib/agama/storage/proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,9 @@ def candidate_devices
# Volume definitions to be used as templates in the interface and to fill the
# information missing in the volumes.
#
# @return [Array<VolumeTemplate>]
def volume_templates
return @volume_templates if @volume_templates

config_volumes = config.data.fetch("storage", {}).fetch("volumes", [])
Volume.read(config_volumes)
# @return [<Volume>]
def volume_template(path)
volume_templates_builder.for(path)
end

# Settings with the data used during the calculation of the storage proposal
Expand Down Expand Up @@ -277,6 +274,14 @@ def force_cleanup(settings)
# Setting #linux_delete_mode to :all is not enough to prevent VG reusing in all cases
settings.lvm_vg_reuse = false
end

def volume_templates_builder
@volume_templates_builder ||= VolumeTemplatesBuilder.new(volume_templates_config)
end

def volume_templates_config
config.data.fetch("storage", {}).fetch("volume_templates", [])
end
end
end
end
45 changes: 6 additions & 39 deletions service/lib/agama/storage/volume.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,12 @@ class Volume
alias_method :auto_size?, :auto_size

# Constructor
def initialize(values)
apply_defaults
load_features(values)
def initialize(mount_path)
@mount_path = mount_path
@mount_options = []
@format_options = []
@btrfs = BtrfsSettings.new
@outline = VolumeOutline.new
end

# Whether the mount point of the volume matches the given one
Expand All @@ -101,42 +104,6 @@ def mounted_at?(path)

Pathname.new(mount_point).cleanpath == Pathname.new(path).cleanpath
end

def self.read(volumes_data)
volumes = volumes_data.map { |v| Volume.new(v) }
volumes.each { |v| v.outline.assign_size_relevant_volumes(v, volumes) }
volumes
end

private

def apply_defaults
@mount_options = []
@format_options = []
@btrfs = BtrfsSettings.new
@outline = VolumeOutline.new
end

def load_features(values)
@mount_path = values.fetch("mount", {}).fetch("path")
# @mount_options = xxx
# @format_options = xxx

type_str = values.fetch("filesystem", {}).fetch("type", "ext4")
@fs_type = Y2Storage::Filesystems::Type.find(type.downcase.to_sym)

btrfs.load_features(values)
outline.load_features(values)

# TODO: part of this logic should likely be moved elsewhere (auto_size setter?)
if outline.adaptative_sizes?
@auto_size = true
else
@auto_size = false
@min_size = outline.base_min_size
@max_size = outline.base_max_size
end
end
end
end
end
63 changes: 14 additions & 49 deletions service/lib/agama/storage/volume_outline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@ module Agama
module Storage
# Set of rules and features used to fully define and validate a given volume
class VolumeOutline
# Whether the volume is optional
# Whether the volume is mandatory
#
# If this is false, the list of volumes used by the storage proposal will always contain a
# If this is true, the list of volumes used by the storage proposal will always contain
# this volume or an equivalent one (ie. one with the same mount_path).
#
# @return [Boolean]
attr_reader :optional
alias_method :optional?, :optional
attr_reader :required
alias_method :required?, :required

# Possible filesystem types for the volume
#
# @return [Array<Y2Storage::Filesystems::Type>]
attr_reader :fs_types
attr_reader :filesystems

# Base value to calculate the min size for the volume (if #auto_size is set to true
# for that final volume) or to use as default value (if #auto_size is false)
Expand All @@ -61,11 +61,11 @@ class VolumeOutline
attr_reader :adjust_by_ram
alias_method :adjust_by_ram?, :adjust_by_ram

# @return [String] mount point of another volume
attr_accessor :fallback_for_min_size
# @return [Array<String>] mount paths of other volumes
attr_accessor :min_size_fallback_for

# @return [String] mount point of another volume
attr_accessor :fallback_for_max_size
# @return [Array<String>] mount paths of other volumes
attr_accessor :max_size_fallback_for

# Whether snapshots option can be configured
#
Expand All @@ -84,39 +84,16 @@ class VolumeOutline
attr_reader :snapshots_percentage

def initialize
@optional = false
@fs_types = []
@filesystems = []
@base_min_size = Y2Storage::DiskSize.zero
@base_max_size = Y2Storage::DiskSize.Unlimited
@size_relevant_volumes = []
@adjust_by_ram = false
@fallback_for_max_size = ""
@fallback_for_min_size = ""
@snapshots_configurable = false
@max_size_fallback_for = []
@min_size_fallback_for = []
end

def load_features(values)
size = values.fetch("size", {})
min = size["min"]
max = size["max"]
@base_min_size = DiskSize.parse(min, legacy_units: true) if min
@base_max_size = DiskSize.parse(max, legacy_units: true) if max

# @optional
# @fs_types
# @adjust_by_ram
# @fallback_for_max_size
# @fallback_for_min_size
# @snapshots_configurable
# @snapshots_size
# @snapshots_percentage
end

# Sets the mount points that affects the sizes of the volume
def assign_size_relevant_volumes(volume, other_volumes)
# FIXME: this should be a responsibility of the Proposal (since it's calculated by
# Proposal::DevicesPlanner)
@size_relevant_volumes = other_volumes.select { |v| fallback?(volume, v) }.map(&:mount_path)
def size_relevant_volumes
(max_size_fallbacks_for + min_size_fallbacks_for).sort.uniq
end

# Whether it makes sense to have automatic size limits for the volume
Expand All @@ -140,18 +117,6 @@ def snapshots_affect_sizes?

snapshots_percentage && !snapshots_percentage.zero?
end

private

# Whether the given volume outline has this volume as fallback for sizes
#
# @param volume [Volume] the volume of this outline
# @param other [Volume]
# @return [Boolean]
def fallback?(volume, other)
volume.mounted_at?(other.outline.fallback_for_min_size) ||
volume.mounted_at?(other.outline.fallback_for_max_size)
end
end
end
end
154 changes: 154 additions & 0 deletions service/lib/agama/storage/volume_templates_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# frozen_string_literal: true

# Copyright (c) [2023] 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 "pathname"
require "yast"
require "y2storage"

module Agama
module Storage
class VolumeTemplatesBuilder
def initialize(config_data)
@data = {}

# TODO: maybe add an entry for "" if no default is provided?
config_data.each do |volume_data|
@data[key(volume_data)] = values(volume_data)
end
end

def for(path)
data = @data[cleanpath(path)] || @data[""]

Volume.new(path).tap do |vol|
vol.btrfs = data[:btrfs]
vol.outline = data[:outline]
vol.filesystem = data[:filesystem]
# vol.mount_options = xxx
# vol.format_options = xxx

if data[:auto_size] && vol.outline.adaptative_sizes?
vol.auto_size = true
else
vol.auto_size = false
vol.min_size = data[:min_size] if data[:min_size]
vol.max_size = data[:max_size] if data[:max_size]
end
end
end

private

def key(data)
path = data["mount_path"]
return "" unless path

cleanpath(path)
end

def values(data)
Hash.new.tap do |values|
values[:btrfs] = btrfs(data)
values[:outline] = outline(data)

# TODO: maybe ensure consistency of values[:filesystem] and values[:outline].filesystems ?
fs = data["filesystem"]
values[:filesystem] = Y2Storage::Filesystems::Type.find(fs.downcase.to_sym) if fs
values[:filesystem] ||= values[:outline].filesystems.first
values[:filesystem] ||= Y2Storage::Filesystems::Type::EXT4

# values[:mount_options] = xxx
# values[:format_options] = xxx

size = outline_data.fetch("size", {})
values[:auto_size] = size.fetch("auto", false)
values[:min_size] = parse_disksize(size["min"])
values[:max_size] = parse_disksize(size["max"])
end
end

def btrfs(data)
btrfs_data = data.fetch("btrfs", {})
BtrfsSettings.new.tap do |btrfs|
btrfs.snapshots = btrfs_data.fetch("snapshots", false)
btrfs.read_only = btrfs_data.fetch("read_only", false)
btrfs.default_subvolume = btrfs_data.fetch("default_subvolume", "")
btrfs.subvolumes = btrfs_data["subvolumes"]
if btrfs.subvolumes
btrfs.subvolumes.map! { |subvol_data| subvolume(subvol_data) }
end
end
end

def subvolume(data)
return Y2Storage::SubvolSpecification.new(data) if data.kind_of?(String)

Y2Storage::SubvolSpecification.new(
data["path"], copy_on_write: data["copy_on_write"], archs: data["archs"]
)
end

def outline(data)
outline_data = data.fetch("outline", {})
VolumeOutline.new.tap do |outline|
outline.required = outline_data.fetch("required", false)
outline.filesystems = outline_data.fetch("filesystems", [])
outline.filesystems.map! { |fs| Y2Storage::Filesystems::Type.find(fs.downcase.to_sym) }
outline.snapshots_configurable = outline_data.fetch("snapshots_configurable", true)

size = outline_data.fetch("auto_size", {})
min = parse_disksize(size["min"])
max = parse_disksize(size["max"])
outline.base_min_size = min if min
outline.base_max_size = max if max
outline.adjust_by_ram = size.fetch("adjust_by_ram", false)
outline.min_size_fallback_for = Array(size["min_fallback_for"])
outline.min_size_fallback_for.map! { |p| cleanpath(p) }
outline.max_size_fallback_for = Array(size["max_fallback_for"])
outline.max_size_fallback_for.map! { |p| cleanpath(p) }


assign_snapshots_increment(outline, size["snapshots_increment"])
end
end

def assign_snapshots_increment(outline, increment)
return if increment.nil

if increment =~ /(\d+)\s*%/
outline.snapshots_percentage = $1.to_i
else
outline.snapshots_size = Y2Storage::DiskSize.parse(increment, legacy_units: true)
end
end

def parse_disksize(value)
return nil unless value

Y2Storage::DiskSize.parse_or(value, nil, legacy_units: true)
end

def cleanpath(path)
Pathname.new(path).cleanpath
end
end
end
end

0 comments on commit c3ac9e8

Please sign in to comment.