Skip to content
This repository was archived by the owner on Jun 2, 2026. It is now read-only.

Commit af654f0

Browse files
committed
fix: Derived VPC Prefix avaialble prefix based on available and aquired IPs
1 parent c39c263 commit af654f0

28 files changed

Lines changed: 8960 additions & 8710 deletions

api/pkg/api/handler/vpcprefix_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,36 @@ func TestVpcPrefixHandler_Get(t *testing.T) {
11711171
})
11721172
require.NoError(t, err)
11731173

1174+
const vpcPrefixUsage28CIDR = "10.217.98.208/28"
1175+
vpcPrefixUsage28Len := 28
1176+
ipbUsage28 := testIPBlockBuildIPBlock(t, dbSession, "testipb-usage-28", site, ip, &tenant1.ID, cdbm.IPBlockRoutingTypeDatacenterOnly, "10.217.98.0", 24, cdbm.IPBlockProtocolVersionV4, false, cdbm.IPBlockStatusReady, ipu)
1177+
_, err = ipam.CreateIpamEntryForIPBlock(ctx, ipamStorage, ipbUsage28.Prefix, ipbUsage28.PrefixLength, ipbUsage28.RoutingType, ipbUsage28.InfrastructureProviderID.String(), ipbUsage28.SiteID.String())
1178+
require.NoError(t, err)
1179+
vpcPrefixUsage28 := common.TestBuildVPCPrefix(t, dbSession, "vpc-prefix-usage-28", site, tenant1, vpc1.ID, cdb.GetUUIDPtr(ipbUsage28.ID), cdb.GetStrPtr(vpcPrefixUsage28CIDR), &vpcPrefixUsage28Len, cdbm.VpcPrefixStatusReady, tnu)
1180+
common.TestBuildStatusDetail(t, dbSession, vpcPrefixUsage28.ID.String(), cdbm.VpcPrefixStatusReady, cdb.GetStrPtr("VpcPrefix has been provisioned on Site"))
1181+
vpEnt, err := cdbm.NewVpcPrefixDAO(dbSession).GetByID(ctx, nil, uuid.MustParse(vpcprefix.ID), nil)
1182+
require.NoError(t, err)
1183+
expectedVpcPrefixUsage24 := cipam.Usage{
1184+
AvailableIPs: 256,
1185+
AcquiredIPs: 0,
1186+
AvailableSmallestPrefixes: 128,
1187+
AvailablePrefixes: []string{testVPCPrefixCIDREntity(t, vpEnt)},
1188+
AcquiredPrefixes: 0,
1189+
}
1190+
expectedVpcPrefixUsage28 := cipam.Usage{
1191+
AvailableIPs: 16,
1192+
AcquiredIPs: 0,
1193+
AvailableSmallestPrefixes: 8,
1194+
AvailablePrefixes: []string{vpcPrefixUsage28CIDR},
1195+
AcquiredPrefixes: 0,
1196+
}
1197+
expectedVpcPrefixUsage24WithIface, err := cdbm.NewVpcPrefixDAO(dbSession).GetPrefixUsage(ctx, nil, vpWorkloadEnt)
1198+
require.NoError(t, err)
1199+
assert.Equal(t, uint64(256), expectedVpcPrefixUsage24WithIface.AvailableIPs)
1200+
assert.Equal(t, uint64(2), expectedVpcPrefixUsage24WithIface.AcquiredIPs)
1201+
assert.Equal(t, uint64(127), expectedVpcPrefixUsage24WithIface.AvailableSmallestPrefixes)
1202+
assert.Greater(t, expectedVpcPrefixUsage24WithIface.AcquiredPrefixes, uint64(0))
1203+
11741204
// OTEL Spanner configuration
11751205
tracer, _, ctx := common.TestCommonTraceProviderSetup(t, ctx)
11761206

@@ -1188,6 +1218,7 @@ func TestVpcPrefixHandler_Get(t *testing.T) {
11881218
expectedVpcName *string
11891219
expectetIPName *string
11901220
expectUsageStatsNonNil bool
1221+
expectUsageStats *cipam.Usage
11911222
verifyUsageAcquisitionFromIface bool
11921223
verifyChildSpanner bool
11931224
}{
@@ -1286,6 +1317,18 @@ func TestVpcPrefixHandler_Get(t *testing.T) {
12861317
expectedStatus: http.StatusOK,
12871318
queryIncludeUsageStats: cdb.GetStrPtr("true"),
12881319
expectUsageStatsNonNil: true,
1320+
expectUsageStats: &expectedVpcPrefixUsage24,
1321+
},
1322+
{
1323+
name: "success when includeUsageStats true for /28 prefix with no interfaces",
1324+
reqOrgName: tnOrg1,
1325+
user: tnu,
1326+
id: vpcPrefixUsage28.ID.String(),
1327+
expectedErr: false,
1328+
expectedStatus: http.StatusOK,
1329+
queryIncludeUsageStats: cdb.GetStrPtr("true"),
1330+
expectUsageStatsNonNil: true,
1331+
expectUsageStats: &expectedVpcPrefixUsage28,
12891332
},
12901333
{
12911334
name: "success case when includeUsageStats true with ethernet iface and IPv4 in prefix",
@@ -1296,6 +1339,7 @@ func TestVpcPrefixHandler_Get(t *testing.T) {
12961339
expectedStatus: http.StatusOK,
12971340
queryIncludeUsageStats: cdb.GetStrPtr("true"),
12981341
expectUsageStatsNonNil: true,
1342+
expectUsageStats: expectedVpcPrefixUsage24WithIface,
12991343
verifyUsageAcquisitionFromIface: true,
13001344
},
13011345
}
@@ -1362,6 +1406,14 @@ func TestVpcPrefixHandler_Get(t *testing.T) {
13621406
require.NotNil(t, rsp.IPBlock)
13631407
require.NotNil(t, rsp.UsageStats)
13641408
}
1409+
if tc.expectUsageStats != nil {
1410+
require.NotNil(t, rsp.UsageStats)
1411+
assert.Equal(t, int(tc.expectUsageStats.AvailableIPs), int(rsp.UsageStats.AvailableIPs))
1412+
assert.Equal(t, int(tc.expectUsageStats.AcquiredIPs), int(rsp.UsageStats.AcquiredIPs))
1413+
assert.Equal(t, int(tc.expectUsageStats.AvailableSmallestPrefixes), int(rsp.UsageStats.AvailableSmallestPrefixes))
1414+
assert.Equal(t, tc.expectUsageStats.AvailablePrefixes, rsp.UsageStats.AvailablePrefixes)
1415+
assert.Equal(t, int(tc.expectUsageStats.AcquiredPrefixes), int(rsp.UsageStats.AcquiredPrefixes))
1416+
}
13651417
if tc.verifyUsageAcquisitionFromIface {
13661418
require.NotNil(t, rsp.UsageStats)
13671419
assert.Greater(t, rsp.UsageStats.AcquiredPrefixes, uint64(0),

api/pkg/api/model/vpcprefix_test.go

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,18 +262,86 @@ func TestNewAPIVpcPrefix_UsageStats(t *testing.T) {
262262
usage: &ipam.Usage{
263263
AvailableIPs: 50,
264264
AcquiredIPs: 14,
265-
AvailableSmallestPrefixes: 200,
265+
AvailableSmallestPrefixes: 25,
266266
AvailablePrefixes: []string{"10.2.0.0/26", "10.2.0.64/26"},
267267
AcquiredPrefixes: 8,
268268
},
269269
want: &APIIPBlockUsageStats{
270270
AvailableIPs: 50,
271271
AcquiredIPs: 14,
272-
AvailableSmallestPrefixes: 200,
272+
AvailableSmallestPrefixes: 25,
273273
AvailablePrefixes: []string{"10.2.0.0/26", "10.2.0.64/26"},
274274
AcquiredPrefixes: 8,
275275
},
276276
},
277+
{
278+
desc: "/28 vpc prefix with no acquired usage",
279+
usage: &ipam.Usage{
280+
AvailableIPs: 16,
281+
AcquiredIPs: 0,
282+
AvailableSmallestPrefixes: 8,
283+
AvailablePrefixes: []string{"10.217.98.208/28"},
284+
AcquiredPrefixes: 0,
285+
},
286+
want: &APIIPBlockUsageStats{
287+
AvailableIPs: 16,
288+
AcquiredIPs: 0,
289+
AvailableSmallestPrefixes: 8,
290+
AvailablePrefixes: []string{"10.217.98.208/28"},
291+
AcquiredPrefixes: 0,
292+
},
293+
},
294+
{
295+
desc: "/31 vpc prefix with no acquired usage",
296+
usage: &ipam.Usage{
297+
AvailableIPs: 2,
298+
AcquiredIPs: 0,
299+
AvailableSmallestPrefixes: 1,
300+
AvailablePrefixes: []string{"10.217.98.208/31"},
301+
AcquiredPrefixes: 0,
302+
},
303+
want: &APIIPBlockUsageStats{
304+
AvailableIPs: 2,
305+
AcquiredIPs: 0,
306+
AvailableSmallestPrefixes: 1,
307+
AvailablePrefixes: []string{"10.217.98.208/31"},
308+
AcquiredPrefixes: 0,
309+
},
310+
},
311+
{
312+
desc: "/28 vpc prefix with one interface derives remaining /31 slots from unacquired IPs",
313+
usage: &ipam.Usage{
314+
AvailableIPs: 16,
315+
AcquiredIPs: 2,
316+
AvailableSmallestPrefixes: 7,
317+
AvailablePrefixes: []string{"10.217.98.208/28"},
318+
AcquiredPrefixes: 1,
319+
},
320+
want: &APIIPBlockUsageStats{
321+
AvailableIPs: 16,
322+
AcquiredIPs: 2,
323+
AvailableSmallestPrefixes: 7,
324+
AvailablePrefixes: []string{"10.217.98.208/28"},
325+
AcquiredPrefixes: 1,
326+
},
327+
},
328+
{
329+
desc: "/31 vpc prefix with one interface reports zero remaining /31 slots",
330+
usage: &ipam.Usage{
331+
AvailableIPs: 2,
332+
AcquiredIPs: 2,
333+
AvailableSmallestPrefixes: 0,
334+
AvailablePrefixes: []string{"10.217.98.208/31"},
335+
AcquiredPrefixes: 1,
336+
},
337+
want: &APIIPBlockUsageStats{
338+
AvailableIPs: 2,
339+
AcquiredIPs: 2,
340+
AvailableSmallestPrefixes: 0,
341+
AvailablePrefixes: []string{"10.217.98.208/31"},
342+
AcquiredPrefixes: 1,
343+
},
344+
},
277345
}
278346
for _, tc := range tests {
279347
t.Run(tc.desc, func(t *testing.T) {

db/pkg/db/model/vpcprefix.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -549,15 +549,20 @@ func (vpsd VpcPrefixSQLDAO) GetPrefixUsage(ctx context.Context, tx *db.Tx, vp *V
549549

550550
usage := ipamPrefix.Usage()
551551

552+
// derive the acquired IPs based on the interface count
552553
acquiredIPs := uint64(ifcCount) * 2
553-
if acquiredIPs > usage.AvailableIPs {
554-
acquiredIPs = usage.AvailableIPs
554+
availableIPs := usage.AvailableIPs
555+
if acquiredIPs > availableIPs {
556+
acquiredIPs = availableIPs
555557
}
556558

559+
// derive the available smallest prefixes based on the available IPs
560+
availableSmallestPrefixes := (availableIPs - acquiredIPs) / 2
561+
557562
return &cipam.Usage{
558-
AvailableIPs: usage.AvailableIPs,
563+
AvailableIPs: availableIPs,
559564
AcquiredIPs: acquiredIPs,
560-
AvailableSmallestPrefixes: usage.AvailableSmallestPrefixes,
565+
AvailableSmallestPrefixes: availableSmallestPrefixes,
561566
AvailablePrefixes: usage.AvailablePrefixes,
562567
AcquiredPrefixes: usage.AcquiredPrefixes,
563568
}, nil

db/pkg/db/model/vpcprefix_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"testing"
2424

2525
"github.com/stretchr/testify/assert"
26+
"github.com/stretchr/testify/require"
2627
otrace "go.opentelemetry.io/otel/trace"
2728

2829
"github.com/NVIDIA/infra-controller-rest/db/pkg/db"
@@ -595,6 +596,122 @@ func TestVpcPrefixSQLDAO_GetAll(t *testing.T) {
595596
}
596597
}
597598

599+
func TestVpcPrefixSQLDAO_GetPrefixUsage(t *testing.T) {
600+
ctx := context.Background()
601+
dbSession := testVpcPrefixInitDB(t)
602+
defer dbSession.Close()
603+
TestSetupSchema(t, dbSession)
604+
vpsd := NewVpcPrefixDAO(dbSession)
605+
606+
tests := []struct {
607+
desc string
608+
vp *VpcPrefix
609+
wantAvailableIPs uint64
610+
wantAvailableSmallestPrefixes uint64
611+
}{
612+
{
613+
desc: "/28 prefix derives /31 slots as availableIPs/2",
614+
vp: &VpcPrefix{ID: uuid.New(), Prefix: "10.217.98.208/28", PrefixLength: 28},
615+
wantAvailableIPs: 16,
616+
wantAvailableSmallestPrefixes: 8,
617+
},
618+
{
619+
desc: "/31 prefix derives one /31 slot",
620+
vp: &VpcPrefix{ID: uuid.New(), Prefix: "10.217.98.208/31", PrefixLength: 31},
621+
wantAvailableIPs: 2,
622+
wantAvailableSmallestPrefixes: 1,
623+
},
624+
{
625+
desc: "/24 prefix without CIDR suffix in prefix field",
626+
vp: &VpcPrefix{ID: uuid.New(), Prefix: "192.168.1.0", PrefixLength: 24},
627+
wantAvailableIPs: 256,
628+
wantAvailableSmallestPrefixes: 128,
629+
},
630+
}
631+
for _, tc := range tests {
632+
t.Run(tc.desc, func(t *testing.T) {
633+
got, err := vpsd.GetPrefixUsage(ctx, nil, tc.vp)
634+
require.NoError(t, err)
635+
require.NotNil(t, got)
636+
assert.Equal(t, tc.wantAvailableIPs, got.AvailableIPs)
637+
assert.Equal(t, tc.wantAvailableSmallestPrefixes, got.AvailableSmallestPrefixes)
638+
assert.Equal(t, uint64(0), got.AcquiredIPs)
639+
})
640+
}
641+
}
642+
643+
func TestVpcPrefixSQLDAO_GetPrefixUsageWithInterfaces(t *testing.T) {
644+
ctx := context.Background()
645+
dbSession := testVpcPrefixInitDB(t)
646+
defer dbSession.Close()
647+
TestSetupSchema(t, dbSession)
648+
649+
user := TestBuildUser(t, dbSession, uuid.NewString(), "test-org", nil)
650+
ip := TestBuildInfrastructureProvider(t, dbSession, "test-ip", "test-org", user)
651+
site := TestBuildSite(t, dbSession, ip, "test-site", user)
652+
tenant := TestBuildTenant(t, dbSession, "test-tenant", "test-org", user)
653+
vpc := TestBuildVPC(t, dbSession, "test-vpc", ip, tenant, site, nil, db.GetUUIDPtr(uuid.New()), nil, VpcStatusReady, user, nil)
654+
vpsd := NewVpcPrefixDAO(dbSession)
655+
656+
buildVpcPrefixWithInterface := func(t *testing.T, prefix string, prefixLength int, ipAddress string) *VpcPrefix {
657+
t.Helper()
658+
vp, err := vpsd.Create(ctx, nil, VpcPrefixCreateInput{
659+
Name: fmt.Sprintf("vp-%s", prefix),
660+
TenantOrg: tenant.Org,
661+
SiteID: site.ID,
662+
VpcID: vpc.ID,
663+
TenantID: tenant.ID,
664+
Prefix: prefix,
665+
PrefixLength: prefixLength,
666+
Status: VpcPrefixStatusReady,
667+
CreatedBy: user.ID,
668+
})
669+
require.NoError(t, err)
670+
671+
it := TestBuildInstanceType(t, dbSession, "it-usage", ip, site, user)
672+
m := TestBuildMachine(t, dbSession, ip, site, it, db.GetStrPtr("test-mt"))
673+
os := TestBuildOperatingSystem(t, dbSession, "os-usage", tenant, OperatingSystemStatusReady, user)
674+
ins := TestBuildInstance(t, dbSession, "inst-usage", tenant, ip, site, it, vpc, m, os)
675+
ifc := TestBuildInterface(t, dbSession, ins, nil, &vp.ID, true, InterfaceStatusReady)
676+
_, err = NewInterfaceDAO(dbSession).Update(ctx, nil, InterfaceUpdateInput{
677+
InterfaceID: ifc.ID,
678+
IpAddresses: []string{ipAddress},
679+
})
680+
require.NoError(t, err)
681+
return vp
682+
}
683+
684+
tests := []struct {
685+
desc string
686+
vp *VpcPrefix
687+
wantAcquiredIPs uint64
688+
wantAvailableSmallestPrefixes uint64
689+
}{
690+
{
691+
desc: "/28 with one interface derives remaining /31 slots from unacquired IPs",
692+
vp: buildVpcPrefixWithInterface(t, "10.217.98.208/28", 28, "10.217.98.209"),
693+
wantAcquiredIPs: 2,
694+
wantAvailableSmallestPrefixes: 7,
695+
},
696+
{
697+
desc: "/31 with one interface reports zero remaining /31 slots",
698+
vp: buildVpcPrefixWithInterface(t, "10.217.98.208/31", 31, "10.217.98.208"),
699+
wantAcquiredIPs: 2,
700+
wantAvailableSmallestPrefixes: 0,
701+
},
702+
}
703+
for _, tc := range tests {
704+
t.Run(tc.desc, func(t *testing.T) {
705+
got, err := vpsd.GetPrefixUsage(ctx, nil, tc.vp)
706+
require.NoError(t, err)
707+
require.NotNil(t, got)
708+
assert.Equal(t, tc.wantAcquiredIPs, got.AcquiredIPs)
709+
assert.Equal(t, tc.wantAvailableSmallestPrefixes, got.AvailableSmallestPrefixes)
710+
assert.Greater(t, got.AcquiredPrefixes, uint64(0))
711+
})
712+
}
713+
}
714+
598715
func TestVpcPrefixSQLDAO_Update(t *testing.T) {
599716
ctx := context.Background()
600717
dbSession := testVpcPrefixInitDB(t)

0 commit comments

Comments
 (0)