Files
ubicloud/rhizome/host/spec/storage_volume_spec.rb
Hadi Moshayedi a8a386862b Fix persistent_device_id for NVMe disks
NVMe disk Ids start with "nvme-eui." not "nmve-eui-".

For example:

```
nvme-eui.3634473057c004720025384e00000001
```
2025-07-16 10:26:54 -07:00

560 lines
25 KiB
Ruby

# frozen_string_literal: true
require_relative "../lib/storage_volume"
require "openssl"
require "base64"
RSpec.describe StorageVolume do
subject(:unencrypted_sv) {
params = {
"disk_index" => 2,
"device_id" => "xyz01",
"encrypted" => false,
"size_gib" => 12,
"image" => "kubuntu"
}
described_class.new("test", params)
}
let(:encrypted_sv) {
params = {
"disk_index" => 2,
"device_id" => "xyz01",
"encrypted" => true,
"size_gib" => 12,
"image" => "kubuntu"
}
described_class.new("test", params)
}
let(:encrypted_vhost_sv) {
params = {
"disk_index" => 2,
"device_id" => "xyz01",
"encrypted" => true,
"size_gib" => 12,
"image" => "kubuntu",
"vhost_block_backend_version" => "v0.1-5",
"num_queues" => 4,
"queue_size" => 128
}
described_class.new("test", params)
}
let(:unencrypted_vhost_sv) {
params = {
"disk_index" => 2,
"device_id" => "xyz01",
"encrypted" => false,
"size_gib" => 12,
"image" => "kubuntu",
"vhost_block_backend_version" => "v0.1-5",
"num_queues" => 4,
"queue_size" => 128
}
described_class.new("test", params)
}
let(:image_path) {
"/var/storage/images/kubuntu.raw"
}
let(:disk_file) {
"/var/storage/test/2/disk.raw"
}
let(:rpc_client) {
instance_double(SpdkRpc)
}
before do
allow(encrypted_sv).to receive(:rpc_client).and_return(rpc_client)
allow(unencrypted_sv).to receive(:rpc_client).and_return(rpc_client)
end
describe "#prep" do
it "can prep a non-imaged unencrypted disk" do
vol = described_class.new("test", {"disk_index" => 1, "encrypted" => false})
expect(File).to receive(:exist?).with("/var/storage").and_return(true)
expect(FileUtils).to receive(:mkdir_p).with("/var/storage/test/1")
expect(FileUtils).to receive(:chown).with("test", "test", "/var/storage/test/1")
expect(vol).to receive(:create_empty_disk_file).with(no_args)
vol.prep(nil)
end
it "can prep a non-imaged encrypted disk" do
key_wrapping_secrets = "key_wrapping_secrets"
vol = described_class.new("test", {"disk_index" => 1, "encrypted" => true})
expect(FileUtils).to receive(:mkdir_p).with("/var/storage/test/1")
expect(FileUtils).to receive(:chown).with("test", "test", "/var/storage/test/1")
expect(File).to receive(:exist?).with("/var/storage").and_return(true)
expect(vol).to receive(:setup_data_encryption_key).with(key_wrapping_secrets)
expect(vol).to receive(:create_empty_disk_file).with(no_args)
vol.prep(key_wrapping_secrets)
end
it "fails if storage root doesn't exist" do
dev_path = "/var/storage/devices/dev01"
vol = described_class.new("test", {"disk_index" => 1, "encrypted" => false, "storage_device" => "dev01"})
expect(File).to receive(:exist?).with(dev_path).and_return(false)
expect { vol.prep(nil) }.to raise_error RuntimeError, "Storage device directory doesn't exist: #{dev_path}"
end
it "can prep an encrypted imaged disk" do
encryption_key = "test_key"
key_wrapping_secrets = "key_wrapping_secrets"
expect(FileUtils).to receive(:mkdir_p).with("/var/storage/test/2")
expect(FileUtils).to receive(:chown).with("test", "test", "/var/storage/test/2")
expect(File).to receive(:exist?).with("/var/storage").and_return(true)
expect(encrypted_sv).to receive(:verify_imaged_disk_size).with(no_args)
expect(encrypted_sv).to receive(:setup_data_encryption_key).with(key_wrapping_secrets).and_return(encryption_key)
expect(encrypted_sv).to receive(:create_empty_disk_file)
expect(encrypted_sv).to receive(:encrypted_image_copy).with(encryption_key, image_path)
encrypted_sv.prep(key_wrapping_secrets)
end
it "can prep an unencrypted imaged disk" do
expect(FileUtils).to receive(:mkdir_p).with("/var/storage/test/2")
expect(FileUtils).to receive(:chown).with("test", "test", "/var/storage/test/2")
expect(File).to receive(:exist?).with("/var/storage").and_return(true)
expect(unencrypted_sv).to receive(:verify_imaged_disk_size).with(no_args)
expect(unencrypted_sv).to receive(:unencrypted_image_copy).with(no_args)
unencrypted_sv.prep(nil)
end
it "can prep an encrypted imaged disk with vhost backend" do
encryption_key = "test_key"
key_wrapping_secrets = "key_wrapping_secrets"
expect(FileUtils).to receive(:mkdir_p).with("/var/storage/test/2")
expect(FileUtils).to receive(:chown).with("test", "test", "/var/storage/test/2")
expect(File).to receive(:exist?).with("/var/storage").and_return(true)
expect(encrypted_vhost_sv).to receive(:setup_data_encryption_key).with(key_wrapping_secrets).and_return(encryption_key)
expect(encrypted_vhost_sv).to receive(:create_empty_disk_file)
expect(encrypted_vhost_sv).to receive(:prep_vhost_backend).with(encryption_key, key_wrapping_secrets)
encrypted_vhost_sv.prep(key_wrapping_secrets)
end
end
describe "#start" do
it "can start an encrypted storage volume" do
encryption_key = "test_key"
key_wrapping_secrets = "key_wrapping_secrets"
expect(encrypted_sv).to receive(:read_data_encryption_key).with(key_wrapping_secrets).and_return(encryption_key)
expect(encrypted_sv).to receive(:setup_spdk_bdev).with(encryption_key)
expect(encrypted_sv).to receive(:set_qos_limits).with(no_args)
expect(encrypted_sv).to receive(:setup_spdk_vhost).with(no_args)
encrypted_sv.start(key_wrapping_secrets)
end
it "can start an uencrypted storage volume" do
expect(unencrypted_sv).to receive(:setup_spdk_bdev).with(nil)
expect(unencrypted_sv).to receive(:set_qos_limits).with(no_args)
expect(unencrypted_sv).to receive(:setup_spdk_vhost).with(no_args)
unencrypted_sv.start(nil)
end
it "retries after purging if spdk artifacts exist" do
expect(unencrypted_sv).to receive(:setup_spdk_bdev).with(nil).and_return(nil, nil)
expect(unencrypted_sv).to receive(:set_qos_limits).with(no_args).and_return(nil, nil)
expect(unencrypted_sv).to receive(:setup_spdk_vhost).with(no_args).and_invoke(
-> { raise SpdkExists.new("Device Exists", -17) },
-> {}
)
expect(unencrypted_sv).to receive(:purge_spdk_artifacts)
unencrypted_sv.start(nil)
end
it "doesn't retry more than once" do
expect(unencrypted_sv).to receive(:setup_spdk_bdev).with(nil).and_return(nil, nil)
expect(unencrypted_sv).to receive(:set_qos_limits).with(no_args).and_return(nil, nil)
expect(unencrypted_sv).to receive(:setup_spdk_vhost).with(no_args).and_invoke(
-> { raise SpdkExists.new("Device Exists", -17) },
-> { raise SpdkExists.new("Device Exists", -17) }
)
expect(unencrypted_sv).to receive(:purge_spdk_artifacts)
expect { unencrypted_sv.start(nil) }.to raise_error SpdkExists
end
it "can start an encrypted storage volume with vhost backend" do
key_wrapping_secrets = "key_wrapping_secrets"
expect(encrypted_vhost_sv).to receive(:vhost_backend_start).with(key_wrapping_secrets)
encrypted_vhost_sv.start(key_wrapping_secrets)
end
end
describe "#purge_spdk_artifacts" do
it "can purge an encrypted disk" do
expect(rpc_client).to receive(:vhost_delete_controller).with("test_2")
expect(rpc_client).to receive(:bdev_crypto_delete).with("xyz01")
expect(rpc_client).to receive(:bdev_aio_delete).with("xyz01_aio")
expect(rpc_client).to receive(:accel_crypto_key_destroy).with("xyz01_key")
expect(FileUtils).to receive(:rm_r).with("/var/storage/vhost/test_2")
encrypted_sv.purge_spdk_artifacts
end
it "can purge an unencrypted disk" do
expect(rpc_client).to receive(:vhost_delete_controller).with("test_2")
expect(rpc_client).to receive(:bdev_aio_delete).with("xyz01")
expect(FileUtils).to receive(:rm_r).with("/var/storage/vhost/test_2")
unencrypted_sv.purge_spdk_artifacts
end
end
describe "#setup_data_encryption_key" do
it "can setup data encryption key" do
key_file = "/var/storage/test/2/data_encryption_key.json"
key_wrapping_secrets = "key_wrapping_secrets"
expect(FileUtils).to receive(:chown).with("test", "test", key_file)
expect(FileUtils).to receive(:chmod).with("u=rw,g=,o=", key_file)
expect(File).to receive(:open).with(key_file, "w")
expect(encrypted_sv).to receive(:sync_parent_dir).with(key_file)
encrypted_sv.setup_data_encryption_key(key_wrapping_secrets)
end
end
describe "#read_data_encryption_key" do
it "can read data encryption key" do
key_file = "/var/storage/test/2/data_encryption_key.json"
dek = "123"
key_wrapping_secrets = "key_wrapping_secrets"
sek = instance_double(StorageKeyEncryption)
expect(sek).to receive(:read_encrypted_dek).with(key_file).and_return(dek)
expect(StorageKeyEncryption).to receive(:new).with(key_wrapping_secrets).and_return(sek)
expect(encrypted_sv.read_data_encryption_key(key_wrapping_secrets)).to eq(dek)
end
end
describe "#unencrypted_image_copy" do
it "can copy an image to an unencrypted volume" do
expect(unencrypted_sv).to receive(:r).with("cp --reflink=auto #{image_path} #{disk_file}")
expect(unencrypted_sv).to receive(:r).with("truncate -s 12G #{disk_file.shellescape}")
expect(unencrypted_sv).to receive(:set_disk_file_permissions)
unencrypted_sv.unencrypted_image_copy
end
end
describe "#encrypted_image_copy" do
it "can copy an image to an encrypted volume" do
encryption_key = {cipher: "aes_xts", key: "key1value", key2: "key2value"}
expect(encrypted_sv).to receive(:r).with(/spdk_dd.*--if #{image_path} --ob crypt0 --bs=[0-9]+\s*$/, stdin: /{.*}/)
encrypted_sv.encrypted_image_copy(encryption_key, image_path)
end
end
describe "#create_ubi_writespace" do
it "can create an unencrypted ubi writespace" do
expect(unencrypted_sv).to receive(:create_empty_disk_file).with(disk_size_mib: 12 * 1024 + 16)
unencrypted_sv.create_ubi_writespace(nil)
end
end
describe "#create_empty_disk_file" do
it "can create an empty disk file" do
expect(FileUtils).to receive(:touch).with(disk_file)
expect(File).to receive(:truncate).with(disk_file, 12288 * 1024 * 1024)
expect(encrypted_sv).to receive(:set_disk_file_permissions)
encrypted_sv.create_empty_disk_file
end
end
describe "#set_disk_file_permissions" do
it "can set disk file permissions" do
expect(FileUtils).to receive(:chown).with("test", "test", disk_file)
expect(FileUtils).to receive(:chmod).with("u=rw,g=r,o=", disk_file)
expect(encrypted_sv).to receive(:r).with(/setfacl.*#{disk_file}/)
encrypted_sv.set_disk_file_permissions
end
end
describe "#setup_spdk_bdev" do
it "can setup encrypted spdk bdev" do
bdev = "xyz01"
encryption_key = {cipher: "aes_xts", key: "key1value", key2: "key2value"}
expect(rpc_client).to receive(:accel_crypto_key_create).with("#{bdev}_key", "aes_xts", "key1value", "key2value")
expect(rpc_client).to receive(:bdev_aio_create).with("#{bdev}_aio", disk_file, 512)
expect(rpc_client).to receive(:bdev_crypto_create).with(bdev, "#{bdev}_aio", "#{bdev}_key")
encrypted_sv.setup_spdk_bdev(encryption_key)
end
it "can setup unencrypted spdk bdev" do
bdev = "xyz01"
disk_file = "/var/storage/test/2/disk.raw"
expect(rpc_client).to receive(:bdev_aio_create).with(bdev, disk_file, 512)
unencrypted_sv.setup_spdk_bdev(nil)
end
end
describe "#set_qos_limits" do
it "can set qos limits" do
sv = described_class.new("test", {
"disk_index" => 1,
"device_id" => "xyz01",
"max_read_mbytes_per_sec" => 200,
"max_write_mbytes_per_sec" => 300
})
rpc_client = instance_double(SpdkRpc)
allow(sv).to receive(:rpc_client).and_return(rpc_client)
expect(rpc_client).to receive(:bdev_set_qos_limit).with(
"xyz01", r_mbytes_per_sec: 200, w_mbytes_per_sec: 300
)
sv.set_qos_limits
end
it "doesn't set qos limits if none are provided" do
sv = described_class.new("test", {
"disk_index" => 1,
"device_id" => "xyz01"
})
expect(sv).not_to receive(:rpc_client)
sv.set_qos_limits
end
end
describe "#setup_spdk_vhost" do
it "can setup spdk vhost" do
device_id = "xyz01"
spdk_vhost_sock = "/var/storage/vhost/test_2"
vm_vhost_sock = "/var/storage/test/2/vhost.sock"
expect(rpc_client).to receive(:vhost_create_blk_controller).with("test_2", device_id)
expect(FileUtils).to receive(:chmod).with("u=rw,g=r,o=", spdk_vhost_sock)
expect(FileUtils).to receive(:ln_s).with(spdk_vhost_sock, vm_vhost_sock)
expect(FileUtils).to receive(:chown).with("test", "test", vm_vhost_sock)
expect(encrypted_sv).to receive(:r).with(/setfacl.*#{spdk_vhost_sock}/)
encrypted_sv.setup_spdk_vhost
end
end
describe "#verify_imaged_disk_size" do
it "can verify imaged disk size" do
expect(File).to receive(:size).and_return(2 * 2**30)
encrypted_sv.verify_imaged_disk_size
end
it "fails if disk size is less than image file size" do
expect(File).to receive(:size).and_return(15 * 2**30)
expect { encrypted_sv.verify_imaged_disk_size }.to raise_error RuntimeError, "Image size greater than requested disk size"
end
end
describe "#paths" do
it "uses correct namespaced paths" do
sv = described_class.new("vm12345", {"storage_device" => "nvme0", "disk_index" => 3})
expect(sv.storage_root).to eq("/var/storage/devices/nvme0/vm12345")
expect(sv.storage_dir).to eq("/var/storage/devices/nvme0/vm12345/3")
expect(sv.disk_file).to eq("/var/storage/devices/nvme0/vm12345/3/disk.raw")
expect(sv.data_encryption_key_path).to eq("/var/storage/devices/nvme0/vm12345/3/data_encryption_key.json")
expect(sv.vhost_sock).to eq("/var/storage/devices/nvme0/vm12345/3/vhost.sock")
end
it "uses correct not-namespaced paths" do
sv = described_class.new("vm12345", {"storage_device" => "DEFAULT", "disk_index" => 3})
expect(sv.storage_root).to eq("/var/storage/vm12345")
expect(sv.storage_dir).to eq("/var/storage/vm12345/3")
expect(sv.disk_file).to eq("/var/storage/vm12345/3/disk.raw")
expect(sv.data_encryption_key_path).to eq("/var/storage/vm12345/3/data_encryption_key.json")
expect(sv.vhost_sock).to eq("/var/storage/vm12345/3/vhost.sock")
end
end
describe "#prep_vhost_backend" do
it "can prep vhost backend with metadata" do
encryption_key = "encryption_key"
key_wrapping_secrets = "key_wrapping_secrets"
expect(encrypted_vhost_sv).to receive(:vhost_backend_create_config).with(encryption_key, key_wrapping_secrets)
expect(encrypted_vhost_sv).to receive(:vhost_backend_create_metadata).with(key_wrapping_secrets)
expect(encrypted_vhost_sv).to receive(:vhost_backend_create_service_file)
encrypted_vhost_sv.prep_vhost_backend(encryption_key, key_wrapping_secrets)
end
end
describe "#vhost_backend_create_config" do
it "can create vhost backend config" do
encryption_key = {
key: "abcdefgh01234567abcdefgh01234567",
key2: "abcdefgh01234567abcdefgh01234567"
}
algorithm = "aes-256-gcm"
cipher = OpenSSL::Cipher.new(algorithm)
key_wrapping_secrets = {
"algorithm" => algorithm,
"key" => Base64.encode64(cipher.random_key),
"init_vector" => Base64.encode64(cipher.random_iv),
"auth_data" => "Ubicloud-Test-Auth"
}
config_path = "/var/storage/test/2/vhost-backend.conf"
f = instance_double(File)
expect(File).to receive(:open).with(config_path, "w", 0o600, flags: File::CREAT | File::EXCL).and_yield(f)
expect(FileUtils).to receive(:chown).with("test", "test", config_path)
expect(f).to receive(:write).with(/image_path/)
expect(encrypted_vhost_sv).to receive(:fsync_or_fail).with(f)
expect(encrypted_vhost_sv).to receive(:sync_parent_dir).with(config_path)
encrypted_vhost_sv.vhost_backend_create_config(encryption_key, key_wrapping_secrets)
end
end
describe "#vhost_backend_create_metadata" do
it "can create vhost backend metadata" do
algorithm = "aes-256-gcm"
cipher = OpenSSL::Cipher.new(algorithm)
key_wrapping_secrets = {
"algorithm" => algorithm,
"key" => Base64.encode64(cipher.random_key),
"init_vector" => Base64.encode64(cipher.random_iv),
"auth_data" => "Ubicloud-Test-Auth"
}
metadata_path = "/var/storage/test/2/metadata"
f = instance_double(File)
expect(encrypted_vhost_sv).to receive(:rm_if_exists).with(metadata_path)
expect(File).to receive(:open).with(metadata_path, "w", 0o600, flags: File::CREAT | File::EXCL).and_yield(f)
expect(FileUtils).to receive(:chown).with("test", "test", metadata_path)
expect(f).to receive(:truncate).with(8 * 1024 * 1024)
expect(encrypted_vhost_sv).to receive(:sync_parent_dir).with(metadata_path)
expect(encrypted_vhost_sv).to receive(:r).with(/.*init-metadata.* --kek \/dev\/stdin/, stdin: /.*aes256-gcm.*/)
encrypted_vhost_sv.vhost_backend_create_metadata(key_wrapping_secrets)
end
it "can create vhost backend metadata for unencrypted vhost" do
metadata_path = "/var/storage/test/2/metadata"
f = instance_double(File)
expect(unencrypted_vhost_sv).to receive(:write_new_file).with(metadata_path, "test").and_yield(f)
expect(f).to receive(:truncate).with(8 * 1024 * 1024)
expect(unencrypted_vhost_sv).to receive(:sync_parent_dir).with(metadata_path)
expect(unencrypted_vhost_sv).to receive(:r).with(/.*init-metadata.*--config \/var\/storage\/test\/2\/vhost-backend.conf $/, stdin: "")
unencrypted_vhost_sv.vhost_backend_create_metadata(nil)
end
end
describe "#vhost_backend_create_service_file" do
it "can create vhost backend service file for encrypted vhost" do
service_file = "/etc/systemd/system/test-2-storage.service"
expect(File).to receive(:write).with(service_file, /vhost-backend.conf --kek/)
encrypted_vhost_sv.vhost_backend_create_service_file
end
it "can create vhost backend service file for unencrypted vhost" do
service_file = "/etc/systemd/system/test-2-storage.service"
expect(File).to receive(:write).with(service_file, /vhost-backend\.conf \nRestart=always/)
unencrypted_vhost_sv.vhost_backend_create_service_file
end
end
describe "#vhost_backend_start" do
it "can start an encrypted vhost backend" do
algorithm = "aes-256-gcm"
cipher = OpenSSL::Cipher.new(algorithm)
key_wrapping_secrets = {
"algorithm" => algorithm,
"key" => Base64.encode64(cipher.random_key),
"init_vector" => Base64.encode64(cipher.random_iv),
"auth_data" => "Ubicloud-Test-Auth"
}
kek_pipe = "/var/storage/test/2/kek.pipe"
expect(encrypted_vhost_sv).to receive(:rm_if_exists).with(kek_pipe)
expect(File).to receive(:mkfifo).with(kek_pipe, 0o600)
expect(FileUtils).to receive(:chown).with("test", "test", kek_pipe)
expect(encrypted_vhost_sv).to receive(:r).with("systemctl stop test-2-storage.service")
expect(encrypted_vhost_sv).to receive(:r).with("systemctl start test-2-storage.service")
expect(File).to receive(:write).with(kek_pipe, /aes256-gcm/)
encrypted_vhost_sv.vhost_backend_start(key_wrapping_secrets)
end
it "can start an unencrypted vhost backend" do
expect(unencrypted_vhost_sv).to receive(:r).with("systemctl stop test-2-storage.service")
expect(unencrypted_vhost_sv).to receive(:r).with("systemctl start test-2-storage.service")
unencrypted_vhost_sv.vhost_backend_start(nil)
end
end
describe "#systemd_io_rate_limits" do
it "returns rate limits if they are set" do
sv = described_class.new("test", {
"disk_index" => 1,
"device_id" => "xyz01",
"max_read_mbytes_per_sec" => 2000,
"max_write_mbytes_per_sec" => 3000
})
expect(sv).to receive(:persistent_device_id).with("/var/storage/test/1").and_return("/dev/disk/by-id/dev1")
expect(sv.systemd_io_rate_limits).to eq(<<~RESULT
IOReadBandwidthMax=/dev/disk/by-id/dev1 2097152000
IOWriteBandwidthMax=/dev/disk/by-id/dev1 3145728000
RESULT
.strip)
end
it "returns empty string if no rate limits are set" do
sv = described_class.new("test", {
"disk_index" => 1,
"device_id" => "xyz01"
})
expect(sv.systemd_io_rate_limits).to eq("")
end
end
describe "#persistent_device_id" do
it "returns the persistent device id for a path" do
paths = ["/dev/disk/by-id/md-name-rescue:0", "/dev/disk/by-id/md-uuid-8e4083eb:1111111:f1c64530:5faaca1b", "/dev/disk/by-id/md-uuid-8e4083eb:4c4a19fb:f1c64530:5faaca1b"]
expect(Dir).to receive(:[]).with("/dev/disk/by-id/*").and_return(paths)
expect(File).to receive(:realpath).with(paths[0]).and_return("/dev/md0")
expect(File).to receive(:realpath).with(paths[1]).and_return("/dev/md1")
expect(File).to receive(:realpath).with(paths[2]).and_return("/dev/md0")
expect(File).to receive(:stat).with("/dev/md0").and_return(instance_double(File::Stat, rdev_major: 8, rdev_minor: 0)).twice
expect(File).to receive(:stat).with("/dev/md1").and_return(instance_double(File::Stat, rdev_major: 9, rdev_minor: 0))
expect(File).to receive(:stat).with("storage_path").and_return(instance_double(File::Stat, dev_major: 8, dev_minor: 0))
expect(encrypted_sv.persistent_device_id("storage_path")).to eq(paths.last)
end
it "returns the persistent device id for a path with NVMe device" do
paths = ["nvme-SAMSUNG_MZQL21T9HCJR-00A07_S64GNN0WC00472", "nvme-SAMSUNG_MZQL21T9HCJR-00A07_S64GNN0WC00475",
"nvme-eui.3634473057c004720025384e00000001", "nvme-eui.3634473057c004750025384e00000001"]
expect(Dir).to receive(:[]).with("/dev/disk/by-id/*").and_return(paths)
expect(File).to receive(:realpath).with(paths[0]).and_return("/dev/nvme0n1")
expect(File).to receive(:realpath).with(paths[1]).and_return("/dev/nvme0n2")
expect(File).to receive(:realpath).with(paths[2]).and_return("/dev/nvme0n1")
expect(File).to receive(:realpath).with(paths[3]).and_return("/dev/nvme0n2")
expect(File).to receive(:stat).with("/dev/nvme0n1").and_return(instance_double(File::Stat, rdev_major: 259, rdev_minor: 0)).twice
expect(File).to receive(:stat).with("/dev/nvme0n2").and_return(instance_double(File::Stat, rdev_major: 259, rdev_minor: 1)).twice
expect(File).to receive(:stat).with("storage_path").and_return(instance_double(File::Stat, dev_major: 259, dev_minor: 1))
expect(encrypted_sv.persistent_device_id("storage_path")).to eq(paths.last)
end
it "handles system call errors gracefully" do
paths = ["/dev/disk/by-id/md-name-rescue:0", "/dev/disk/by-id/md-uuid-8e4083eb:4c4a19fb:f1c64530:5faaca1b"]
expect(Dir).to receive(:[]).with("/dev/disk/by-id/*").and_return(paths)
expect(File).to receive(:realpath).with(paths.first).and_raise(Errno::EACCES)
expect(File).to receive(:realpath).with(paths.last).and_return("/dev/md0")
expect(File).to receive(:stat).with("/dev/md0").and_return(instance_double(File::Stat, rdev_major: 8, rdev_minor: 0))
expect(File).to receive(:stat).with("storage_path").and_return(instance_double(File::Stat, dev_major: 8, dev_minor: 0))
expect(encrypted_sv.persistent_device_id("storage_path")).to eq(paths.last)
end
it "raises an error if no matching device is found" do
expect(Dir).to receive(:[]).with("/dev/disk/by-id/*").and_return([])
expect(File).to receive(:stat).with("storage_path").and_return(instance_double(File::Stat, dev_major: 8, dev_minor: 0))
expect { encrypted_sv.persistent_device_id("storage_path") }.to raise_error RuntimeError, "No persistent device ID found for storage path: storage_path"
end
end
describe "#stop_service_if_loaded" do
it "stops the service if it is loaded" do
expect(encrypted_vhost_sv).to receive(:r).with("systemctl stop test-2-storage.service")
encrypted_vhost_sv.stop_service_if_loaded("test-2-storage.service")
end
it "does nothing if the service is not loaded" do
allow(encrypted_vhost_sv).to receive(:r).and_raise(CommandFail.new("error", "", "Unit test-2-storage.service not loaded."))
expect { encrypted_vhost_sv.stop_service_if_loaded("test-2-storage.service") }.not_to raise_error
end
it "raises an error for unexpected command failures" do
allow(encrypted_vhost_sv).to receive(:r).and_raise(CommandFail.new("unexpected error", "some output", "Some error"))
expect { encrypted_vhost_sv.stop_service_if_loaded("test-2-storage.service") }.to raise_error(CommandFail, /unexpected error/)
end
end
end