package ipranger import ( "fmt" "testing" "google.golang.org/api/compute/v1" "gotest.tools/v3/assert" ) var testProjectID = "mock-edge-test-foreman" func TestFindOrCreateSubnet(t *testing.T) { r := &IPRanger{subnetSvc: &mockComputeSubnetsSvc{}} for i := 0; i < 512; i++ { subnet, err := r.FindOrCreateSubnet(testProjectID, "region0") assert.NilError(t, err) assert.Assert(t, subnet != Subnet{}) assert.Assert(t, subnet.aliasRangeQuota >= rangePerRequest, "got subnet with insufficient range %s", subnet.Name) // FindOrCreateSubnet corresponds 1:1 with cluster creation, so simulate // what should happen in external state: two alias ranges get created r.subnetSvc.(*mockComputeSubnetsSvc).createAliasRangesForGKEinSubnet(subnet) } // reload "external" state from mock service assert.NilError(t, r.InitState(testProjectID), "failed to re-initialize state after updating mock service") assert.Assert(t, len(r.stores[testProjectID].subnets["region0"]) == 35) // keep this around for cheap debugging // for _, nets := range r.stores[testProjectID].subnets { // for k, v := range nets { // fmt.Println(k, v.Name, v.aliasRangeQuota, v.ipRange, v.region, v.Network) // } // } } func TestInitState(t *testing.T) { r := &IPRanger{subnetSvc: &mockComputeSubnetsSvc{}} assert.NilError(t, r.InitState(testProjectID), "failed to initialize state") // single region, "region0" assert.Assert(t, len(r.stores[testProjectID].subnets) == 1) // 3 subnets in region assert.Assert(t, len(r.stores[testProjectID].subnets["region0"]) == 3) } func TestFindAvailableSubnet(t *testing.T) { r := &IPRanger{subnetSvc: &mockComputeSubnetsSvc{}} assert.NilError(t, r.InitState(testProjectID), "failed to initialize state") // using mock state ok, subnet := r.FindAvailableSubnet(testProjectID, "region0") assert.Assert(t, ok, "expected to find subnet with available range space, but didnt") assert.Assert(t, subnet != Subnet{}) // literal test states // no ranges in any region r.stores[testProjectID].subnets = map[string]map[string]Subnet{ "region0": { "subnet0": { aliasRangeQuota: 1, }, "subnet1": { aliasRangeQuota: 0, }, }, } ok, subnet = r.FindAvailableSubnet(testProjectID, "region0") assert.Assert(t, !ok, "expected to not find subnet with available range space, but did") assert.Assert(t, subnet == Subnet{}, "expected subnet to be empty") // no ranges in region0, ranges in region1 r.stores[testProjectID].subnets = map[string]map[string]Subnet{ "region0": { "subnet0": { aliasRangeQuota: 1, }, }, "region1": { "subnet0": { Name: "subnet0", aliasRangeQuota: 2, }, }, } ok, subnet = r.FindAvailableSubnet(testProjectID, "region0") assert.Assert(t, !ok, "expected to not find subnet with available range space, but did") assert.Assert(t, subnet == Subnet{}, "expected subnet to be empty") ok, subnet = r.FindAvailableSubnet(testProjectID, "region1") assert.Assert(t, ok, "expected to find subnet with available range space, but didnt") assert.Assert(t, subnet != Subnet{}) // no ranges if no regions r.stores[testProjectID].subnets = map[string]map[string]Subnet{} ok, subnet = r.FindAvailableSubnet(testProjectID, "region0") assert.Assert(t, !ok, "expected to not find subnet with available range space, but did") assert.Assert(t, subnet == Subnet{}, "expected subnet to be empty") } func TestNextSubnetName(t *testing.T) { r := &IPRanger{subnetSvc: &mockComputeSubnetsSvc{}} assert.NilError(t, r.InitState(testProjectID), "failed to initialize state") next := r.nextSubnetName(testProjectID) assert.Assert(t, "subnet2" == next, "incorrect next subnet name, should be incrementally increasing") } func TestBuildRangeStore(t *testing.T) { r := &IPRanger{subnetSvc: &mockComputeSubnetsSvc{}} snlist, err := r.subnetSvc.List(testProjectID) assert.NilError(t, err) store, err := buildRangeStore(snlist) assert.NilError(t, err) // empirically observed values. ensures calculations dont drift given the same input assert.Assert(t, len(store.ipset.Ranges()) == 1, "incorrect amount of ip ranges in built set") c := 0 for { _, newSet, ok := store.ipset.RemoveFreePrefix(Netmask) if !ok { break } store.ipset = newSet c++ } assert.Assert(t, 4086 == c, "incorrect amount of allocatable /%d ranges", Netmask) } // Mock service implementations type mockComputeSubnetsSvc struct { fakeState computeSubnetAggregatedList } func (g *mockComputeSubnetsSvc) List(project string) (computeSubnetAggregatedList, error) { if g.fakeState == nil { g.fakeState = computeSubnetAggregatedList{ "regions/region0": compute.SubnetworksScopedList{ Subnetworks: []*compute.Subnetwork{ { Name: "subnet0", Region: toRegionURI(project, "region0"), IpCidrRange: "10.0.0.0/20", SecondaryIpRanges: []*compute.SubnetworkSecondaryRange{ { RangeName: "alias-range0", IpCidrRange: "10.0.32.0/20", }, { RangeName: "alias-range1", IpCidrRange: "10.0.48.0/20", }, }, }, { Name: "subnet1", Region: toRegionURI(project, "region0"), IpCidrRange: "10.0.16.0/20", }, { Name: "default", Region: toRegionURI(project, "region0"), IpCidrRange: "10.0.64.0/20", }, }, }, } } return g.fakeState, nil } func (g *mockComputeSubnetsSvc) Create(project, ipRange, region, name string) (*compute.Subnetwork, error) { toCreate := &compute.Subnetwork{ Name: name, Region: toRegionURI(project, region), IpCidrRange: ipRange, SecondaryIpRanges: []*compute.SubnetworkSecondaryRange{}, } rkey := fmt.Sprintf("regions/%s", region) sns := g.fakeState[rkey].Subnetworks g.fakeState[rkey] = compute.SubnetworksScopedList{ Subnetworks: append(sns, toCreate), } return toCreate, nil } func (g *mockComputeSubnetsSvc) createAliasRangesForGKEinSubnet(subnet Subnet) { rkey := fmt.Sprintf("regions/%s", subnet.region) for _, s := range g.fakeState[rkey].Subnetworks { if s.Name == subnet.Name { s.SecondaryIpRanges = append(s.SecondaryIpRanges, &compute.SubnetworkSecondaryRange{ RangeName: "fake-gkeABC-pods-range", IpCidrRange: "10.0.0.0/21", }) s.SecondaryIpRanges = append(s.SecondaryIpRanges, &compute.SubnetworkSecondaryRange{ RangeName: "fake-gkeABC-services-range", IpCidrRange: "10.0.8.0/21", }) } } }