Files
ubicloud/spec/prog/test/vm_spec.rb
Jeremy Evans 215f09541a Make access_tag only for project <-> accounts join table
For the includers of HyperTagMethods, this changes the authorization
code and object_tag member validation code to look at the project_id
column for the object, instead of looking a row with the project and
object in the access_tag table.

This removes all calls to associate_with_project, other than
those for Account.  It removes the projects association for
the includers of HyperTagMethods, and adds a project association to
the models that didn't already have one, since there is only a single
project for each object now.

Most HyperTagMethods code is inlined into Account, since it is only
user of the code now.  Temporarily, other models will still include
HyperTagMethods for the before_destroy hook, but eventually it will
go away completely.

The associations in Projects that previous used access_tag as a join
table, are changed from many_to_many to one_to_many, except for
Account (which still uses the join table).

Project#has_resources now needs separate queries for all of the
resource classes to see if there any associated objects.

This causes a lot of fallout in the specs, but unfortunately that is
unavoidable due the extensive use of projects.first in the specs to
get the related project for the objects, as well as the extensive
use of associate_with_project.
2025-01-17 08:32:46 -08:00

262 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_with_id(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 "#verify_extra_disks" do
it "verifies extra disks" do
disk_path = "/dev/disk/by-id/disk_1"
mount_path = "/home/ubi/mnt0"
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_iops" do
it "returns iops" do
output = {
"jobs" => [
{
"read" => {"iops" => 502},
"write" => {"iops" => 601}
}
]
}
expect(sshable).to receive(:cmd).with(/sudo fio.*/).and_return output.to_json
expect(vm_test.get_iops).to eq 1103
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_ios_per_sec: 100000, 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_ios_per_sec: nil, 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_iops).and_return 99000
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 iops exceeds the limit" do
expect(vm_test).to receive(:get_iops).and_return 140000
expect(vm_test.strand).to receive(:update).with(exitval: {msg: "exceeded iops limit: 140000"})
expect { vm_test.verify_io_rates }.to hop("failed")
end
it "fails if read mbytes per sec exceeds the limit" do
expect(vm_test).to receive(:get_iops).and_return 100
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_iops).and_return 100
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