Skip to content

Commit

Permalink
storage: make proposal delete partitions
Browse files Browse the repository at this point in the history
  • Loading branch information
joseivanlopez committed Sep 4, 2024
1 parent d34dd46 commit a9d02a6
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 18 deletions.
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
136 changes: 136 additions & 0 deletions service/test/y2storage/agama_proposal_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,26 @@
end
end

context "when the config has 2 drives" do
let(:scenario) { "partitioned_disk.yaml" }

let(:drives) { [drive0, drive1] }

let(:drive1) do
Agama::Storage::Configs::Drive.new.tap { |d| d.partitions = [home_partition] }
end

it "proposes the expected devices" do
devicegraph = proposal.propose

root = devicegraph.find_by_name("/dev/vda4")
expect(root.filesystem.mount_path).to eq("/")

home = devicegraph.find_by_name("/dev/vdb1")
expect(home.filesystem.mount_path).to eq("/home")
end
end

context "when a partition table type is specified for a drive" do
let(:drive0) do
Agama::Storage::Configs::Drive.new.tap do |drive|
Expand Down Expand Up @@ -434,5 +454,121 @@
expect(home.filesystem.mount_path).to eq("/home")
end
end

def partition_config(name)
Agama::Storage::Configs::Partition.new.tap do |partition_config|
partition_config.search = Agama::Storage::Configs::Search.new.tap do |search_config|
search_config.name = name
end
end
end

context "forcing to delete some partitions" do
let(:scenario) { "partitioned_disk.yaml" }

let(:partitions0) { [root_partition, vda2, vda3] }

let(:vda2) do
partition_config("/dev/vda2").tap { |c| c.delete = true }
end

let(:vda3) do
partition_config("/dev/vda3").tap { |c| c.delete = true }
end

before do
drive0.search.name = "/dev/vda"
end

it "deletes the partitions" do
vda1_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda1").sid
vda2_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda2").sid
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid

devicegraph = proposal.propose

expect(devicegraph.find_device(vda1_sid)).to_not be_nil
expect(devicegraph.find_device(vda2_sid)).to be_nil
expect(devicegraph.find_device(vda3_sid)).to be_nil

root = devicegraph.find_by_name("/dev/vda2")
expect(root.filesystem.mount_path).to eq("/")
end
end

context "allowing to delete some partition" do
let(:scenario) { "partitioned_disk.yaml" }

let(:partitions0) { [root_partition, vda3] }

let(:vda3) do
partition_config("/dev/vda3").tap { |c| c.delete_if_needed = true }
end

before do
# vda has 18 GiB of free space.
drive0.search.name = "/dev/vda"
end

context "if deleting the partition is not needed" do
before do
root_partition.size.min = Y2Storage::DiskSize.GiB(15)
end

it "does not delete the partition" do
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid

devicegraph = proposal.propose
expect(devicegraph.find_device(vda3_sid)).to_not be_nil

root = devicegraph.find_by_name("/dev/vda4")
expect(root.filesystem.mount_path).to eq("/")
end
end

context "if the partition has to be deleted" do
before do
root_partition.size.min = Y2Storage::DiskSize.GiB(20)
end

it "deletes the partition" do
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid

devicegraph = proposal.propose
expect(devicegraph.find_device(vda3_sid)).to be_nil

root = devicegraph.find_by_name("/dev/vda3")
expect(root.filesystem.mount_path).to eq("/")
end
end
end

# Testing precedence. This configuration should not be possible.
context "if the partition config indicates both force to delete and allow to delete" do
let(:scenario) { "partitioned_disk.yaml" }

let(:partitions0) { [root_partition, vda3] }

let(:vda3) do
partition_config("/dev/vda3").tap do |config|
config.delete = true
config.delete_if_needed = true
end
end

before do
drive0.search.name = "/dev/vda"
end

it "deletes the partition" do
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid

devicegraph = proposal.propose
expect(devicegraph.find_device(vda3_sid)).to be_nil

root = devicegraph.find_by_name("/dev/vda3")
expect(root.filesystem.mount_path).to eq("/")
end
end
end
end

0 comments on commit a9d02a6

Please sign in to comment.