Files
ubicloud/spec/prog/test/vm_spec.rb
Jeremy Evans 4b819d3cb2 Change all create_with_id to create
It hasn't been necessary to use create_with_id since
ebc79622df, in December 2024.

I have plans to introduce:

```ruby
def create_with_id(id, values)
  obj = new(values)
  obj.id = id
  obj.save_changes
end
```

This will make it easier to use the same id when creating
multiple objects.  The first step is removing the existing
uses of create_with_id.
2025-08-06 01:55:51 +09:00

259 lines
11 KiB
Ruby

# frozen_string_literal: true
require_relative "../../model/spec_helper"
require "netaddr"
RSpec.describe Prog::Test::Vm do
subject(:vm_test) {
described_class.new(Strand.new(prog: "Test::Vm"))
}
let(:sshable) {
instance_double(Sshable)
}
before {
subnet1 = instance_double(PrivateSubnet, id: "subnet1")
subnet2 = instance_double(PrivateSubnet, id: "subnet2")
main_storage_volume = instance_double(VmStorageVolume, device_path: "/dev/disk/by-id/disk_0")
extra_storage_volume = instance_double(VmStorageVolume, device_path: "/dev/disk/by-id/disk_1")
nic1 = instance_double(Nic,
private_ipv6: NetAddr::IPv6Net.parse("fd01:0db8:85a1::/64"),
private_ipv4: NetAddr::IPv4Net.parse("192.168.0.1/32"))
vm1 = instance_double(Vm, id: "vm1",
private_subnets: [subnet1],
ephemeral_net4: "1.1.1.1",
ephemeral_net6: NetAddr::IPv6Net.parse("2001:0db8:85a1::/64"),
nics: [nic1],
vm_storage_volumes: [main_storage_volume, extra_storage_volume])
nic2 = instance_double(Nic,
private_ipv6: NetAddr::IPv6Net.parse("fd01:0db8:85a2::/64"),
private_ipv4: NetAddr::IPv4Net.parse("192.168.0.2/32"))
vm2 = instance_double(Vm, id: "vm2",
private_subnets: [subnet1],
ephemeral_net4: "1.1.1.2",
ephemeral_net6: NetAddr::IPv6Net.parse("2001:0db8:85a2::/64"),
nics: [nic2])
nic3 = instance_double(Nic,
private_ipv6: NetAddr::IPv6Net.parse("fd01:0db8:85a3::/64"),
private_ipv4: NetAddr::IPv4Net.parse("192.168.0.3/32"))
vm3 = instance_double(Vm, id: "vm3",
private_subnets: [subnet2],
ephemeral_net4: "1.1.1.3",
ephemeral_net6: NetAddr::IPv6Net.parse("2001:0db8:85a3::/64"),
nics: [nic3])
project = Project.create(name: "default")
allow(project).to receive(:vms).and_return([vm1, vm2, vm3])
allow(vm1).to receive(:project).and_return project
allow(vm_test).to receive_messages(sshable: sshable, vm: vm1)
}
describe "#start" do
it "hops to verify_dd" do
expect { vm_test.start }.to hop("verify_dd")
end
end
describe "#verify_dd" do
it "verifies dd" do
expect(sshable).to receive(:cmd).with("dd if=/dev/urandom of=~/1.txt bs=512 count=1000000")
expect(sshable).to receive(:cmd).with("sync ~/1.txt")
expect(sshable).to receive(:cmd).with("ls -s ~/1.txt").and_return "500004 /home/xyz/1.txt"
expect { vm_test.verify_dd }.to hop("install_packages")
end
it "fails to verify if size is not in expected range" do
expect(sshable).to receive(:cmd).with("dd if=/dev/urandom of=~/1.txt bs=512 count=1000000")
expect(sshable).to receive(:cmd).with("sync ~/1.txt")
expect(sshable).to receive(:cmd).with("ls -s ~/1.txt").and_return "300 /home/xyz/1.txt"
expect(vm_test.strand).to receive(:update).with(exitval: {msg: "unexpected size after dd"})
expect { vm_test.verify_dd }.to hop("failed")
end
end
describe "#install_packages" do
it "installs packages for ubuntu images and hops to next step" do
expect(vm_test).to receive(:vm).and_return(instance_double(Vm, boot_image: "ubuntu-jammy")).at_least(:once)
expect(sshable).to receive(:cmd).with("sudo apt update")
expect(sshable).to receive(:cmd).with("sudo apt install -y build-essential fio")
expect { vm_test.install_packages }.to hop("verify_extra_disks")
end
it "installs packages for debian images and hops to next step" do
expect(vm_test).to receive(:vm).and_return(instance_double(Vm, boot_image: "debian-12")).at_least(:once)
expect(sshable).to receive(:cmd).with("sudo apt update")
expect(sshable).to receive(:cmd).with("sudo apt install -y build-essential fio")
expect { vm_test.install_packages }.to hop("verify_extra_disks")
end
it "installs packages for almalinux images and hops to next step" do
expect(vm_test).to receive(:vm).and_return(instance_double(Vm, boot_image: "almalinux-9")).at_least(:once)
expect(sshable).to receive(:cmd).with("sudo dnf check-update || [ $? -eq 100 ]")
expect(sshable).to receive(:cmd).with("sudo dnf install -y gcc gcc-c++ make fio")
expect { vm_test.install_packages }.to hop("verify_extra_disks")
end
it "fails to install packages if the vm has unexpected boot image" do
expect(vm_test).to receive(:vm).and_return(instance_double(Vm, boot_image: "windows")).at_least(:once)
expect(vm_test.strand).to receive(:update).with(exitval: {msg: "unexpected boot image: windows"})
expect { vm_test.install_packages }.to hop("failed")
end
end
describe "#umount_if_mounted" do
it "unmounts if mounted" do
mount_path = "/home/ubi/mnt0"
expect(sshable).to receive(:cmd).with("sudo umount #{mount_path}")
expect { vm_test.umount_if_mounted(mount_path) }.not_to raise_error
end
it "does not raise error if not mounted" do
mount_path = "/home/ubi/mnt0"
expect(sshable).to receive(:cmd).with("sudo umount #{mount_path}").and_raise(Sshable::SshError.new("sudo umount #{mount_path}", "", "umount: #{mount_path}: not mounted.\n", nil, nil))
expect { vm_test.umount_if_mounted(mount_path) }.not_to raise_error
end
it "raises error for unexpected ssh error" do
mount_path = "/home/ubi/mnt0"
expect(sshable).to receive(:cmd).with("sudo umount #{mount_path}").and_raise(Sshable::SshError.new("unexpected error", "", "", nil, nil))
expect { vm_test.umount_if_mounted(mount_path) }.to raise_error Sshable::SshError, /unexpected error/
end
end
describe "#verify_extra_disks" do
it "verifies extra disks" do
disk_path = "/dev/disk/by-id/disk_1"
mount_path = "/home/ubi/mnt0"
expect(vm_test).to receive(:umount_if_mounted).with(mount_path)
expect(sshable).to receive(:cmd).with("mkdir -p #{mount_path}")
expect(sshable).to receive(:cmd).with("sudo mkfs.ext4 #{disk_path}")
expect(sshable).to receive(:cmd).with("sudo mount #{disk_path} #{mount_path}")
expect(sshable).to receive(:cmd).with("sudo chown ubi #{mount_path}")
expect(sshable).to receive(:cmd).with("dd if=/dev/urandom of=#{mount_path}/1.txt bs=512 count=10000")
expect(sshable).to receive(:cmd).with("sync #{mount_path}/1.txt")
expect { vm_test.verify_extra_disks }.to hop("ping_google")
end
end
describe "#ping_google" do
it "pings google and hops to next step" do
expect(sshable).to receive(:cmd).with("ping -c 2 google.com")
expect { vm_test.ping_google }.to hop("verify_io_rates")
end
end
describe "#get_read_bw_bytes" do
it "returns read bw in mbytes" do
output = {
"jobs" => [
{
"read" => {"bw_bytes" => 1048576}
}
]
}
expect(sshable).to receive(:cmd).with(/sudo fio.*/).and_return output.to_json
expect(vm_test.get_read_bw_bytes).to eq 1048576
end
end
describe "#get_write_bw_bytes" do
it "returns write bw in bytes" do
output = {
"jobs" => [
{
"write" => {"bw_bytes" => 1048576}
}
]
}
expect(sshable).to receive(:cmd).with(/sudo fio.*/).and_return output.to_json
expect(vm_test.get_write_bw_bytes).to eq 1048576
end
end
describe "#verify_io_rates" do
before {
vol = instance_double(VmStorageVolume, device_path: "/dev/disk/by-id/disk_0",
max_read_mbytes_per_sec: 200, max_write_mbytes_per_sec: 150)
allow(vm_test.vm).to receive(:vm_storage_volumes).and_return([vol])
}
it "skips if io rates are not set" do
vol = instance_double(VmStorageVolume, device_path: "/dev/disk/by-id/disk_0",
max_read_mbytes_per_sec: nil, max_write_mbytes_per_sec: nil)
allow(vm_test.vm).to receive(:vm_storage_volumes).and_return([vol]).at_least(:once)
expect { vm_test.verify_io_rates }.to hop("ping_vms_in_subnet")
end
it "verifies io rates" do
expect(vm_test).to receive(:get_read_bw_bytes).and_return 180 * 1024 * 1024
expect(vm_test).to receive(:get_write_bw_bytes).and_return 150 * 1024 * 1024
expect { vm_test.verify_io_rates }.to hop("ping_vms_in_subnet")
end
it "fails if read mbytes per sec exceeds the limit" do
expect(vm_test).to receive(:get_read_bw_bytes).and_return 280 * 1024 * 1024
expect(vm_test.strand).to receive(:update).with(exitval: {msg: "exceeded read bw limit: 293601280"})
expect { vm_test.verify_io_rates }.to hop("failed")
end
it "fails if write mbytes per sec exceeds the limit" do
expect(vm_test).to receive(:get_read_bw_bytes).and_return 200 * 1024 * 1024
expect(vm_test).to receive(:get_write_bw_bytes).and_return 320 * 1024 * 1024
expect(vm_test.strand).to receive(:update).with(exitval: {msg: "exceeded write bw limit: 335544320"})
expect { vm_test.verify_io_rates }.to hop("failed")
end
end
describe "#ping_vms_in_subnet" do
it "pings vm in same subnect and hops to next step" do
expect(sshable).to receive(:cmd).with("ping -c 2 1.1.1.2")
expect(sshable).to receive(:cmd).with("ping -c 2 192.168.0.2")
expect(sshable).to receive(:cmd).with("ping -c 2 2001:db8:85a2::2")
expect(sshable).to receive(:cmd).with("ping -c 2 fd01:db8:85a2::2")
expect { vm_test.ping_vms_in_subnet }.to hop("ping_vms_not_in_subnet")
end
end
describe "#ping_vms_not_in_subnet" do
it "fails to ping private interfaces of vms not in the same subnect and hops to next step" do
expect(sshable).to receive(:cmd).with("ping -c 2 1.1.1.3")
expect(sshable).to receive(:cmd).with("ping -c 2 192.168.0.3").and_raise Sshable::SshError.new("ping failed", "", "", nil, nil)
expect(sshable).to receive(:cmd).with("ping -c 2 2001:db8:85a3::2")
expect(sshable).to receive(:cmd).with("ping -c 2 fd01:db8:85a3::2").and_raise Sshable::SshError.new("ping failed", "", "", nil, nil)
expect { vm_test.ping_vms_not_in_subnet }.to hop("finish")
end
it "raises error if pinging private ipv4 of vms in other subnets succeed" do
expect(sshable).to receive(:cmd).with("ping -c 2 1.1.1.3")
expect(sshable).to receive(:cmd).with("ping -c 2 192.168.0.3")
expect(sshable).to receive(:cmd).with("ping -c 2 2001:db8:85a3::2")
expect(sshable).to receive(:cmd).with("ping -c 2 fd01:db8:85a3::2").and_raise Sshable::SshError.new("ping failed", "", "", nil, nil)
expect { vm_test.ping_vms_not_in_subnet }.to raise_error RuntimeError, "Unexpected successful ping to private ip4 of a vm in different subnet"
end
it "raises error if pinging private ipv9 of vms in other subnets succeed" do
expect(sshable).to receive(:cmd).with("ping -c 2 1.1.1.3")
expect(sshable).to receive(:cmd).with("ping -c 2 2001:db8:85a3::2")
expect(sshable).to receive(:cmd).with("ping -c 2 fd01:db8:85a3::2")
expect { vm_test.ping_vms_not_in_subnet }.to raise_error RuntimeError, "Unexpected successful ping to private ip6 of a vm in different subnet"
end
end
describe "#finish" do
it "exits" do
expect { vm_test.finish }.to exit({"msg" => "Verified VM!"})
end
end
describe "#failed" do
it "naps" do
expect { vm_test.failed }.to nap(15)
end
end
end