osbuild / images

Image builder image definition library
Apache License 2.0
21 stars 49 forks source link

Advanced partitioning customizations (COMPOSER-2355) #926

Open achilleas-k opened 2 weeks ago

achilleas-k commented 2 weeks ago

This PR adds a new advanced partitioning customization to the blueprint that has the following structure:

{
  "customizations": {
    "partitioning": {
      "plain": {
        "filesystems": [
          {
            "mountpoint": "",
            "minsize": 0,
            "label": "",
            "fstype": ""
          }
        ],
        "lvm": {
          "volume-groups": [ # only one supported, but maybe we can support multiple in the future
            {
              "name": "",
              "logical-volumes": [ # similar to filesystems but with lvm stuff
                {
                  "mountpoint": "",
                  "minsize": 0,
                  "label": "",
                  "name": "", # lv name
                  "type": ""
                }
              ]
            }
          ]
        },
        "btrfs": {
          "volumes": [ # only one supported, but maybe we can support multiple in the future
            {
              "size": 0,
              "subvolumes": [
                {
                  "name": "",
                  "mountpoint": "",
                  "size": 0
                }
              ]
            }
          ]
        }
      }
    }
  }
}

The customization is currently enabled only in Fedora. I plan to add support and tests in RHEL and CentOS in a follow-up PR.

When the new customizations are used, the base partition table for the image type is ignored. Instead, a new "custom" partition table is constructed from scratch by iterating through the customizations and creating partitions, volume groups and logical volumes, and btrfs volumes and subvolumes as needed. Boot, EFI, and root partitions are created automatically to make the partition table valid and usable. Parameters such as boot mode and partition table type should be determined by the caller of the function, which should be based on the distro, architecture, and image type. This is, in a way, much simpler than our existing ensureLVM() and ensureBtrfs() functions, which needed to modify an existing partition table's structure in a way that preserved some parts and not others.

The custom partition table creation function (NewCustomPartitionTable()) supports all kinds of valid but perhaps unwanted configurations. For example, it's possible to create a partition table with both LVM volume groups and btrfs volumes, or multiples of each. The customizations however don't allow this, but the structure supports it (see above) in case we want to expand what we support in the future.

Customization validation happens in the blueprint. Invalid values such as duplicate mountpoints, empty btrfs subvolume names, etc are caught by a validator in the blueprint code itself.

What's next:

achilleas-k commented 2 weeks ago

A meta-question - how will this interplay with the otk-gen-partition-table helper?

I don't know, but I'll tell you how I'm thinking about this.

The existing filesystem customizations are too simple for the target use cases of otk. Back in the day, the only partition table customization we had was setting the size of the disk (which grew /), and partition tables were fixed. Then we got the addition of extra filesystems, which would be applied to the existing base partition table for each image type and "upgrade" the layout to LVM automatically for easier management. Then we got the concept of "partitioning modes" which affected how the partition table would be modified to accommodate the new filesystems (add them as plain, convert to LVM, or convert to btrfs). It's all a bit automatic and maybe hard to explain to users (people interacting through blueprints), but it fit the kind of high-level space where we imagine blueprints should live in.

What this PR does is move a lot more of the description of the partition table up from inside the image definitions into the blueprint. I think this move would have been a bit controversial a couple of years ago. We would generally try to keep the blueprints simple, the general philosophy being that the base image definitions are well tested, they're a great starting point for just about anyone, and the limited range of customizations is a feature that offers safety and stability. The general thinking was that the wider we spread the range of customizations the less we can test, the number of "invalid" configurations grow, and the weaker our guarantees of stability become. Note that this might just be me talking, I don't mean to project my ideas of how things were and are onto the entire team, even though I might be presenting it that way.

I think we're slowly realising that this has been holding us back, that many (most?) users want more control, and they don't mind the occasional footgun (though I suspect many who claim to want "lots of flexibility, with a chance of footgun" tend to underestimate the pain they're opening themselves up to).

What this all means for otk: Otk was conceived as the tool for distro maintainers. osbuild-mpp done right. It's easier to think about the level of customization and flexibility that otk should expose: pretty much anything goes. There's no regard for lack of safety, users of otk "should know what they're doing". My first thought when I read your question was that the level of flexibility exposed by these customizations can be the same for the otk tooling. That seems strange to me though because blueprints should be more abstract/high-level than the distro maintainer tooling. It certainly seems (to me right now) that we could have blueprint customizations for partitioning that are powerful enough for a distro maintainer using otk, but I am worried about the requirements of otk pushing for more advanced features in blueprints. For example, as I see it, there should be no way using blueprints in osbuild-composer to build an image configured for "hybrid boot" without a BIOS boot partition. It should, however, be possible to draw up whatever partition table one wants using otk, because otk shouldn't be that smart.

So where does otk fit in the space between blueprint partitioning and make your own partition table to the dot? It's certainly annoying to have to write out a whole PT, but isn't that what otk is for? I'm actually not sure.

Examining a few answers

The otk external tooling should work exactly like blueprints (take in blueprint.customizations.partitioning and generate a full PT) and otk users that want more should write everything from scratch.

This doesn't really work though because the "from scratch" option would have no tooling for things like sector alignment and UUID generation, which would be a PITA.

The otk external should remain as it is (Partitions + Modifications).

This solution kinda sucks because it actually seems less expressive than the new partitioning customizations right now, which is backwards.

The otk external should work like blueprints + extra stuff (i.e. import blueprint.customizations.partitioning and expand the config objects with more options/fields, like fstab fields as well as some high level bits like BootMode for automatic partition creation).

Probably the best solution and close to what we have now, but needs a bit of work.

The otk external should take in a PT object (disk.PartitionTable) and fill in any gaps.

I kinda like this idea. It means that the new customizations in this PR have no relation to otk at all, but it gives flexibility to a distro maintainer to do whatever while getting some help from the disk module for proper PT and osbuild stage generation. The way I imagine this is that otk-gen-partition-table inputs should look like a disk.PartitionTable, with the option to add UUIDs, sizes, even start sectors, and anything NOT set gets filled in by the external. So you could, for example, have 3 partitions with filesystems, fill in the UUIDs for 2 of them, and the third gets an automatic/random UUID. Same for things like mount options. An empty string implies defaults, but the field is exposed in the input and can be overridden.

achilleas-k commented 1 week ago

Fixed up the following from comments:

There are a couple of more comments still open.

achilleas-k commented 1 week ago

I think I covered all the existing comments and I expanded the test matrix for the new function.

Marking as ready.

bcl commented 6 days ago

Wow, great job with this! I only had a couple small comments and one thing I didn't see -- is there anything that catches overrlapping partitions? I didn't notice anything in the validation function or tests.

achilleas-k commented 4 days ago

Wow, great job with this! I only had a couple small comments and one thing I didn't see -- is there anything that catches overrlapping partitions? I didn't notice anything in the validation function or tests.

The validator in the blueprint normalises mountpoints and checks for duplicates across the whole customization block. It's probably a good idea to have the same checks in the NewCustomPartitionTable() function since it can be used externally without going through the blueprint (like, if BIB consumes the function directly somehow).