899 lines
26 KiB
Ruby
899 lines
26 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/storage_helpers"
|
|
require "agama/config"
|
|
require "agama/storage/config"
|
|
require "agama/storage/config_conversions/from_json"
|
|
require "agama/storage/system"
|
|
require "y2storage"
|
|
require "y2storage/agama_proposal"
|
|
|
|
describe Y2Storage::AgamaProposal do
|
|
using Y2Storage::Refinements::SizeCasts
|
|
include Agama::RSpec::StorageHelpers
|
|
|
|
subject(:proposal) do
|
|
described_class.new(config, storage_system, issues_list: issues_list)
|
|
end
|
|
|
|
let(:storage_system) { Agama::Storage::System.new }
|
|
|
|
let(:config) { config_from_json }
|
|
|
|
let(:config_from_json) do
|
|
Agama::Storage::ConfigConversions::FromJSON
|
|
.new(config_json)
|
|
.convert
|
|
end
|
|
|
|
let(:issues_list) { [] }
|
|
|
|
before do
|
|
mock_storage(devicegraph: scenario)
|
|
# To speed-up the tests
|
|
allow(Y2Storage::EncryptionMethod::TPM_FDE).to receive(:possible?).and_return(true)
|
|
end
|
|
|
|
let(:scenario) { "empty-hd-50GiB.yaml" }
|
|
|
|
describe "#propose" do
|
|
context "when the config has LVM volume groups" do
|
|
let(:scenario) { "empty-hd-50GiB.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
partitions: [
|
|
{
|
|
alias: "system-pv",
|
|
size: "40 GiB"
|
|
},
|
|
{
|
|
alias: "vg1-pv",
|
|
size: "5 GiB"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
volumeGroups: [
|
|
{
|
|
name: "system",
|
|
extentSize: "2 MiB",
|
|
physicalVolumes: ["system-pv"],
|
|
logicalVolumes: [
|
|
{
|
|
name: "root",
|
|
size: "10 GiB",
|
|
filesystem: {
|
|
path: "/",
|
|
type: "btrfs"
|
|
},
|
|
encryption: {
|
|
luks2: { password: "12345" }
|
|
}
|
|
},
|
|
{
|
|
alias: "system-pool",
|
|
name: "pool",
|
|
pool: true,
|
|
size: "20 GiB",
|
|
stripes: 10,
|
|
stripeSize: "4 KiB"
|
|
},
|
|
{
|
|
name: "data",
|
|
size: "50 GiB",
|
|
usedPool: "system-pool",
|
|
filesystem: { type: "xfs" }
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "vg1",
|
|
physicalVolumes: ["vg1-pv"],
|
|
logicalVolumes: [
|
|
{
|
|
name: "home",
|
|
filesystem: {
|
|
path: "/home",
|
|
type: "xfs"
|
|
},
|
|
size: "2 GiB"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
it "proposes the expected devices" do
|
|
devicegraph = proposal.propose
|
|
|
|
expect(devicegraph.lvm_vgs).to contain_exactly(
|
|
an_object_having_attributes(
|
|
vg_name: "system",
|
|
extent_size: 2.MiB
|
|
),
|
|
an_object_having_attributes(
|
|
vg_name: "vg1",
|
|
extent_size: 4.MiB
|
|
)
|
|
)
|
|
|
|
system_vg = devicegraph.find_by_name("/dev/system")
|
|
system_pvs = system_vg.lvm_pvs.map(&:plain_blk_device)
|
|
system_lvs = system_vg.lvm_lvs
|
|
|
|
expect(system_pvs).to contain_exactly(
|
|
an_object_having_attributes(name: "/dev/sda2", size: 40.GiB)
|
|
)
|
|
expect(system_lvs).to contain_exactly(
|
|
an_object_having_attributes(
|
|
lv_name: "root",
|
|
lv_type: Y2Storage::LvType::NORMAL,
|
|
size: 10.GiB,
|
|
filesystem: an_object_having_attributes(
|
|
type: Y2Storage::Filesystems::Type::BTRFS,
|
|
mount_path: "/"
|
|
),
|
|
encryption: an_object_having_attributes(
|
|
type: Y2Storage::EncryptionType::LUKS2,
|
|
password: "12345"
|
|
)
|
|
),
|
|
an_object_having_attributes(
|
|
lv_name: "pool",
|
|
lv_type: Y2Storage::LvType::THIN_POOL,
|
|
size: 20.GiB,
|
|
filesystem: be_nil,
|
|
encryption: be_nil,
|
|
stripes: 10,
|
|
stripe_size: 4.KiB,
|
|
lvm_lvs: contain_exactly(
|
|
an_object_having_attributes(
|
|
lv_name: "data",
|
|
lv_type: Y2Storage::LvType::THIN,
|
|
size: 50.GiB,
|
|
filesystem: an_object_having_attributes(
|
|
type: Y2Storage::Filesystems::Type::XFS
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
vg1 = devicegraph.find_by_name("/dev/vg1")
|
|
vg1_pvs = vg1.lvm_pvs.map(&:plain_blk_device)
|
|
vg1_lvs = vg1.lvm_lvs
|
|
expect(vg1_pvs).to contain_exactly(
|
|
an_object_having_attributes(name: "/dev/sda3", size: 5.GiB)
|
|
)
|
|
expect(vg1_lvs).to contain_exactly(
|
|
an_object_having_attributes(
|
|
lv_name: "home",
|
|
lv_type: Y2Storage::LvType::NORMAL,
|
|
size: 2.GiB,
|
|
filesystem: an_object_having_attributes(
|
|
type: Y2Storage::Filesystems::Type::XFS,
|
|
mount_path: "/home"
|
|
)
|
|
)
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when a LVM physical volume is not found" do
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
partitions: [
|
|
{
|
|
size: "40 GiB"
|
|
},
|
|
{
|
|
alias: "pv1",
|
|
size: "5 GiB"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
volumeGroups: [
|
|
{
|
|
name: "system",
|
|
extentSize: "2 MiB",
|
|
physicalVolumes: ["pv1", "pv2"],
|
|
logicalVolumes: [
|
|
{
|
|
name: "root",
|
|
filesystem: {
|
|
path: "/"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
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 LVM physical volume with alias 'pv2'/
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when a LVM thin pool volume is not found" do
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
partitions: [
|
|
{
|
|
size: "40 GiB"
|
|
},
|
|
{
|
|
alias: "pv1",
|
|
size: "5 GiB"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
volumeGroups: [
|
|
{
|
|
name: "system",
|
|
extentSize: "2 MiB",
|
|
physicalVolumes: ["pv1"],
|
|
logicalVolumes: [
|
|
{
|
|
pool: true
|
|
},
|
|
{
|
|
name: "root",
|
|
filesystem: {
|
|
path: "/"
|
|
},
|
|
usedPool: "pool"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
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 LVM thin pool volume with alias 'pool'/
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when the config has LVM volume groups with generated physical volumes" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{
|
|
alias: "vda",
|
|
partitions: [
|
|
{
|
|
search: "/dev/vda2",
|
|
size: { min: "0", max: "current" }
|
|
},
|
|
{
|
|
size: { min: "4 GiB" },
|
|
filesystem: { path: "/foo" }
|
|
}
|
|
]
|
|
},
|
|
{
|
|
alias: "vdb"
|
|
}
|
|
],
|
|
volumeGroups: [
|
|
{
|
|
name: "system",
|
|
physicalVolumes: [
|
|
{ generate: { targetDevices: ["vda"] } }
|
|
],
|
|
logicalVolumes: [
|
|
{
|
|
name: "root",
|
|
size: "10 GiB",
|
|
filesystem: {
|
|
path: "/",
|
|
type: "btrfs"
|
|
}
|
|
},
|
|
{
|
|
name: "data",
|
|
size: "10 GiB",
|
|
filesystem: { type: "xfs" }
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "vg1",
|
|
physicalVolumes: [
|
|
{
|
|
generate: {
|
|
targetDevices: ["vdb"],
|
|
encryption: { luks2: { password: "s3cr3t" } }
|
|
}
|
|
}
|
|
],
|
|
logicalVolumes: [
|
|
{
|
|
name: "home",
|
|
filesystem: {
|
|
path: "/home",
|
|
type: "xfs"
|
|
},
|
|
size: "20 GiB"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
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
|
|
|
|
it "proposes the expected devices" do
|
|
devicegraph = proposal.propose
|
|
|
|
resized = devicegraph.find_by_name("/dev/vda2")
|
|
expect(resized.filesystem.label).to eq("previous_root")
|
|
expect(resized.size).to be > 15.GiB
|
|
expect(resized.size).to be < 16.GiB
|
|
|
|
foo = devicegraph.find_by_name("/dev/vda4")
|
|
expect(foo.filesystem.mount_path).to eq("/foo")
|
|
expect(foo.size).to be > 4.GiB
|
|
expect(foo.size).to be < 5.GiB
|
|
|
|
system = devicegraph.find_by_name("/dev/system")
|
|
expect(system.lvm_lvs.size).to eq 2
|
|
expect(Y2Storage::DiskSize.sum(system.lvm_lvs.map(&:size))).to eq 20.GiB
|
|
expect(system.lvm_pvs.size).to eq 2
|
|
|
|
vg1 = devicegraph.find_by_name("/dev/vg1")
|
|
expect(vg1.lvm_lvs.size).to eq 1
|
|
expect(vg1.lvm_lvs.first.size).to eq 20.GiB
|
|
expect(vg1.lvm_pvs.size).to eq 1
|
|
|
|
pv_vg1 = vg1.lvm_pvs.first
|
|
expect(pv_vg1.blk_device.is?(:encryption)).to eq true
|
|
expect(pv_vg1.blk_device.type.is?(:luks2)).to eq true
|
|
end
|
|
end
|
|
|
|
context "when two volume groups with generated physical volumes share a disk" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{ alias: "vda" },
|
|
{ alias: "vdb" },
|
|
{ alias: "vdc" }
|
|
],
|
|
volumeGroups: [
|
|
{
|
|
name: "system",
|
|
physicalVolumes: [
|
|
{ generate: { targetDevices: ["vdb", "vdc"] } }
|
|
],
|
|
logicalVolumes: [
|
|
{
|
|
name: "root",
|
|
size: "10 GiB",
|
|
filesystem: {
|
|
path: "/",
|
|
type: "btrfs"
|
|
}
|
|
},
|
|
{
|
|
name: "home",
|
|
size: {
|
|
min: "30 GiB"
|
|
},
|
|
filesystem: {
|
|
path: "/home",
|
|
type: "xfs"
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "vg1",
|
|
physicalVolumes: [
|
|
{
|
|
generate: {
|
|
targetDevices: ["vdc"],
|
|
encryption: { luks1: { password: "s3cr3t" } }
|
|
}
|
|
}
|
|
],
|
|
logicalVolumes: [
|
|
{
|
|
name: "data",
|
|
filesystem: { type: "xfs" },
|
|
size: { min: "30 GiB", max: "40 GiB" }
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
it "proposes the expected devices" do
|
|
devicegraph = proposal.propose
|
|
|
|
system = devicegraph.find_by_name("/dev/system")
|
|
expect(system.lvm_lvs.map { |lv| lv.mount_point.path }).to contain_exactly("/", "/home")
|
|
expect(system.lvm_pvs.map { |pv| pv.blk_device.partitionable.name })
|
|
.to contain_exactly("/dev/vdb", "/dev/vdc")
|
|
|
|
vg1 = devicegraph.find_by_name("/dev/vg1")
|
|
expect(vg1.lvm_lvs.map(&:lv_name)).to contain_exactly("data")
|
|
expect(vg1.lvm_pvs.map { |pv| pv.plain_blk_device.partitionable.name })
|
|
.to contain_exactly("/dev/vdc")
|
|
|
|
pv_vg1 = vg1.lvm_pvs.first
|
|
expect(pv_vg1.blk_device.is?(:encryption)).to eq true
|
|
expect(pv_vg1.blk_device.type.is?(:luks1)).to eq true
|
|
end
|
|
end
|
|
|
|
context "when creating new volumes in an existing volume group" do
|
|
let(:scenario) { "several_vgs.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
boot: { configure: false },
|
|
volumeGroups: [
|
|
{
|
|
search: "/dev/data",
|
|
logicalVolumes: previous_lvs + new_lvs
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
let(:previous_lvs) { [] }
|
|
let(:new_lvs) do
|
|
[
|
|
{
|
|
name: "root",
|
|
size: "5 GiB",
|
|
filesystem: {
|
|
path: "/",
|
|
type: "btrfs"
|
|
}
|
|
},
|
|
{
|
|
name: "home",
|
|
size: { min: home_min },
|
|
filesystem: {
|
|
path: "/home",
|
|
type: "xfs"
|
|
}
|
|
}
|
|
]
|
|
end
|
|
|
|
context "if the LVs fit into the available space at the VG" do
|
|
let(:home_min) { "5 GiB" }
|
|
|
|
it "adds the new volumes to the volume group keeping the previous ones" do
|
|
vg_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/data").sid
|
|
proposal.propose
|
|
|
|
vg = proposal.devices.find_by_name("/dev/data")
|
|
expect(vg.sid).to eq vg_sid
|
|
# The previous one plus the two new ones
|
|
expect(vg.lvm_lvs.size).to eq 3
|
|
paths = vg.lvm_lvs.map(&:filesystem).map(&:mount_path)
|
|
expect(paths).to contain_exactly(nil, "/", "/home")
|
|
end
|
|
end
|
|
|
|
context "if the LVs fit into the total size of the VG but not in the available space" do
|
|
let(:home_min) { "15 GiB" }
|
|
|
|
context "and nothing is configured to make space in the VG" do
|
|
it "raises an error" do
|
|
expect { proposal.propose }.to raise_error(Y2Storage::NoDiskSpaceError)
|
|
end
|
|
end
|
|
|
|
context "and all previous LVs should be deleted " do
|
|
let(:previous_lvs) do
|
|
[
|
|
{ search: "*", delete: true }
|
|
]
|
|
end
|
|
|
|
it "adds the new volumes to the volume group deleting the previous ones" do
|
|
vg_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/data").sid
|
|
proposal.propose
|
|
|
|
vg = proposal.devices.find_by_name("/dev/data")
|
|
expect(vg.sid).to eq vg_sid
|
|
expect(vg.lvm_lvs.size).to eq 2
|
|
paths = vg.lvm_lvs.map(&:filesystem).map(&:mount_path)
|
|
expect(paths).to contain_exactly("/", "/home")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "if the LVs do not fit into the total size of the VG" do
|
|
let(:home_min) { "150 GiB" }
|
|
|
|
context "and nothing is configured to make space in the VG" do
|
|
it "raises an error" do
|
|
expect { proposal.propose }.to raise_error(Y2Storage::NoDiskSpaceError)
|
|
end
|
|
end
|
|
|
|
context "and all previous LVs should be deleted " do
|
|
let(:previous_lvs) do
|
|
[
|
|
{ search: "*", delete: true }
|
|
]
|
|
end
|
|
|
|
it "raises an error" do
|
|
expect { proposal.propose }.to raise_error(Y2Storage::NoDiskSpaceError)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when using existing logical volumes" do
|
|
let(:scenario) { "several_vgs.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
boot: { configure: false },
|
|
volumeGroups: [
|
|
{
|
|
search: "/dev/data",
|
|
logicalVolumes: [
|
|
{
|
|
name: "root",
|
|
size: { min: "5 GiB" },
|
|
filesystem: {
|
|
path: "/",
|
|
type: "btrfs"
|
|
}
|
|
},
|
|
{
|
|
search: { max: 1 },
|
|
filesystem: {
|
|
path: "/home",
|
|
reuseIfPossible: true
|
|
},
|
|
size: home_size
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
let(:home_size) { nil }
|
|
|
|
it "uses the volume group and the logical volume" do
|
|
initial_vg = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/data")
|
|
vg_sid = initial_vg.sid
|
|
fs_sid = initial_vg.lvm_lvs.first.filesystem.sid
|
|
proposal.propose
|
|
|
|
vg = proposal.devices.find_by_name("/dev/data")
|
|
expect(vg.sid).to eq vg_sid
|
|
expect(vg.lvm_lvs.size).to eq 2
|
|
filesystems = vg.lvm_lvs.map(&:filesystem)
|
|
expect(filesystems.map(&:mount_path)).to contain_exactly("/", "/home")
|
|
expect(filesystems.map(&:sid)).to include fs_sid
|
|
end
|
|
|
|
it "correctly distributes the available space" do
|
|
proposal.propose
|
|
|
|
vg = proposal.devices.find_by_name("/dev/data")
|
|
root = vg.lvm_lvs.find { |v| v.filesystem.mount_path == "/" }
|
|
home = vg.lvm_lvs.find { |v| v.filesystem.mount_path == "/home" }
|
|
expect(vg.available_space).to be_zero
|
|
expect(root.size).to be > 12.GiB
|
|
expect(home.size).to eq 30.GiB
|
|
end
|
|
|
|
context "if some existing logical volume is chosen for growing" do
|
|
let(:home_size) { { min: "30 GiB", max: "35 GiB" } }
|
|
|
|
it "grows the LV and distributes the remaining space" do
|
|
proposal.propose
|
|
|
|
vg = proposal.devices.find_by_name("/dev/data")
|
|
root = vg.lvm_lvs.find { |v| v.filesystem.mount_path == "/" }
|
|
home = vg.lvm_lvs.find { |v| v.filesystem.mount_path == "/home" }
|
|
expect(vg.available_space).to be_zero
|
|
expect(home.size).to eq 35.GiB
|
|
expect(root.size).to be > 7.GiB
|
|
expect(root.size).to be < 8.GiB
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when using an existing thin pool and existing logical thin volume" do
|
|
let(:scenario) { "lvm_with_nested_thin_lvs.xml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
boot: { configure: false },
|
|
volumeGroups: [
|
|
{
|
|
search: "/dev/vg_b",
|
|
logicalVolumes: [
|
|
{
|
|
search: "/dev/vg_b/tv_01",
|
|
filesystem: { path: "/" }
|
|
},
|
|
{
|
|
search: "/dev/vg_b/tpool",
|
|
alias: "pool"
|
|
},
|
|
{
|
|
name: "home",
|
|
usedPool: "pool",
|
|
size: "5 GiB",
|
|
filesystem: { path: "/home" }
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
it "uses the volume group and the logical volumes" do
|
|
vg_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vg_b").sid
|
|
lv_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vg_b/tv_01").sid
|
|
pool = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/vg_b/tpool")
|
|
pool_sid = pool.sid
|
|
pool_size = pool.lvm_lvs.size
|
|
proposal.propose
|
|
|
|
vg = proposal.devices.find_by_name("/dev/vg_b")
|
|
expect(vg.sid).to eq vg_sid
|
|
expect(vg.thin_pool_lvm_lvs.size).to eq 1
|
|
pool = vg.thin_pool_lvm_lvs.first
|
|
expect(pool.sid).to eq pool_sid
|
|
thin_vols = pool.lvm_lvs
|
|
expect(thin_vols.size).to eq(pool_size + 1)
|
|
expect(thin_vols.map(&:sid)).to include lv_sid
|
|
filesystems = thin_vols.map(&:filesystem).compact
|
|
expect(filesystems.map(&:mount_path)).to contain_exactly("/", "/home")
|
|
end
|
|
end
|
|
|
|
context "when adding more PVs to an existing volume group" do
|
|
let(:scenario) { "several_vgs.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
boot: { configure: false },
|
|
drives: [
|
|
{
|
|
search: "/dev/sdc",
|
|
partitions: [
|
|
{
|
|
size: "40 GiB",
|
|
alias: "pv1"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
volumeGroups: [
|
|
{
|
|
search: "/dev/data",
|
|
logicalVolumes: [
|
|
{
|
|
name: "root",
|
|
size: { min: "5 GiB" },
|
|
filesystem: {
|
|
path: "/",
|
|
type: "btrfs"
|
|
}
|
|
}
|
|
],
|
|
physicalVolumes: ["pv1"]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
it "uses the volume group" do
|
|
vg_sid = Y2Storage::StorageManager.instance.probed.find_by_name("/dev/data").sid
|
|
proposal.propose
|
|
|
|
vg = proposal.devices.find_by_name("/dev/data")
|
|
expect(vg.sid).to eq vg_sid
|
|
expect(vg.lvm_lvs.map(&:filesystem).map(&:mount_path)).to include "/"
|
|
end
|
|
|
|
it "grows the volume group" do
|
|
proposal.propose
|
|
|
|
vg = proposal.devices.find_by_name("/dev/data")
|
|
expect(vg.lvm_pvs.size).to eq 3
|
|
expect(vg.size).to be > Y2Storage::DiskSize.GiB(80)
|
|
end
|
|
|
|
it "uses the new volume group space to allocate the logical volumes" do
|
|
proposal.propose
|
|
|
|
lv = proposal.devices.find_by_name("/dev/data/root")
|
|
expect(lv.size).to be > Y2Storage::DiskSize.GiB(50)
|
|
end
|
|
end
|
|
|
|
context "when deleting volumes in a new volume group" do
|
|
let(:config_json) do
|
|
{
|
|
boot: { configure: false },
|
|
drives: [
|
|
{
|
|
partitions: [
|
|
{
|
|
alias: "system-pv",
|
|
size: "40 GiB"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
volumeGroups: [
|
|
{
|
|
name: "system",
|
|
physicalVolumes: ["system-pv"],
|
|
logicalVolumes: [
|
|
{ search: "*", delete: true },
|
|
{
|
|
name: "root",
|
|
size: "5 GiB",
|
|
filesystem: {
|
|
path: "/",
|
|
type: "btrfs"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
it "proposes the expected devices" do
|
|
devicegraph = proposal.propose
|
|
|
|
vg = devicegraph.find_by_name("/dev/system")
|
|
expect(vg.lvm_lvs.map { |lv| lv.mount_point.path }).to contain_exactly("/")
|
|
end
|
|
end
|
|
|
|
context "when a volume group has generated physical volumes with useAvailable space policy" do
|
|
let(:scenario) { "disks.yaml" }
|
|
|
|
let(:config_json) do
|
|
{
|
|
drives: [
|
|
{ alias: "vda" },
|
|
{ alias: "vdb" }
|
|
],
|
|
volumeGroups: [
|
|
{
|
|
name: "system",
|
|
physicalVolumes: [
|
|
{
|
|
generate: {
|
|
targetDevices: ["vda", "vdb"],
|
|
spacePolicy: "useAvailable"
|
|
}
|
|
}
|
|
],
|
|
logicalVolumes: [
|
|
{
|
|
name: "root",
|
|
size: "10 GiB",
|
|
filesystem: {
|
|
path: "/",
|
|
type: "btrfs"
|
|
}
|
|
},
|
|
{
|
|
name: "home",
|
|
size: { min: "20 GiB" },
|
|
filesystem: {
|
|
path: "/home",
|
|
type: "xfs"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
end
|
|
|
|
it "proposes the expected devices" do
|
|
devicegraph = proposal.propose
|
|
|
|
system = devicegraph.find_by_name("/dev/system")
|
|
expect(system.lvm_lvs.size).to eq 2
|
|
|
|
root = system.lvm_lvs.find { |lv| lv.lv_name == "root" }
|
|
home = system.lvm_lvs.find { |lv| lv.lv_name == "home" }
|
|
|
|
expect(root.size).to eq 10.GiB
|
|
expect(root.filesystem.type).to eq Y2Storage::Filesystems::Type::BTRFS
|
|
expect(root.filesystem.mount_path).to eq "/"
|
|
|
|
expect(home.size).to be > 20.GiB
|
|
expect(home.filesystem.type).to eq Y2Storage::Filesystems::Type::XFS
|
|
expect(home.filesystem.mount_path).to eq "/home"
|
|
|
|
# With useAvailable policy, PVs should use all available space on the target disks. There
|
|
# is ~20 GiB in vda and ~50 GiB in vdb.
|
|
expect(system.size).to be > 69.GiB
|
|
|
|
vda_pv = system.lvm_pvs.find { |pv| pv.plain_blk_device.partitionable.name == "/dev/vda" }
|
|
vdb_pv = system.lvm_pvs.find { |pv| pv.plain_blk_device.partitionable.name == "/dev/vdb" }
|
|
expect(vda_pv.plain_blk_device.size).to be > 19.GiB
|
|
expect(vdb_pv.plain_blk_device.size).to be > 49.GiB
|
|
end
|
|
end
|
|
end
|
|
end
|