Skip to content

Commit 26bedf0

Browse files
feat(web): add support for LVM (#2216)
Add basic support for LVM in the web UI: * Add, edit and delete new volume groups. * Add, edit and delete new logical volumes. Out of scope: * Reuse existings volume groups. * Thin pools and thin volumes. * Create physical volumes manually.
2 parents f0c90a2 + 157d436 commit 26bedf0

File tree

106 files changed

+7108
-1622
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+7108
-1622
lines changed

rust/agama-lib/share/examples/storage/model.json

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
"drives": [
1414
{
1515
"name": "/dev/vda",
16-
"alias": "root",
1716
"mountPath": "/",
1817
"filesystem": {
1918
"default": true,
@@ -75,6 +74,33 @@
7574
}
7675
}
7776
]
77+
},
78+
{
79+
"name": "/dev/vdc",
80+
"spacePolicy": "delete"
81+
}
82+
],
83+
"volumeGroups": [
84+
{
85+
"vgName": "vg0",
86+
"extentSize": 1024,
87+
"targetDevices": ["/dev/vdc"],
88+
"logicalVolumes": [
89+
{
90+
"lvName": "lv0",
91+
"mountPath": "/data",
92+
"filesystem": {
93+
"default": false,
94+
"type": "ext4"
95+
},
96+
"size": {
97+
"default": true,
98+
"min": 1111
99+
},
100+
"stripes": 8,
101+
"stripeSize": 1204
102+
}
103+
]
78104
}
79105
]
80106
}

rust/agama-lib/share/storage.model.schema.json

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
"drives": {
1010
"type": "array",
1111
"items": { "$ref": "#/$defs/drive" }
12+
},
13+
"volumeGroups": {
14+
"type": "array",
15+
"items": { "$ref": "#/$defs/volumeGroup" }
1216
}
1317
},
1418
"$defs": {
@@ -52,7 +56,6 @@
5256
"required": ["name"],
5357
"properties": {
5458
"name": { "type": "string" },
55-
"alias": { "$ref": "#/$defs/alias" },
5659
"mountPath": { "type": "string" },
5760
"filesystem": { "$ref": "#/$defs/filesystem" },
5861
"spacePolicy": { "$ref": "#/$defs/spacePolicy" },
@@ -68,7 +71,6 @@
6871
"additionalProperties": false,
6972
"properties": {
7073
"name": { "type": "string" },
71-
"alias": { "$ref": "#/$defs/alias" },
7274
"id": { "$ref": "#/$defs/partitionId" },
7375
"mountPath": { "type": "string" },
7476
"filesystem": { "$ref": "#/$defs/filesystem" },
@@ -79,9 +81,34 @@
7981
"resizeIfNeeded": { "type": "boolean" }
8082
}
8183
},
82-
"alias": {
83-
"description": "Alias used to reference a device.",
84-
"type": "string"
84+
"volumeGroup": {
85+
"type": "object",
86+
"additionalProperties": false,
87+
"required": ["vgName"],
88+
"properties": {
89+
"vgName": { "type": "string" },
90+
"extentSize": { "type": "integer" },
91+
"targetDevices": {
92+
"type": "array",
93+
"items": { "type": "string" }
94+
},
95+
"logicalVolumes": {
96+
"type": "array",
97+
"items": { "$ref": "#/$defs/logicalVolume" }
98+
}
99+
}
100+
},
101+
"logicalVolume": {
102+
"type": "object",
103+
"additionalProperties": false,
104+
"properties": {
105+
"lvName": { "type": "string" },
106+
"mountPath": { "type": "string" },
107+
"filesystem": { "$ref": "#/$defs/filesystem" },
108+
"size": { "$ref": "#/$defs/size" },
109+
"stripes": { "type": "integer" },
110+
"stripeSize": { "type": "integer" }
111+
}
85112
},
86113
"spacePolicy": {
87114
"enum": ["delete", "resize", "keep", "custom"]

rust/agama-lib/share/storage.schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
"description": "LVM volume group.",
160160
"type": "object",
161161
"additionalProperties": false,
162+
"required": ["name"],
162163
"properties": {
163164
"name": {
164165
"description": "Volume group name.",

rust/package/agama.changes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ Mon Mar 10 12:13:19 UTC 2025 - José Iván López González <jlopez@suse.com>
5050
- Package and install the storage model schema
5151
(gh#agama-project/agama#2135).
5252

53+
-------------------------------------------------------------------
54+
Fri Mar 7 11:40:56 UTC 2025 - José Iván López González <jlopez@suse.com>
55+
56+
- Extend storage model schema with LVM (gh#agama-project/agama#2089).
57+
5358
-------------------------------------------------------------------
5459
Thu Mar 6 12:51:42 UTC 2025 - Imobach Gonzalez Sosa <igonzalezsosa@suse.com>
5560

service/lib/agama/storage/config.rb

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
# Copyright (c) [2024] SUSE LLC
3+
# Copyright (c) [2024-2025] SUSE LLC
44
#
55
# All Rights Reserved.
66
#
@@ -60,16 +60,39 @@ def initialize
6060
@nfs_mounts = []
6161
end
6262

63-
# Name of the device that will be used to boot the target system, if any.
64-
#
65-
# @note The config has to be solved.
66-
#
67-
# @return [String, nil]
63+
# @return [Configs::Drive, nil]
6864
def boot_device
6965
return unless boot.configure? && boot.device.device_alias
7066

71-
boot_drive = drives.find { |d| d.alias?(boot.device.device_alias) }
72-
boot_drive&.found_device&.name
67+
drives.find { |d| d.alias?(boot.device.device_alias) }
68+
end
69+
70+
# Device config containing root.
71+
#
72+
# @return [Configs::Drive, Configs::VolumeGroup, nil]
73+
def root_device
74+
root_drive || root_volume_group
75+
end
76+
77+
# Drive config containing root.
78+
#
79+
# @return [Configs::Drive, nil]
80+
def root_drive
81+
drives.find { |d| d.root? || d.partitions.any?(&:root?) }
82+
end
83+
84+
# Volume group config containing a logical volume for root.
85+
#
86+
# @return [Configs::LogicalVolume, nil]
87+
def root_volume_group
88+
volume_groups.find { |v| v.logical_volumes.any?(&:root?) }
89+
end
90+
91+
# Drive with the given alias.
92+
#
93+
# @return [Configs::Drive, nil]
94+
def drive(device_alias)
95+
drives.find { |d| d.alias?(device_alias) }
7396
end
7497

7598
# @return [Array<Configs::Partition>]

service/lib/agama/storage/config_checkers/boot.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,17 @@ def device_alias
5151
storage_config.boot.device.device_alias
5252
end
5353

54+
# Error if a boot device is required and unknown.
55+
#
56+
# This happens when the config solver is not able to infer a boot device, see
57+
# {ConfigSolvers::Boot}.
58+
#
5459
# @return [Issue, nil]
5560
def missing_alias_issue
5661
return unless configure? && device_alias.nil?
5762

58-
# Currently this situation only happens because the config solver was not able to find
59-
# a device config containing a root volume. The message could become inaccurate if the
60-
# solver logic changes.
6163
error(
62-
_("The boot device cannot be automatically selected because there is no root (/) " \
63-
"file system"),
64+
_("The boot device cannot be automatically selected"),
6465
kind: :no_root
6566
)
6667
end

service/lib/agama/storage/config_checkers/volume_group.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
# Copyright (c) [2024] SUSE LLC
3+
# Copyright (c) [2024-2025] SUSE LLC
44
#
55
# All Rights Reserved.
66
#
@@ -41,23 +41,33 @@ def initialize(config, storage_config, product_config)
4141
@config = config
4242
end
4343

44-
# Volume group config issues.
44+
# Issues from a volume group config.
4545
#
4646
# @return [Array<Issue>]
4747
def issues
4848
[
49+
name_issue,
4950
logical_volumes_issues,
5051
physical_volumes_issues,
5152
physical_volumes_devices_issues,
5253
physical_volumes_encryption_issues
53-
].flatten
54+
].compact.flatten
5455
end
5556

5657
private
5758

5859
# @return [Configs::VolumeGroup]
5960
attr_reader :config
6061

62+
# Issue if the volume group name is missing.
63+
#
64+
# @return [Issue, nil]
65+
def name_issue
66+
return if config.name && !config.name.empty?
67+
68+
error(_("There is a volume group without name"))
69+
end
70+
6171
# Issues from logical volumes.
6272
#
6373
# @return [Array<Issue>]

service/lib/agama/storage/config_conversions/from_model_conversions.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
require "agama/storage/config_conversions/from_model_conversions/encryption"
2727
require "agama/storage/config_conversions/from_model_conversions/filesystem"
2828
require "agama/storage/config_conversions/from_model_conversions/filesystem_type"
29+
require "agama/storage/config_conversions/from_model_conversions/logical_volume"
2930
require "agama/storage/config_conversions/from_model_conversions/partition"
3031
require "agama/storage/config_conversions/from_model_conversions/search"
3132
require "agama/storage/config_conversions/from_model_conversions/size"
33+
require "agama/storage/config_conversions/from_model_conversions/volume_group"
3234

3335
module Agama
3436
module Storage

service/lib/agama/storage/config_conversions/from_model_conversions/boot.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
# Copyright (c) [2024] SUSE LLC
3+
# Copyright (c) [2024-2025] SUSE LLC
44
#
55
# All Rights Reserved.
66
#
@@ -29,8 +29,18 @@ module ConfigConversions
2929
module FromModelConversions
3030
# Boot conversion from model according to the JSON schema.
3131
class Boot < Base
32+
# @param model_json [Hash] Boot model.
33+
# @param drives [Array<Configs::Drive>]
34+
def initialize(model_json, drives)
35+
super(model_json)
36+
@drives = drives
37+
end
38+
3239
private
3340

41+
# @return [Array<Configs::Drive>]
42+
attr_reader :drives
43+
3444
# @see Base
3545
# @return [Configs::Boot]
3646
def default_config
@@ -51,7 +61,7 @@ def convert_device
5161
boot_device_model = model_json[:device]
5262
return if boot_device_model.nil?
5363

54-
FromModelConversions::BootDevice.new(boot_device_model).convert
64+
FromModelConversions::BootDevice.new(boot_device_model, drives).convert
5565
end
5666
end
5767
end

service/lib/agama/storage/config_conversions/from_model_conversions/boot_device.rb

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
# Copyright (c) [2024] SUSE LLC
3+
# Copyright (c) [2024-2025] SUSE LLC
44
#
55
# All Rights Reserved.
66
#
@@ -28,8 +28,18 @@ module ConfigConversions
2828
module FromModelConversions
2929
# Boot device conversion from model according to the JSON schema.
3030
class BootDevice < Base
31+
# @param model_json [Hash] Boot device model.
32+
# @param drives [Array<Configs::Drive>]
33+
def initialize(model_json, drives)
34+
super(model_json)
35+
@drives = drives
36+
end
37+
3138
private
3239

40+
# @return [Array<Configs::Drive>]
41+
attr_reader :drives
42+
3343
# @see Base
3444
# @return [Configs::Boot]
3545
def default_config
@@ -40,9 +50,24 @@ def default_config
4050
# @return [Hash]
4151
def conversions
4252
{
43-
default: model_json[:default]
53+
default: model_json[:default],
54+
device_alias: convert_device_alias
4455
}
4556
end
57+
58+
# @return [String, nil]
59+
def convert_device_alias
60+
# Avoid setting an alias if using the default boot device.
61+
return if model_json[:default]
62+
63+
name = model_json[:name]
64+
return unless name
65+
66+
drive = drives.find { |d| d.device_name == name }
67+
return unless drive
68+
69+
drive.ensure_alias
70+
end
4671
end
4772
end
4873
end

0 commit comments

Comments
 (0)