Skip to content

Commit

Permalink
feat(doc): document process for solving storage config
Browse files Browse the repository at this point in the history
  • Loading branch information
joseivanlopez committed Dec 18, 2024
1 parent 97dc174 commit 0f8fd49
Showing 1 changed file with 210 additions and 0 deletions.
210 changes: 210 additions & 0 deletions doc/storage_proposal_from_profile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Calculating a proposal from a profile

The Agama proposal can be calculated either from a very detailed JSON profile or from a "sparse
profile". The information not provided by the profile is automatically inferred (solved) by Agama.
Several layers are involved in the process of obtaining the final storage config used by the Agama
proposal, as shown in the following diagram:

```
JSON profile ------------> JSON profile (solved) ------------> Storage::Config ------------> Storage::Config (solved)
| | |
(JSON solver) (config conversion) (config solver)
```

## JSON solver

The JSON profile provides the *generator* concept. A *generator* allows indicating what volumes to
create without explicitly defining them. The JSON solver (`Agama::Storage::JSONConfigSolver` class)
takes care of replacing the volume generator by the corresponding JSON volumes according to the
product.

For example, a JSON profile like this:

~~~json
{
"drives": [
{
"partitions": [ { "generate": "default" } ]
}
]
}
~~~

would be solved to something like:

~~~json
{
"drives": [
{
"partitions": [
{ "filesystem": { "path": "/" } },
{ "filesystem": { "path": "swap" } }
]
}
]
}
~~~

The volumes are solved with their very minimum information (i.e., a mount path). The resulting
solved JSON is used for getting the storage config object.

## Config conversion

The class `Agama::Storage::ConfigConversions::FromJSON` takes a solved JSON profile and generates a
`Agama::Storage::Config` object. The resulting config only contains the information provided by the
profile. For example, if the profile does not specify a file system type for a partition, then the
config would not have any information about the file system to use for such a partition.

If something is not provided by the profile (e.g., "boot", "size", "filesystem"), then the config
marks that values as default ones. For example:

```json
{
"drives": [
{
"partitions": [
{ "filesystem": { "path": "/" } }
]
}
]
}
```

generates a config with these default values:

```ruby
config.boot.device.default? #=> true

partition = config.drives.first.partitions.first
partition.size.default? #=> true
partition.filesystem.type.default? #=> true
```

The configs set as default and any other missing value have to be solved to a value provided by the
product definition.

## Config solver

The config solver (`Agama::Storage::ConfigSolver` class) assigns a value to all the unknown properties
of a config object. As result, the config object is totally complete and ready to be used by the agama
proposal.

### How sizes are solved

A volume size in the profile:

* Can be totally omitted.
* Can omit the max size.
* Can use "custom" as value for min and/or max.

Let's see each case.

#### Omitting size completely

```json
"partitions": [
{ "filesystem": { "path": "/" } }
]
```

In this case, the config conversion would generate something like:

```ruby
partition.size.default? #=> true
partition.size.min #=> nil
partition.size.max #=> nil
```

If the size is default, then the config solver always assigns a value for `#min` and `#max` according
to the product definition and ignoring the current values assigned to `#min` and `#max`. The solver
takes into account the mount path, the fallback devices and swap config in order to set the proper
sizes.

#### Omitting the max size

```json
"partitions": [
{
"size": { "min": "10 GiB" },
"filesystem": { "path": "/" }
}
]
```

The config conversion generates:

```ruby
partition.size.default? #=> false
partition.size.min #=> Y2Storage::DiskSize.GiB(10)
partition.size.max #=> Y2Storage::DiskSize.Unlimited
```

Note that `#max` is set to unlimited when it does not appear in the profile. In this case, nothing
has to be solved because both `#min` and `#max` have a value.

#### Using "custom"

The profile admits "custom" as a valid value for *min* and *max* sizes. If "custom" is used, then
the config conversion simply sets that values to `nil`.

```json
"partitions": [
{
"size": { "min": "custom" },
"filesystem": { "path": "/" }
}
]
```

The config conversion generates:

```ruby
partition.size.default? #=> false
partition.size.min #=> nil
partition.size.max #=> Y2Storage::DiskSize.Unlimited
```

And the final value assigned by the config solver depends on the volume:

* If the volume already exists, then the `nil` values are replaced by the volume size.
* If the volume is going to be created, then the `nil` values are replaced by the size calculated
from the product definition.

For example:

```json
"partitions": [
{
"search": "/dev/vda1",
"size": { "min": "custom" },
"filesystem": { "path": "/" }
}
]
```

and let's say that */dev/vda1* has 10 GiB, the resulting config is:

```ruby
partition.size.default? #=> false
partition.size.min #=> Y2Storage::DiskSize.GiB(10)
partition.size.max #=> Y2Storage::DiskSize.Unlimited
```

If the volume is new and the default size for root is 15 GiB:

```json
"partitions": [
{
"size": { "min": "custom" },
"filesystem": { "path": "/" }
}
]
```

then the resulting config is:

```ruby
partition.size.default? #=> false
partition.size.min #=> Y2Storage::DiskSize.GiB(15)
partition.size.max #=> Y2Storage::DiskSize.Unlimited
```

0 comments on commit 0f8fd49

Please sign in to comment.