An unused semaphore is added to Nic Nexus which allows us to notify the Subnet to start the rekeying logic through the Nic. In the former version, Vm would directly call the Subnet Nexus to start the rekey process but if an unlucky nic didn't get enough CPU time to reach the wait_setup state, it would miss the start_rekey semaphore and since when we decr a semaphore, it is set to zero, Nic strand would get stuck on wait_setup until a manual trigger is done. Now each nic is used as the bridge between Vm and Subnet and this way, we make sure Nic strand is in the right state before subnet starts triggering different semaphores and logics.
305 lines
12 KiB
Ruby
305 lines
12 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe Prog::Vnet::NicNexus do
|
|
subject(:nx) {
|
|
described_class.new(st)
|
|
}
|
|
|
|
let(:st) { Strand.new }
|
|
let(:ps) {
|
|
PrivateSubnet.create_with_id(name: "ps", location_id: Location::HETZNER_FSN1_ID, net6: "fd10:9b0b:6b4b:8fbb::/64",
|
|
net4: "10.0.0.0/26", state: "waiting", project_id: Project.create(name: "test").id).tap { it.id = "57afa8a7-2357-4012-9632-07fbe13a3133" }
|
|
}
|
|
|
|
describe ".assemble" do
|
|
it "fails if subnet doesn't exist" do
|
|
expect {
|
|
described_class.assemble("0a9a166c-e7e7-4447-ab29-7ea442b5bb0e")
|
|
}.to raise_error RuntimeError, "Given subnet doesn't exist with the id 0a9a166c-e7e7-4447-ab29-7ea442b5bb0e"
|
|
end
|
|
|
|
it "uses ipv6_addr if passed" do
|
|
expect(PrivateSubnet).to receive(:[]).with("57afa8a7-2357-4012-9632-07fbe13a3133").and_return(ps)
|
|
expect(ps).to receive(:random_private_ipv4).and_return("10.0.0.12/32")
|
|
expect(ps).not_to receive(:random_private_ipv6)
|
|
expect(described_class).to receive(:rand).and_return(123).exactly(6).times
|
|
nic = instance_double(Nic, private_subnet: ps, id: "0a9a166c-e7e7-4447-ab29-7ea442b5bb0e")
|
|
expect(Nic).to receive(:create).with(
|
|
private_ipv6: "fd10:9b0b:6b4b:8fbb::/128",
|
|
private_ipv4: "10.0.0.12/32",
|
|
mac: "7a:7b:7b:7b:7b:7b",
|
|
private_subnet_id: "57afa8a7-2357-4012-9632-07fbe13a3133",
|
|
name: "demonic"
|
|
).and_return(nic)
|
|
expect(Strand).to receive(:create).with(prog: "Vnet::NicNexus", label: "wait_allocation").and_yield(Strand.new).and_return(Strand.new)
|
|
described_class.assemble(ps.id, ipv6_addr: "fd10:9b0b:6b4b:8fbb::/128", name: "demonic")
|
|
end
|
|
|
|
it "uses ipv4_addr if passed" do
|
|
expect(PrivateSubnet).to receive(:[]).with("57afa8a7-2357-4012-9632-07fbe13a3133").and_return(ps)
|
|
expect(ps).to receive(:random_private_ipv6).and_return("fd10:9b0b:6b4b:8fbb::/128")
|
|
expect(ps).not_to receive(:random_private_ipv4)
|
|
expect(described_class).to receive(:gen_mac).and_return("00:11:22:33:44:55")
|
|
nic = instance_double(Nic, private_subnet: ps, id: "0a9a166c-e7e7-4447-ab29-7ea442b5bb0e")
|
|
expect(Nic).to receive(:create).with(
|
|
private_ipv6: "fd10:9b0b:6b4b:8fbb::/128",
|
|
private_ipv4: "10.0.0.12/32",
|
|
mac: "00:11:22:33:44:55",
|
|
private_subnet_id: "57afa8a7-2357-4012-9632-07fbe13a3133",
|
|
name: "demonic"
|
|
).and_return(nic)
|
|
expect(Strand).to receive(:create).with(prog: "Vnet::NicNexus", label: "wait_allocation").and_yield(Strand.new).and_return(Strand.new)
|
|
described_class.assemble(ps.id, ipv4_addr: "10.0.0.12/32", name: "demonic")
|
|
end
|
|
|
|
it "hops to create_aws_nic if location is aws" do
|
|
expect(ps).to receive(:location).and_return(instance_double(Location, provider: "aws"))
|
|
expect(PrivateSubnet).to receive(:[]).with("57afa8a7-2357-4012-9632-07fbe13a3133").and_return(ps).at_least(:once)
|
|
expect(ps).to receive(:random_private_ipv6).and_return("fd10:9b0b:6b4b:8fbb::/128")
|
|
expect(ps).to receive(:random_private_ipv4).and_return("10.0.0.12/32")
|
|
expect(described_class).to receive(:gen_mac).and_return("00:11:22:33:44:55")
|
|
nic = instance_double(Nic, private_subnet: ps, id: "0a9a166c-e7e7-4447-ab29-7ea442b5bb0e")
|
|
expect(Nic).to receive(:create).with(
|
|
private_ipv6: "fd10:9b0b:6b4b:8fbb::/128",
|
|
private_ipv4: "10.0.0.12/32",
|
|
mac: "00:11:22:33:44:55",
|
|
private_subnet_id: "57afa8a7-2357-4012-9632-07fbe13a3133",
|
|
name: "demonic"
|
|
).and_return(nic)
|
|
expect(Strand).to receive(:create).with(prog: "Vnet::NicNexus", label: "create_aws_nic").and_yield(Strand.new).and_return(Strand.new)
|
|
described_class.assemble(ps.id, name: "demonic")
|
|
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 "#create_aws_nic" do
|
|
it "naps if subnet is not created" do
|
|
expect(nx).to receive(:nic).and_return(instance_double(Nic, private_subnet: instance_double(PrivateSubnet, strand: instance_double(Strand, label: "create_aws_vpc"))))
|
|
expect { nx.create_aws_nic }.to nap(10)
|
|
end
|
|
|
|
it "hops to wait_aws_nic_created if subnet is created" do
|
|
ps = Prog::Vnet::SubnetNexus.assemble(Project.create(name: "test").id)
|
|
nic = described_class.assemble(ps.id, name: "demonic").subject
|
|
ps.update(label: "wait")
|
|
expect(nx).to receive(:bud).with(Prog::Aws::Nic, {"subject_id" => nic.id}, :create_network_interface)
|
|
expect(nx).to receive(:nic).and_return(nic).at_least(:once)
|
|
expect { nx.create_aws_nic }.to hop("wait_aws_nic_created")
|
|
end
|
|
end
|
|
|
|
describe "#wait_aws_nic_created" do
|
|
it "reaps and hops to wait if leaf" do
|
|
expect(nx).to receive(:reap)
|
|
expect(nx).to receive(:leaf?).and_return(true)
|
|
expect { nx.wait_aws_nic_created }.to hop("wait")
|
|
end
|
|
|
|
it "naps if not leaf" do
|
|
expect(nx).to receive(:reap)
|
|
expect(nx).to receive(:leaf?).and_return(false)
|
|
expect { nx.wait_aws_nic_created }.to nap(10)
|
|
end
|
|
end
|
|
|
|
describe "#wait_allocation" do
|
|
it "naps if nothing to do" do
|
|
expect { nx.wait_allocation }.to nap(5)
|
|
end
|
|
|
|
it "hops to wait_setup if allocated" do
|
|
expect(nx).to receive(:when_vm_allocated_set?).and_yield
|
|
expect { nx.wait_allocation }.to hop("wait_setup")
|
|
end
|
|
end
|
|
|
|
describe "#wait_setup" do
|
|
it "naps if nothing to do" do
|
|
expect(nx).to receive(:decr_vm_allocated)
|
|
expect { nx.wait_setup }.to nap(5)
|
|
end
|
|
|
|
it "incrs add_new_nic when setup_nic is set" do
|
|
private_subnet = instance_double(PrivateSubnet)
|
|
nic = instance_double(Nic, private_subnet: private_subnet)
|
|
|
|
expect(nx).to receive(:nic).and_return(nic)
|
|
expect(nx).to receive(:decr_vm_allocated)
|
|
expect(nx).to receive(:when_setup_nic_set?).and_yield
|
|
expect(nx).to receive(:decr_setup_nic)
|
|
expect(private_subnet).to receive(:incr_add_new_nic)
|
|
expect { nx.wait_setup }.to nap(5)
|
|
end
|
|
|
|
it "starts rekeying if setup is triggered" do
|
|
expect(nx).to receive(:decr_vm_allocated)
|
|
expect(nx).to receive(:when_start_rekey_set?).and_yield
|
|
expect { nx.wait_setup }.to hop("start_rekey")
|
|
end
|
|
end
|
|
|
|
describe "#wait" do
|
|
it "naps if nothing to do" do
|
|
expect { nx.wait }.to nap(6 * 60 * 60)
|
|
end
|
|
|
|
it "hops to start rekey if needed" do
|
|
expect(nx).to receive(:when_start_rekey_set?).and_yield
|
|
expect { nx.wait }.to hop("start_rekey")
|
|
end
|
|
|
|
it "hops to repopulate if needed" do
|
|
expect(nx).to receive(:when_repopulate_set?).and_yield
|
|
ps = instance_double(PrivateSubnet, incr_refresh_keys: true)
|
|
expect(nx).to receive(:nic).and_return(instance_double(Nic, private_subnet: ps))
|
|
expect { nx.wait }.to nap(6 * 60 * 60)
|
|
end
|
|
end
|
|
|
|
describe "#rekey" do
|
|
let(:nic) { instance_double(Nic) }
|
|
|
|
before do
|
|
allow(nx).to receive(:nic).and_return(nic)
|
|
end
|
|
|
|
it "pushes rekey with setup_inbound and naps" do
|
|
expect(nx).to receive(:push).with(Prog::Vnet::RekeyNicTunnel, {}, :setup_inbound)
|
|
nx.start_rekey
|
|
end
|
|
|
|
it "hops to wait_rekey_outbound_trigger if inbound_setup is completed" do
|
|
expect(nx).to receive(:retval).and_return({"msg" => "inbound_setup is complete"})
|
|
expect(nx).to receive(:decr_start_rekey)
|
|
expect { nx.start_rekey }.to hop("wait_rekey_outbound_trigger")
|
|
end
|
|
|
|
it "if outbound setup is not triggered, just naps" do
|
|
expect(nx).to receive(:when_trigger_outbound_update_set?).and_return(false)
|
|
expect { nx.wait_rekey_outbound_trigger }.to nap(5)
|
|
end
|
|
|
|
it "if outbound setup is triggered, pushes setup_outbound and naps" do
|
|
expect(nx).to receive(:when_trigger_outbound_update_set?).and_yield
|
|
expect(nx).to receive(:decr_trigger_outbound_update)
|
|
expect(nx).to receive(:push).with(Prog::Vnet::RekeyNicTunnel, {}, :setup_outbound)
|
|
expect { nx.wait_rekey_outbound_trigger }.to nap(5)
|
|
end
|
|
|
|
it "hops to wait_rekey_old_state_drop_trigger if outbound_setup is completed" do
|
|
expect(nx).to receive(:retval).and_return({"msg" => "outbound_setup is complete"})
|
|
expect { nx.wait_rekey_outbound_trigger }.to hop("wait_rekey_old_state_drop_trigger")
|
|
end
|
|
|
|
it "wait_rekey_old_state_drop_trigger naps if trigger is not set" do
|
|
expect(nx).to receive(:when_old_state_drop_trigger_set?).and_return(false)
|
|
|
|
expect { nx.wait_rekey_old_state_drop_trigger }.to nap(5)
|
|
end
|
|
|
|
it "wait_rekey_old_state_drop_trigger pushes drop_old_state and naps if trigger is set" do
|
|
expect(nx).to receive(:when_old_state_drop_trigger_set?).and_yield
|
|
expect(nx).to receive(:decr_old_state_drop_trigger)
|
|
expect(nx).to receive(:push).with(Prog::Vnet::RekeyNicTunnel, {}, :drop_old_state)
|
|
expect { nx.wait_rekey_old_state_drop_trigger }.to nap(5)
|
|
end
|
|
|
|
it "hops to wait if drop_old_state is completed" do
|
|
expect(nx).to receive(:retval).and_return({"msg" => "drop_old_state is complete"})
|
|
expect { nx.wait_rekey_old_state_drop_trigger }.to hop("wait")
|
|
end
|
|
end
|
|
|
|
describe "#destroy" do
|
|
let(:ps) {
|
|
PrivateSubnet.create_with_id(name: "ps", location_id: Location::HETZNER_FSN1_ID, net6: "fd10:9b0b:6b4b:8fbb::/64",
|
|
net4: "1.1.1.0/26", state: "waiting", project_id: Project.create(name: "test").id).tap { it.id = "57afa8a7-2357-4012-9632-07fbe13a3133" }
|
|
}
|
|
let(:nic) {
|
|
Nic.new(private_subnet_id: ps.id,
|
|
private_ipv6: "fd10:9b0b:6b4b:8fbb:abc::",
|
|
private_ipv4: "10.0.0.1",
|
|
mac: "00:00:00:00:00:00",
|
|
encryption_key: "0x736f6d655f656e6372797074696f6e5f6b6579",
|
|
name: "default-nic").tap { it.id = "0a9a166c-e7e7-4447-ab29-7ea442b5bb0e" }
|
|
}
|
|
let(:ipsec_tunnels) {
|
|
[
|
|
instance_double(IpsecTunnel),
|
|
instance_double(IpsecTunnel)
|
|
]
|
|
}
|
|
|
|
before do
|
|
allow(nx).to receive(:nic).and_return(nic)
|
|
end
|
|
|
|
it "destroys nic" do
|
|
expect(nic).to receive(:private_subnet).and_return(ps).at_least(:once)
|
|
expect(ps).to receive(:incr_refresh_keys).and_return(true)
|
|
expect(nic).to receive(:destroy).and_return(true)
|
|
expect { nx.destroy }.to exit({"msg" => "nic deleted"})
|
|
end
|
|
|
|
it "fails if there is vm attached" do
|
|
expect(nic).to receive(:vm).and_return(true)
|
|
expect { nx.destroy }.to nap(5)
|
|
end
|
|
|
|
it "buds aws nic destroy if location is aws" do
|
|
expect(nic).to receive(:private_subnet).and_return(ps).at_least(:once)
|
|
expect(nx).to receive(:bud).with(Prog::Aws::Nic, {"subject_id" => "0a9a166c-e7e7-4447-ab29-7ea442b5bb0e"}, :destroy)
|
|
expect(ps).to receive(:location).and_return(instance_double(Location, provider: "aws"))
|
|
expect { nx.destroy }.to hop("wait_aws_nic_destroyed")
|
|
end
|
|
end
|
|
|
|
describe "#wait_aws_nic_destroyed" do
|
|
it "reaps and destroys nic if leaf" do
|
|
expect(nx).to receive(:reap)
|
|
expect(nx).to receive(:leaf?).and_return(true)
|
|
nic = instance_double(Nic, id: "0a9a166c-e7e7-4447-ab29-7ea442b5bb0e")
|
|
expect(nx).to receive(:nic).and_return(nic)
|
|
expect(nic).to receive(:destroy)
|
|
expect { nx.wait_aws_nic_destroyed }.to exit({"msg" => "nic deleted"})
|
|
end
|
|
|
|
it "naps if not leaf" do
|
|
expect(nx).to receive(:reap)
|
|
expect(nx).to receive(:leaf?).and_return(false)
|
|
expect { nx.wait_aws_nic_destroyed }.to nap(10)
|
|
end
|
|
end
|
|
|
|
describe "nic fetch" do
|
|
let(:nic) {
|
|
Nic.new(private_subnet_id: ps.id,
|
|
private_ipv6: "fd10:9b0b:6b4b:8fbb:abc::",
|
|
private_ipv4: "10.0.0.1",
|
|
mac: "00:00:00:00:00:00",
|
|
encryption_key: "0x736f6d655f656e6372797074696f6e5f6b6579",
|
|
name: "default-nic").tap { it.id = "0a9a166c-e7e7-4447-ab29-7ea442b5bb0e" }
|
|
}
|
|
|
|
before do
|
|
nx.instance_variable_set(:@nic, nic)
|
|
end
|
|
|
|
it "returns nic" do
|
|
expect(nx.nic).to eq(nic)
|
|
end
|
|
end
|
|
end
|