Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support NSX-T Edge Gatway IP count limits #682

Merged
merged 9 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changes/v2.25.0/682-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* Added method `NsxtEdgeGateway.GetUsedAndUnusedExternalIPAddressCountWithLimit` to count used
and unused IPs assigned to Edge Gateway. It supports a `limitTo` argument that can prevent
exhausting system resources when counting IPs in assigned subnets [GH-682]
61 changes: 56 additions & 5 deletions govcd/nsxt_edgegateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"net/netip"
"net/url"
"time"

"github.com/vmware/go-vcloud-director/v2/types/v56"
"github.com/vmware/go-vcloud-director/v2/util"
Expand Down Expand Up @@ -512,6 +513,9 @@ func (egw *NsxtEdgeGateway) GetUnusedExternalIPAddresses(requiredIpCount int, op

// GetAllUnusedExternalIPAddresses will retrieve all unassigned IP addresses for Edge Gateway It is
// similar to GetUnusedExternalIPAddresses but returns all unused IPs instead of a specific amount
//
// Note. In case a very large subnet of IPv6 is present this function might exhaust memory. Please
// use GetUnusedExternalIPAddressesWithCountLimit in such cases
func (egw *NsxtEdgeGateway) GetAllUnusedExternalIPAddresses(refresh bool) ([]netip.Addr, error) {
if refresh {
err := egw.Refresh()
Expand All @@ -524,7 +528,35 @@ func (egw *NsxtEdgeGateway) GetAllUnusedExternalIPAddresses(refresh bool) ([]net
return nil, fmt.Errorf("error getting used IP addresses for Edge Gateway: %s", err)
}

return getAllUnusedExternalIPAddresses(egw.EdgeGateway.EdgeGatewayUplinks, usedIpAddresses, netip.Prefix{})
return getAllUnusedExternalIPAddresses(egw.EdgeGateway.EdgeGatewayUplinks, usedIpAddresses, netip.Prefix{}, 0)
}

// GetUsedAndUnusedExternalIPAddressCountWithLimit will count IPs and can limit their total count up
// to 'limitTo' which can be used to count IPs with huge IPv6 subnets
//
// Return order - usedIpCount, unusedIpCount, error
func (egw *NsxtEdgeGateway) GetUsedAndUnusedExternalIPAddressCountWithLimit(refresh bool, limitTo int64) (int64, int64, error) {
if refresh {
err := egw.Refresh()
if err != nil {
return 0, 0, fmt.Errorf("error refreshing Edge Gateway: %s", err)
}
}
usedIpAddresses, err := egw.GetUsedIpAddresses(nil)
if err != nil {
return 0, 0, fmt.Errorf("error getting used IP addresses for Edge Gateway: %s", err)
}

assignedIpAddresses, err := flattenEdgeGatewayUplinkToIpSlice(egw.EdgeGateway.EdgeGatewayUplinks, limitTo)
if err != nil {
return 0, 0, fmt.Errorf("error listing all IPs in Edge Gateway: %s", err)
}

usedIpCount := int64(len(usedIpAddresses))
assignedIpCount := int64(len(assignedIpAddresses))
unusedIpCount := assignedIpCount - usedIpCount

return usedIpCount, unusedIpCount, nil
}

// GetAllocatedIpCount traverses all subnets in Edge Gateway and returns a count of allocated IP
Expand Down Expand Up @@ -890,11 +922,11 @@ func (egw *NsxtEdgeGateway) UpdateSlaacProfile(slaacProfileConfig *types.NsxtEdg
return updatedSlaacProfile, nil
}

func getAllUnusedExternalIPAddresses(uplinks []types.EdgeGatewayUplinks, usedIpAddresses []*types.GatewayUsedIpAddress, optionalSubnet netip.Prefix) ([]netip.Addr, error) {
func getAllUnusedExternalIPAddresses(uplinks []types.EdgeGatewayUplinks, usedIpAddresses []*types.GatewayUsedIpAddress, optionalSubnet netip.Prefix, limitTo int64) ([]netip.Addr, error) {
// 1. Flatten all IP ranges in Edge Gateway using Go's native 'netip.Addr' IP container instead
// of plain strings because it is more robust (supports IPv4 and IPv6 and also comparison
// operator)
assignedIpSlice, err := flattenEdgeGatewayUplinkToIpSlice(uplinks)
assignedIpSlice, err := flattenEdgeGatewayUplinkToIpSlice(uplinks, limitTo)
if err != nil {
return nil, fmt.Errorf("error listing all IPs in Edge Gateway: %s", err)
}
Expand Down Expand Up @@ -925,7 +957,7 @@ func getAllUnusedExternalIPAddresses(uplinks []types.EdgeGatewayUplinks, usedIpA
}

func getUnusedExternalIPAddress(uplinks []types.EdgeGatewayUplinks, usedIpAddresses []*types.GatewayUsedIpAddress, requiredIpCount int, optionalSubnet netip.Prefix) ([]netip.Addr, error) {
unusedIps, err := getAllUnusedExternalIPAddresses(uplinks, usedIpAddresses, optionalSubnet)
unusedIps, err := getAllUnusedExternalIPAddresses(uplinks, usedIpAddresses, optionalSubnet, 0)
if err != nil {
return nil, fmt.Errorf("error getting all unused IPs: %s", err)
}
Expand All @@ -941,9 +973,17 @@ func getUnusedExternalIPAddress(uplinks []types.EdgeGatewayUplinks, usedIpAddres

// flattenEdgeGatewayUplinkToIpSlice processes Edge Gateway Uplink structure and creates a slice of
// all available IPs
func flattenEdgeGatewayUplinkToIpSlice(uplinks []types.EdgeGatewayUplinks) ([]netip.Addr, error) {
// Note. Having a huge IPv6 block might become a long running task and potentially exhaust system
// memory. One can use 'limitTo' setting to set upper limit for number of IPs that one wants to
// retrieve. Setting `limitTo` to 0 means that not limitation is applied.
func flattenEdgeGatewayUplinkToIpSlice(uplinks []types.EdgeGatewayUplinks, limitTo int64) ([]netip.Addr, error) {
start := time.Now()
util.Logger.Printf("[TRACE] flattenEdgeGatewayUplinkToIpSlice starting at %s with limitTo %d", start.String(), limitTo)
util.Logger.Printf("[TRACE] flattenEdgeGatewayUplinkToIpSlice Edge Gateway uplink count %d", len(uplinks))
assignedIpSlice := make([]netip.Addr, 0)

var counter int64

for _, edgeGatewayUplink := range uplinks {
for _, edgeGatewayUplinkSubnet := range edgeGatewayUplink.Subnets.Values {
for _, r := range edgeGatewayUplinkSubnet.IPRanges.Values {
Expand All @@ -970,13 +1010,24 @@ func flattenEdgeGatewayUplinkToIpSlice(uplinks []types.EdgeGatewayUplinks) ([]ne
// Expression 'ip.Compare(endIp) == 1' means that 'ip > endIp' and the loop should stop
for ip := startIp; ip.Compare(endIp) != 1; ip = ip.Next() {
assignedIpSlice = append(assignedIpSlice, ip)
counter++
if limitTo != 0 && counter >= limitTo {
util.Logger.Printf("[TRACE] flattenEdgeGatewayUplinkToIpSlice hit limitTo %d at %s with IP range", limitTo, time.Since(start))
return assignedIpSlice, nil
}
}
} else { // if there is no end address in the range, then it is only a single IP - startIp
assignedIpSlice = append(assignedIpSlice, startIp)
counter++
if limitTo != 0 && counter >= limitTo {
util.Logger.Printf("[TRACE] flattenEdgeGatewayUplinkToIpSlice hit limitTo %d at %s with single IP", limitTo, time.Since(start))
return assignedIpSlice, nil
}
}
}
}
}
util.Logger.Printf("[TRACE] flattenEdgeGatewayUplinkToIpSlice finished %s", time.Since(start))

return assignedIpSlice, nil
}
Expand Down
6 changes: 6 additions & 0 deletions govcd/nsxt_edgegateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ func (vcd *TestVCD) Test_NsxtEdgeGatewayUsedAndUnusedIPs(check *C) {
check.Assert(ipsCompared, Equals, true)
check.Assert(len(allIps), Equals, 22)

// Get used and unused IP counts
usedIpCount, unusedIpCount, err := createdEdge.GetUsedAndUnusedExternalIPAddressCountWithLimit(false, 5)
check.Assert(err, IsNil)
check.Assert(unusedIpCount, Equals, int64(4))
check.Assert(usedIpCount, Equals, int64(1))

// Verify that GetAllocatedIpCount returns correct number of allocated IPs
totalAllocationIpCount, err := createdEdge.GetAllocatedIpCount(true)
check.Assert(err, IsNil)
Expand Down
61 changes: 60 additions & 1 deletion govcd/nsxt_edgegateway_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ func Test_ipSliceDifference(t *testing.T) {
func Test_flattenEdgeGatewayUplinkToIpSlice(t *testing.T) {
type args struct {
uplinks []types.EdgeGatewayUplinks
limitTo int64
}
tests := []struct {
name string
Expand Down Expand Up @@ -402,6 +403,64 @@ func Test_flattenEdgeGatewayUplinkToIpSlice(t *testing.T) {
},
wantErr: false,
},
{
name: "IPv6BigSubnetLimit3",
args: args{
uplinks: []types.EdgeGatewayUplinks{
{
Subnets: types.OpenAPIEdgeGatewaySubnets{
Values: []types.OpenAPIEdgeGatewaySubnetValue{
{
IPRanges: &types.OpenApiIPRanges{
Values: []types.OpenApiIPRangeValues{
{
StartAddress: "2a02:a404:11:0:0:0:0:1",
EndAddress: "2a02:a404:11:0:ffff:ffff:ffff:fffd",
},
},
},
},
},
},
},
},
limitTo: 3,
},
want: []netip.Addr{
netip.MustParseAddr("2a02:a404:11:0:0:0:0:1"),
netip.MustParseAddr("2a02:a404:11:0:0:0:0:2"),
netip.MustParseAddr("2a02:a404:11:0:0:0:0:3"),
},
wantErr: false,
},
{
name: "IPv6BigSubnetLimit1",
args: args{
uplinks: []types.EdgeGatewayUplinks{
{
Subnets: types.OpenAPIEdgeGatewaySubnets{
Values: []types.OpenAPIEdgeGatewaySubnetValue{
{
IPRanges: &types.OpenApiIPRanges{
Values: []types.OpenApiIPRangeValues{
{
StartAddress: "2a02:a404:11:0:0:0:0:1",
EndAddress: "2a02:a404:11:0:ffff:ffff:ffff:fffd",
},
},
},
},
},
},
},
},
limitTo: 1,
},
want: []netip.Addr{
netip.MustParseAddr("2a02:a404:11:0:0:0:0:1"),
},
wantErr: false,
},
{
name: "ReverseStartAndEnd",
args: args{
Expand Down Expand Up @@ -557,7 +616,7 @@ func Test_flattenEdgeGatewayUplinkToIpSlice(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := flattenEdgeGatewayUplinkToIpSlice(tt.args.uplinks)
got, err := flattenEdgeGatewayUplinkToIpSlice(tt.args.uplinks, tt.args.limitTo)
if (err != nil) != tt.wantErr {
t.Errorf("ipSliceFromEdgeGatewayUplinks() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
Loading