1455 lines
44 KiB
Ruby
1455 lines
44 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Copyright (c) [2024-2026] SUSE LLC
|
|
#
|
|
# All Rights Reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it
|
|
# under the terms of version 2 of the GNU General Public License as published
|
|
# by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
# more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with this program; if not, contact SUSE LLC.
|
|
#
|
|
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
|
# find current contact information at www.suse.com.
|
|
|
|
require_relative "../agama/storage/config_context"
|
|
require "y2storage"
|
|
require "y2storage/agama_proposal"
|
|
|
|
using Y2Storage::Refinements::SizeCasts
|
|
|
|
# @param config [Agama::Storage::Configs::Drive, Agama::Storage::Configs::Partition]
|
|
# @param name [String, nil] e.g., "/dev/vda"
|
|
# @param filesystem [String, nil] e.g., "xfs"
|
|
def block_device_config(config, name: nil, filesystem: nil)
|
|
if name
|
|
config.search = Agama::Storage::Configs::Search.new.tap do |search_config|
|
|
search_config.name = name
|
|
end
|
|
end
|
|
|
|
if filesystem
|
|
config.filesystem = Agama::Storage::Configs::Filesystem.new.tap do |fs_config|
|
|
fs_config.type = Agama::Storage::Configs::FilesystemType.new.tap do |type_config|
|
|
type_config.fs_type = Y2Storage::Filesystems::Type.find(filesystem)
|
|
end
|
|
end
|
|
end
|
|
|
|
config
|
|
end
|
|
|
|
# @param name [String, nil] e.g., "/dev/vda"
|
|
# @param filesystem [String, nil] e.g., "xfs"
|
|
def drive_config(name: nil, filesystem: nil)
|
|
config = Agama::Storage::Configs::Drive.new
|
|
block_device_config(config, name: name, filesystem: filesystem)
|
|
end
|
|
|
|
# @param name [String, nil] e.g., "/dev/vda"
|
|
# @param filesystem [String, nil] e.g., "xfs"
|
|
# @param size [Y2Storage::DiskSize]
|
|
def partition_config(name: nil, filesystem: nil, size: nil)
|
|
config = Agama::Storage::Configs::Partition.new
|
|
block_device_config(config, name: name, filesystem: filesystem)
|
|
|
|
config.size = Agama::Storage::Configs::Size.new.tap do |size_config|
|
|
size_config.min = size || Y2Storage::DiskSize.zero
|
|
size_config.max = size || Y2Storage::DiskSize.unlimited
|
|
end
|
|
|
|
config
|
|
end
|
|
|
|
describe Y2Storage::AgamaProposal do
|
|
include_context "config"
|
|
|
|
subject(:proposal) do
|
|
described_class.new(
|
|
config,
|
|
storage_system,
|
|
product_config: product_config,
|
|
bootloader_config: bootloader_config,
|
|
issues_list: issues_list
|
|
)
|
|
end
|
|
|
|
let(:default_config) do
|
|
Agama::Storage::Config.new.tap do |settings|
|
|
settings.drives = drives
|
|
end
|
|
end
|
|
|
|
let(:product_data) do
|
|
{
|
|
"storage" => {
|
|
"lvm" => false,
|
|
"space_policy" => "delete",
|
|
"encryption" => {
|
|
"method" => "luks2"
|
|
},
|
|
"volumes" => ["/", "swap"],
|
|
"volume_templates" => [
|
|
{
|
|
"mount_path" => "/",
|
|
"filesystem" => "btrfs",
|
|
"size" => { "auto" => true },
|
|
"btrfs" => {
|
|
"snapshots" => true,
|
|
"default_subvolume" => "@",
|
|
"subvolumes" => ["home", "opt", "root", "srv"]
|
|
},
|
|
"outline" => {
|
|
"required" => false,
|
|
"snapshots_configurable" => true,
|
|
"filesystems" => ["btrfs", "xfs", "ext3", "ext4"],
|
|
"auto_size" => {
|
|
"base_min" => "5 GiB",
|
|
"base_max" => "10 GiB",
|
|
"min_fallback_for" => ["/home"],
|
|
"max_fallback_for" => ["/home"],
|
|
"snapshots_increment" => "300%"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"mount_path" => "/home",
|
|
"size" => { "auto" => false, "min" => "5 GiB" },
|
|
"filesystem" => "xfs",
|
|
"outline" => {
|
|
"required" => false,
|
|
"filesystems" => ["xfs", "ext4"]
|
|
}
|
|
},
|
|
{
|
|
"mount_path" => "swap",
|
|
"filesystem" => "swap",
|
|
"outline" => {
|
|
"required" => false,
|
|
"filesystems" => ["swap"]
|
|
}
|
|
},
|
|
{
|
|
"mount_path" => "",
|
|
"filesystem" => "ext4",
|
|
"size" => { "min" => "100 MiB" },
|
|
"outline" => {
|
|
"filesystems" => ["xfs", "ext4"]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
end
|
|
|
|
let(:issues_list) { [] }
|
|
|
|
let(:drives) { [drive0] }
|
|
|
|
let(:drive0) { Agama::Storage::Configs::Drive.new.tap { |d| d.partitions = partitions0 } }
|
|
|
|
let(:partitions0) { [root_partition] }
|
|
|
|
let(:root_partition) do
|
|
Agama::Storage::Configs::Partition.new.tap do |part|
|
|
part.filesystem = Agama::Storage::Configs::Filesystem.new.tap do |fs|
|
|
fs.path = "/"
|
|
fs.type = Agama::Storage::Configs::FilesystemType.new.tap do |type|
|
|
type.fs_type = Y2Storage::Filesystems::Type::BTRFS
|
|
end
|
|
end
|
|
part.size = Agama::Storage::Configs::Size.new.tap do |size|
|
|
size.default = false
|
|
size.min = 8.5.GiB
|
|
size.max = Y2Storage::DiskSize.unlimited
|
|
end
|
|
end
|
|
end
|
|
|
|
let(:home_partition) do
|
|
Agama::Storage::Configs::Partition.new.tap do |part|
|
|
part.filesystem = Agama::Storage::Configs::Filesystem.new.tap do |fs|
|
|
fs.path = "/home"
|
|
fs.type = Agama::Storage::Configs::FilesystemType.new.tap do |type|
|
|
type.fs_type = Y2Storage::Filesystems::Type::EXT4
|
|
end
|
|
end
|
|
part.size = Agama::Storage::Configs::Size.new.tap do |size|
|
|
size.min = 10.GiB
|
|
size.max = Y2Storage::DiskSize.unlimited
|
|
end
|
|
end
|
|
end
|
|
|
|
let(:scenario) { "empty-hd-50GiB.yaml" }
|
|
|
|
describe "#propose" do
|
|
context "when only the root partition is specified" do
|
|
let(:config) { default_config }
|
|
|
|
context "if no configuration about boot devices is specified" do
|
|
it "proposes to create the root device and the boot-related partition" do
|
|
proposal.propose
|
|
partitions = proposal.devices.partitions
|
|
expect(partitions.size).to eq 2
|
|
expect(partitions.first.id).to eq Y2Storage::PartitionId::BIOS_BOOT
|
|
root_part = partitions.last
|
|
expect(root_part.size).to be > 49.GiB
|
|
root_fs = root_part.filesystem
|
|
expect(root_fs.root?).to eq true
|
|
expect(root_fs.type.is?(:btrfs)).to eq true
|
|
end
|
|
end
|
|
|
|
context "if no boot devices should be created" do
|
|
before do
|
|
config.boot = Agama::Storage::Configs::Boot.new.tap { |b| b.configure = false }
|
|
end
|
|
|
|
it "proposes to create only the root device" do
|
|
proposal.propose
|
|
partitions = proposal.devices.partitions
|
|
expect(partitions.size).to eq 1
|
|
root_part = partitions.first
|
|
expect(root_part.id).to eq Y2Storage::PartitionId::LINUX
|
|
expect(root_part.size).to be > 49.GiB
|
|
root_fs = root_part.filesystem
|
|
expect(root_fs.root?).to eq true
|
|
expect(root_fs.type.is?(:btrfs)).to eq true
|
|
end
|
|
end
|
|
end
|
|
|
|
context "on an EFI system" do
|
|
let(:config) { default_config }
|
|
|
|
before do
|
|
allow_any_instance_of(Y2Storage::Arch).to receive(:efiboot?).and_return(true)
|
|
allow(Yast::Arch).to receive(:x86_64).and_return(true)
|
|
allow(Yast::Arch).to receive(:i386).and_return(false)
|
|
allow(Yast::Arch).to receive(:aarch64).and_return(false)
|
|
allow(Yast::Arch).to receive(:arm).and_return(false)
|
|
allow(Yast::Arch).to receive(:riscv64).and_return(false)
|
|
end
|
|
|
|
context "when systemd-boot is configured as bootloader" do
|
|
before do
|
|
allow(bootloader_config).to receive(:type)
|
|
.and_return Y2Storage::BootloaderType::SYSTEMD_BOOT
|
|
end
|
|
|
|
it "proposes the corresponding boot partitions" do
|
|
proposal.propose
|
|
efi_partition = proposal.devices.partitions.find do |part|
|
|
part.filesystem&.mount_path == "/boot"
|
|
end
|
|
|
|
expect(efi_partition).to_not be_nil
|
|
expect(efi_partition.size).to eq(1.GiB)
|
|
end
|
|
end
|
|
|
|
context "when Grub2 is configued as bootloader" do
|
|
before do
|
|
allow(bootloader_config).to receive(:type).and_return Y2Storage::BootloaderType::GRUB2
|
|
end
|
|
|
|
it "proposes the corresponding boot partitions" do
|
|
proposal.propose
|
|
efi_partition = proposal.devices.partitions.find do |part|
|
|
part.filesystem&.mount_path == "/boot/efi"
|
|
end
|
|
|
|
expect(efi_partition).to_not be_nil
|
|
expect(efi_partition.size).to eq(512.MiB)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when only 'default' partitions is specified" do
|
|
let(:scenario) { "empty-hd-50GiB.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
partitions: [
|
|
{ generate: "default" }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
it "proposes the expected devices" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.partitions.size).to eq(3)
|
|
|
|
boot = devicegraph.find_by_name("/dev/sda1")
|
|
expect(boot.id).to eq(Y2Storage::PartitionId::BIOS_BOOT)
|
|
expect(boot.filesystem).to be_nil
|
|
expect(boot.size).to eq(8.MiB)
|
|
|
|
root = devicegraph.find_by_name("/dev/sda2")
|
|
expect(root.filesystem.mount_path).to eq("/")
|
|
expect(root.filesystem.type.is?(:btrfs)).to eq(true)
|
|
expect(root.size).to be_between(44.99.GiB, 45.GiB)
|
|
|
|
swap = devicegraph.find_by_name("/dev/sda3")
|
|
expect(swap.filesystem.mount_path).to eq("swap")
|
|
expect(swap.filesystem.type.is?(:swap)).to eq(true)
|
|
expect(swap.size).to be_between(4.99.GiB, 5.GiB)
|
|
end
|
|
end
|
|
|
|
context "when the config has 2 drives" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config) { default_config }
|
|
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 trying to reuse a file system from a drive" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config) { default_config }
|
|
let(:drives) { [drive] }
|
|
|
|
let(:drive) do
|
|
drive_config(name: name, filesystem: "ext3").tap { |c| c.filesystem.reuse = true }
|
|
end
|
|
|
|
before do
|
|
config.boot.configure = false
|
|
end
|
|
|
|
context "if the drive is already formatted" do
|
|
let(:name) { "/dev/vdc" }
|
|
|
|
it "reuses the file system" do
|
|
vdc = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vdc")
|
|
fs_sid = vdc.filesystem.sid
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
filesystem = devicegraph.find_by_name("/dev/vdc").filesystem
|
|
expect(filesystem.sid).to eq(fs_sid)
|
|
end
|
|
end
|
|
|
|
context "if the drive is not formatted" do
|
|
let(:name) { "/dev/vdb" }
|
|
|
|
it "creates the file system" do
|
|
devicegraph = proposal.propose
|
|
|
|
filesystem = devicegraph.find_by_name("/dev/vdb").filesystem
|
|
expect(filesystem).to_not be_nil
|
|
expect(filesystem.type).to eq(Y2Storage::Filesystems::Type::EXT3)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when a partition table type is specified for a drive" do
|
|
let(:config) { default_config }
|
|
|
|
let(:drive0) do
|
|
Agama::Storage::Configs::Drive.new.tap do |drive|
|
|
drive.partitions = partitions0
|
|
drive.ptable_type = Y2Storage::PartitionTables::Type::MSDOS
|
|
end
|
|
end
|
|
|
|
it "tries to propose a partition table of the requested type" do
|
|
proposal.propose
|
|
ptable = proposal.devices.disks.first.partition_table
|
|
expect(ptable.type).to eq Y2Storage::PartitionTables::Type::MSDOS
|
|
end
|
|
|
|
it "honors the partition table type if possible when calculating the boot partitions" do
|
|
proposal.propose
|
|
partitions = proposal.devices.partitions
|
|
expect(partitions.map(&:id)).to_not include Y2Storage::PartitionId::BIOS_BOOT
|
|
end
|
|
end
|
|
|
|
context "when encrypting some devices" do
|
|
let(:config) { default_config }
|
|
let(:partitions0) { [root_partition, home_partition] }
|
|
|
|
let(:home_encryption) do
|
|
Agama::Storage::Configs::Encryption.new.tap do |enc|
|
|
enc.password = "notSecreT"
|
|
enc.method = encryption_method
|
|
end
|
|
end
|
|
|
|
let(:encryption_method) { Y2Storage::EncryptionMethod::LUKS2 }
|
|
let(:available?) { true }
|
|
|
|
before do
|
|
home_partition.encryption = home_encryption
|
|
|
|
# Mocking only the object at encryption_method introduces a problem with serialization
|
|
allow_any_instance_of(Y2Storage::EncryptionMethod::Luks2)
|
|
.to receive(:available?).and_return(available?)
|
|
end
|
|
|
|
context "if the encryption settings contain all the detailed information" do
|
|
let(:home_encryption) do
|
|
Agama::Storage::Configs::Encryption.new.tap do |enc|
|
|
enc.password = "notSecreT"
|
|
enc.method = encryption_method
|
|
enc.pbkd_function = Y2Storage::PbkdFunction::ARGON2I
|
|
enc.label = "luks_label"
|
|
enc.cipher = "aes-xts-plain64"
|
|
enc.key_size = 512
|
|
end
|
|
end
|
|
|
|
it "proposes the right encryption layer" do
|
|
proposal.propose
|
|
partition = proposal.devices.partitions.find do |part|
|
|
part.blk_filesystem&.mount_path == "/home"
|
|
end
|
|
expect(partition.encrypted?).to eq true
|
|
expect(partition.encryption).to have_attributes(
|
|
method: Y2Storage::EncryptionMethod::LUKS2,
|
|
password: "notSecreT",
|
|
pbkdf: Y2Storage::PbkdFunction::ARGON2I,
|
|
label: "luks_label",
|
|
cipher: "aes-xts-plain64",
|
|
# libstorage-ng uses bytes instead of bits to represent the key size, contrary to
|
|
# all LUKS documentation and to cryptsetup
|
|
key_size: 64
|
|
)
|
|
end
|
|
end
|
|
|
|
context "if the encryption settings contains pervasive method" do
|
|
before do
|
|
allow(Y2Storage::EncryptionProcesses::SecureKey).to receive(:all).and_return([])
|
|
allow(Y2Storage::EncryptionProcesses::Apqn).to receive(:all).and_return(apqns)
|
|
end
|
|
|
|
let(:apqns) { [apqn1, apqn2] }
|
|
let(:apqn1) { Y2Storage::EncryptionProcesses::Apqn.new("01.0001", "", "", "") }
|
|
let(:apqn2) { Y2Storage::EncryptionProcesses::Apqn.new("01.0002", "", "", "") }
|
|
|
|
let(:home_encryption) do
|
|
Agama::Storage::Configs::Encryption.new.tap do |enc|
|
|
enc.method = Y2Storage::EncryptionMethod::PERVASIVE_LUKS2
|
|
enc.password = "notSecreT"
|
|
enc.apqns = ["01.0001", "01.0002", "01.0003"]
|
|
enc.pervasive_key_type = "CCA-AESCIPHER"
|
|
end
|
|
end
|
|
|
|
it "proposes the right encryption layer" do
|
|
proposal.propose
|
|
partition = proposal.devices.partitions.find do |part|
|
|
part.blk_filesystem&.mount_path == "/home"
|
|
end
|
|
expect(partition.encrypted?).to eq true
|
|
expect(partition.encryption).to have_attributes(
|
|
method: Y2Storage::EncryptionMethod::PERVASIVE_LUKS2,
|
|
password: "notSecreT"
|
|
)
|
|
expect(partition.encryption.encryption_process.apqns.map(&:name))
|
|
.to contain_exactly("01.0001", "01.0002")
|
|
expect(partition.encryption.encryption_process.key_type).to eq("CCA-AESCIPHER")
|
|
end
|
|
end
|
|
|
|
context "if the encryption method is not available for this system" do
|
|
let(:available?) { false }
|
|
|
|
it "aborts the proposal process" do
|
|
proposal.propose
|
|
expect(proposal.failed?).to eq true
|
|
end
|
|
|
|
it "reports the corresponding error" do
|
|
proposal.propose
|
|
expect(proposal.issues_list).to include an_object_having_attributes(
|
|
description: /Regular LUKS2 is not available/
|
|
)
|
|
end
|
|
end
|
|
|
|
context "if the encryption method is not suitable" do
|
|
let(:encryption_method) { Y2Storage::EncryptionMethod::RANDOM_SWAP }
|
|
|
|
it "aborts the proposal process" do
|
|
proposal.propose
|
|
expect(proposal.failed?).to eq true
|
|
end
|
|
|
|
it "reports the corresponding error" do
|
|
proposal.propose
|
|
expect(proposal.issues_list).to include an_object_having_attributes(
|
|
description: /Encryption with Volatile Random Key is not suitable/
|
|
)
|
|
end
|
|
end
|
|
|
|
context "if the method requires a password but none is provided" do
|
|
let(:home_encryption) do
|
|
Agama::Storage::Configs::Encryption.new.tap do |enc|
|
|
enc.method = encryption_method
|
|
end
|
|
end
|
|
|
|
it "aborts the proposal process" do
|
|
proposal.propose
|
|
expect(proposal.failed?).to eq true
|
|
end
|
|
|
|
it "reports the corresponding error" do
|
|
proposal.propose
|
|
expect(proposal.issues_list).to include an_object_having_attributes(
|
|
description: /No passphrase provided/
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when there are more drives than disks in the system" do
|
|
let(:config) { default_config }
|
|
let(:drives) { [drive0, drive1] }
|
|
|
|
let(:drive1) do
|
|
Agama::Storage::Configs::Drive.new.tap do |drive|
|
|
drive.search = Agama::Storage::Configs::Search.new.tap do |search|
|
|
search.if_not_found = if_not_found
|
|
end
|
|
end
|
|
end
|
|
|
|
context "if if_not_found is set to :skip for the surplus drive" do
|
|
let(:if_not_found) { :skip }
|
|
|
|
it "calculates a proposal if possible" do
|
|
proposal.propose
|
|
expect(proposal.failed?).to eq false
|
|
end
|
|
|
|
it "does not register any issue about missing disks" do
|
|
proposal.propose
|
|
expect(proposal.issues_list).to be_empty
|
|
end
|
|
end
|
|
|
|
context "if if_not_found is set to :error for the surplus drive" do
|
|
let(:if_not_found) { :error }
|
|
|
|
it "aborts the proposal" do
|
|
proposal.propose
|
|
expect(proposal.failed?).to eq true
|
|
end
|
|
|
|
it "registers a critical issue" do
|
|
proposal.propose
|
|
expect(proposal.issues_list).to include an_object_having_attributes(
|
|
description: "Mandatory drive not found"
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when searching for an existent drive" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config) { default_config }
|
|
|
|
before do
|
|
drive0.search.name = "/dev/vdb"
|
|
end
|
|
|
|
it "uses the drive" do
|
|
proposal.propose
|
|
|
|
root = proposal.devices.partitions.find do |part|
|
|
part.filesystem&.mount_path == "/"
|
|
end
|
|
|
|
expect(root.disk.name).to eq("/dev/vdb")
|
|
end
|
|
end
|
|
|
|
context "when searching for any drive" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config) { default_config }
|
|
let(:drives) { [drive0, drive1] }
|
|
|
|
let(:drive0) do
|
|
Agama::Storage::Configs::Drive.new.tap { |d| d.partitions = [root_partition] }
|
|
end
|
|
|
|
let(:drive1) do
|
|
Agama::Storage::Configs::Drive.new.tap { |d| d.partitions = [home_partition] }
|
|
end
|
|
|
|
it "uses the first unassigned drive" do
|
|
proposal.propose
|
|
|
|
root = proposal.devices.partitions.find do |part|
|
|
part.filesystem&.mount_path == "/"
|
|
end
|
|
|
|
home = proposal.devices.partitions.find do |part|
|
|
part.filesystem&.mount_path == "/home"
|
|
end
|
|
|
|
expect(root.disk.name).to eq("/dev/vda")
|
|
expect(home.disk.name).to eq("/dev/vdb")
|
|
end
|
|
end
|
|
|
|
context "when searching for a missing partition" do
|
|
let(:config) { default_config }
|
|
let(:partitions0) { [root_partition, missing_partition] }
|
|
|
|
let(:missing_partition) do
|
|
Agama::Storage::Configs::Partition.new.tap do |part|
|
|
part.search = Agama::Storage::Configs::Search.new.tap do |search|
|
|
search.if_not_found = if_not_found
|
|
end
|
|
part.filesystem = Agama::Storage::Configs::Filesystem.new.tap do |fs|
|
|
fs.path = "/foo"
|
|
end
|
|
end
|
|
end
|
|
|
|
context "if if_not_found is set to :skip" do
|
|
let(:if_not_found) { :skip }
|
|
|
|
it "calculates a proposal if possible" do
|
|
proposal.propose
|
|
expect(proposal.failed?).to eq false
|
|
end
|
|
|
|
it "does not register any issue about missing partitions" do
|
|
proposal.propose
|
|
expect(proposal.issues_list).to be_empty
|
|
end
|
|
end
|
|
|
|
context "if if_not_found is set to :error" do
|
|
let(:if_not_found) { :error }
|
|
|
|
it "aborts the proposal" do
|
|
proposal.propose
|
|
expect(proposal.failed?).to eq true
|
|
end
|
|
|
|
it "registers a critical issue" do
|
|
proposal.propose
|
|
expect(proposal.issues_list).to include an_object_having_attributes(
|
|
description: "Mandatory partition not found"
|
|
)
|
|
end
|
|
end
|
|
|
|
context "if if_not_found is set to :create" do
|
|
let(:if_not_found) { :create }
|
|
|
|
it "calculates a proposal if possible" do
|
|
proposal.propose
|
|
expect(proposal.failed?).to eq false
|
|
end
|
|
|
|
it "does not register any issue about missing partitions" do
|
|
proposal.propose
|
|
expect(proposal.issues_list).to be_empty
|
|
end
|
|
|
|
it "creates the missing partition" do
|
|
proposal.propose
|
|
expect(proposal.devices.partitions.map(&:filesystem))
|
|
.to include an_object_having_attributes(mount_path: "/foo")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when searching for an existent partition" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config) { default_config }
|
|
let(:partitions0) { [root_partition, home_partition] }
|
|
|
|
before do
|
|
home_partition.search = Agama::Storage::Configs::Search.new.tap do |search|
|
|
search.name = "/dev/vda3"
|
|
end
|
|
end
|
|
|
|
it "reuses the partition" do
|
|
vda3 = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3")
|
|
proposal.propose
|
|
|
|
partition = proposal.devices.find_by_name("/dev/vda3")
|
|
expect(partition.sid).to eq(vda3.sid)
|
|
expect(partition.filesystem.mount_path).to eq("/home")
|
|
end
|
|
end
|
|
|
|
context "when searching for any partition" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config) { default_config }
|
|
let(:partitions0) { [root_partition, home_partition] }
|
|
|
|
before do
|
|
home_partition.search = Agama::Storage::Configs::Search.new.tap { |s| s.max = 1 }
|
|
end
|
|
|
|
# TODO: Is this correct? The first partition (boot partition) is reused for home.
|
|
it "reuses the first unassigned partition" do
|
|
vda1 = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda1")
|
|
proposal.propose
|
|
|
|
partition = proposal.devices.find_by_name("/dev/vda1")
|
|
expect(partition.sid).to eq(vda1.sid)
|
|
expect(partition.filesystem.mount_path).to eq("/home")
|
|
end
|
|
|
|
it "does not reuse the same partition twice" do
|
|
vda1 = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda1")
|
|
vda2 = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda2")
|
|
root_partition.search = Agama::Storage::Configs::Search.new.tap { |s| s.max = 1 }
|
|
proposal.propose
|
|
|
|
root = proposal.devices.find_by_name("/dev/vda1")
|
|
expect(root.sid).to eq(vda1.sid)
|
|
expect(root.filesystem.mount_path).to eq("/")
|
|
|
|
home = proposal.devices.find_by_name("/dev/vda2")
|
|
expect(home.sid).to eq(vda2.sid)
|
|
expect(home.filesystem.mount_path).to eq("/home")
|
|
end
|
|
end
|
|
|
|
context "forcing to delete some partitions" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config) { default_config }
|
|
let(:partitions0) { [root_partition, vda2, vda3] }
|
|
|
|
let(:vda2) do
|
|
partition_config(name: "/dev/vda2").tap { |c| c.delete = true }
|
|
end
|
|
|
|
let(:vda3) do
|
|
partition_config(name: "/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) { "disks.yaml" }
|
|
|
|
let(:config) { default_config }
|
|
let(:partitions0) { [root_partition, vda3] }
|
|
|
|
let(:vda3) do
|
|
partition_config(name: "/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 = 15.GiB
|
|
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 = 20.GiB
|
|
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) { "disks.yaml" }
|
|
|
|
let(:config) { default_config }
|
|
let(:partitions0) { [root_partition, vda3] }
|
|
|
|
let(:vda3) do
|
|
partition_config(name: "/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
|
|
|
|
context "when reusing a partition" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
partitions: [
|
|
{
|
|
search: name,
|
|
filesystem: {
|
|
reuseIfPossible: reuse,
|
|
path: "/",
|
|
type: "ext3"
|
|
},
|
|
size: size
|
|
},
|
|
{
|
|
filesystem: {
|
|
path: "/home"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
let(:reuse) { nil }
|
|
|
|
let(:size) { nil }
|
|
|
|
context "if trying to reuse the file system" do
|
|
let(:reuse) { true }
|
|
|
|
context "and the partition is already formatted" do
|
|
let(:name) { "/dev/vda2" }
|
|
|
|
it "reuses the file system" do
|
|
vda2 = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda2")
|
|
fs_sid = vda2.filesystem.sid
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
filesystem = devicegraph.find_by_name("/dev/vda2").filesystem
|
|
expect(filesystem.sid).to eq(fs_sid)
|
|
end
|
|
end
|
|
|
|
context "and the partition is not formatted" do
|
|
let(:name) { "/dev/vda1" }
|
|
|
|
it "creates the file system" do
|
|
devicegraph = proposal.propose
|
|
|
|
filesystem = devicegraph.find_by_name("/dev/vda1").filesystem
|
|
expect(filesystem).to_not be_nil
|
|
expect(filesystem.type).to eq(Y2Storage::Filesystems::Type::EXT3)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "if not trying to reuse the file system" do
|
|
let(:reuse) { false }
|
|
|
|
context "and the partition is already formatted" do
|
|
let(:name) { "/dev/vda2" }
|
|
|
|
it "creates the file system" do
|
|
vda2 = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda2")
|
|
fs_sid = vda2.filesystem.sid
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
filesystem = devicegraph.find_by_name("/dev/vda2").filesystem
|
|
expect(filesystem.sid).to_not eq(fs_sid)
|
|
expect(filesystem.type).to eq(Y2Storage::Filesystems::Type::EXT3)
|
|
end
|
|
end
|
|
|
|
context "and the partition is not formatted" do
|
|
let(:name) { "/dev/vda1" }
|
|
|
|
it "creates the file system" do
|
|
devicegraph = proposal.propose
|
|
|
|
filesystem = devicegraph.find_by_name("/dev/vda1").filesystem
|
|
expect(filesystem).to_not be_nil
|
|
expect(filesystem.type).to eq(Y2Storage::Filesystems::Type::EXT3)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "if no size is indicated" do
|
|
let(:name) { "/dev/vda2" }
|
|
|
|
let(:size) { nil }
|
|
|
|
it "does not resize the partition" do
|
|
devicegraph = proposal.propose
|
|
|
|
vda2 = devicegraph.find_by_name("/dev/vda2")
|
|
expect(vda2.size).to eq(20.GiB)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when creating a new partition" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
partitions: [
|
|
{
|
|
filesystem: {
|
|
reuseIfPossible: reuse,
|
|
path: "/",
|
|
type: "ext3"
|
|
},
|
|
size: size
|
|
},
|
|
{
|
|
filesystem: {
|
|
path: "/home"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
let(:reuse) { nil }
|
|
|
|
let(:size) { nil }
|
|
|
|
context "if trying to reuse the file system" do
|
|
let(:reuse) { true }
|
|
|
|
it "creates the file system" do
|
|
devicegraph = proposal.propose
|
|
|
|
filesystem = devicegraph.find_by_name("/dev/vda4").filesystem
|
|
expect(filesystem).to_not be_nil
|
|
expect(filesystem.type).to eq(Y2Storage::Filesystems::Type::EXT3)
|
|
end
|
|
end
|
|
|
|
context "if not trying to reuse the file system" do
|
|
let(:reuse) { false }
|
|
|
|
it "creates the file system" do
|
|
devicegraph = proposal.propose
|
|
|
|
filesystem = devicegraph.find_by_name("/dev/vda4").filesystem
|
|
expect(filesystem).to_not be_nil
|
|
expect(filesystem.type).to eq(Y2Storage::Filesystems::Type::EXT3)
|
|
end
|
|
end
|
|
|
|
context "if no size is indicated" do
|
|
let(:size) { nil }
|
|
|
|
it "creates the partition according to the size from the product definition" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.partitions).to include(
|
|
an_object_having_attributes(
|
|
filesystem: an_object_having_attributes(mount_path: "/"),
|
|
size: 10.GiB - 1.MiB
|
|
)
|
|
)
|
|
end
|
|
end
|
|
|
|
context "if a size is indicated" do
|
|
let(:size) { "5 GiB" }
|
|
|
|
it "creates the partition according to the given size" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.partitions).to include(
|
|
an_object_having_attributes(
|
|
filesystem: an_object_having_attributes(mount_path: "/"),
|
|
size: 5.GiB
|
|
)
|
|
)
|
|
end
|
|
end
|
|
|
|
context "if 'current' size is indicated" do
|
|
let(:size) { { min: "current" } }
|
|
|
|
it "creates the partition according to the size from the product definition" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.partitions).to include(
|
|
an_object_having_attributes(
|
|
filesystem: an_object_having_attributes(mount_path: "/"),
|
|
size: 10.GiB - 1.MiB
|
|
)
|
|
)
|
|
end
|
|
end
|
|
|
|
context "if the size is not indicated for some partition with fallbacks" do
|
|
let(:scenario) { "empty-hd-50GiB.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
partitions: [
|
|
{
|
|
filesystem: {
|
|
path: "/",
|
|
type: {
|
|
btrfs: { snapshots: snapshots }
|
|
}
|
|
}
|
|
},
|
|
{
|
|
filesystem: { path: other_path }
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
context "and the other partitions are omitted" do
|
|
let(:other_path) { nil }
|
|
let(:snapshots) { false }
|
|
|
|
it "creates the partition adding the fallback sizes" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.partitions).to include(
|
|
an_object_having_attributes(
|
|
filesystem: an_object_having_attributes(mount_path: "/"),
|
|
size: 29.95.GiB - 2.80.MiB
|
|
)
|
|
)
|
|
end
|
|
|
|
context "and snapshots are enabled" do
|
|
let(:snapshots) { true }
|
|
|
|
it "creates the partition adding the fallback and snapshots sizes" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.partitions).to include(
|
|
an_object_having_attributes(
|
|
filesystem: an_object_having_attributes(mount_path: "/"),
|
|
size: 44.95.GiB - 2.80.MiB
|
|
)
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "and the other partitions are present" do
|
|
let(:other_path) { "/home" }
|
|
let(:snapshots) { false }
|
|
|
|
it "creates the partition ignoring the fallback sizes" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.partitions).to include(
|
|
an_object_having_attributes(
|
|
filesystem: an_object_having_attributes(mount_path: "/"),
|
|
size: 10.GiB
|
|
)
|
|
)
|
|
end
|
|
|
|
context "and snapshots are enabled" do
|
|
let(:snapshots) { true }
|
|
|
|
it "creates the partition adding the snapshots sizes" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.partitions).to include(
|
|
an_object_having_attributes(
|
|
filesystem: an_object_having_attributes(mount_path: "/"),
|
|
size: 32.50.GiB - 4.MiB
|
|
)
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "if the partition has to be enlarged according to RAM size" do
|
|
let(:scenario) { "empty-hd-50GiB.yaml" }
|
|
|
|
let(:product_data) do
|
|
{
|
|
"storage" => {
|
|
"volume_templates" => [
|
|
{
|
|
"mount_path" => "swap",
|
|
"filesystem" => "swap",
|
|
"size" => { "auto" => true },
|
|
"outline" => {
|
|
"filesystems" => ["swap"],
|
|
"auto_size" => {
|
|
"adjust_by_ram" => true,
|
|
"base_min" => "2 GiB",
|
|
"base_max" => "4 GiB"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
end
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
partitions: [
|
|
{
|
|
filesystem: {
|
|
path: "swap"
|
|
},
|
|
size: size
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
before do
|
|
allow_any_instance_of(Y2Storage::Arch).to receive(:ram_size).and_return(8.GiB)
|
|
config.boot.configure = false
|
|
end
|
|
|
|
context "and the partition size is not indicated" do
|
|
let(:size) { nil }
|
|
|
|
it "creates the partition as big as the RAM" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.partitions).to include(
|
|
an_object_having_attributes(
|
|
filesystem: an_object_having_attributes(mount_path: "swap"),
|
|
size: 8.GiB
|
|
)
|
|
)
|
|
end
|
|
end
|
|
|
|
context "and the partition size is indicated" do
|
|
let(:size) { "2 GiB" }
|
|
|
|
it "creates the partition with the given size" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.partitions).to include(
|
|
an_object_having_attributes(
|
|
filesystem: an_object_having_attributes(mount_path: "swap"),
|
|
size: 2.GiB
|
|
)
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# Regression test for https://github.com/agama-project/agama/issues/2985
|
|
context "when searching or creating a partition" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
partitions: [
|
|
{
|
|
search: {
|
|
condition: {
|
|
number: partition_nr
|
|
},
|
|
ifNotFound: :create
|
|
},
|
|
filesystem: {
|
|
path: "/",
|
|
type: "ext3"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
context "if the partition already exists" do
|
|
let(:partition_nr) { 3 }
|
|
|
|
it "reuses the partition" do
|
|
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
vda3 = devicegraph.find_device(vda3_sid)
|
|
expect(vda3).to_not be_nil
|
|
expect(vda3.filesystem.mount_path).to eq("/")
|
|
end
|
|
end
|
|
|
|
context "if the partition does not exist" do
|
|
let(:partition_nr) { 5 }
|
|
|
|
it "creates a new partition" do
|
|
parts = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda").partitions.size
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
vda = devicegraph.find_by_name("/dev/vda")
|
|
expect(vda.partitions.size).to eq(parts + 1)
|
|
expect(vda.partitions.map(&:filesystem))
|
|
.to include an_object_having_attributes(mount_path: "/")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "resizing an existing partition" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
search: "/dev/vda",
|
|
partitions: [
|
|
{
|
|
filesystem: {
|
|
type: "btrfs",
|
|
path: "/"
|
|
},
|
|
size: root_size
|
|
},
|
|
{
|
|
search: "/dev/vda3",
|
|
size: vda3_size
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
let(:root_size) { ["8.5 GiB"] }
|
|
|
|
let(:vda3_size) { nil }
|
|
|
|
before do
|
|
allow_any_instance_of(Y2Storage::Partition)
|
|
.to(receive(:detect_resize_info))
|
|
.and_return(resize_info)
|
|
end
|
|
|
|
let(:resize_info) do
|
|
instance_double(
|
|
Y2Storage::ResizeInfo, resize_ok?: true,
|
|
min_size: Y2Storage::DiskSize::GiB(3), max_size: Y2Storage::DiskSize::GiB(35)
|
|
)
|
|
end
|
|
|
|
context "when the reused partition is expected to grow with no enforced limit" do
|
|
let(:vda3_size) { ["current"] }
|
|
|
|
it "grows the device as much as allowed by the min size of the new partitions" do
|
|
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
vda3 = devicegraph.find_device(vda3_sid)
|
|
expect(vda3).to_not be_nil
|
|
|
|
root = devicegraph.find_by_name("/dev/vda4")
|
|
expect(root.filesystem.mount_path).to eq("/")
|
|
expect(vda3.size).to be > 21.GiB
|
|
gpt_adjustment = 1.MiB - 16.5.KiB
|
|
expect(root.size).to eq(8.5.GiB + gpt_adjustment)
|
|
end
|
|
end
|
|
|
|
context "when the reused partition is expected to grow up to a limit" do
|
|
let(:vda3_size) { ["10 GiB", "15 GiB"] }
|
|
|
|
it "grows the device up to the limit so the new partitions can exceed their mins" do
|
|
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
vda3 = devicegraph.find_device(vda3_sid)
|
|
expect(vda3).to_not be_nil
|
|
|
|
root = devicegraph.find_by_name("/dev/vda4")
|
|
expect(root.filesystem.mount_path).to eq("/")
|
|
expect(vda3.size).to eq 15.GiB
|
|
expect(root.size).to be > 10.GiB
|
|
end
|
|
end
|
|
|
|
context "when the reused partition is expected to shrink as much as needed" do
|
|
let(:vda3_size) { ["0 KiB", "current"] }
|
|
|
|
context "if there is no need to shrink the partition" do
|
|
it "does not modify the size of the partition" do
|
|
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
vda3 = devicegraph.find_device(vda3_sid)
|
|
expect(vda3).to_not be_nil
|
|
root = devicegraph.find_by_name("/dev/vda4")
|
|
expect(root).to_not be_nil
|
|
|
|
expect(vda3.size).to eq 10.GiB
|
|
end
|
|
end
|
|
|
|
context "if the partition needs to be shrunk to allocate the new ones" do
|
|
let(:root_size) { "24 GiB" }
|
|
|
|
it "shrinks the partition as needed" do
|
|
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
vda3 = devicegraph.find_device(vda3_sid)
|
|
expect(vda3).to_not be_nil
|
|
root = devicegraph.find_by_name("/dev/vda4")
|
|
expect(root).to_not be_nil
|
|
|
|
expect(vda3.size).to be < 6.GiB
|
|
gpt_adjustment = 1.MiB - 16.5.KiB
|
|
expect(root.size).to eq(24.GiB + gpt_adjustment)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the reused partition is expected to shrink in all cases" do
|
|
let(:vda3_size) { ["0 KiB", "6 GiB"] }
|
|
|
|
context "if there is no need to shrink the partition" do
|
|
it "shrinks the partition to the specified max size" do
|
|
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
vda3 = devicegraph.find_device(vda3_sid)
|
|
expect(vda3).to_not be_nil
|
|
root = devicegraph.find_by_name("/dev/vda4")
|
|
expect(root).to_not be_nil
|
|
|
|
expect(vda3.size).to eq 6.GiB
|
|
end
|
|
end
|
|
|
|
context "if the partition needs to be shrunk to allocate the new ones" do
|
|
let(:root_size) { "25 Gib" }
|
|
|
|
it "shrinks the partition as needed" do
|
|
vda3_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vda3").sid
|
|
|
|
devicegraph = proposal.propose
|
|
|
|
vda3 = devicegraph.find_device(vda3_sid)
|
|
expect(vda3).to_not be_nil
|
|
root = devicegraph.find_by_name("/dev/vda4")
|
|
expect(root).to_not be_nil
|
|
|
|
expect(vda3.size).to be < 5.GiB
|
|
gpt_adjustment = 1.MiB - 16.5.KiB
|
|
expect(root.size).to eq(25.GiB + gpt_adjustment)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|