Skip to content

Commit 5434566

Browse files
authored
Enhance Subnet totalip calculation for shared subnet (#1384)
User can create IPReservation and SubnetPort on shared Subnet from NSX side. Thus we need to always check the shared Subnet ip pool for totalip and allocated ip changes. Signed-off-by: Yanjun Zhou <yanjun.zhou@broadcom.com>
1 parent 1e42bf1 commit 5434566

File tree

8 files changed

+80
-47
lines changed

8 files changed

+80
-47
lines changed

pkg/controllers/common/utils.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func GetSubnetFromSubnetSet(client k8sclient.Client, subnetSet *v1alpha1.SubnetS
133133
continue
134134
}
135135
}
136-
canAllocate, err := subnetPortService.AllocatePortFromSubnet(nsxSubnet)
136+
canAllocate, err := subnetPortService.AllocatePortFromSubnet(nsxSubnet, servicecommon.IsSharedSubnet(subnetCR))
137137
if err != nil {
138138
log.Error(err, "Failed to check capacity of NSX Subnet", "Subnet", subnetName, "SubnetSet", subnetSet.Name, "Namespace", subnetSet.Namespace, "NSXSubnet", nsxSubnet.Id)
139139
errList = append(errList, err)
@@ -198,7 +198,7 @@ func AllocateSubnetFromSubnetSet(client k8sclient.Client, apiReader k8sclient.Re
198198
defer WUnlockSubnetSet(subnetSet.GetUID(), subnetSetLock)
199199
subnetList := subnetService.GetSubnetsByIndex(servicecommon.TagScopeSubnetSetCRUID, string(subnetSet.GetUID()))
200200
for _, nsxSubnet := range subnetList {
201-
canAllocate, err := subnetPortService.AllocatePortFromSubnet(nsxSubnet)
201+
canAllocate, err := subnetPortService.AllocatePortFromSubnet(nsxSubnet, false)
202202
if err != nil {
203203
return "", nil, nil, err
204204
}
@@ -221,7 +221,7 @@ func AllocateSubnetFromSubnetSet(client k8sclient.Client, apiReader k8sclient.Re
221221
if err != nil {
222222
return "", nil, nil, err
223223
}
224-
canAllocate, err := subnetPortService.AllocatePortFromSubnet(nsxSubnet)
224+
canAllocate, err := subnetPortService.AllocatePortFromSubnet(nsxSubnet, false)
225225
if err != nil {
226226
return "", nil, nil, err
227227
}

pkg/controllers/common/utils_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func TestAllocateSubnetFromSubnetSet(t *testing.T) {
122122
},
123123
})
124124
spsp.(*pkg_mock.MockSubnetPortServiceProvider).On("GetPortsOfSubnet", mock.Anything).Return([]*model.VpcSubnetPort{})
125-
spsp.(*pkg_mock.MockSubnetPortServiceProvider).On("AllocatePortFromSubnet", mock.Anything).Return(true, nil)
125+
spsp.(*pkg_mock.MockSubnetPortServiceProvider).On("AllocatePortFromSubnet", mock.Anything, mock.Anything).Return(true, nil)
126126
},
127127
expectedResult: expectedSubnetPath,
128128
subnetSet: subnetSet,
@@ -146,7 +146,7 @@ func TestAllocateSubnetFromSubnetSet(t *testing.T) {
146146
ssp.(*pkg_mock.MockSubnetServiceProvider).On("GenerateSubnetNSTags", mock.Anything)
147147
vsp.(*pkg_mock.MockVPCServiceProvider).On("ListVPCInfo", mock.Anything).Return([]servicecommon.VPCResourceInfo{{}})
148148
ssp.(*pkg_mock.MockSubnetServiceProvider).On("CreateOrUpdateSubnet", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.VpcSubnet{Path: &expectedSubnetPath}, nil)
149-
spsp.(*pkg_mock.MockSubnetPortServiceProvider).On("AllocatePortFromSubnet", mock.Anything).Return(true, nil)
149+
spsp.(*pkg_mock.MockSubnetPortServiceProvider).On("AllocatePortFromSubnet", mock.Anything, mock.Anything).Return(true, nil)
150150
},
151151
expectedResult: expectedSubnetPath,
152152
subnetSet: &v1alpha1.SubnetSet{
@@ -735,7 +735,7 @@ func TestGetSubnetFromSubnetSet(t *testing.T) {
735735
mockSetup: func(ms *pkg_mock.MockSubnetServiceProvider, mp *pkg_mock.MockSubnetPortServiceProvider) *gomonkey.Patches {
736736
// We use mock.Anything here to avoid pointer address issues
737737
ms.On("GetSubnetByCR", mock.Anything).Return(nsxSubnet1, nil)
738-
mp.On("AllocatePortFromSubnet", mock.Anything).Return(true, nil)
738+
mp.On("AllocatePortFromSubnet", mock.Anything, mock.Anything).Return(true, nil)
739739
return nil
740740
},
741741
expectedPath: path1,
@@ -754,8 +754,8 @@ func TestGetSubnetFromSubnetSet(t *testing.T) {
754754
mockSetup: func(ms *pkg_mock.MockSubnetServiceProvider, mp *pkg_mock.MockSubnetPortServiceProvider) *gomonkey.Patches {
755755
ms.On("GetSubnetByCR", mock.Anything).Return(nsxSubnet1, nil)
756756
// First call returns false (full), second call returns true
757-
mp.On("AllocatePortFromSubnet", mock.Anything).Return(false, nil).Once()
758-
mp.On("AllocatePortFromSubnet", mock.Anything).Return(true, nil).Once()
757+
mp.On("AllocatePortFromSubnet", mock.Anything, mock.Anything).Return(false, nil).Once()
758+
mp.On("AllocatePortFromSubnet", mock.Anything, mock.Anything).Return(true, nil).Once()
759759
return nil
760760
},
761761
expectedPath: path1,
@@ -780,7 +780,7 @@ func TestGetSubnetFromSubnetSet(t *testing.T) {
780780
mockSetup: func(ms *pkg_mock.MockSubnetServiceProvider, mp *pkg_mock.MockSubnetPortServiceProvider) *gomonkey.Patches {
781781
ms.On("GetSubnetByCR", mock.Anything).Return(nsxSubnet1, nil).Once()
782782
ms.On("GetSubnetByCR", mock.Anything).Return(nsxSubnet2, nil).Once()
783-
mp.On("AllocatePortFromSubnet", mock.Anything).Return(true, nil).Once()
783+
mp.On("AllocatePortFromSubnet", mock.Anything, mock.Anything).Return(true, nil).Once()
784784
patches := gomonkey.ApplyFunc(GetVpcNetworkConfig, func(service servicecommon.VPCServiceProvider, ns string) (*v1alpha1.VPCNetworkConfiguration, error) {
785785
return &v1alpha1.VPCNetworkConfiguration{
786786
Spec: v1alpha1.VPCNetworkConfigurationSpec{
@@ -807,7 +807,7 @@ func TestGetSubnetFromSubnetSet(t *testing.T) {
807807
},
808808
mockSetup: func(ms *pkg_mock.MockSubnetServiceProvider, mp *pkg_mock.MockSubnetPortServiceProvider) *gomonkey.Patches {
809809
ms.On("GetSubnetByCR", mock.Anything).Return(nsxSubnet1, nil)
810-
mp.On("AllocatePortFromSubnet", mock.Anything).Return(false, nil)
810+
mp.On("AllocatePortFromSubnet", mock.Anything, mock.Anything).Return(false, nil)
811811
return nil
812812
},
813813
expectedPath: "",
@@ -853,7 +853,7 @@ func TestGetSubnetFromSubnetSet(t *testing.T) {
853853
},
854854
mockSetup: func(ms *pkg_mock.MockSubnetServiceProvider, mp *pkg_mock.MockSubnetPortServiceProvider) *gomonkey.Patches {
855855
ms.On("GetSubnetByCR", mock.Anything).Return(nsxSubnet1, nil)
856-
mp.On("AllocatePortFromSubnet", mock.Anything).Return(false, errors.New("capacity-check-failed"))
856+
mp.On("AllocatePortFromSubnet", mock.Anything, mock.Anything).Return(false, errors.New("capacity-check-failed"))
857857
return nil
858858
},
859859
wantErr: true,

pkg/controllers/subnetport/subnetport_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ func (r *SubnetPortReconciler) CheckAndGetSubnetPathForSubnetPort(ctx context.Co
864864
return
865865
}
866866
var canAllocate bool
867-
canAllocate, err = r.SubnetPortService.AllocatePortFromSubnet(nsxSubnet)
867+
canAllocate, err = r.SubnetPortService.AllocatePortFromSubnet(nsxSubnet, servicecommon.IsSharedSubnet(subnetCR))
868868
if err != nil {
869869
return
870870
}

pkg/controllers/subnetport/subnetport_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,7 @@ func TestSubnetPortReconciler_CheckAndGetSubnetPathForSubnetPort(t *testing.T) {
13081308
}}
13091309
})
13101310
patches.ApplyFunc((*subnetport.SubnetPortService).AllocatePortFromSubnet,
1311-
func(s *subnetport.SubnetPortService, nsxSubnet *model.VpcSubnet) (bool, error) {
1311+
func(s *subnetport.SubnetPortService, nsxSubnet *model.VpcSubnet, sharedSubnet bool) (bool, error) {
13121312
return true, nil
13131313
})
13141314
return patches

pkg/mock/services_mock.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ func (m *MockSubnetPortServiceProvider) GetPortsOfSubnet(subnetPath string) (por
146146
return args.Get(0).([]*model.VpcSubnetPort)
147147
}
148148

149-
func (m *MockSubnetPortServiceProvider) AllocatePortFromSubnet(subnet *model.VpcSubnet) (bool, error) {
150-
args := m.Called(subnet)
149+
func (m *MockSubnetPortServiceProvider) AllocatePortFromSubnet(subnet *model.VpcSubnet, sharedSubnet bool) (bool, error) {
150+
args := m.Called(subnet, sharedSubnet)
151151
return args.Bool(0), args.Error(1)
152152
}
153153

pkg/nsx/services/common/services.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type SubnetServiceProvider interface {
4444

4545
type SubnetPortServiceProvider interface {
4646
GetPortsOfSubnet(subnetPath string) (ports []*model.VpcSubnetPort)
47-
AllocatePortFromSubnet(subnet *model.VpcSubnet) (bool, error)
47+
AllocatePortFromSubnet(subnet *model.VpcSubnet, sharedSubnet bool) (bool, error)
4848
ReleasePortInSubnet(path string)
4949
IsEmptySubnet(path string) bool
5050
DeletePortCount(path string)

pkg/nsx/services/subnetport/subnetport.go

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ func (service *SubnetPortService) ResetSubnetTotalIP(path string) {
409409
// AllocatePortFromSubnet checks the number of SubnetPorts on the Subnet.
410410
// If the Subnet has capacity for the new SubnetPorts, it will increase
411411
// the number of SubnetPort under creation and return true.
412-
func (service *SubnetPortService) AllocatePortFromSubnet(subnet *model.VpcSubnet) (bool, error) {
412+
func (service *SubnetPortService) AllocatePortFromSubnet(subnet *model.VpcSubnet, sharedSubnet bool) (bool, error) {
413413
dhcpMode := "DHCP_DEACTIVATED"
414414
subnetInfo, _ := servicecommon.ParseVPCResourcePath(*subnet.Path)
415415
if subnet.SubnetDhcpConfig != nil && subnet.SubnetDhcpConfig.Mode != nil {
@@ -435,6 +435,7 @@ func (service *SubnetPortService) AllocatePortFromSubnet(subnet *model.VpcSubnet
435435
info.lock.Lock()
436436
defer info.lock.Unlock()
437437

438+
var allocatedIPNumber int
438439
// For DHCP Server mode Subnet, get total IPs from DHCP IP Pool from NSX each time
439440
// since user might update reservedIPRanges for the subnet and it impacts the DHCP Pool size
440441
if dhcpMode == "DHCP_SERVER" {
@@ -446,45 +447,62 @@ func (service *SubnetPortService) AllocatePortFromSubnet(subnet *model.VpcSubnet
446447
if len(dhcpServerStats.IpPoolStats) > 0 && dhcpServerStats.IpPoolStats[0].PoolSize != nil {
447448
info.totalIP = int(*dhcpServerStats.IpPoolStats[0].PoolSize)
448449
}
450+
if sharedSubnet && len(dhcpServerStats.IpPoolStats) > 0 && dhcpServerStats.IpPoolStats[0].AllocatedNumber != nil {
451+
allocatedIPNumber = int(*dhcpServerStats.IpPoolStats[0].AllocatedNumber)
452+
}
449453
}
450-
451-
if !ok || info.totalIP == 0 {
454+
// When SubnetIPReservation is created/deleted, we will reset the info.totalIP and
455+
// expect the totalIP is updated from NSX ip pool API
456+
// For shared Subnet, user can create IPReservation and SubnetPort from NSX side.
457+
// We call NSX ip pool API to for latest total ip and requested ip.
458+
if (!ok || info.totalIP == 0 || sharedSubnet) && dhcpMode == "DHCP_DEACTIVATED" {
452459
// For DHCP Deactivated mode Subnet, get total IPs from IP pool static-ipv4-default
453-
if dhcpMode == "DHCP_DEACTIVATED" {
454-
// only get Subnet total IPs from static IP Pool if staticIpAllocation enabled
455-
if staticIpAllocationEnabled {
456-
staticIPPool, err := service.NSXClient.IPPoolClient.Get(subnetInfo.OrgID, subnetInfo.ProjectID, subnetInfo.VPCID, subnetInfo.ID, "static-ipv4-default")
457-
if err != nil {
458-
log.Error(err, "Failed to get Subnet static IP Pool static-ipv4-default", "Subnet", *subnet.Path)
459-
return false, err
460-
}
461-
if staticIPPool.PoolUsage.TotalIps != nil {
462-
info.totalIP = int(*staticIPPool.PoolUsage.TotalIps)
463-
}
460+
// only get Subnet total IPs from static IP Pool if staticIpAllocation enabled
461+
if staticIpAllocationEnabled {
462+
staticIPPool, err := service.NSXClient.IPPoolClient.Get(subnetInfo.OrgID, subnetInfo.ProjectID, subnetInfo.VPCID, subnetInfo.ID, "static-ipv4-default")
463+
if err != nil {
464+
log.Error(err, "Failed to get Subnet static IP Pool static-ipv4-default", "Subnet", *subnet.Path)
465+
return false, err
464466
}
465-
}
466-
// For DHCP Relay mode Subnet, assume 4 reserved IPs
467-
if dhcpMode == "DHCP_RELAY" {
468-
var totalIP int
469-
if subnet.Ipv4SubnetSize != nil {
470-
totalIP = int(*subnet.Ipv4SubnetSize)
467+
if staticIPPool.PoolUsage != nil && staticIPPool.PoolUsage.TotalIps != nil {
468+
info.totalIP = int(*staticIPPool.PoolUsage.TotalIps)
471469
}
472-
if len(subnet.IpAddresses) > 0 {
473-
// totalIP will be overrided if IpAddresses are specified.
474-
totalIP, _ = util.CalculateIPFromCIDRs(subnet.IpAddresses)
470+
if sharedSubnet && staticIPPool.PoolUsage != nil && staticIPPool.PoolUsage.RequestedIpAllocations != nil {
471+
allocatedIPNumber = int(*staticIPPool.PoolUsage.RequestedIpAllocations)
475472
}
476-
// NSX reserves 4 ip addresses in each subnet for network address, gateway address,
477-
// dhcp server address and broadcast address.
478-
info.totalIP = totalIP - 4
479473
}
480474
}
475+
if !ok && dhcpMode == "DHCP_RELAY" {
476+
// For DHCP Relay mode Subnet, assume 4 reserved IPs
477+
var totalIP int
478+
if subnet.Ipv4SubnetSize != nil {
479+
totalIP = int(*subnet.Ipv4SubnetSize)
480+
}
481+
if len(subnet.IpAddresses) > 0 {
482+
// totalIP will be overrided if IpAddresses are specified.
483+
totalIP, _ = util.CalculateIPFromCIDRs(subnet.IpAddresses)
484+
}
485+
// NSX reserves 4 ip addresses in each subnet for network address, gateway address,
486+
// dhcp server address and broadcast address.
487+
info.totalIP = totalIP - 4
488+
}
481489

482490
if time.Since(info.exhaustedCheckTime) < IPReleaseTime {
483491
return false, nil
484492
}
493+
494+
existingPortCount := len(service.GetPortsOfSubnet(*subnet.Path))
495+
// A shared Subnet can be used by other supervisors or other places where SubnetPort
496+
// is created and not in operator cache.
497+
// For DHCPServer Subnet, the allocated IP number wont change before the VM request IP
498+
// from the DHCPServer.
499+
// Thus we use the max number of port record in store and allocated number from API to
500+
// reduce the possibility to create SubnetPort on a Subnet without available IP
501+
if sharedSubnet {
502+
existingPortCount = max(existingPortCount, allocatedIPNumber)
503+
}
485504
// Number of SubnetPorts on the Subnet includes the SubnetPorts under creation
486505
// and the SubnetPorts already created
487-
existingPortCount := len(service.GetPortsOfSubnet(*subnet.Path))
488506
if info.dirtyCount+existingPortCount < info.totalIP {
489507
info.dirtyCount += 1
490508
log.Trace("Allocate Subnetport to Subnet", "Subnet", *subnet.Path, "dirtyPortCount", info.dirtyCount, "existingPortCount", existingPortCount)

pkg/nsx/services/subnetport/subnetport_test.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,7 @@ func TestSubnetPortService_AllocateAndReleasePortFromSubnet(t *testing.T) {
931931
subnetPortService := createSubnetPortService(t)
932932
// Reset Subnet totalIP without SubnetPort does not influence the port count info
933933
subnetPortService.ResetSubnetTotalIP(subnetPath)
934-
ok, err := subnetPortService.AllocatePortFromSubnet(subnet1)
934+
ok, err := subnetPortService.AllocatePortFromSubnet(subnet1, false)
935935
assert.True(t, ok)
936936
require.NoError(t, err)
937937
empty := subnetPortService.IsEmptySubnet(subnetPath)
@@ -944,7 +944,7 @@ func TestSubnetPortService_AllocateAndReleasePortFromSubnet(t *testing.T) {
944944
subnetPortService.updateExhaustedSubnet(subnetPath)
945945
// Reset Subnet totalIP does not change other port count info
946946
subnetPortService.ResetSubnetTotalIP(subnetPath)
947-
ok, err = subnetPortService.AllocatePortFromSubnet(subnet1)
947+
ok, err = subnetPortService.AllocatePortFromSubnet(subnet1, false)
948948
assert.False(t, ok)
949949
assert.Nil(t, err)
950950
}
@@ -1006,6 +1006,7 @@ func TestSubnetPortService_AllocatePortFromSubnet(t *testing.T) {
10061006
tests := []struct {
10071007
name string
10081008
subnet *model.VpcSubnet
1009+
sharedSubnet bool
10091010
prepareFunc func(service *SubnetPortService) *gomonkey.Patches
10101011
expectedValue bool
10111012
expectedErr string
@@ -1089,6 +1090,20 @@ func TestSubnetPortService_AllocatePortFromSubnet(t *testing.T) {
10891090
},
10901091
expectedValue: true,
10911092
},
1093+
{
1094+
name: "Allocate SubnetPort from static shared Subnet failed",
1095+
subnet: staticSubnet,
1096+
prepareFunc: func(service *SubnetPortService) *gomonkey.Patches {
1097+
patches := gomonkey.ApplyMethod(reflect.TypeOf(service.NSXClient.IPPoolClient), "Get", func(c *fakeIPPoolClient, orgIdParam string, projectIdParam string, vpcIdParam string, subnetIdParam string, poolIdParam string) (model.IpAddressPool, error) {
1098+
return model.IpAddressPool{
1099+
PoolUsage: &model.PolicyPoolUsage{TotalIps: common.Int64(10), RequestedIpAllocations: common.Int64(10)},
1100+
}, nil
1101+
})
1102+
return patches
1103+
},
1104+
expectedValue: false,
1105+
sharedSubnet: true,
1106+
},
10921107
{
10931108
name: "Allocate SubnetPort from dhcp server Subnet",
10941109
subnet: dhcpServerSubnet,
@@ -1119,15 +1134,15 @@ func TestSubnetPortService_AllocatePortFromSubnet(t *testing.T) {
11191134
if patches != nil {
11201135
defer patches.Reset()
11211136
}
1122-
canAllocate, err := subnetPortService.AllocatePortFromSubnet(tt.subnet)
1137+
canAllocate, err := subnetPortService.AllocatePortFromSubnet(tt.subnet, tt.sharedSubnet)
11231138
assert.Equal(t, tt.expectedValue, canAllocate)
11241139
if tt.expectedErr != "" {
11251140
assert.Contains(t, err.Error(), tt.expectedErr)
11261141
} else {
11271142
assert.Nil(t, err)
11281143
}
11291144
if tt.expectedValue {
1130-
canAllocate, err = subnetPortService.AllocatePortFromSubnet(tt.subnet)
1145+
canAllocate, err = subnetPortService.AllocatePortFromSubnet(tt.subnet, tt.sharedSubnet)
11311146
assert.Equal(t, tt.expectedValue, canAllocate)
11321147
if tt.expectedErr != "" {
11331148
assert.Contains(t, err.Error(), tt.expectedErr)

0 commit comments

Comments
 (0)