Files
ubicloud/spec/prog/aws/vpc_spec.rb
2025-08-07 02:13:08 +09:00

281 lines
18 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Prog::Aws::Vpc do
subject(:nx) {
described_class.new(st)
}
let(:st) {
Strand.create(prog: "Aws::Vpc", stack: [{"subject_id" => ps.id}], label: "create_vpc")
}
let(:ps) {
prj = Project.create(name: "test-prj")
loc = Location.create(name: "us-west-2", provider: "aws", project_id: prj.id, display_name: "aws-us-west-2", ui_name: "AWS US East 1", visible: true)
LocationCredential.create_with_id(loc.id, access_key: "test-access-key", secret_key: "test-secret-key")
ps = Prog::Vnet::SubnetNexus.assemble(prj.id, name: "test-ps", location_id: loc.id).subject
PrivateSubnetAwsResource.create_with_id(ps.id)
ps
}
let(:client) {
Aws::EC2::Client.new(stub_responses: true)
}
before do
allow(nx).to receive(:private_subnet).and_return(ps)
expect(Aws::EC2::Client).to receive(:new).with(access_key_id: "test-access-key", secret_access_key: "test-secret-key", region: "us-west-2").and_return(client)
end
describe "#create_vpc" do
it "creates a vpc" do
client.stub_responses(:describe_vpcs, vpcs: [])
expect(client).to receive(:create_vpc).with({cidr_block: ps.net4.to_s, amazon_provided_ipv_6_cidr_block: true, tag_specifications: Util.aws_tag_specifications("vpc", ps.name)}).and_return(instance_double(Aws::EC2::Types::CreateVpcResult, vpc: instance_double(Aws::EC2::Types::Vpc, vpc_id: "vpc-0123456789abcdefg")))
expect(ps.private_subnet_aws_resource).to receive(:update).with(vpc_id: "vpc-0123456789abcdefg")
expect { nx.create_vpc }.to hop("wait_vpc_created")
end
it "reuses existing vpc" do
client.stub_responses(:describe_vpcs, vpcs: [{vpc_id: "vpc-existing"}])
expect(client).not_to receive(:create_vpc)
expect(ps.private_subnet_aws_resource).to receive(:update).with(vpc_id: "vpc-existing")
expect { nx.create_vpc }.to hop("wait_vpc_created")
end
end
describe "#wait_vpc_created" do
before do
client.stub_responses(:modify_vpc_attribute)
client.stub_responses(:create_security_group, group_id: "sg-0123456789abcdefg")
client.stub_responses(:authorize_security_group_ingress)
end
it "checks if vpc is available, if not naps" do
client.stub_responses(:describe_vpcs, vpcs: [{state: "pending", vpc_id: "vpc-0123456789abcdefg"}])
expect { nx.wait_vpc_created }.to nap(1)
end
it "creates a security group and authorizes ingress" do
client.stub_responses(:describe_vpcs, vpcs: [{state: "available", vpc_id: "vpc-0123456789abcdefg"}])
expect(client).to receive(:describe_vpcs).with({filters: [{name: "vpc-id", values: ["vpc-0123456789abcdefg"]}]}).and_call_original
expect(client).to receive(:create_security_group).with({group_name: "aws-us-west-2-#{ps.ubid}", description: "Security group for aws-us-west-2-#{ps.ubid}", vpc_id: "vpc-0123456789abcdefg", tag_specifications: Util.aws_tag_specifications("security-group", ps.name)}).and_call_original
ps.firewalls.map { it.firewall_rules.map { |fw| fw.destroy } }
FirewallRule.create(firewall_id: ps.firewalls.first.id, cidr: "0.0.0.1/32", port_range: 22..80)
ps.reload
expect(ps.private_subnet_aws_resource).to receive(:vpc_id).and_return("vpc-0123456789abcdefg").at_least(:once)
expect(client).to receive(:authorize_security_group_ingress).with({group_id: "sg-0123456789abcdefg", ip_permissions: [{ip_protocol: "tcp", from_port: 22, to_port: 80, ip_ranges: [{cidr_ip: "0.0.0.1/32"}]}]}).and_call_original
expect { nx.wait_vpc_created }.to hop("create_subnet")
end
it "does not create a security group if it already exists" do
client.stub_responses(:describe_vpcs, vpcs: [{state: "available", vpc_id: "vpc-0123456789abcdefg"}])
client.stub_responses(:create_security_group, Aws::EC2::Errors::InvalidGroupDuplicate.new(nil, nil))
client.stub_responses(:describe_security_groups, security_groups: [{group_id: "sg-0123456789abcdefg"}])
ps.firewalls.map { it.firewall_rules.map { |fw| fw.destroy } }
FirewallRule.create(firewall_id: ps.firewalls.first.id, cidr: "0.0.0.1/32", port_range: 22..80)
FirewallRule.create(firewall_id: ps.firewalls.first.id, cidr: "::/32", port_range: 22..80)
ps.reload
expect { nx.wait_vpc_created }.to hop("create_subnet")
end
it "skips security group ingress rule if it already exists" do
client.stub_responses(:describe_vpcs, vpcs: [{state: "available", vpc_id: "vpc-0123456789abcdefg"}])
client.stub_responses(:authorize_security_group_ingress, Aws::EC2::Errors::InvalidPermissionDuplicate.new(nil, nil))
ps.firewalls.map { it.firewall_rules.map { |fw| fw.destroy } }
FirewallRule.create(firewall_id: ps.firewalls.first.id, cidr: "0.0.0.1/32", port_range: 22..80)
ps.reload
expect { nx.wait_vpc_created }.to hop("create_subnet")
end
end
describe "#create_subnet" do
it "creates a subnet and hops to wait_subnet_created" do
client.stub_responses(:describe_vpcs, vpcs: [{ipv_6_cidr_block_association_set: [{ipv_6_cidr_block: "2600:1f14:1000::/56"}], vpc_id: ps.name}])
client.stub_responses(:describe_subnets, subnets: [])
client.stub_responses(:create_subnet, subnet: {subnet_id: "subnet-0123456789abcdefg"})
client.stub_responses(:modify_subnet_attribute)
expect(client).to receive(:modify_subnet_attribute).with({subnet_id: "subnet-0123456789abcdefg", assign_ipv_6_address_on_creation: {value: true}}).and_call_original
expect(ps.private_subnet_aws_resource).to receive(:update).with(subnet_id: "subnet-0123456789abcdefg")
expect { nx.create_subnet }.to hop("wait_subnet_created")
end
it "reuses existing subnet" do
client.stub_responses(:describe_vpcs, vpcs: [{ipv_6_cidr_block_association_set: [{ipv_6_cidr_block: "2600:1f14:1000::/56"}], vpc_id: ps.name}])
client.stub_responses(:describe_subnets, subnets: [{subnet_id: "subnet-existing"}])
client.stub_responses(:modify_subnet_attribute)
expect(client).not_to receive(:create_subnet)
expect(ps.private_subnet_aws_resource).to receive(:update).with(subnet_id: "subnet-existing")
expect { nx.create_subnet }.to hop("wait_subnet_created")
end
end
describe "#wait_subnet_created" do
it "checks if subnet is available, if not naps" do
client.stub_responses(:describe_subnets, subnets: [{state: "pending"}])
expect { nx.wait_subnet_created }.to nap(1)
end
it "checks if subnet is available, if so hops to create_route_table" do
client.stub_responses(:describe_subnets, subnets: [{state: "available"}])
expect { nx.wait_subnet_created }.to hop("create_route_table")
end
end
describe "#create_route_table" do
it "creates a route table with new internet gateway" do
client.stub_responses(:describe_route_tables, route_tables: [{route_table_id: "rtb-0123456789abcdefg", associations: []}])
client.stub_responses(:describe_internet_gateways, internet_gateways: [])
client.stub_responses(:create_internet_gateway, internet_gateway: {internet_gateway_id: "igw-0123456789abcdefg"})
client.stub_responses(:create_route)
client.stub_responses(:attach_internet_gateway)
client.stub_responses(:associate_route_table)
expect(ps.private_subnet_aws_resource).to receive(:update).with(internet_gateway_id: "igw-0123456789abcdefg")
expect(ps.private_subnet_aws_resource).to receive(:update).with(route_table_id: "rtb-0123456789abcdefg")
expect(ps.private_subnet_aws_resource).to receive(:vpc_id).and_return("vpc-0123456789abcdefg").at_least(:once)
expect(client).to receive(:attach_internet_gateway).with({internet_gateway_id: "igw-0123456789abcdefg", vpc_id: "vpc-0123456789abcdefg"}).and_call_original
expect(client).to receive(:create_route).with({route_table_id: "rtb-0123456789abcdefg", destination_ipv_6_cidr_block: "::/0", gateway_id: "igw-0123456789abcdefg"}).and_call_original
expect(client).to receive(:create_route).with({route_table_id: "rtb-0123456789abcdefg", destination_cidr_block: "0.0.0.0/0", gateway_id: "igw-0123456789abcdefg"}).and_call_original
expect(client).to receive(:associate_route_table).with({route_table_id: "rtb-0123456789abcdefg", subnet_id: ps.private_subnet_aws_resource.subnet_id}).and_call_original
expect { nx.create_route_table }.to exit({"msg" => "subnet created"})
end
it "reuses existing internet gateway and attaches if needed" do
client.stub_responses(:describe_route_tables, route_tables: [{route_table_id: "rtb-0123456789abcdefg", associations: []}])
client.stub_responses(:describe_internet_gateways, internet_gateways: [{internet_gateway_id: "igw-existing", attachments: []}])
client.stub_responses(:create_route)
client.stub_responses(:attach_internet_gateway)
client.stub_responses(:associate_route_table)
expect(client).not_to receive(:create_internet_gateway)
expect(ps.private_subnet_aws_resource).to receive(:update).with(internet_gateway_id: "igw-existing")
expect(ps.private_subnet_aws_resource).to receive(:update).with(route_table_id: "rtb-0123456789abcdefg")
expect(ps.private_subnet_aws_resource).to receive(:vpc_id).and_return("vpc-0123456789abcdefg").at_least(:once)
expect(client).to receive(:attach_internet_gateway).with({internet_gateway_id: "igw-existing", vpc_id: "vpc-0123456789abcdefg"}).and_call_original
expect(client).to receive(:create_route).with({route_table_id: "rtb-0123456789abcdefg", destination_ipv_6_cidr_block: "::/0", gateway_id: "igw-existing"}).and_call_original
expect(client).to receive(:create_route).with({route_table_id: "rtb-0123456789abcdefg", destination_cidr_block: "0.0.0.0/0", gateway_id: "igw-existing"}).and_call_original
expect(client).to receive(:associate_route_table).with({route_table_id: "rtb-0123456789abcdefg", subnet_id: ps.private_subnet_aws_resource.subnet_id}).and_call_original
expect { nx.create_route_table }.to exit({"msg" => "subnet created"})
end
it "reuses existing internet gateway and skips attachment if already attached" do
client.stub_responses(:describe_route_tables, route_tables: [{route_table_id: "rtb-0123456789abcdefg", associations: []}])
client.stub_responses(:describe_internet_gateways, internet_gateways: [{internet_gateway_id: "igw-existing", attachments: [{vpc_id: "vpc-0123456789abcdefg"}]}])
client.stub_responses(:create_route)
client.stub_responses(:associate_route_table)
expect(client).not_to receive(:create_internet_gateway)
expect(client).not_to receive(:attach_internet_gateway)
expect(ps.private_subnet_aws_resource).to receive(:update).with(internet_gateway_id: "igw-existing")
expect(ps.private_subnet_aws_resource).to receive(:update).with(route_table_id: "rtb-0123456789abcdefg")
expect(ps.private_subnet_aws_resource).to receive(:vpc_id).and_return("vpc-0123456789abcdefg").at_least(:once)
expect(client).to receive(:create_route).with({route_table_id: "rtb-0123456789abcdefg", destination_ipv_6_cidr_block: "::/0", gateway_id: "igw-existing"}).and_call_original
expect(client).to receive(:create_route).with({route_table_id: "rtb-0123456789abcdefg", destination_cidr_block: "0.0.0.0/0", gateway_id: "igw-existing"}).and_call_original
expect(client).to receive(:associate_route_table).with({route_table_id: "rtb-0123456789abcdefg", subnet_id: ps.private_subnet_aws_resource.subnet_id}).and_call_original
expect { nx.create_route_table }.to exit({"msg" => "subnet created"})
end
it "omits if the route already exists" do
client.stub_responses(:describe_route_tables, route_tables: [{route_table_id: "rtb-0123456789abcdefg", associations: []}])
client.stub_responses(:describe_internet_gateways, internet_gateways: [])
client.stub_responses(:create_internet_gateway, internet_gateway: {internet_gateway_id: "igw-0123456789abcdefg"})
client.stub_responses(:create_route, Aws::EC2::Errors::RouteAlreadyExists.new(nil, nil))
client.stub_responses(:attach_internet_gateway)
client.stub_responses(:associate_route_table)
expect(client).to receive(:describe_route_tables).with({filters: [{name: "vpc-id", values: ["vpc-0123456789abcdefg"]}]}).and_call_original
expect(client).to receive(:describe_route_tables).with({route_table_ids: ["rtb-0123456789abcdefg"]}).and_call_original
expect(ps.private_subnet_aws_resource).to receive(:update).with(internet_gateway_id: "igw-0123456789abcdefg")
expect(ps.private_subnet_aws_resource).to receive(:update).with(route_table_id: "rtb-0123456789abcdefg")
expect(ps.private_subnet_aws_resource).to receive(:vpc_id).and_return("vpc-0123456789abcdefg").at_least(:once)
expect(client).to receive(:attach_internet_gateway).with({internet_gateway_id: "igw-0123456789abcdefg", vpc_id: "vpc-0123456789abcdefg"}).and_call_original
expect(client).to receive(:create_route).with({route_table_id: "rtb-0123456789abcdefg", destination_ipv_6_cidr_block: "::/0", gateway_id: "igw-0123456789abcdefg"}).and_call_original
expect(client).to receive(:associate_route_table).with({route_table_id: "rtb-0123456789abcdefg", subnet_id: ps.private_subnet_aws_resource.subnet_id})
expect { nx.create_route_table }.to exit({"msg" => "subnet created"})
end
it "skips route table association if already associated" do
client.stub_responses(:describe_route_tables, route_tables: [{route_table_id: "rtb-0123456789abcdefg", associations: [{subnet_id: "subnet-existing"}]}])
client.stub_responses(:describe_internet_gateways, internet_gateways: [])
client.stub_responses(:create_internet_gateway, internet_gateway: {internet_gateway_id: "igw-0123456789abcdefg"})
client.stub_responses(:create_route)
client.stub_responses(:attach_internet_gateway)
expect(client).not_to receive(:associate_route_table)
expect(ps.private_subnet_aws_resource).to receive(:update).with(internet_gateway_id: "igw-0123456789abcdefg")
expect(ps.private_subnet_aws_resource).to receive(:update).with(route_table_id: "rtb-0123456789abcdefg")
expect(ps.private_subnet_aws_resource).to receive(:vpc_id).and_return("vpc-0123456789abcdefg").at_least(:once)
expect(client).to receive(:attach_internet_gateway).with({internet_gateway_id: "igw-0123456789abcdefg", vpc_id: "vpc-0123456789abcdefg"}).and_call_original
expect(client).to receive(:create_route).with({route_table_id: "rtb-0123456789abcdefg", destination_ipv_6_cidr_block: "::/0", gateway_id: "igw-0123456789abcdefg"}).and_call_original
expect(client).to receive(:create_route).with({route_table_id: "rtb-0123456789abcdefg", destination_cidr_block: "0.0.0.0/0", gateway_id: "igw-0123456789abcdefg"}).and_call_original
expect { nx.create_route_table }.to exit({"msg" => "subnet created"})
end
end
describe "#destroy" do
before do
ps.private_subnet_aws_resource.update(subnet_id: "subnet-0123456789abcdefg", security_group_id: "sg-0123456789abcdefg", internet_gateway_id: "igw-0123456789abcdefg")
client.stub_responses(:describe_subnets, subnets: [{state: "available"}])
end
it "naps if subnet is not available" do
client.stub_responses(:describe_subnets, subnets: [{state: "pending"}])
expect { nx.destroy }.to nap(5)
end
it "deletes the subnet and hops to delete_security_group" do
client.stub_responses(:delete_subnet)
expect(client).to receive(:delete_subnet).with({subnet_id: "subnet-0123456789abcdefg"}).and_call_original
expect { nx.destroy }.to hop("delete_security_group")
end
it "hops to delete_security_group if subnet is not found" do
client.stub_responses(:describe_subnets, subnets: [])
expect { nx.destroy }.to hop("delete_security_group")
end
it "naps if delete_subnet gives dependency violation" do
client.stub_responses(:delete_subnet, Aws::EC2::Errors::DependencyViolation.new(nil, nil))
expect { nx.destroy }.to nap(5)
end
it "deletes the security group and hops to delete_internet_gateway" do
client.stub_responses(:delete_security_group)
expect(client).to receive(:delete_security_group).with({group_id: "sg-0123456789abcdefg"}).and_call_original
expect { nx.delete_security_group }.to hop("delete_internet_gateway")
end
it "hops to delete_internet_gateway if security group is not found" do
client.stub_responses(:delete_security_group, Aws::EC2::Errors::InvalidGroupNotFound.new(nil, nil))
expect { nx.delete_security_group }.to hop("delete_internet_gateway")
end
it "deletes the internet gateway and hops to delete_vpc" do
client.stub_responses(:delete_internet_gateway)
client.stub_responses(:detach_internet_gateway)
expect(ps.private_subnet_aws_resource).to receive(:vpc_id).and_return("vpc-0123456789abcdefg")
expect(client).to receive(:delete_internet_gateway).with({internet_gateway_id: "igw-0123456789abcdefg"}).and_call_original
expect(client).to receive(:detach_internet_gateway).with({internet_gateway_id: "igw-0123456789abcdefg", vpc_id: "vpc-0123456789abcdefg"}).and_call_original
expect { nx.delete_internet_gateway }.to hop("delete_vpc")
end
it "hops to delete_vpc if internet gateway is not found" do
client.stub_responses(:delete_internet_gateway, Aws::EC2::Errors::InvalidInternetGatewayIDNotFound.new(nil, nil))
client.stub_responses(:detach_internet_gateway)
expect(ps.private_subnet_aws_resource).to receive(:vpc_id).and_return("vpc-0123456789abcdefg")
expect { nx.delete_internet_gateway }.to hop("delete_vpc")
end
it "deletes the vpc" do
client.stub_responses(:delete_vpc)
expect(ps.private_subnet_aws_resource).to receive(:vpc_id).and_return("vpc-0123456789abcdefg")
expect(client).to receive(:delete_vpc).with({vpc_id: "vpc-0123456789abcdefg"}).and_call_original
expect { nx.delete_vpc }.to exit({"msg" => "vpc destroyed"})
end
it "pops if vpc is not found" do
client.stub_responses(:delete_vpc, Aws::EC2::Errors::InvalidVpcIDNotFound.new(nil, nil))
expect(ps.private_subnet_aws_resource).to receive(:vpc_id).and_return("vpc-0123456789abcdefg")
expect { nx.delete_vpc }.to exit({"msg" => "vpc destroyed"})
end
end
end