When we increase the semaphore, we have already scheduled the strand for
now.
If the label is just waiting for the semaphore to increase, there's no
need for short naps.
Most of our wait labels are just waiting for the semaphore to increase,
so I extended their naps to 6 hours.
It will help decrease the load on the respirate on production
[^1]: 28dacb968b/model/semaphore.rb (L10)
583 lines
24 KiB
Ruby
583 lines
24 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "../../model/spec_helper"
|
|
|
|
RSpec.describe Prog::Vm::HostNexus do
|
|
subject(:nx) { described_class.new(st) }
|
|
|
|
let(:st) { described_class.assemble("192.168.0.1") }
|
|
let(:hetzner_ips) {
|
|
[
|
|
["127.0.0.1", "127.0.0.1", false],
|
|
["30.30.30.32/29", "127.0.0.1", true],
|
|
["2a01:4f8:10a:128b::/64", "127.0.0.1", true]
|
|
].map {
|
|
Hosting::HetznerApis::IpInfo.new(ip_address: _1, source_host_ip: _2, is_failover: _3)
|
|
}
|
|
}
|
|
|
|
let(:vms) { [instance_double(Vm, memory_gib: 1), instance_double(Vm, memory_gib: 2)] }
|
|
let(:spdk_installations) { [instance_double(SpdkInstallation, cpu_count: 4, hugepages: 4)] }
|
|
let(:vm_host_slices) { [instance_double(VmHostSlice, name: "standard1"), instance_double(VmHostSlice, name: "standard2")] }
|
|
let(:vm_host) { instance_double(VmHost, spdk_installations: spdk_installations, vms: vms, slices: vm_host_slices, id: "1d422893-2955-4c2c-b41c-f2ec70bcd60d", spdk_cpu_count: 2) }
|
|
let(:sshable) { instance_double(Sshable, raw_private_key_1: "bogus") }
|
|
|
|
before do
|
|
allow(nx).to receive_messages(vm_host: vm_host, sshable: sshable)
|
|
allow(sshable).to receive(:start_fresh_session).and_return(instance_double(Net::SSH::Connection::Session))
|
|
end
|
|
|
|
describe ".assemble" do
|
|
it "fails if location doesn't exist" do
|
|
expect {
|
|
described_class.assemble("127.0.0.1", location_id: nil)
|
|
}.to raise_error RuntimeError, "No existing Location"
|
|
end
|
|
|
|
it "creates addresses properly for a regular host" do
|
|
st = described_class.assemble("127.0.0.1")
|
|
expect(st).to be_a Strand
|
|
expect(st.label).to eq("start")
|
|
expect(st.subject.assigned_subnets.count).to eq(1)
|
|
expect(st.subject.assigned_subnets.first.cidr.to_s).to eq("127.0.0.1/32")
|
|
|
|
expect(st.subject.assigned_host_addresses.count).to eq(1)
|
|
expect(st.subject.assigned_host_addresses.first.ip.to_s).to eq("127.0.0.1/32")
|
|
expect(st.subject.provider).to be_nil
|
|
end
|
|
|
|
it "creates addresses properly and sets the server name for a hetzner host" do
|
|
expect(Hosting::Apis).to receive(:pull_ips).and_return(hetzner_ips)
|
|
expect(Hosting::Apis).to receive(:pull_data_center).and_return("fsn1-dc14")
|
|
expect(Hosting::Apis).to receive(:set_server_name).and_return(nil)
|
|
st = described_class.assemble("127.0.0.1", provider_name: HostProvider::HETZNER_PROVIDER_NAME, server_identifier: "1")
|
|
expect(st).to be_a Strand
|
|
expect(st.label).to eq("start")
|
|
expect(st.subject.assigned_subnets.count).to eq(3)
|
|
expect(st.subject.assigned_subnets.map { _1.cidr.to_s }.sort).to eq(["127.0.0.1/32", "30.30.30.32/29", "2a01:4f8:10a:128b::/64"].sort)
|
|
|
|
expect(st.subject.assigned_host_addresses.count).to eq(1)
|
|
expect(st.subject.assigned_host_addresses.first.ip.to_s).to eq("127.0.0.1/32")
|
|
expect(st.subject.provider_name).to eq(HostProvider::HETZNER_PROVIDER_NAME)
|
|
expect(st.subject.data_center).to eq("fsn1-dc14")
|
|
end
|
|
|
|
it "does not set the server name in development" do
|
|
expect(Config).to receive(:development?).and_return(true)
|
|
expect(Hosting::Apis).to receive(:pull_ips).and_return(hetzner_ips)
|
|
expect(Hosting::Apis).to receive(:pull_data_center).and_return("fsn1-dc14")
|
|
expect(Hosting::Apis).not_to receive(:set_server_name)
|
|
|
|
described_class.assemble("127.0.0.1", provider_name: HostProvider::HETZNER_PROVIDER_NAME, server_identifier: "1")
|
|
end
|
|
end
|
|
|
|
describe "#before_run" do
|
|
it "hops to destroy when needed" do
|
|
expect(nx).to receive(:when_destroy_set?).and_yield
|
|
expect { nx.before_run }.to hop("destroy")
|
|
end
|
|
|
|
it "does not hop to destroy if already in the destroy state" do
|
|
expect(nx).to receive(:when_destroy_set?).and_yield
|
|
expect(nx.strand).to receive(:label).and_return("destroy")
|
|
expect { nx.before_run }.not_to hop("destroy")
|
|
end
|
|
end
|
|
|
|
describe "#start" do
|
|
it "hops to setup_ssh_keys" do
|
|
expect { nx.start }.to hop("setup_ssh_keys")
|
|
end
|
|
end
|
|
|
|
describe "#setup_ssh_keys" do
|
|
it "generates a keypair if one is not set" do
|
|
expect(Config).to receive(:hetzner_ssh_private_key).and_return(nil)
|
|
expect(sshable).to receive(:raw_private_key_1).and_return(nil)
|
|
expect(sshable).to receive(:update) do |**args|
|
|
key = args[:raw_private_key_1]
|
|
expect(key).to be_instance_of String
|
|
expect(key.length).to eq 64
|
|
end
|
|
|
|
expect { nx.setup_ssh_keys }.to hop("bootstrap_rhizome")
|
|
end
|
|
|
|
it "does not generate a keypair if one is already set" do
|
|
expect(Config).to receive(:hetzner_ssh_private_key).and_return(nil)
|
|
expect(sshable).to receive(:raw_private_key_1).and_return("bogus")
|
|
expect(sshable).not_to receive(:update)
|
|
expect { nx.setup_ssh_keys }.to hop("bootstrap_rhizome")
|
|
end
|
|
|
|
it "skips if private key is not set" do
|
|
expect(Config).to receive(:hetzner_ssh_private_key).and_return(nil)
|
|
expect(Util).not_to receive(:rootish_ssh)
|
|
|
|
expect { nx.setup_ssh_keys }.to hop("bootstrap_rhizome")
|
|
end
|
|
|
|
it "adds a public key if private key is set" do
|
|
root_key = SshKey.generate
|
|
vmhost_key = SshKey.generate
|
|
test_public_keys = vmhost_key.public_key.to_s
|
|
|
|
expect(Config).to receive(:hetzner_ssh_private_key).exactly(2).and_return(root_key.private_key)
|
|
expect(Config).to receive(:operator_ssh_public_keys).and_return(nil)
|
|
expect(sshable).to receive(:keys).and_return([vmhost_key])
|
|
expect(sshable).to receive(:host).and_return("127.0.0.1")
|
|
expect(Util).to receive(:rootish_ssh).with("127.0.0.1", "root", anything, "echo '#{test_public_keys}' > ~/.ssh/authorized_keys")
|
|
|
|
expect { nx.setup_ssh_keys }.to hop("bootstrap_rhizome")
|
|
end
|
|
|
|
it "adds operational keys if set" do
|
|
root_key = SshKey.generate
|
|
vmhost_key = SshKey.generate
|
|
operational_key_1 = SshKey.generate
|
|
operational_key_2 = SshKey.generate
|
|
test_public_keys = "#{vmhost_key.public_key}\n#{operational_key_1.public_key}\n#{operational_key_2.public_key}"
|
|
|
|
expect(Config).to receive(:hetzner_ssh_private_key).exactly(2).and_return(root_key.private_key)
|
|
expect(Config).to receive(:operator_ssh_public_keys).exactly(2).and_return("#{operational_key_1.public_key}\n#{operational_key_2.public_key}")
|
|
expect(sshable).to receive(:keys).and_return([vmhost_key])
|
|
expect(sshable).to receive(:host).and_return("127.0.0.1")
|
|
expect(Util).to receive(:rootish_ssh).with("127.0.0.1", "root", anything, "echo '#{test_public_keys}' > ~/.ssh/authorized_keys")
|
|
|
|
expect { nx.setup_ssh_keys }.to hop("bootstrap_rhizome")
|
|
end
|
|
end
|
|
|
|
describe "#bootstrap_rhizome" do
|
|
it "pushes a bootstrap rhizome process" do
|
|
expect(nx).to receive(:push).with(Prog::BootstrapRhizome, {"target_folder" => "host"}).and_call_original
|
|
expect { nx.bootstrap_rhizome }.to hop("start", "BootstrapRhizome")
|
|
end
|
|
|
|
it "hops once BootstrapRhizome has returned" do
|
|
nx.strand.retval = {"msg" => "rhizome user bootstrapped and source installed"}
|
|
expect { nx.bootstrap_rhizome }.to hop("prep")
|
|
end
|
|
end
|
|
|
|
describe "#prep" do
|
|
it "starts a number of sub-programs" do
|
|
expect(vm_host).to receive(:net6).and_return(NetAddr.parse_net("2a01:4f9:2b:35a::/64"))
|
|
budded = []
|
|
expect(nx).to receive(:bud) do
|
|
budded << _1
|
|
end.at_least(:once)
|
|
|
|
expect { nx.prep }.to hop("wait_prep")
|
|
|
|
expect(budded).to eq([
|
|
Prog::Vm::PrepHost,
|
|
Prog::LearnMemory,
|
|
Prog::LearnOs,
|
|
Prog::LearnCpu,
|
|
Prog::LearnStorage,
|
|
Prog::LearnPci,
|
|
Prog::InstallDnsmasq,
|
|
Prog::SetupSysstat,
|
|
Prog::SetupNftables
|
|
])
|
|
end
|
|
|
|
it "learns the network from the host if it is not set a-priori" do
|
|
expect(vm_host).to receive(:net6).and_return(nil)
|
|
budded_learn_network = false
|
|
expect(nx).to receive(:bud) do
|
|
budded_learn_network ||= (_1 == Prog::LearnNetwork)
|
|
end.at_least(:once)
|
|
|
|
expect { nx.prep }.to hop("wait_prep")
|
|
|
|
expect(budded_learn_network).to be true
|
|
end
|
|
end
|
|
|
|
describe "#wait_prep" do
|
|
it "updates the vm_host record from the finished programs" do
|
|
expect(nx).to receive(:leaf?).and_return(true)
|
|
expect(vm_host).to receive(:update).with(total_mem_gib: 1)
|
|
expect(vm_host).to receive(:update).with(os_version: "ubuntu-22.04")
|
|
expect(vm_host).to receive(:update).with(arch: "arm64", total_cores: 4, total_cpus: 5, total_dies: 3, total_sockets: 2)
|
|
expect(nx).to receive(:reap).and_return([
|
|
instance_double(Strand, prog: "LearnMemory", exitval: {"mem_gib" => 1}),
|
|
instance_double(Strand, prog: "LearnOs", exitval: {"os_version" => "ubuntu-22.04"}),
|
|
instance_double(Strand, prog: "LearnCpu", exitval: {"arch" => "arm64", "total_sockets" => 2, "total_dies" => 3, "total_cores" => 4, "total_cpus" => 5}),
|
|
instance_double(Strand, prog: "ArbitraryOtherProg")
|
|
])
|
|
|
|
(0..4).each do |i|
|
|
expect(VmHostCpu).to receive(:create).with(vm_host_id: vm_host.id, cpu_number: i, spdk: i < 2)
|
|
end
|
|
|
|
expect { nx.wait_prep }.to hop("setup_hugepages")
|
|
end
|
|
|
|
it "crashes if an expected field is not set for LearnMemory" do
|
|
expect(nx).to receive(:reap).and_return([instance_double(Strand, prog: "LearnMemory", exitval: {})])
|
|
expect { nx.wait_prep }.to raise_error KeyError, "key not found: \"mem_gib\""
|
|
end
|
|
|
|
it "crashes if an expected field is not set for LearnCpu" do
|
|
expect(nx).to receive(:reap).and_return([instance_double(Strand, prog: "LearnCpu", exitval: {})])
|
|
expect { nx.wait_prep }.to raise_error KeyError, "key not found: \"arch\""
|
|
end
|
|
|
|
it "donates to children if they are not exited yet" do
|
|
expect(nx).to receive(:reap).and_return([])
|
|
expect(nx).to receive(:leaf?).and_return(false)
|
|
expect(nx).to receive(:donate).and_call_original
|
|
expect { nx.wait_prep }.to nap(1)
|
|
end
|
|
end
|
|
|
|
describe "#setup_hugepages" do
|
|
it "pushes the hugepage program" do
|
|
expect { nx.setup_hugepages }.to hop("start", "SetupHugepages")
|
|
end
|
|
|
|
it "hops once SetupHugepages has returned" do
|
|
nx.strand.retval = {"msg" => "hugepages installed"}
|
|
expect { nx.setup_hugepages }.to hop("setup_spdk")
|
|
end
|
|
end
|
|
|
|
describe "#setup_spdk" do
|
|
it "pushes the spdk program" do
|
|
expect(nx).to receive(:push).with(Prog::Storage::SetupSpdk,
|
|
{
|
|
"version" => Config.spdk_version,
|
|
"start_service" => false,
|
|
"allocation_weight" => 100
|
|
}).and_call_original
|
|
expect { nx.setup_spdk }.to hop("start", "Storage::SetupSpdk")
|
|
end
|
|
|
|
it "hops once SetupSpdk has returned" do
|
|
nx.strand.retval = {"msg" => "SPDK was setup"}
|
|
vmh = instance_double(VmHost)
|
|
spdk_installation = SpdkInstallation.new(cpu_count: 4)
|
|
allow(vmh).to receive_messages(
|
|
spdk_installations: [spdk_installation],
|
|
total_cores: 48,
|
|
total_cpus: 96
|
|
)
|
|
allow(nx).to receive(:vm_host).and_return(vmh)
|
|
expect(vmh).to receive(:update).with({used_cores: 2})
|
|
expect { nx.setup_spdk }.to hop("download_boot_images")
|
|
end
|
|
end
|
|
|
|
describe "#download_boot_images" do
|
|
it "pushes the download boot image program" do
|
|
expect(nx).to receive(:frame).and_return({"default_boot_images" => ["ubuntu-jammy", "github-ubuntu-2204"]})
|
|
expect(nx).to receive(:bud).with(Prog::DownloadBootImage, {"image_name" => "ubuntu-jammy"})
|
|
expect(nx).to receive(:bud).with(Prog::DownloadBootImage, {"image_name" => "github-ubuntu-2204"})
|
|
expect { nx.download_boot_images }.to hop("wait_download_boot_images")
|
|
end
|
|
end
|
|
|
|
describe "#wait_download_boot_images" do
|
|
it "hops to prep_reboot if all tasks are done" do
|
|
expect(nx).to receive(:reap).and_return([])
|
|
expect(nx).to receive(:leaf?).and_return true
|
|
|
|
expect { nx.wait_download_boot_images }.to hop("prep_reboot")
|
|
end
|
|
|
|
it "donates its time if child strands are still running" do
|
|
expect(nx).to receive(:reap).and_return([])
|
|
expect(nx).to receive(:leaf?).and_return false
|
|
expect(nx).to receive(:donate).and_call_original
|
|
|
|
expect { nx.wait_download_boot_images }.to nap(1)
|
|
end
|
|
end
|
|
|
|
describe "#wait" do
|
|
it "naps" do
|
|
expect { nx.wait }.to nap(6 * 60 * 60)
|
|
end
|
|
|
|
it "hops to prep_graceful_reboot when needed" do
|
|
expect(nx).to receive(:when_graceful_reboot_set?).and_yield
|
|
expect { nx.wait }.to hop("prep_graceful_reboot")
|
|
end
|
|
|
|
it "hops to prep_reboot when needed" do
|
|
expect(nx).to receive(:when_reboot_set?).and_yield
|
|
expect { nx.wait }.to hop("prep_reboot")
|
|
end
|
|
|
|
it "hops to prep_hardware_reset when needed" do
|
|
expect(nx).to receive(:when_hardware_reset_set?).and_yield
|
|
expect { nx.wait }.to hop("prep_hardware_reset")
|
|
end
|
|
|
|
it "hops to unavailable based on the host's available status" do
|
|
expect(nx).to receive(:when_checkup_set?).and_yield
|
|
expect(nx).to receive(:available?).and_return(false)
|
|
expect { nx.wait }.to hop("unavailable")
|
|
|
|
expect(nx).to receive(:when_checkup_set?).and_yield
|
|
expect(nx).to receive(:available?).and_return(true)
|
|
expect { nx.wait }.to nap(6 * 60 * 60)
|
|
end
|
|
end
|
|
|
|
describe "#unavailable" do
|
|
it "registers an immediate deadline if host is unavailable" do
|
|
expect(nx).to receive(:register_deadline).with("wait", 0)
|
|
expect(nx).to receive(:available?).and_return(false)
|
|
expect { nx.unavailable }.to nap(30)
|
|
end
|
|
|
|
it "hops to wait if host is available" do
|
|
expect(nx).to receive(:available?).and_return(true)
|
|
expect { nx.unavailable }.to hop("wait")
|
|
end
|
|
end
|
|
|
|
describe "#destroy" do
|
|
it "updates allocation state and naps" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("accepting")
|
|
expect(vm_host).to receive(:update).with(allocation_state: "draining")
|
|
expect { nx.destroy }.to nap(5)
|
|
end
|
|
|
|
it "waits draining" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("draining")
|
|
expect(Clog).to receive(:emit).with("Cannot destroy the vm host with active virtual machines, first clean them up").and_call_original
|
|
expect { nx.destroy }.to nap(15)
|
|
end
|
|
|
|
it "deletes and exists" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("draining")
|
|
expect(vm_host).to receive(:vms).and_return([])
|
|
expect(vm_host).to receive(:destroy)
|
|
expect(sshable).to receive(:destroy)
|
|
expect { nx.destroy }.to exit({"msg" => "vm host deleted"})
|
|
end
|
|
end
|
|
|
|
describe "host graceful reboot" do
|
|
it "prep_graceful_reboot sets allocation_state to draining if it is in accepting" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("accepting")
|
|
expect(vm_host).to receive(:update).with(allocation_state: "draining")
|
|
expect(vm_host).to receive(:vms_dataset).and_return([true])
|
|
expect { nx.prep_graceful_reboot }.to nap(30)
|
|
end
|
|
|
|
it "prep_graceful_reboot does not change allocation_state if it is already draining" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("draining")
|
|
expect(vm_host).not_to receive(:update)
|
|
expect(vm_host).to receive(:vms_dataset).and_return([true])
|
|
expect { nx.prep_graceful_reboot }.to nap(30)
|
|
end
|
|
|
|
it "prep_graceful_reboot fails if not in accepting or draining state" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("unprepared")
|
|
expect(vm_host).not_to receive(:update)
|
|
expect(vm_host).not_to receive(:vms_dataset)
|
|
expect { nx.prep_graceful_reboot }.to raise_error(RuntimeError)
|
|
end
|
|
|
|
it "prep_graceful_reboot transitions to prep_reboot if there are no VMs" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("draining")
|
|
expect(vm_host).to receive(:vms_dataset).and_return([])
|
|
expect { nx.prep_graceful_reboot }.to hop("prep_reboot")
|
|
end
|
|
|
|
it "prep_graceful_reboot transitions to nap if there are VMs" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("draining")
|
|
expect(vm_host).to receive(:vms_dataset).and_return([true])
|
|
expect { nx.prep_graceful_reboot }.to nap(30)
|
|
end
|
|
end
|
|
|
|
describe "host reboot" do
|
|
it "prep_reboot transitions to reboot" do
|
|
expect(nx).to receive(:get_boot_id).and_return("xyz")
|
|
expect(vm_host).to receive(:update).with(last_boot_id: "xyz")
|
|
expect(vms).to all receive(:update).with(display_state: "rebooting")
|
|
expect(nx).to receive(:decr_reboot)
|
|
expect { nx.prep_reboot }.to hop("reboot")
|
|
end
|
|
|
|
it "reboot naps if host sshable is not available" do
|
|
expect(sshable).to receive(:available?).and_return(false)
|
|
|
|
expect { nx.reboot }.to nap(30)
|
|
end
|
|
|
|
it "reboot naps if reboot-host returns empty string" do
|
|
expect(sshable).to receive(:available?).and_return(true)
|
|
expect(vm_host).to receive(:last_boot_id).and_return("xyz")
|
|
expect(sshable).to receive(:cmd).with("sudo host/bin/reboot-host xyz").and_return ""
|
|
|
|
expect { nx.reboot }.to nap(30)
|
|
end
|
|
|
|
it "reboot updates last_boot_id and hops to verify_spdk" do
|
|
expect(sshable).to receive(:available?).and_return(true)
|
|
expect(vm_host).to receive(:last_boot_id).and_return("xyz")
|
|
expect(sshable).to receive(:cmd).with("sudo host/bin/reboot-host xyz").and_return "pqr\n"
|
|
expect(vm_host).to receive(:update).with(last_boot_id: "pqr")
|
|
|
|
expect { nx.reboot }.to hop("verify_spdk")
|
|
end
|
|
|
|
it "verify_spdk hops to verify_hugepages if spdk started" do
|
|
expect(vm_host).to receive(:spdk_installations).and_return([
|
|
SpdkInstallation.new(version: "v1.0"),
|
|
SpdkInstallation.new(version: "v3.0")
|
|
])
|
|
expect(sshable).to receive(:cmd).with("sudo host/bin/setup-spdk verify v1.0")
|
|
expect(sshable).to receive(:cmd).with("sudo host/bin/setup-spdk verify v3.0")
|
|
expect { nx.verify_spdk }.to hop("verify_hugepages")
|
|
end
|
|
|
|
it "start_slices starts slices" do
|
|
slice1 = instance_double(VmHostSlice)
|
|
slice2 = instance_double(VmHostSlice)
|
|
expect(vm_host).to receive(:slices).and_return([slice1, slice2])
|
|
expect(slice1).to receive(:incr_start_after_host_reboot)
|
|
expect(slice2).to receive(:incr_start_after_host_reboot)
|
|
expect { nx.start_slices }.to hop("start_vms")
|
|
end
|
|
|
|
it "start_vms starts vms & becomes accepting & hops to wait if unprepared" do
|
|
expect(vms).to all receive(:incr_start_after_host_reboot)
|
|
expect(vm_host).to receive(:allocation_state).and_return("unprepared")
|
|
expect(vm_host).to receive(:update).with(allocation_state: "accepting")
|
|
expect { nx.start_vms }.to hop("wait")
|
|
end
|
|
|
|
it "start_vms starts vms & becomes accepting & hops to wait if was draining an in graceful reboot" do
|
|
expect(vm_host).to receive(:allocation_state).twice.and_return("draining")
|
|
expect(nx).to receive(:when_graceful_reboot_set?).and_yield
|
|
expect(vms).to all receive(:incr_start_after_host_reboot)
|
|
expect(vm_host).to receive(:update).with(allocation_state: "accepting")
|
|
expect { nx.start_vms }.to hop("wait")
|
|
end
|
|
|
|
it "start_vms starts vms & raises if not in draining and in graceful reboot" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("accepting")
|
|
expect(nx).to receive(:when_graceful_reboot_set?).and_yield
|
|
expect(vms).to all receive(:incr_start_after_host_reboot)
|
|
expect { nx.start_vms }.to raise_error(RuntimeError)
|
|
end
|
|
|
|
it "start_vms starts vms & hops to wait if accepting" do
|
|
expect(vms).to all receive(:incr_start_after_host_reboot)
|
|
expect(vm_host).to receive(:allocation_state).and_return("accepting")
|
|
expect { nx.start_vms }.to hop("wait")
|
|
end
|
|
|
|
it "start_vms starts vms & hops to wait if draining" do
|
|
expect(vms).to all receive(:incr_start_after_host_reboot)
|
|
expect(vm_host).to receive(:allocation_state).and_return("draining")
|
|
expect { nx.start_vms }.to hop("wait")
|
|
end
|
|
|
|
it "can get boot id" do
|
|
expect(sshable).to receive(:cmd).with("cat /proc/sys/kernel/random/boot_id").and_return("xyz\n")
|
|
expect(nx.get_boot_id).to eq("xyz")
|
|
end
|
|
end
|
|
|
|
describe "host hardware reset" do
|
|
it "prep_hardware_reset transitions to hardware_reset" do
|
|
vms_dataset = instance_double(Vm.dataset.class)
|
|
expect(vm_host).to receive(:vms_dataset).and_return(vms_dataset)
|
|
expect(vms_dataset).to receive(:update).with(display_state: "rebooting")
|
|
expect(nx).to receive(:decr_hardware_reset)
|
|
expect { nx.prep_hardware_reset }.to hop("hardware_reset")
|
|
end
|
|
|
|
it "hardware_reset transitions to reboot if is in draining state" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("draining")
|
|
expect(vm_host).to receive(:hardware_reset)
|
|
expect { nx.hardware_reset }.to hop("reboot")
|
|
end
|
|
|
|
it "hardware_reset transitions to reboot if is not in draining state but has no vms" do
|
|
expect(vm_host).to receive(:allocation_state).and_return("accepting")
|
|
vms_dataset = instance_double(Vm.dataset.class)
|
|
expect(vm_host).to receive(:vms_dataset).and_return(vms_dataset)
|
|
expect(vms_dataset).to receive(:empty?).and_return(true)
|
|
expect(vm_host).to receive(:hardware_reset)
|
|
expect { nx.hardware_reset }.to hop("reboot")
|
|
end
|
|
|
|
it "hardware_reset fails if has vms and is not in draining state" do
|
|
vms_dataset = instance_double(Vm.dataset.class)
|
|
expect(vm_host).to receive(:vms_dataset).and_return(vms_dataset)
|
|
expect(vms_dataset).to receive(:empty?).and_return(false)
|
|
expect(vm_host).to receive(:allocation_state).and_return("accepting")
|
|
expect { nx.hardware_reset }.to raise_error RuntimeError, "Host has VMs and is not in draining state"
|
|
end
|
|
end
|
|
|
|
describe "#verify_hugepages" do
|
|
it "fails if hugepagesize!=1G" do
|
|
expect(sshable).to receive(:cmd).with("cat /proc/meminfo").and_return("Hugepagesize: 2048 kB\n")
|
|
expect { nx.verify_hugepages }.to raise_error RuntimeError, "Couldn't set hugepage size to 1G"
|
|
end
|
|
|
|
it "fails if total hugepages couldn't be extracted" do
|
|
expect(sshable).to receive(:cmd).with("cat /proc/meminfo").and_return("Hugepagesize: 1048576 kB\n")
|
|
expect { nx.verify_hugepages }.to raise_error RuntimeError, "Couldn't extract total hugepage count"
|
|
end
|
|
|
|
it "fails if free hugepages couldn't be extracted" do
|
|
expect(sshable).to receive(:cmd).with("cat /proc/meminfo")
|
|
.and_return("Hugepagesize: 1048576 kB\nHugePages_Total: 5")
|
|
expect { nx.verify_hugepages }.to raise_error RuntimeError, "Couldn't extract free hugepage count"
|
|
end
|
|
|
|
it "fails if not enough hugepages for VMs" do
|
|
expect(sshable).to receive(:cmd).with("cat /proc/meminfo")
|
|
.and_return("Hugepagesize: 1048576 kB\nHugePages_Total: 5\nHugePages_Free: 2")
|
|
expect { nx.verify_hugepages }.to raise_error RuntimeError, "Not enough hugepages for VMs"
|
|
end
|
|
|
|
it "fails if used hugepages exceed spdk hugepages" do
|
|
expect(sshable).to receive(:cmd).with("cat /proc/meminfo")
|
|
.and_return("Hugepagesize: 1048576 kB\nHugePages_Total: 10\nHugePages_Free: 5")
|
|
expect { nx.verify_hugepages }.to raise_error RuntimeError, "Used hugepages exceed SPDK hugepages"
|
|
end
|
|
|
|
it "updates vm_host with hugepage stats and hops" do
|
|
expect(sshable).to receive(:cmd).with("cat /proc/meminfo")
|
|
.and_return("Hugepagesize: 1048576 kB\nHugePages_Total: 10\nHugePages_Free: 8")
|
|
expect(vm_host).to receive(:update)
|
|
.with(total_hugepages_1g: 10, used_hugepages_1g: 7)
|
|
expect { nx.verify_hugepages }.to hop("start_slices")
|
|
end
|
|
end
|
|
|
|
describe "#available?" do
|
|
it "returns the available status when disks are healthy" do
|
|
expect(sshable).to receive(:connect).and_return(nil)
|
|
expect(vm_host).to receive(:perform_health_checks).and_return(true)
|
|
expect(nx.available?).to be true
|
|
end
|
|
|
|
it "returns the available status when disks are not healthy" do
|
|
expect(sshable).to receive(:connect).and_return(nil)
|
|
allow(vm_host).to receive(:perform_health_checks).and_return(false)
|
|
expect(nx.available?).to be false
|
|
end
|
|
|
|
it "returns an error trying to connect to VmHost" do
|
|
expect(sshable).to receive(:connect).and_raise Sshable::SshError.new("ssh failed", "", "", nil, nil)
|
|
expect(nx.available?).to be false
|
|
end
|
|
end
|
|
end
|