Files
ubicloud/spec/prog/test/connected_subnets_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

318 lines
19 KiB
Ruby

# frozen_string_literal: true
require_relative "../../model/spec_helper"
require "netaddr"
RSpec.describe Prog::Test::ConnectedSubnets do
subject(:connected_subnets_test) {
described_class.new(Strand.new(prog: "Test::ConnectedSubnets"))
}
let(:ps_multiple) {
Prog::Vnet::SubnetNexus.assemble(project.id, name: "ps-multiple", location_id: Location::HETZNER_FSN1_ID).subject
}
let(:ps_single) {
Prog::Vnet::SubnetNexus.assemble(project.id, name: "ps-single", location_id: Location::HETZNER_FSN1_ID).subject
}
let(:project) {
Project.create(name: "project1")
}
let(:sshable) {
instance_double(Sshable)
}
before do
ps_multiple
ps_single
allow(connected_subnets_test).to receive(:frame).and_return({"subnet_id_multiple" => ps_multiple.id, "subnet_id_single" => ps_single.id}).at_least(:once)
end
describe "#start" do
it "updates firewall rules for both subnets, connects them, updates the stack, and naps" do
expect(connected_subnets_test).to receive(:update_firewall_rules).with(ps_single, ps_multiple, config: :perform_tests_public_blocked)
expect(connected_subnets_test).to receive(:update_firewall_rules).with(ps_multiple, ps_single, config: :perform_tests_public_blocked)
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
vm1 = instance_double(Vm, id: "1ae5f1c2-2f48-4eac-84e3-cfe35b2a9865", sshable: sshable, ephemeral_net4: NetAddr::IPv4Net.parse("0.0.0.0"), boot_image: "debian-12")
vm2 = instance_double(Vm, id: "3f2f4ed0-88b1-49c6-b66a-0d2ed4910ad0", sshable: sshable, boot_image: "almalinux-9")
expect(ps_multiple).to receive(:vms).and_return([vm1, vm2]).at_least(:once)
expect(sshable).to receive(:cmd).with("sudo yum install -y nc")
expect(sshable).to receive(:cmd).with("sudo apt-get update && sudo apt-get install -y netcat-openbsd")
expect(sshable).to receive(:cmd).with("echo '[Unit]
Description=A lightweight port 8080 listener
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/nc -l 8080
' | sudo tee /etc/systemd/system/listening_ipv4.service > /dev/null")
expect(sshable).to receive(:cmd).with("echo '[Unit]
Description=A lightweight port 8080 listener
After=network.target
[Service]
Type=simple
ExecStart=nc -l 8080 -6
' | sudo tee /etc/systemd/system/listening_ipv6.service > /dev/null")
expect(sshable).to receive(:cmd).with("sudo systemctl daemon-reload")
expect(sshable).to receive(:cmd).with("sudo systemctl enable listening_ipv4.service")
expect(sshable).to receive(:cmd).with("sudo systemctl enable listening_ipv6.service")
expect(connected_subnets_test).to receive(:update_stack).with({"vm_to_be_connected_id" => vm1.id})
expect { connected_subnets_test.start }.to nap(5)
end
it "hops to perform_tests_public_blocked" do
expect(connected_subnets_test).to receive(:frame).and_return({"vm_to_be_connected_id" => true})
ps_multiple.strand.update(label: "wait")
ps_single.strand.update(label: "wait")
Semaphore.all.map(&:destroy)
expect { connected_subnets_test.start }.to hop("perform_tests_public_blocked")
end
end
describe "#perform_tests_public_blocked" do
it "tests connection between the two subnets and fails" do
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
expect(connected_subnets_test).to receive(:ps_single).and_return(ps_single).at_least(:once)
vm1 = instance_double(Vm, sshable: sshable, ephemeral_net4: NetAddr::IPv4Net.parse("0.0.0.0"))
vm2 = instance_double(Vm, sshable: sshable)
expect(ps_multiple).to receive(:vms).and_return([vm1, vm2]).at_least(:once)
expect(ps_single).to receive(:vms).and_return([vm2]).at_least(:once)
expect(sshable).to receive(:cmd).with("ping -c 2 google.com").at_least(:once)
expect(sshable).to receive(:cmd).with("sudo systemctl start listening_ipv4.service")
expect(sshable).to receive(:cmd).with("sudo systemctl stop listening_ipv6.service")
expect(connected_subnets_test).to receive(:test_connection).with(vm1.ephemeral_net4, vm2, should_fail: true, ipv4: true)
expect { connected_subnets_test.perform_tests_public_blocked }.to hop("perform_tests_private_ipv4")
end
end
describe "#perform_tests_private_ipv4" do
it "updates firewall rules, updates the stack, and naps" do
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
expect(connected_subnets_test).to receive(:update_firewall_rules).with(ps_multiple, ps_single, config: :perform_connected_private_ipv4)
expect(connected_subnets_test).to receive(:update_stack).with({"firewalls" => "connected_private_ipv4"})
expect(ps_multiple).to receive(:update_firewall_rules_set?).and_return(true)
expect { connected_subnets_test.perform_tests_private_ipv4 }.to nap(5)
end
it "tests connection between the two subnets and hops to perform_tests_private_ipv6" do
expect(connected_subnets_test).to receive(:frame).and_return({"firewalls" => "connected_private_ipv4"})
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
vm1 = instance_double(Vm, sshable: sshable, nics: [instance_double(Nic, private_ipv4: NetAddr::IPv4Net.parse("10.0.0.0/26"))])
vm2 = instance_double(Vm)
expect(connected_subnets_test).to receive(:vm_to_be_connected).and_return(vm1).at_least(:once)
expect(connected_subnets_test).to receive(:vm_to_connect).and_return(vm2).at_least(:once)
expect(connected_subnets_test).to receive(:vm_to_connect_outside).and_return(vm2).at_least(:once)
expect(sshable).to receive(:cmd).with("sudo systemctl start listening_ipv4.service")
expect(sshable).to receive(:cmd).with("sudo systemctl stop listening_ipv6.service")
expect(connected_subnets_test).to receive(:test_connection).with(vm1.nics.first.private_ipv4.nth(0).to_s, vm2, should_fail: false, ipv4: true)
expect(connected_subnets_test).to receive(:test_connection).with(vm1.nics.first.private_ipv4.nth(0).to_s, vm2, should_fail: true, ipv4: true)
expect { connected_subnets_test.perform_tests_private_ipv4 }.to hop("perform_tests_private_ipv6")
end
end
describe "#perform_tests_private_ipv6" do
it "updates firewall rules, updates the stack, and naps" do
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
expect(connected_subnets_test).to receive(:update_firewall_rules).with(ps_multiple, ps_single, config: :perform_connected_private_ipv6)
expect(connected_subnets_test).to receive(:update_stack).with({"firewalls" => "connected_private_ipv6"})
expect(ps_multiple).to receive(:update_firewall_rules_set?).and_return(true)
expect { connected_subnets_test.perform_tests_private_ipv6 }.to nap(5)
end
it "tests connection between the two subnets and hops to perform_blocked_private_ipv4" do
expect(connected_subnets_test).to receive(:frame).and_return({"firewalls" => "connected_private_ipv6"})
vm1 = instance_double(Vm, sshable: sshable, nics: [instance_double(Nic, private_ipv6: NetAddr::IPv6Net.parse("2001:db8::/64"))], private_ipv6: NetAddr::IPv6.parse("2001:db8::2"))
vm2 = instance_double(Vm)
expect(connected_subnets_test).to receive(:vm_to_be_connected).and_return(vm1).at_least(:once)
expect(connected_subnets_test).to receive(:vm_to_connect).and_return(vm2).at_least(:once)
expect(connected_subnets_test).to receive(:vm_to_connect_outside).and_return(vm2).at_least(:once)
expect(sshable).to receive(:cmd).with("sudo systemctl start listening_ipv6.service")
expect(sshable).to receive(:cmd).with("sudo systemctl stop listening_ipv4.service")
expect(connected_subnets_test).to receive(:test_connection).with(vm1.nics.first.private_ipv6.nth(2).to_s, vm2, should_fail: false, ipv4: false)
expect(connected_subnets_test).to receive(:test_connection).with(vm1.nics.first.private_ipv6.nth(2).to_s, vm2, should_fail: true, ipv4: false)
expect { connected_subnets_test.perform_tests_private_ipv6 }.to hop("perform_blocked_private_ipv4")
end
end
describe "#perform_blocked_private_ipv4" do
it "updates firewall rules, updates the stack, and naps" do
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
expect(connected_subnets_test).to receive(:update_firewall_rules).with(ps_multiple, ps_multiple, config: :perform_blocked_private_ipv4)
expect(connected_subnets_test).to receive(:update_stack).with({"firewalls" => "blocked_private_ipv4"})
expect(ps_multiple).to receive(:update_firewall_rules_set?).and_return(true)
expect { connected_subnets_test.perform_blocked_private_ipv4 }.to nap(5)
end
it "tests connection between the two subnets and hops to perform_blocked_private_ipv6" do
expect(connected_subnets_test).to receive(:frame).and_return({"firewalls" => "blocked_private_ipv4"})
vm1 = instance_double(Vm, sshable: sshable, nics: [instance_double(Nic, private_ipv4: NetAddr::IPv4Net.parse("10.0.0.0/26"))])
vm2 = instance_double(Vm)
expect(connected_subnets_test).to receive(:vm_to_be_connected).and_return(vm1).at_least(:once)
expect(connected_subnets_test).to receive(:vm_to_connect).and_return(vm2).at_least(:once)
expect(connected_subnets_test).to receive(:vm_to_connect_outside).and_return(vm2).at_least(:once)
expect(sshable).to receive(:cmd).with("sudo systemctl start listening_ipv4.service")
expect(sshable).to receive(:cmd).with("sudo systemctl stop listening_ipv6.service")
expect(connected_subnets_test).to receive(:test_connection).with(vm1.nics.first.private_ipv4.nth(0).to_s, vm2, should_fail: false, ipv4: true)
expect(connected_subnets_test).to receive(:test_connection).with(vm1.nics.first.private_ipv4.nth(0).to_s, vm2, should_fail: true, ipv4: true)
expect { connected_subnets_test.perform_blocked_private_ipv4 }.to hop("perform_blocked_private_ipv6")
end
end
describe "#perform_blocked_private_ipv6" do
it "updates firewall rules, updates the stack, and naps" do
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
expect(connected_subnets_test).to receive(:update_firewall_rules).with(ps_multiple, ps_multiple, config: :perform_blocked_private_ipv6)
expect(connected_subnets_test).to receive(:update_stack).with({"firewalls" => "blocked_private_ipv6"})
expect(ps_multiple).to receive(:update_firewall_rules_set?).and_return(true)
expect { connected_subnets_test.perform_blocked_private_ipv6 }.to nap(5)
end
it "tests connection between the two subnets and hops to perform_tests_public_blocked" do
expect(connected_subnets_test).to receive(:frame).and_return({"firewalls" => "blocked_private_ipv6"})
vm1 = instance_double(Vm, sshable: sshable, nics: [instance_double(Nic, private_ipv6: NetAddr::IPv6Net.parse("2001:db8::/64"))], private_ipv6: NetAddr::IPv6.parse("2001:db8::2"))
vm2 = instance_double(Vm)
expect(connected_subnets_test).to receive(:vm_to_be_connected).and_return(vm1).at_least(:once)
expect(connected_subnets_test).to receive(:vm_to_connect).and_return(vm2).at_least(:once)
expect(connected_subnets_test).to receive(:vm_to_connect_outside).and_return(vm2).at_least(:once)
expect(sshable).to receive(:cmd).with("sudo systemctl start listening_ipv6.service")
expect(sshable).to receive(:cmd).with("sudo systemctl stop listening_ipv4.service")
expect(connected_subnets_test).to receive(:test_connection).with(vm1.nics.first.private_ipv6.nth(2).to_s, vm2, should_fail: false, ipv4: false)
expect(connected_subnets_test).to receive(:test_connection).with(vm1.nics.first.private_ipv6.nth(2).to_s, vm2, should_fail: true, ipv4: false)
expect { connected_subnets_test.perform_blocked_private_ipv6 }.to hop("finish")
end
end
describe "#finish" do
it "pops a message" do
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
expect(connected_subnets_test).to receive(:ps_single).and_return(ps_single).at_least(:once)
expect(connected_subnets_test).to receive(:update_firewall_rules).with(ps_multiple, nil, config: :allow_all_traffic)
expect(connected_subnets_test).to receive(:update_firewall_rules).with(ps_single, nil, config: :allow_all_traffic)
expect(ps_multiple).to receive(:disconnect_subnet).with(ps_single)
expect(connected_subnets_test).to receive(:pop).with("Verified Connected Subnets!")
connected_subnets_test.finish
end
end
describe "#failed" do
it "naps" do
expect(connected_subnets_test).to receive(:nap).with(15)
connected_subnets_test.failed
end
end
describe ".update_firewall_rules" do
it "updates the firewall rules for different configurations" do
expect(Sequel).to receive(:pg_range).with(22..22).and_return("22..22").at_least(:once)
expect(Sequel).to receive(:pg_range).with(8080..8080).and_return("8080..8080").at_least(:once)
expect(Sequel).to receive(:pg_range).with(0..65535).and_return("0..65535").at_least(:once)
expect(Net::HTTP).to receive(:get).with(URI("https://api.ipify.org")).and_return("100.100.100.100").at_least(:once)
expect(ps_multiple.firewalls.first).to receive(:replace_firewall_rules).with([{cidr: "100.100.100.100/32", port_range: "22..22"}])
connected_subnets_test.update_firewall_rules(ps_multiple, ps_multiple, config: :perform_tests_public_blocked)
expect(ps_multiple.firewalls.first).to receive(:replace_firewall_rules).with([{cidr: "100.100.100.100/32", port_range: "22..22"}, {cidr: ps_single.net4.to_s, port_range: "8080..8080"}])
connected_subnets_test.update_firewall_rules(ps_multiple, ps_single, config: :perform_connected_private_ipv4)
expect(ps_multiple.firewalls.first).to receive(:replace_firewall_rules).with([{cidr: "100.100.100.100/32", port_range: "22..22"}, {cidr: ps_single.net6.to_s, port_range: "8080..8080"}])
connected_subnets_test.update_firewall_rules(ps_multiple, ps_single, config: :perform_connected_private_ipv6)
expect(ps_multiple.firewalls.first).to receive(:replace_firewall_rules).with([{cidr: "100.100.100.100/32", port_range: "22..22"}, {cidr: ps_multiple.net4.to_s, port_range: "8080..8080"}])
connected_subnets_test.update_firewall_rules(ps_multiple, ps_multiple, config: :perform_blocked_private_ipv4)
expect(ps_multiple.firewalls.first).to receive(:replace_firewall_rules).with([{cidr: "100.100.100.100/32", port_range: "22..22"}, {cidr: ps_multiple.net6.to_s, port_range: "8080..8080"}])
connected_subnets_test.update_firewall_rules(ps_multiple, ps_multiple, config: :perform_blocked_private_ipv6)
expect(ps_multiple.firewalls.first).to receive(:replace_firewall_rules).with([{cidr: "100.100.100.100/32", port_range: "22..22"}, {cidr: "0.0.0.0/0", port_range: "0..65535"}, {cidr: "::/0", port_range: "0..65535"}])
connected_subnets_test.update_firewall_rules(ps_multiple, nil, config: :allow_all_traffic)
expect { connected_subnets_test.update_firewall_rules(ps_multiple, ps_multiple, config: :unknown) }.to raise_error("Unknown config: unknown")
end
end
describe ".vm_to_be_connected" do
it "returns the vm to be connected" do
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
vm1 = instance_double(Vm)
vm2 = instance_double(Vm)
expect(ps_multiple).to receive(:vms).and_return([vm1, vm2]).at_least(:once)
expect(connected_subnets_test.vm_to_be_connected).to eq(vm1)
end
it "returns the vm to be connected when already connected" do
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
vm1 = instance_double(Vm, id: "vm1")
vm2 = instance_double(Vm, id: "vm2")
expect(ps_multiple).to receive(:vms).and_return([vm1, vm2]).at_least(:once)
expect(connected_subnets_test).to receive(:frame).and_return({"vm_to_be_connected_id" => vm2.id})
expect(connected_subnets_test.vm_to_be_connected).to eq(vm2)
end
end
describe ".vm_to_connect" do
it "returns the vm to connect" do
expect(connected_subnets_test).to receive(:ps_multiple).and_return(ps_multiple).at_least(:once)
vm1 = instance_double(Vm, id: "vm1")
vm2 = instance_double(Vm, id: "vm2")
expect(ps_multiple).to receive(:vms).and_return([vm1, vm2]).at_least(:once)
expect(connected_subnets_test.vm_to_connect).to eq(vm2)
end
end
describe ".vm_to_connect_outside" do
it "returns the vm to connect outside" do
expect(connected_subnets_test).to receive(:ps_single).and_return(ps_single).at_least(:once)
vm = instance_double(Vm)
expect(ps_single).to receive(:vms).and_return([vm]).at_least(:once)
expect(connected_subnets_test.vm_to_connect_outside).to eq(vm)
end
end
describe ".test_connection" do
it "tests the connection" do
to_connect_ip = "1.1.1.1"
connecting = instance_double(Vm, sshable: sshable, inhost_name: "connecting")
expect(sshable).to receive(:cmd).with("nc -zvw 1 1.1.1.1 8080 ").and_return("success!")
expect { connected_subnets_test.test_connection(to_connect_ip, connecting, should_fail: false, ipv4: true) }.not_to raise_error
expect(sshable).to receive(:cmd).with("nc -zvw 1 1.1.1.1 8080 ").and_raise("error")
expect(connected_subnets_test).to receive(:fail_test).with("connecting should be able to connect to 1.1.1.1 on port 8080")
connected_subnets_test.test_connection(to_connect_ip, connecting, should_fail: false, ipv4: true)
expect(sshable).to receive(:cmd).with("nc -zvw 1 1.1.1.1 8080 -6").and_return("success!")
expect { connected_subnets_test.test_connection(to_connect_ip, connecting, should_fail: false, ipv4: false) }.not_to raise_error
expect(sshable).to receive(:cmd).with("nc -zvw 1 1.1.1.1 8080 -6").and_raise("error")
expect(connected_subnets_test).to receive(:fail_test).with("connecting should be able to connect to 1.1.1.1 on port 8080")
connected_subnets_test.test_connection(to_connect_ip, connecting, should_fail: false, ipv4: false)
expect(sshable).to receive(:cmd).with("nc -zvw 1 1.1.1.1 8080 ").and_return("success!")
expect(connected_subnets_test).to receive(:fail_test).with("connecting should not be able to connect to 1.1.1.1 on port 8080")
connected_subnets_test.test_connection(to_connect_ip, connecting, should_fail: true, ipv4: true)
expect(sshable).to receive(:cmd).with("nc -zvw 1 1.1.1.1 8080 -6").and_return("success!")
expect(connected_subnets_test).to receive(:fail_test).with("connecting should not be able to connect to 1.1.1.1 on port 8080")
connected_subnets_test.test_connection(to_connect_ip, connecting, should_fail: true, ipv4: false)
expect(sshable).to receive(:cmd).with("nc -zvw 1 1.1.1.1 8080 ").and_raise("error")
expect(connected_subnets_test.test_connection(to_connect_ip, connecting, should_fail: true, ipv4: true)).to eq(0)
expect(sshable).to receive(:cmd).with("nc -zvw 1 1.1.1.1 8080 -6").and_raise("error")
expect(connected_subnets_test.test_connection(to_connect_ip, connecting, should_fail: true, ipv4: false)).to eq(0)
end
end
end