Skip to content

Commit

Permalink
Automatic generation of physical volumes (#1655)
Browse files Browse the repository at this point in the history
#1652 adds support for configuring the automatic generation of LVM
physical volumes. Read there the details about how the feature works.

This pull request implements a first version of the functionality
described there, including a couple of unit tests.

Depends on yast/yast-storage-ng#1392
  • Loading branch information
ancorgs authored Oct 9, 2024
2 parents c48515c + 483cfd5 commit 143314d
Show file tree
Hide file tree
Showing 12 changed files with 625 additions and 352 deletions.
57 changes: 49 additions & 8 deletions service/lib/agama/storage/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ def boot_device
explicit_boot_device || implicit_boot_device
end

# return [Array<Configs::Partition>]
def partitions
drives.flat_map(&:partitions)
end

# return [Array<Configs::LogicalVolume>]
def logical_volumes
volume_groups.flat_map(&:logical_volumes)
end

private

# Device used for booting the target system
#
# @return [String, nil] nil if no disk is explicitly chosen
Expand All @@ -73,24 +85,53 @@ def explicit_boot_device

# Device that seems to be expected to be used for booting, according to the drive definitions
#
# @return [String, nil] nil if the information cannot be inferred from the list of drives
# @return [String, nil] nil if the information cannot be inferred from the config
def implicit_boot_device
# NOTE: preliminary implementation with very simplistic checks
implicit_drive_boot_device || implicit_lvm_boot_device
end

# @see #implicit_boot_device
#
# @return [String, nil] nil if the information cannot be inferred from the list of drives
def implicit_drive_boot_device
root_drive = drives.find do |drive|
drive.partitions.any? { |p| p.filesystem&.root? }
end

root_drive&.found_device&.name
end

# return [Array<Configs::Partition>]
def partitions
drives.flat_map(&:partitions)
# @see #implicit_boot_device
#
# @return [String, nil] nil if the information cannot be inferred from the list of LVM VGs
def implicit_lvm_boot_device
root_vg = root_volume_group
return nil unless root_vg

root_drives = drives.select { |d| drive_for_vg?(d, root_vg) }
names = root_drives.map { |d| d.found_device&.name }.compact
# Return the first name in alphabetical order
names.min
end

# return [Array<Configs::LogicalVolume>]
def logical_volumes
volume_groups.flat_map(&:logical_volumes)
# @see #implicit_lvm_boot_device
#
# @return [Configs::VolumeGroup, nil]
def root_volume_group
volume_groups.find do |vg|
vg.logical_volumes.any? { |lv| lv.filesystem&.root? }
end
end

# @see #implicit_lvm_boot_device
#
# @return [Boolean]
def drive_for_vg?(drive, volume_group)
return true if volume_group.physical_volumes_devices.any? { |d| drive.alias?(d) }

volume_group.physical_volumes.any? do |pv|
drive.partitions.any? { |p| p.alias?(pv) }
end
end
end
end
Expand Down
23 changes: 13 additions & 10 deletions service/lib/y2storage/agama_proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def calculate_initial_planned(devicegraph)
def clean_graph(devicegraph)
remove_empty_partition_tables(devicegraph)
# {Proposal::SpaceMaker#prepare_devicegraph} returns a copy of the given devicegraph.
space_maker.prepare_devicegraph(devicegraph, partitions_for_clean)
space_maker.prepare_devicegraph(devicegraph, disks_for_clean)
end

# Configures the disk devices on the given devicegraph to prefer the appropriate partition table
Expand Down Expand Up @@ -206,23 +206,26 @@ def drives_with_empty_partition_table(devicegraph)
devices.select { |d| d.partition_table && d.partitions.empty? }
end

# Planned partitions that will hold the given planned devices
# Devices for which the mandatory actions must be executed
#
# @return [Array<Planned::Partition>]
def partitions_for_clean
# The current logic is quite trivial, but this is implemented as a separate method because
# some extra logic is expected in the future (eg. considering partitions on pre-existing
# RAIDs and more stuff). See the equivalent method at DevicegraphGenerator.
planned_devices.partitions
# @return [Array<String>] names of partitionable devices
def disks_for_clean
(drives_names + [config.boot_device]).compact.uniq
end

# Creates the planned devices on a given devicegraph
#
# @param devicegraph [Devicegraph] the graph gets modified
def create_devices(devicegraph)
devices_creator = Proposal::AgamaDevicesCreator.new(devicegraph, issues_list)
names = config.drives.map(&:found_device).compact.map(&:name)
result = devices_creator.populated_devicegraph(planned_devices, names, space_maker)
result = devices_creator.populated_devicegraph(planned_devices, drives_names, space_maker)
end

# Names of all the devices that correspond to a drive from the config
#
# @return [Array<String>]
def drives_names
@drives_names ||= config.drives.map(&:found_device).compact.map(&:name)
end

# Equivalent device at the given devicegraph for the given configuration setting (eg. drive)
Expand Down
26 changes: 13 additions & 13 deletions service/lib/y2storage/proposal/agama_devices_creator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
# find current contact information at www.suse.com.

require "y2storage/exceptions"
require "y2storage/proposal/agama_lvm_helper"
require "y2storage/proposal/lvm_creator"
require "y2storage/proposal/partition_creator"

Expand Down Expand Up @@ -126,13 +125,11 @@ def process_devices
def process_existing_partitionables
partitions = partitions_for_existing(planned_devices)

# lvm_lvs = system_lvm_over_existing? ? system_lvs(planned_devices) : []
lvm_lvs = []
lvm_helper = AgamaLvmHelper.new(lvm_lvs)

# Check whether there is any chance of getting an unwanted order for the planned partitions
# within a disk
space_result = provide_space(partitions, original_graph, lvm_helper)
space_result = space_maker.provide_space(
original_graph, partitions: partitions, volume_groups: automatic_vgs
)
self.devicegraph = space_result[:devicegraph]
distribution = space_result[:partitions_distribution]

Expand All @@ -146,6 +143,16 @@ def process_volume_groups
planned_devices.vgs.map { |v| create_volume_group(v) }
end

# Planned volume groups for which the proposal must automatically create the needed physical
# volumes
#
# @return [Array<Planned::LvmVg>]
def automatic_vgs
planned_devices.select do |dev|
dev.is_a?(Planned::LvmVg) && dev.pvs_candidate_devices.any?
end
end

# Creates a volume group for the the given planned device.
#
# @param planned [Planned::LvmVg]
Expand Down Expand Up @@ -174,13 +181,6 @@ def physical_volumes_for(vg_name)
new_pvs + reused_pvs
end

# @see #process_existing_partitionables
def provide_space(planned_partitions, devicegraph, lvm_helper)
result = space_maker.provide_space(devicegraph, planned_partitions, lvm_helper)
log.info "Found enough space"
result
end

# @see #process_existing_partitionables
def grow_and_reuse_devices(distribution)
planned_devices.select(&:reuse?).each do |planned|
Expand Down
2 changes: 1 addition & 1 deletion service/lib/y2storage/proposal/agama_devices_planner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def planned_drives(config)
def planned_vgs(config)
config.volume_groups.flat_map do |vg|
planner = AgamaVgPlanner.new(devicegraph, issues_list)
planner.planned_devices(vg)
planner.planned_devices(vg, config)
end
end
end
Expand Down
52 changes: 0 additions & 52 deletions service/lib/y2storage/proposal/agama_lvm_helper.rb

This file was deleted.

24 changes: 10 additions & 14 deletions service/lib/y2storage/proposal/agama_space_maker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,26 @@
module Y2Storage
module Proposal
# Space maker for Agama.
#
# FIXME: this class must dissappear. It does not implement any own logic compared to the
# original SpaceMaker. It simply encapsulates the conversion from Agama config to
# ProposalSpaceSettings.
class AgamaSpaceMaker < SpaceMaker
# @param disk_analyzer [DiskAnalyzer]
# @param config [Agama::Storage::Config]
def initialize(disk_analyzer, config)
super(disk_analyzer, guided_settings(config))
super(disk_analyzer, space_settings(config))
end

private

# Method used by the constructor to somehow simulate a typical Guided Proposal
# Method used by the constructor to convert the Agama config to ProposalSpaceSettings
#
# @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 = space_actions(config)

boot_device = config.boot_device

target.root_device = boot_device
target.candidate_devices = [boot_device].compact
def space_settings(config)
Y2Storage::ProposalSpaceSettings.new.tap do |target|
target.strategy = :bigger_resize
target.actions = space_actions(config)
end
end

Expand Down
45 changes: 38 additions & 7 deletions service/lib/y2storage/proposal/agama_vg_planner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,57 @@ module Proposal
class AgamaVgPlanner < AgamaDevicePlanner
# @param config [Agama::Storage::Configs::VolumeGroup]
# @return [Array<Planned::Device>]
def planned_devices(config)
[planned_vg(config)]
def planned_devices(vg_config, config)
[planned_vg(vg_config, config)]
end

private

# @param config [Agama::Storage::Configs::VolumeGroup]
# @param vg_config [Agama::Storage::Configs::VolumeGroup]
# @param config [Agama::Storage::Config]
# @return [Planned::LvmVg]
def planned_vg(config)
def planned_vg(vg_config, config)
# TODO: A volume group name is expected. Otherwise, the planned physical volumes cannot
# be associated to the planned volume group. Should the volume group name be
# automatically generated if missing?
#
# @see AgamaDevicePlanner#configure_pv
Y2Storage::Planned::LvmVg.new(volume_group_name: config.name).tap do |planned|
planned.extent_size = config.extent_size
planned.lvs = planned_lvs(config)
Y2Storage::Planned::LvmVg.new(volume_group_name: vg_config.name).tap do |planned|
planned.extent_size = vg_config.extent_size
planned.lvs = planned_lvs(vg_config)
planned.size_strategy = :use_needed
planned.pvs_candidate_devices = devices_for_pvs(vg_config, config)
configure_pvs_encryption(planned, vg_config)
end
end

# Names of the devices that must be used to calculate automatic physical volumes
# for the given volume group
#
# @param vg_config [Agama::Storage::Configs::VolumeGroup]
# @param config [Agama::Storage::Config]
# @return [Array<String>]
def devices_for_pvs(vg_config, config)
drives = vg_config.physical_volumes_devices.map do |dev_alias|
config.drives.find { |d| d.alias?(dev_alias) }
end.compact

drives.map { |d| d.found_device.name }
end

# Configures the encryption-related fields of the given planned volume group
#
# @param planned [Planned::LvmVg]
# @param config [Agama::Storage::Configs::VolumeGroup]
def configure_pvs_encryption(planned, config)
enc = config.physical_volumes_encryption
return unless enc

planned.pvs_encryption_method = enc.method
planned.pvs_encryption_password = enc.password
planned.pvs_encryption_pbkdf = enc.pbkd_function
end

# @param config [Agama::Storage::Configs::VolumeGroup]
# @return [Array<Planned::LvmLv>]
def planned_lvs(config)
Expand Down
2 changes: 1 addition & 1 deletion service/package/gem2rpm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
Requires: yast2-iscsi-client >= 4.5.7
Requires: yast2-network
Requires: yast2-proxy
Requires: yast2-storage-ng >= 5.0.18
Requires: yast2-storage-ng >= 5.0.20
Requires: yast2-users
%ifarch s390 s390x
Requires: yast2-s390 >= 4.6.4
Expand Down
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 @@
-------------------------------------------------------------------
Tue Oct 8 12:25:08 UTC 2024 - Ancor Gonzalez Sosa <ancor@suse.com>

- Storage: added support for automatic creation of physical volumes
(gh#agama-project/agama#1655).

-------------------------------------------------------------------
Mon Oct 7 06:58:48 UTC 2024 - José Iván López González <jlopez@suse.com>

Expand Down
2 changes: 2 additions & 0 deletions service/test/fixtures/disks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
size: 20 GiB
name: "/dev/vda2"
id: linux
label: "previous_root"
file_system: btrfs
- partition:
size: 10 GiB
name: "/dev/vda3"
id: linux
label: "previous_home"
file_system: xfs
- disk:
name: "/dev/vdb"
Expand Down
Loading

0 comments on commit 143314d

Please sign in to comment.