-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(doc): document process for solving storage config
- Loading branch information
1 parent
97dc174
commit 0f8fd49
Showing
1 changed file
with
210 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |