Skip to content

Instantly share code, notes, and snippets.

@joseivanlopez
Last active May 27, 2024 09:30
Show Gist options
  • Save joseivanlopez/4f2381eb1f6c3aca562aadc2ad920a72 to your computer and use it in GitHub Desktop.
Save joseivanlopez/4f2381eb1f6c3aca562aadc2ad920a72 to your computer and use it in GitHub Desktop.

Agama Auto-installation (Storage Settings)

Format specification

Storage
  drives <Drive[]>
  volume_groups <VolumeGroup[]>
  software_raids <SoftwareRaid[]>
  bcache_devices <BCache[]>
  nfs_devices <NFS[]>
  btrfs_raids <BtrfsRaid[]>
  guided <Guided>

Drive
  search [<Search>]
  alias [<string>]
  encrypt [<EncryptAction>]
  format [<FormatAction>]
  mount [<MountAction>]
  ptable_type [<string>]
  partitions [<Partition[]>]

EncryptAction
  method <string>
  key [<string>]
  pdkdf [<string>]
  label [<string>]
  cipher [<string>]
  key_size [<number>]

FormatAction
  filesystem <string|Btrfs>
  label [<string>]
  mkfs_options [<string[]>]

Btrfs
  subvolume_prefix [<string>]
  subvolumes [<Subvolume[]>]
  snapshots [<boolean=false>]
  quotas [<boolean=false>]

MountAction
  path <string>
  mount_options [<string[]>]
  mount_by [<string>]

Partition
  search [Search]
  alias [<string>]
  id [<string>]
  type [<string>]
  size [<Size>]
  encrypt [EncryptAction]
  format [<FormatAction>]
  mount [<MountAction>]
  delete [<boolean=false>]

VolumeGroup
  search [<Search>]
  alias [<string>]
  name [<string>]
  pesize [<number>]
  physical_volumes [<string[]>]
  logical_volumes [<LogicalVolume[]>]
  delete [<boolean=false>]

LogicalVolume
  search [<Search>]
  alias [<string>]
  name [<string>]
  size [<Size>]
  pool [<boolean>]
  used_pool [<string>]
  stripes [<number>]
  strip_size [<number>]
  encrypt [<EncryptAction>]
  format [<FormatAction>]
  mount [<MountAction>]
  delete [<boolean=false>]

SoftwareRaid
  search [<Search>]
  alias [<string>]
  name [<string>]
  level [<string>]
  chunk_size [<number>]
  devices [<string[]>]
  encrypt [<EncryptAction>]
  format [<FormatAction>]
  mount [<MountAction>]
  ptable_type [<string>]
  partitions [<Partition[]>]
  delete [<boolean=false>]

BtrfsRaid
  search [<Search>]
  data_raid_level <string>
  metadata_raid_level <string>
  devices <string[]>
  label [<string>]
  mkfs_options [<string[]>]
  [Btrfs]
  delete [<boolean=false>]

Size <'default'|string|SizeRange>

SizeRange
  min <string>
  max <string>

Search
  condition [<Condition>]
  sort [<Sort>]
  min [<number>]
  max [<number>]
  if_not_found [<NotFoundAction='skip'>]

Condition <Rule|OperatorAnd|OperatorOr>

OperatorAnd
  and: <Condition[]>

OperatorOr
  or: <Condition[]>

Rule
  property <string>
  value <any>
  operator [<Operator='equal'>]

Operator <'equal'|'not_equal'|'less'|'greater'|'less_or_equal'|'greater_or_equal'>

Sort
  property <string>
  order <'asc'|'desc'>

NotFoundAction <'continue'|'skip'|'error'>

Examples

Format and mount a disk

storage: {
  drives: [
    {
      format: {
        filesystem: 'xfs',
        label: 'data'
      },
      mount: {
        path: '/home'
      }
    }
  ]
}

Create partitions

storage: {
  drives: [
    {
      ptable_type: 'gpt',
      partitions: [
        {
          size: '10GiB',
          format: { filesystem: 'btrfs' },
          mount: { path: '/' }
        },
        {
          size: '50GiB',
          format: { filesystem: 'xfs' },
          mount: { path: '/home' }
        }
      ]
    }
  ]
}

Create a volume group

storage: {
  drives: [
    {
      ptable_type: 'gpt',
      partitions: [
        {
          alias: 'pv1',
          size: '10GiB'
        },
        {
          alias: 'pv2',
          size: '50GiB'
        }
      ]
    }
  ],
  volume_groups: [
    {
      name: 'vg0',
      physical_volumes: ['pv1', 'pv2'],
      logical_volumes: [
        {
          name: 'home',
          size: '10GiB',
          format: { filesystem: 'btrfs' },
          mount: { path: '/' }
        }
      ]
    }
  ]
}

Format and configure a Btrfs

storage: {
  drives: [
    {
      ptable_type: 'gpt',
      partitions: [
        {
          size: '10GiB',
          format: {
            btrfs: {
              subvolumes: [
                { path: '/home', nocow: true }
              ],
              snapshots: true
            }
          },
          mount: { path: '/' }
        }
      ]
    }
  ]
}

Search a device

storage: {
  drives: [
    {
      // Search for a device with size >= 5GiB and it is not '/dev/vda'.
      search: {
        condition: {
          and: [
            { property: 'size', value: '5GiB', operator: 'greater_than' },
            { property: 'name', value: '/dev/vda', operator: 'not_equal' }
          ]
        }
      },
      format: { filesystem: 'xfs' }
    }
  ]
}

Delete all partitions

storage: {
  drives: [
    {
      partitions: [
        // Search without condition, so everything is selected.
        { search: {}, delete: true }
      ]
    }
  ]
}

Mount an existing partition

storage: {
  drives: [
    {
      partitions: [
        {
          // First partition with XFS file system
          search: { property: 'filesystem', value: 'xfs', max: 1 },
          mount: { path: '/home' }
        },
        {
          // And also create a new partition
          size: '10GiB'
        }
      ]
    }
  ]
}

Delete the 3 first volume groups

storage: {
  volume_groups: [
    {
      { search: { max: 3 }, delete: true }
    }
  ]
}

Find a disk or use any

storage: {
  drives: [
    {
      search: {
        condition: { property: 'size', value: '10GiB', max: 1 },
        if_not_found: 'continue'
      },
      format: { filesystem: 'xfs' }
    }
  ]
}

Reuse or create a partition

storage: {
  drives: [
    {
      partitions: [
        {
          // Try to reuse, but continue if not found.
          search: {
            condition: { property: 'filesystem', value: 'xfs' },
            if_not_found: 'continue'
          },
          size: '10GiB',
          format: { filesystem: 'xfs' }
        }
      ]
    }
  ]
}

Condititional search

Create a script:

#!/bin/sh
# check_intel.sh

if grep -i intel /proc/cpuinfo > /dev/null; then
  echo -n "intel"
else
  echo -n "non_intel"
fi;

Pass script output as a jsonnet external variable:

$ jsonnet --ext-str cpu="$(check_intel.sh)" profile.jsonnet

Add search section if needed:

storage: {
  drives: [
    {
      [if std.extVar('cpu') == 'intel' then 'search']: {
        condition: { property: 'size', value: '10GiB', max: 1 }
      },
      format: { filesystem: 'xfs' }
    }
  ]
}
@ancorgs
Copy link

ancorgs commented May 21, 2024

I'm not sure whether we have all the needed mechanism to match/find disks, MDs, VGs and partitions.

This seems to rely a bit too much on knowing the name and/or the UUIDs, which are usually not known beforehand for all systems.

Also I fail to see how can I tell which partitions should be deleted. I guess that everything that is not mentioned/matched somehow gets deleted. Is that correct?

@ancorgs
Copy link

ancorgs commented May 21, 2024

On the other hand. Is "Disk" a good name? I understand it include disks, DASD devices, multipath devices, Firmware RAIDs, SD Cards maybe also Xen partitions...

I like "drive", although I know it comes with some AutoYaST heritage. Maybe some synonym.

@ancorgs
Copy link

ancorgs commented May 21, 2024

One of the many potential problems I see on relying on names for reusing. Imagine this I write this because I want to create a new volume group

lvm: {
  name: vg0
  [...]
}

But turns out I run it on a system in which there is already a volume group with that name. Will it then reuse it?

@ancorgs
Copy link

ancorgs commented May 21, 2024

How do you envision multi-device Btrfs?

@joseivanlopez
Copy link
Author

joseivanlopez commented May 21, 2024

  • Yes sure, probably we will need a more powerful way for matching devices (jsonnet could help here?).
  • Yes, my idea is "you get what you write". So, not listed partitions would be deleted. I don't know if we can go with this idea. Something to discuss.
  • You are right, Disk is used for all that devices. And I agree, surely there is a better name.
  • My idea is that it would reuse the matching LVM volumn group if you don't indicate its phsyical volumes. And similar for MDs. In general my plan for all the devices is to reuse it if you indicate something about the existing device (UUID, name, ...). But, in some cases, the device is recreated if you indicate something else (e.g., its physical volumes). Again, I am not sure if it is a good approach.
  • I have added an example about how I envision multi-device Btrfs.

@ancorgs
Copy link

ancorgs commented May 22, 2024

This is an alternative to the excerpt presented at Dynamically Select Partitions:

It tries to achieve the same without relying that much in jsonnet.

Elements of type disk (I prefer the name "drive"), multidevice_btrfs, partition, volume_group, logical_volume, or md can contain a section of type devices. For devices that can be created (all the mentioned ones except "disk"), not having a devices section means they will be created. For disks without a devices section, the first available one will be used.

Devices
  rule <DeviceMatchingRule>
  min [<integer>]
  max [<integer>]

Several devices can match with the rule. Or maybe none. Max allows to select only the first N elements that match. Min allows to fail if not enough elements match.

disks: [
  {
    devices {
        rule {
            // Matcher omitted because "==" is the default
            attribute: "name",
            value: "/dev/vda"
        },
        // Max not really needed because matching by disk name cannot result in more than one
        max: 1
    },
    partition_table: {
        delete: [
            {
                 devices { 
                     rule {
                        matcher: "==",
                        attribute: "filesystem_label",
                        value: "DELETEME"
                     },
                     // There is no max, all partitions with the label DELETEME will be matched
                 }
            }
        ],
        partitions: [
            {
                devices { 
                     rule {
                        attribute: "name",
                        value: "/dev/vda1"
                     }
                },
                filesystem: { type: xfs }
            },
            {
                size: 10GiB,
                fileystem: { type: xfs }
            } 
        ]
    }
  }
]       

Combining both approaches.

disks: [
  {
    devices {
        rule {
            attribute: "name",
            value: "/dev/vda"
        },
    },
    partition_table: {
        delete: [
            {
                 devices { 
                     rule {
                        matcher: "==",
                        attribute: "name",
                        value: select_partition_names([
                            { key: name, value: /dev/vda\d+, condition: match },
                            { key: filesystem_label, value: DELETEME }
                        ])
                     }
                 }
            }
        ],
        partitions: [
            {
                devices { 
                     rule {
                        attribute: "name",
                        value: find_partition_name([ { key: name, value: /dev/vda1 }])
                     }
                },
                filesystem: { type: xfs }
            },
            {
                size: 10GiB,
                fileystem: { type: xfs }
            } 
        ]
    }
  }
]       

@ancorgs
Copy link

ancorgs commented May 22, 2024

Some mental notes for myself about the devices section, its rules, etc.

  1. Adding sorting? It may become complex but (when combined with max) it may also allow to select, for example, "the biggest NVMe disk"

  2. Rules as attribute name so we save one level.

@joseivanlopez
Copy link
Author

As suggested by @imobachgs, we could get inspired by json-rules-engine in order to find a proper API for matching devices.

@kobliha
Copy link

kobliha commented May 23, 2024

Note 1: Additionally, it would be also great to have the ability to select the "fastest" disk, but as lshw does not provide that info, something like hdparm -t /dev/device would have to be used, and each such call takes ~6.2 sec on my system.

Note 2: Some of our customers/partners have written very sophisticated AY scripts, but they seem to be mainly about partitioning - something that AY was not able to do or they did not know how. It would be great to compare if the suggested way is actually simpler than using a script.

@joseivanlopez
Copy link
Author

joseivanlopez commented May 24, 2024

Note 1: Additionally, it would be also great to have the ability to select the "fastest" disk, but as lshw does not provide that info, something like hdparm -t /dev/device would have to be used, and each such call takes ~6.2 sec on my system.

Jsonnet could be used, for example:

{
  conditions: { fact: 'name', value: find_fastest_disk() },
  partitions: [...]
}

I don't know if jsonnet allows to "embed" script code, but at least we can inject the output of some script as parameter when compiling the jsonnet file.

@joseivanlopez
Copy link
Author

It seems we could do something like the following:

  • Use std.extVar to get values from command line:
{
  conditions: { fact: 'name', value: std.extVar('fastest_disk') },
  partitions: [...]
}
  • Pass the variable when compiling, fidding it with a script output:

jsonnet --ext-str fastest_disk="$(find_fastest_disk.sh)" profile.jsonnet

@kobliha
Copy link

kobliha commented May 24, 2024

I'm still thinking about conditions->fact. For me, the name "fact" is somehow questionable. It IMO rather something like "object" or "subject" or even "quality" (not really).

@joseivanlopez
Copy link
Author

joseivanlopez commented May 27, 2024

I'm still thinking about conditions->fact. For me, the name "fact" is somehow questionable. It IMO rather something like "object" or "subject" or even "quality" (not really).

Yes. The syntax for searching devices is still under discussion. In fact, the lastest proposal is to use the "search" keyword:

{
  "search": {
    "condition": { ... },
    "short": { ... }
  }
}

And there could be different ways to express a rule (we have to decide one):

"condition": { "name": "/dev/vda" } 

"condition": { "property": "name", "value": "/dev/vda" } 

BTW, probably this document will become obsolete in some parts. @ancorgs already started writing a document describing the format and agreements, see https://github.com/openSUSE/agama/pull/1256/files.

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