Skip to content

Instantly share code, notes, and snippets.

@joseivanlopez
Last active July 18, 2023 15:28
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 joseivanlopez/ce50025c2703c7b5ce04b8e0853c0c18 to your computer and use it in GitHub Desktop.
Save joseivanlopez/ce50025c2703c7b5ce04b8e0853c0c18 to your computer and use it in GitHub Desktop.

Agama Storage Settings

This document proposes a possible solution for defining the new storage settings for Agama. The result is based on this other document, which uses the VolumeTemplate concept for representing the settings of a volume coming from the control file.

Why yet another proposal for settings

The definition of the storage settings in the control file, and its transcription to Agama code by means of the VolumeTemplate approach seem to have some flaws.

One thing to consider is the repetition of fields. A VolumeTemplate and the resulting Volumes created from them have some fields in common. Repetition is not a problem per se, but it does not sound totally correct. Moreover, it seems that a VolumeTemplate is used for several things at the same time:

  • To store the initial values for a volume (e.g., min_size, fs_type, etc).
  • To define what the proposal should do with the volume (by_default, optional).
  • And probably, to set permissions for some settings (e.g., snapshots_configurable).

And regarding the control file, most likely some kind of product customization will be requested in the future. For example, whether encryption can be disabled or whether LVM should be offered. For this, it would be ideal to differentiate among the initial values for the settings and the permissions. Moreover, the volumes setting mixes two different things. On one hand, the list of volumes is used for defining the product volumes and, on the other hand, it is also used to configure how the proposal should initially manage the volumes. This could lead to have some volumes in the volumes list that are not considered by the initial proposal at all. These unused volumes are defined there with the intention of offering them as a possible volume for being added by the user. An example could be a /home volume with by_default=false.

Control file settings

Taking everything exposed above into account, let's start by proposing a new structure for the control file settings. The goals are:

  • To define the initial values for calculating the storage proposal in a format that allows to be directly overwritten by the DBus API and/or the command line
  • To define permissions separately (if needed).
  • To define possible volumes for the product (without direct relationship with the initial proposal).
storage:
    lvm: false
    encryption:
        enabled: true
        method: ""
        pbkd_function: ""
    space_policy: delete
    volumes:
        - mount_path: "/"
        - mount_path: "swap"
    volume_templates:
        ...
    permissions:
        lvm: ro
        encryption: rw
        space_policy: rw

The permissions and volume_templates sections cannot be overwritten via DBus or CLI and their purpose is to describe the product and its possibilities.

The permissions section defines the permissions for each setting, indicating whether a setting is read-only (ro) or read-write (rw). A read-only setting does not allow to modify its default value.

The volume_templates represent the default values for the various fields of every possible volume and also the so-called outline, used to define the acceptable values for some fields and how the volume sizes should be auto-calculated, if possible.

The other sections and values like lvm, encryption or volumes represent the default values for the calls to the method calculate() and can be modified or extended in any call to that method, but always honoring the permissions and the outlines for the corresponding volume templates. The default values will be used for settings or volumes that are omitted in the call to calculate().

storage
    permissions:
        ...
    volumes:
        ...
    volume_templates:
        -
        mount_path: "/"
        filesystem: btrfs
        btrfs:
            snapshots: true
            read_only_root: true
        format_options:
            - "--sector-size 4096"
        size:
            auto: true

        outline:
            mandatory: true
            filesystems:
                - btrfs
                - ext3
                - ext4
            auto_size:
                base_min: "10 GiB"
                base_max: "unlimited"
                snapshots_increment: "10 GiB" # can be a percentage (e.g., 20%)
                min_fallback_for:
                    - "/home"
                max_fallback_for:
                    - "/home"
        -
        mount_path: "swap"
        filesystem: swap
        size:
            auto: true

        outline:
            mandatory: false
            filesystems:
                - swap
            auto_size:
                base_min: "1 GiB"
                base_max: "2 GiB"
                adjust_by_ram: true

        -
        mount_path: "/home"
        filesystem: xfs
        mount_options:
            - "data=ordered"  # Just to have an example, even if it makes little sense
        size:
            auto: false
            min: "20 GiB"
            max: "unlimited"

        outline:
            mandatory: false
            filesystems:
                - xfs
                - ext3
                - ext4
        -
        # No attribute mount_path, which implies this is the default template to be used
        # as starting point for arbitrary volumes
        filesystem: xfs
        size:
            auto: false
            min: "1 GiB"
            max: "unlimited"

        outline:
            mandatory: false
            filesystems:
                - xfs
                - ext3
                - ext4

Agama classes

From the code point of view, a set of default volumes are created from the control file settings. There are no templates, and the VolumeConstraints concept is introduced instead. Each volume would have a set of associated constraints. The constraints represent what is immovable for the volume, independently of the permissions of the settings. For example, a constraint would be the possible file system types. And the Volume would be basically the same as the Volume with the VolumeTemplate approach.

class Volume
  # Constraints that apply to this volume (e.g., possible fs types)
  attr_accessor :constraints

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

  # Filesystem options
  #
  # @return [FilesystemOptions]
  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

class VolumeConstraints
  # Whether the volume is optional (can be skipped in the list of volumes to create)
  #
  # @return [Boolean]
  attr_reader :optional
  alias_method :optional?, :optional

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

  # 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?(snapshots)
    return false unless snapshots || snapshots_configurable

    return true if snapshots_size && !snapshots_size.zero?

    snapshots_percentage && !snapshots_percentage.zero?
  end

  attr_reader :snaphots_configurable

  # 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
end

D-Bus API

The new settings (e.g., encryption method) and the default values for a new volume (including the relevant information from its oultline) could be reflected in the D-Bus API with only a few changes.

The new settings can be directly added as new properties to the org.opensuse.Agama.Storage1.Proposal interface:

Interface: org.opensuse.Agama.Storage1.Proposal

BootDevice              readable s
LVM                     readable b
EncryptionPassword      readable s
EncryptionMethod        readable s
EncryptionPBKDFunction  readable s
SpacePolicy             readable s
Volumes                 readable aa{sv}
Actions                 readable aa{sv}

And the org.opensuse.Agama.Storage1.Proposal.Calculator would offer a method for getting a default volume instead of having a VolumeTemplates attribute:

Interface: org.opensuse.Agama.Storage1.Proposal.Calculator

Calculate(in  a{sv} settings, out u result)
DefaultVolume(in s mount_path, out a{sv} volume)

AvailableDevices  readable  ao
Result            readable  o

The Volumes property from Storage1.Proposal interface and the DefaultVolume method from Storage1.Calculator interface would return the information of the volumes, including a new Outline key:

MountPath                 s
TargetDevice              s
TargetVG                  s
AutoSize                  b
MinSize                   x
MaxSize                   x
FsType                    s
Snapshots                 b
Outline
    Mandatory                 b
    FsTypes                   as
    SupportAutoSize           b
    SnapshotsConfigurable     b
    SnapshotsAffectSizes      b
    SizeRelevantVolumes       as

And the settings for Calculate method would be:

BootDevice              s
LVM                     b
EncryptionPassword      s
EncryptionMethod        s
EncryptionPBKDFunction  s
SpacePolicy             s
Volumes                 aa{sv}

Note that the volumes here do not require to include the Outline key:

MountPath                 s
TargetDevice              s
TargetVG                  s
AutoSize                  b
MinSize                   x
MaxSize                   x
FsType                    s
Snapshots                 b
@ancorgs
Copy link

ancorgs commented Jul 18, 2023

Control file settings

Taking everything exposed above into account, let's start by proposing a new structure for the control file settings. The goals are:

  • To define the initial values for calculating the storage proposal in a format that allows to be directly overwritten by the DBus API and/or the command line
  • To define permissions separately (if needed).
  • To define possible volumes for the product (without direct relationship with the initial proposal).
storage:
    lvm: false
    encryption:
        enabled: true
        method: ""
        pbkd_function: ""
    space_policy: delete
    volumes:
        - mount_path: "/"
        - mount_path: "swap"
    volume_templates:
        ...
    permissions:
        lvm: ro
        encryption: rw
        space_policy: rw

The permissions and volume_templates sections cannot be overwritten via DBus or CLI and their purpose is to describe the product and its possibilities.

The permissions section defines the permissions for each setting, indicating whether a setting is read-only (ro) or read-write (rw). A read-only setting does not allow to modify its default value.

The volume_templates represent the default values for the various fields of every possible volume and also the so-called outline, used to define the acceptable values for some fields and how the volume sizes should be auto-calculated, if possible.

The other sections and values like lvm, encryption or volumes represent the default values for the calls to the method calculate() and can be modified or extended in any call to that method, but always honoring the permissions and the outlines for the corresponding volume templates. The default values will be used for settings or volumes that are omitted in the call to calculate().

storage
    permissions:
        ...
    volumes:
        ...
    volume_templates:
        -
        mount_path: "/"
        filesystem: btrfs
        btrfs:
            snapshots: true
            read_only_root: true
        format_options:
            - "--sector-size 4096"
        size:
            auto: true

        outline:
            mandatory: true
            filesystems:
                - btrfs
                - ext3
                - ext4
            auto_size:
                base_min: "10 GiB"
                base_max: "unlimited"
                snapshots_increment: "10 GiB" # can be a percentage (e.g., 20%)
                min_fallback_for:
                    - "/home"
                max_fallback_for:
                    - "/home"
        -
        mount_path: "swap"
        filesystem: swap
        size:
            auto: true

        outline:
            mandatory: false
            filesystems:
                - swap
            auto_size:
                base_min: "1 GiB"
                base_max: "2 GiB"
                adjust_by_ram: true

        -
        mount_path: "/home"
        filesystem: xfs
        mount_options:
            - "data=ordered"  # Just to have an example, even if it makes little sense
        size:
            auto: false
            min: "20 GiB"
            max: "unlimited"

        outline:
            mandatory: false
            filesystems:
                - xfs
                - ext3
                - ext4
        -
        # No attribute mount_path, which implies this is the default template to be used
        # as starting point for arbitrary volumes
        filesystem: xfs
        size:
            auto: false
            min: "1 GiB"
            max: "unlimited"

        outline:
            mandatory: false
            filesystems:
                - xfs
                - ext3
                - ext4

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