Skip to content

Latest commit

 

History

History
152 lines (123 loc) · 7.76 KB

proposal.md

File metadata and controls

152 lines (123 loc) · 7.76 KB

The proposal classes

The storage proposal is a rather complex piece of software. This document tries to provide a high level view of its code organization.

Using the existent code to generate a proposal is very simple and only requires the usage of two easy to understand classes. The complexity is hidden in those classes in a layered way - as the reader "zooms" into the solution, more classes appear. This document will use that layered approach to explain the goal, responsibility and interactions of each class.

Zoom level 0: the public classes

The proposal only offers two public classes, GuidedProposal and ProposalSettings, that can be used like shown in this example:

settings = ProposalSettings.new
# Adjust the default settings if needed
settings.use_separate_home = true
proposal = GuidedProposal.new(settings: settings)
proposal.propose
proposal.devices # => Returns the proposed devicegraph

ProposalSettings is basically a struct-like class to store several attributes and to read those attributes from the control file. All the magic (and complexity) lives in GuidedProposal, so let's zoom into it.

Zoom level 1: the proposal steps

The whole proposal mechanism is divided into two steps, each one of them implemented in its own class.

In the first place, an instance of Proposal::PlannedDevicesGenerator (to be renamed in the future to Proposal::DevicesPlanner) is used to decide which partitions, LVM logical volumes and Btrfs subvolumes should be created or reused. Every one of those devices is represented by an instance of Planned::Partition, Planned::LvmLv or Planned::BtrfsSubvolume, which offer a relatively flexible specification of the corresponding devices (e.g. min size, max size and weight, instead of a fixed final size).

Once the requirements are known, it's time to start allocating those devices. That second step is performed by an instance of Proposal::DevicegraphGenerator. Given an initial devicegraph, a list of planned devices and a set of proposal settings, it will return a new devicegraph containing the final devices.

This whole process is potentially repeated twice. In the first attempt, the instance of Proposal::PlannedDevicesGenerator is asked for the desired set of planned devices, so it will aim for the best possible size for each planned device. If the instance of Proposal::DevicegraphGenerator is not able to accommodate those planned devices, a second attempt will be performed. On that second attempt, the device generator will aim for a minimalistic set of planned devices, reducing the size expectations as much as possible. If the devicegraph generator also fails to allocate that smaller version of the planned devices, an exception is raised.

During the installation a first proposal is automatically calculated when the installer reaches the proposal step. This initial proposal is performed without user interaction, and it is based on the settings defined for the current product. There is a specialized class InititalGuidedProposal to calculate this proposal, considering all available devices in the system as possible candidate devices. Firstly, it will try to make a valid proposal by using each disk individually. A new disk is not considered until all possible attempts have been carried out. That is, it will try to disable some settings properties (e.g., snapshots) before switching to another candidate device. When no proposal was possible by using each individual device, a last attempt is performed by using all the available devices together.

Apart from the two main steps and the classes representing the set of planned devices, there is another relevant class in this level of zoom. DiskAnalyzer is used to analyze the initial devicegraph (that is, the content of the disks at the beginning of the installation process) and to provide useful information about it to all the other components.

All the components in this level are relatively simple except Proposal::DevicegraphGenerator, so let's zoom into it.

Zoom level 2: steps to generate the devicegraph

The first step when trying to allocate volumes is to ensure there is enough space for them. That usually implies deleting or resizing existing partitions in a sensible way. That's done by an instance of Proposal::SpaceMaker.

But finding a place for the volumes is not only a matter of having enough free space. Two separate slots of 3 GiB provide 6 GiB of free space, but do not make possible to allocate a single partition of 4 GiB. In an MBR partition table, a single free slot cannot be used to allocate four volumes if there is already an extended partition in other part of the disk, no matter how big the free slot is. And so on, the examples are countless.

That's why there is a class called Planned::PartitionsDistribution that represents a distribution of Planned::Partition objects alongside all the available free space slots in a devicegraph. A Planned::PartitionsDistribution will only be valid if it honors all the restrictions imposed by the disks and by the planned partitions.

So the ultimate goal of a Proposal::SpaceMaker is to delete and resize partitions until it finds a suitable Planned::PartitionsDistribution. If at a given point in time there are several possible distributions, it will return the best one.

Once the space is generated and the proposal knows how the partitions should be distributed, it's the turn of Proposal::PartitionCreator. One instance of that class takes the space distribution created in the previous step and makes it real by creating the partitions and the filesystems.

It's worth mentioning that the previous classes has very little knowledge about LVM. They mainly work at partition level trusting an instance of Proposal::LvmHelper for everything related to LVM.

The instance of Proposal::LvmHelper will take care (indirectly, see below) of adding the needed physical volumes, if any, to every attempt of Planned::PartitionsDistribution, taking into account all the roundings and overheads involved in any LVM setup.

Once the instance of Proposal::PartitionCreator is done with its job (which also includes creating the physical volumes contained in the space distribution), the original instance of Proposal::LvmHelper will take care of creating the volume group, the logical volumes and the filesystems for all the planned volumes that must reside in LVM.

Zoom level 3: utility classes

The classes described in the previous zoom level rely on some extra classes to do their job.

An instance of Planned::PartitionsDistribution is basically a collection of Planned::AssignedSpace objects. Every one of those objects relates a free disk slot with its set of planned partitions and also provides additional information, like the restrictions imposed by the disk to the partitions potentially created on that slot (#partition_type) or how many of the partitions should be logical to fulfill the distribution (#num_logical).

To calculate the best space distribution for a given disk layout and to decide how much the existing partitions must be resized, Proposal::SpaceMaker relies on a class called Proposal::PartitionsDistributionCalculator.

Additionally, to delete the partitions, an operation that may be more complex that it looks like, the space maker relies on another utility class called Proposal::PartitionKiller.

Last but not least, the class Proposal::PhysVolCalculator helps Proposal::PartitionsDistributionCalculator and Proposal::LvmHelper in the task of creating a planned partition object for each needed physical volume and adding those volumes to all the potential space distributions.