Skip to content

Commit

Permalink
feat(storage): config support to delete partitions (#1572)
Browse files Browse the repository at this point in the history
Allow using *delete* and *deleteIfNeeded* for a partition section in the
storage JSON config, according to the
[auto_storage](https://github.com/openSUSE/agama/blob/master/doc/auto_storage.md)
document.

Notes about the schema:

* *delete* and *deleteIfNeeded* cannot be set at the same time.
* If *delete* is set, then *search* is mandatory and no other property
is accepted.
* If *deleteIfNeeded* is set, then *search* is mandatory and *size* is
optional. No other property is accepted.

Examples:

* Delete a partition:

~~~json
{
  "search": "/dev/vda",
  "delete": true
}
~~~

* Delete a partition on demand:

~~~json
{
  "search": "/dev/vda",
  "deleteIfNeeded": true
}
~~~

* Resize or delete a partition on demand (resize is not implemenetd
yet):

~~~json
{
  "search": "/dev/vda",
  "size": { "min": 0 },
  "deleteIfNeeded": true
}
~~~
  • Loading branch information
joseivanlopez authored Sep 4, 2024
2 parents 615f22e + 607b251 commit a6926d6
Show file tree
Hide file tree
Showing 10 changed files with 406 additions and 51 deletions.
46 changes: 34 additions & 12 deletions rust/agama-lib/share/examples/storage.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
"search": "/dev/vda",
"ptableType": "gpt",
"partitions": [
{
"search": {
"ifNotFound": "skip"
},
"delete": true
},
{
"id": "linux",
"size": "10 GiB",
Expand All @@ -30,12 +36,6 @@
}
},
{
"search": {
"condition": {
"name": "/dev/vda2"
},
"ifNotFound": "skip"
},
"encryption": {
"luks2": {
"password": "notsecret",
Expand All @@ -48,21 +48,43 @@
}
},
{
"search": {},
"encryption": "random_swap",
"filesystem": {
"type": "swap",
"path": "swap"
}
},
"size": "2 GiB"
}
]
},
{
"search": {
"condition": {
"name": "/dev/vda"
"search": "/dev/vdb",
"partitions": [
{
"search": {
"condition": { "name": "/dev/vdb1" },
"ifNotFound": "skip"
},
"deleteIfNeeded": true
},
{
"search": {
"ifNotFound": "skip"
},
"delete": true
},
"ifNotFound": "error"
{
"filesystem": {
"type": "xfs",
"path": "/data"
},
"size": { "min": "50 GiB" }
}
]
},
{
"search": {
"ifNotFound": "skip"
},
"filesystem": {
"type": "ext4",
Expand Down
83 changes: 62 additions & 21 deletions rust/agama-lib/share/profile.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -909,32 +909,73 @@
},
"partitions": {
"title": "Partitions",
"description": "Partitions to create, reuse or delete.",
"type": "array",
"items": {
"title": "Partition options",
"type": "object",
"additionalProperties": false,
"properties": {
"search": {
"description": "The search is limited to the partitions of the selected device scope.",
"$ref": "#/$defs/search"
},
"id": {
"title": "Partition ID",
"enum": ["linux", "swap", "lvm", "raid", "esp", "prep", "bios_boot"]
},
"size": {
"title": "Partition size",
"$ref": "#/$defs/sizeValue"
"anyOf": [
{
"title": "Partition to create or reuse",
"type": "object",
"additionalProperties": false,
"properties": {
"search": {
"description": "The search is limited to the partitions of the selected device scope.",
"$ref": "#/$defs/search"
},
"id": {
"title": "Partition ID",
"enum": ["linux", "swap", "lvm", "raid", "esp", "prep", "bios_boot"]
},
"size": {
"title": "Partition size",
"$ref": "#/$defs/size"
},
"encryption": {
"$ref": "#/$defs/encryption"
},
"filesystem": {
"$ref": "#/$defs/filesystem"
}
}
},
"encryption": {
"$ref": "#/$defs/encryption"
{
"title": "Partition to delete if needed",
"type": "object",
"additionalProperties": false,
"required": ["deleteIfNeeded", "search"],
"properties": {
"deleteIfNeeded": {
"title": "Delete if needed",
"description": "Delete the partition if needed to make space.",
"const": true
},
"search": {
"description": "The search is limited to the partitions of the selected device scope.",
"$ref": "#/$defs/search"
},
"size": {
"title": "Partition size",
"$ref": "#/$defs/size"
}
}
},
"filesystem": {
"$ref": "#/$defs/filesystem"
{
"title": "Partition to delete",
"type": "object",
"additionalProperties": false,
"required": ["delete", "search"],
"properties": {
"delete": {
"title": "Delete",
"description": "Delete the partition.",
"const": true
},
"search": {
"description": "The search is limited to the partitions of the selected device scope.",
"$ref": "#/$defs/search"
}
}
}
}
]
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,14 @@ def convert(default = nil)

convert_block_device(default_config).tap do |config|
search = convert_search(config.search)
delete = partition_json[:delete]
delete_if_needed = partition_json[:deleteIfNeeded]
id = convert_id
size = convert_size(config.size)

config.search = search if search
config.delete = delete unless delete.nil?
config.delete_if_needed = delete_if_needed unless delete_if_needed.nil?
config.id = id if id
config.size = size if size
end
Expand Down
10 changes: 10 additions & 0 deletions service/lib/agama/storage/configs/partition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ class Partition
# @return [Search, nil]
attr_accessor :search

# @return [Boolean]
attr_accessor :delete
alias_method :delete?, :delete

# @return [Boolean]
attr_accessor :delete_if_needed
alias_method :delete_if_needed?, :delete_if_needed

# @return [Y2Storage::PartitionId, nil]
attr_accessor :id

Expand All @@ -43,6 +51,8 @@ class Partition

def initialize
@size = Size.new
@delete = false
@delete_if_needed = false
end

# Assigned device according to the search.
Expand Down
5 changes: 3 additions & 2 deletions service/lib/y2storage/agama_proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def initialize(initial_settings, devicegraph: nil, disk_analyzer: nil, issues_li

private

# @return [Proposal::SpaceMaker]
# @return [Proposal::AgamaSpaceMaker]
attr_reader :space_maker

# Whether the list of issues generated so far already includes any serious error
Expand Down Expand Up @@ -140,7 +140,7 @@ def propose_devicegraph
return if fatal_error?

configure_ptable_types(devicegraph)
clean_graph(devicegraph)
devicegraph = clean_graph(devicegraph)
complete_planned(devicegraph)
return if fatal_error?

Expand All @@ -162,6 +162,7 @@ def calculate_initial_planned(devicegraph)
def clean_graph(devicegraph)
remove_empty_partition_tables(devicegraph)
protect_sids
# {Proposal::SpaceMaker#prepare_devicegraph} returns a copy of the given devicegraph.
space_maker.prepare_devicegraph(devicegraph, partitions_for_clean)
end

Expand Down
24 changes: 14 additions & 10 deletions service/lib/y2storage/proposal/agama_device_planner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,21 +188,25 @@ def configure_size(planned, settings)
end

# @param planned [Planned::Disk]
# @param settings [Agama::Storage::Configs::Drive]
def configure_partitions(planned, settings)
planned.partitions = settings.partitions.map do |partition_settings|
planned_partition(partition_settings).tap { |p| p.disk = settings.found_device.name }
# @param config [Agama::Storage::Configs::Drive]
def configure_partitions(planned, config)
partition_configs = config.partitions
.reject(&:delete?)
.reject(&:delete_if_needed?)

planned.partitions = partition_configs.map do |partition_config|
planned_partition(partition_config).tap { |p| p.disk = config.found_device.name }
end
end

# @param settings [Agama::Storage::Configs::Partition]
# @param config [Agama::Storage::Configs::Partition]
# @return [Planned::Partition]
def planned_partition(settings)
def planned_partition(config)
Planned::Partition.new(nil, nil).tap do |planned|
planned.partition_id = settings.id
configure_reuse(planned, settings)
configure_device(planned, settings)
configure_size(planned, settings.size)
planned.partition_id = config.id
configure_reuse(planned, config)
configure_device(planned, config)
configure_size(planned, config.size)
end
end
end
Expand Down
69 changes: 63 additions & 6 deletions service/lib/y2storage/proposal/agama_space_maker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,85 @@ module Y2Storage
module Proposal
# Space maker for Agama.
class AgamaSpaceMaker < SpaceMaker
# Constructor
def initialize(disk_analyzer, settings)
super(disk_analyzer, guided_settings(settings))
# @param disk_analyzer [DiskAnalyzer]
# @param config [Agama::Storage::Config]
def initialize(disk_analyzer, config)
super(disk_analyzer, guided_settings(config))
end

private

# Method used by the constructor to somehow simulate a typical Guided Proposal
def guided_settings(settings)
#
# @param config [Agama::Storage::Config]
def guided_settings(config)
# Despite the "current_product" part in the name of the constructor, it only applies
# generic default values that are independent of the product (there is no YaST
# ProductFeatures mechanism in place).
Y2Storage::ProposalSettings.new_for_current_product.tap do |target|
target.space_settings.strategy = :bigger_resize
target.space_settings.actions = {}
target.space_settings.actions = space_actions(config)

boot_device = settings.boot_device
boot_device = config.boot_device

target.root_device = boot_device
target.candidate_devices = [boot_device].compact
end
end

# Space actions from the given config.
#
# @param config [Agama::Storage::Config]
# @return [Hash]
def space_actions(config)
force_delete_actions = force_delete_actions(config)
delete_actions = delete_actions(config)

force_delete_actions.merge(delete_actions)
end

# Space actions for devices that must be deleted.
#
# @param config [Agama::Storage::Config]
# @return [Hash]
def force_delete_actions(config)
partition_configs = partitions(config).select(&:delete?)
partition_names = device_names(partition_configs)

partition_names.each_with_object({}) { |p, a| a[p] = :force_delete }
end

# Space actions for devices that might be deleted.
#
# @note #delete? takes precedence over #delete_if_needed?.
#
# @param config [Agama::Storage::Config]
# @return [Hash]
def delete_actions(config)
partition_configs = partitions(config).select(&:delete_if_needed?).reject(&:delete?)
partition_names = device_names(partition_configs)

partition_names.each_with_object({}) { |p, a| a[p] = :delete }
end

# All partition configs from the given config.
#
# @param config [Agama::Storage::Config]
# @return [Array<Agama::Storage::Configs::Partition>]
def partitions(config)
config.drives.flat_map(&:partitions)
end

# Device names from the given configs.
#
# @param configs [Array<#found_device>]
# @return [Array<String>]
def device_names(configs)
configs
.map(&:found_device)
.compact
.map(&:name)
end
end
end
end
6 changes: 6 additions & 0 deletions service/package/rubygem-agama-yast.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Wed Sep 4 08:55:29 UTC 2024 - José Iván López González <[email protected]>

- Storage: add support for deleting partitions in the storage
config (gh#openSUSE/agama#1572).

-------------------------------------------------------------------
Tue Sep 3 08:14:23 UTC 2024 - José Iván López González <[email protected]>

Expand Down
Loading

0 comments on commit a6926d6

Please sign in to comment.