Skip to content

Instantly share code, notes, and snippets.

@ancorgs
Last active July 19, 2023 16:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ancorgs/afaf1e8b9279b05f25ffe6a7bb1efefc to your computer and use it in GitHub Desktop.
Save ancorgs/afaf1e8b9279b05f25ffe6a7bb1efefc to your computer and use it in GitHub Desktop.
Settings for the Agama proposal

This is a draft for a possible OOP solution for https://trello.com/c/drW2FI0q/165-agama-storage-proposal-settings, but certainly not the only one. The chosen solution must be discussed before progressing with the implementation.

Representing the desired Agama behavior on Agama's storage backend

This shows how the classes at the storage backend of Agama could be organized. These classes may be exposed in the D-Bus interface as some kind of direct translation (having interfaces like Settings, EncryptionSettings, Volume, VolumeTemplate and so on) or with a more conservative interface similar to the current one that is based only on the interface Storage1.Proposal, which contains only a few properties and an array of volumes described as plain hashes.

This classes represent how the configuration of the Agama proposal could be represented in the Agama backend, to be consistent with the behavior described at the Trello card.

class Settings
  # @return [String]
  attr_accessor :boot_device
  
  # @return [EncryptionSettings]
  attr_accessor :encryption
  
  # @return [LvmSettings]
  attr_accessor :lvm

  # @return [SpaceSettings]
  attr_accessor :space

  # @return [array<Volume>]
  attr_accessor :volumes
end

class EncryptionSettings
  # @return [Boolean]
  attr_accessor :enabled

  # @return [Symbol]
  attr_accessor :method

  # @return [Symbol]
  attr_accessor :pbkd_function

  # @return [String]
  attr_accessor :passphrase
end

class LvmSettings
  # @return [Boolean]
  attr_accessor :enabled

  # @return [Array<String>, nil]
  attr_accessor :system_lv_devices
end

class SpaceSettings
  # @return [Symbol] :delete, :keep, :resize, :custom
  attr_accessor :policy

  # @return [Hash] used only for :custom
  attr_accessor :actions
end

The attribute #volumes will be specified as an array of Volume objects. In that regard it makes sense to rethink the whole approach of relying directly on Y2Storage::VolumeSpecification. But that makes very little sense for the long term. On the one hand, that includes many fields we don't care about (ie. weight or desired sizes). On the other hand, that implies we have to keep several fields consistent at agama.yaml just to not produce weird results.

Of course we still need to generate VolumeSpecification objects to be passed to Y2Storage, but we don't want to base the configuration of Agama on them, neither the configuration coming from agama.yaml nor the one specified by the user via UI or D-Bus interface.

It actually makes sense to break that into two concepts - Volume to represent the input/output of every individual proposal attempt, and VolumeTemplate to represent the configuration of the possible product volumes that is specified via agama.yaml.

class Volume
  # Mount path
  #
  # Used also to match the corresponding template
  #
  # @return [String]
  attr_accessor :mount_path

  # Filesystem for the volume
  #
  # @return [Y2Storage::Filesystems::Type]
  attr_accessor :fs_type

  # These two would be used to locate the volume in a separate disk
  attr_accessor :device
  attr_accessor :separate_vg_name
  
  # @return [Array<String]
  attr_accessor :mount_options
  
  # @return [Array<String]
  attr_accessor :format_options

  # @return [BtrfsOptions, nil]
  attr_accessor :btrfs_options
  
  # Whether {#min_size} and {#max_size} should be automatically calculated by the proposal
  # based on the attributes of the corresponding template.
  #
  # If set to false, {#min_size} and {#max_size} must be handled by the proposal caller (ie. must be
  # explicitly set).
  #
  # It can only be true for volumes with a template where VolumeTemplate#adaptative_sizes? is true.
  #
  # @return [Boolean]
  attr_accessor :auto_size
  alias_method :auto_size?, :auto_size

  # Min size for the volume
  #
  # @return [Y2Storage::DiskSize]
  attr_accessor :min_size
  
  # Max size for the volume
  #
  # @return [Y2Storage::DiskSize]
  attr_accessor :max_size
end

Options that are specific to Btrfs deserve their own separate class

class BtrfsOptions
  # Whether the volume contains Btrfs snapshots
  #
  # @return [Boolean]
  attr_accessor :snapshots
  
  # @return [Boolean]
  attr_accessor :read_only

  # @return [Array<String>]
  attr_accessor :subvolumes
  
  # @return [String]
  attr_accessor :default_subvolume
end

The configuration for each product/role at agama.yaml will contain several volume templates.

class VolumeTemplate
  # Mount path.
  #
  # Used to match the volume being defined with the template to use as base. 
  #
  # @return [String]
  attr_accessor :mount_path

  # Whether the corresponding volume should be in the initial list of volumes created by default
  #
  # @return [Boolean]
  attr_reader :by_default
  alias_method :by_default?, :by_default

  # Whether the volume is optional (can be skipped in the list of volumes to create)
  #
  # @return [Boolean]
  attr_reader :optional
  alias_method :optional?, :optional
  
  # Default filesystem for the volume
  #
  # @return [Y2Storage::Filesystems::Type]
  attr_accessor :fs_type
  
  # Possible filesystem types for the volume
  #
  # @return [Array<Y2Storage::Filesystems::Type>]
  attr_reader :fs_types

  # Default list of mount options
  #
  # @return [Array<String]
  attr_accessor :mount_options
  
  # Default set of options for Btrfs, if btrfs is one of the acceptable fs_types
  #
  # @return [BtrfsOptions, nil]
  attr_accessor :btrfs_options

  # Base value to calculate the min size for the volume, if #auto_size is set to true for that final volume.
  #
  # @return [Y2Storage::DiskSize]
  attr_accessor :base_min_size
  
  # Base value to calculate the max size for the volume, if #auto_size is set to true for that final volume.
  #
  # @return [Y2Storage::DiskSize]
  attr_accessor :base_max_size
  
  # Related volumes that may affect the calculation of the automatic size limits
  #
  # @note This is set by calling to {#assign_size_relevant_volumes} method.
  #
  # @return [Array<String>]
  attr_reader :size_relevant_volumes

  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 [String] mount point of another volume
  attr_accessor :fallback_for_max_size
  
  # Whether it makes sense to have automatic size limits for the volume
  #
  # @return [Boolean]
  def adaptive_sizes?
    snapshots_affect_sizes? || size_relevant_volumes.any? || adjust_by_ram?
  end

  # Whether snapshots affect the automatic calculation of the size limits
  #
  # @return [Boolean]
  def snapshots_affect_sizes?
    return false unless snapshots || snapshots_configurable

    return true if snapshots_size && !snapshots_size.zero?

    snapshots_percentage && !snapshots_percentage.zero?
  end

  # Size required for snapshots
  #
  # @return [Y2Storage::DiskSize, nil]
  attr_reader :snapshots_size

  # Percentage of space required for snapshots
  #
  # @return [Integer, nil]
  attr_reader :snapshots_percentage

  # Whether the given spec has the volume as fallback for sizes
  #
  # @param spec [Y2Storage::VolumeSpecification]
  # @return [Boolean]
  def fallback?(spec)
    mounted_at?(spec.fallback_for_min_size) ||
      mounted_at?(spec.fallback_for_max_size) ||
      mounted_at?(spec.fallback_for_max_size_lvm)
  end
end

Translating from Agama to YaST

As mentioned, we obviously still need to translate the classes above to Y2Storage::ProposalSettings containing a set of Y2Storage::VolumeSpecification. But there is a quite direct conversion.

ProposalSettings for the partition-based proposal

  • root_device: boot disk
  • candidate_devices: [root_device]
  • Assign device to volumes that don't go to the boot disk

ProposalSettings for LVM-based proposal

  • root_device: boot disk
  • candidate_devices: LvmSettings#system_lv_devices. By default, [root_device]
  • separate_vgs: true
  • Assign separate_vg_name and device to volumes that don't go to the system VG

Volumes in both cases

  • Assign a weight of 100 to all volumes. We don't want the weight concept at Agama.

Sizes in both cases

  • Assigning the MANY related Y2Storage::VolumeSpecification fields in a consistent way should be relatively easy based on auto_size, min_size, max_size and all the values from the volume template.

Making space

  • Delete everything policy
    • space_settings.strategy set to :bigger_resize
    • space_settings.actions containing an entry for each partition with :force_delete as value.
  • Keep everything policy
    • space_settings.strategy set to :bigger_resize
    • space_settings.actions set to an empty hash (default)
  • Resize existing partitions policy
    • space_settings.strategy set to :bigger_resize
    • space_settings.actions containing an entry for each reasonable partition with :resize as value.
  • Custom policy
    • space_settings.strategy set to :bigger_resize
    • space_settings.actions with information filled by the user.
@joseivanlopez
Copy link

I was rethinking about the control file settings and how to manage the info in the code, please see https://gist.github.com/joseivanlopez/ce50025c2703c7b5ce04b8e0853c0c18. What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment