Skip to content

Commit 327b0b6

Browse files
authored
Ensure pending programming IPs will be released before available IPs when scale down. (#750)
* Ensure pending programming IPs will be released first when scale down. * Addressed feedbacks for testing and coding style.
1 parent 98f838e commit 327b0b6

File tree

6 files changed

+125
-18
lines changed

6 files changed

+125
-18
lines changed

cns/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ type HTTPService interface {
4343
GetAllocatedIPConfigs() []IPConfigurationStatus
4444
GetPendingReleaseIPConfigs() []IPConfigurationStatus
4545
GetPodIPConfigState() map[string]IPConfigurationStatus
46-
MarkIPsAsPending(numberToMark int) (map[string]IPConfigurationStatus, error)
46+
MarkIPAsPendingRelease(numberToMark int) (map[string]IPConfigurationStatus, error)
4747
}
4848

4949
// This is used for KubernetesCRD orchastrator Type where NC has multiple ips.

cns/fakes/cnsfake.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func (ipm *IPStateManager) ReleaseIPConfig(ipconfigID string) (cns.IPConfigurati
132132
return ipm.AvailableIPConfigState[ipconfigID], nil
133133
}
134134

135-
func (ipm *IPStateManager) MarkIPsAsPending(numberOfIPsToMark int) (map[string]cns.IPConfigurationStatus, error) {
135+
func (ipm *IPStateManager) MarkIPAsPendingRelease(numberOfIPsToMark int) (map[string]cns.IPConfigurationStatus, error) {
136136
ipm.Lock()
137137
defer ipm.Unlock()
138138

@@ -292,8 +292,8 @@ func (fake *HTTPServiceFake) GetPodIPConfigState() map[string]cns.IPConfiguratio
292292
}
293293

294294
// TODO: Populate on scale down
295-
func (fake *HTTPServiceFake) MarkIPsAsPending(numberToMark int) (map[string]cns.IPConfigurationStatus, error) {
296-
return fake.IPStateManager.MarkIPsAsPending(numberToMark)
295+
func (fake *HTTPServiceFake) MarkIPAsPendingRelease(numberToMark int) (map[string]cns.IPConfigurationStatus, error) {
296+
return fake.IPStateManager.MarkIPAsPendingRelease(numberToMark)
297297
}
298298

299299
func (fake *HTTPServiceFake) GetOption(string) interface{} {

cns/ipampoolmonitor/ipampoolmonitor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func (pm *CNSIPAMPoolMonitor) decreasePoolSize() error {
124124
pm.cachedNNC.Spec.RequestedIPCount -= pm.scalarUnits.BatchSize
125125

126126
// mark n number of IP's as pending
127-
pendingIpAddresses, err := pm.cns.MarkIPsAsPending(int(pm.scalarUnits.BatchSize))
127+
pendingIpAddresses, err := pm.cns.MarkIPAsPendingRelease(int(pm.scalarUnits.BatchSize))
128128
if err != nil {
129129
return err
130130
}

cns/restserver/ipam.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,26 +92,40 @@ func (service *HTTPRestService) releaseIPConfigHandler(w http.ResponseWriter, r
9292
return
9393
}
9494

95-
func (service *HTTPRestService) MarkIPsAsPending(numberToMark int) (map[string]cns.IPConfigurationStatus, error) {
96-
pendingReleaseIPs := make(map[string]cns.IPConfigurationStatus)
97-
markedIPCount := 0
95+
// MarkIPAsPendingRelease will mark IPs to pending release state.
96+
func (service *HTTPRestService) MarkIPAsPendingRelease(numberToMark int) (map[string]cns.IPConfigurationStatus, error) {
97+
allReleasedIPs := make(map[string]cns.IPConfigurationStatus)
98+
// Ensure PendingProgramming IPs will be release before Available ones.
99+
ipStateTypes := [2]string{cns.PendingProgramming, cns.Available}
98100

99101
service.Lock()
100102
defer service.Unlock()
101-
for uuid, _ := range service.PodIPConfigState {
102-
mutableIPConfig := service.PodIPConfigState[uuid]
103-
if mutableIPConfig.State == cns.Available || mutableIPConfig.State == cns.PendingProgramming {
103+
for _, ipStateType := range ipStateTypes {
104+
pendingReleaseIPs := service.markSpecificIPTypeAsPending(numberToMark, ipStateType)
105+
for uuid, pependingReleaseIP := range pendingReleaseIPs {
106+
allReleasedIPs[uuid] = pependingReleaseIP
107+
}
108+
numberToMark -= len(pendingReleaseIPs)
109+
if numberToMark == 0 {
110+
return allReleasedIPs, nil
111+
}
112+
}
113+
return nil, fmt.Errorf("Failed to mark %d IP's as pending, only marked %d IP's", numberToMark, len(allReleasedIPs))
114+
}
115+
116+
func (service *HTTPRestService) markSpecificIPTypeAsPending(numberToMark int, ipStateType string) map[string]cns.IPConfigurationStatus {
117+
pendingReleaseIPs := make(map[string]cns.IPConfigurationStatus)
118+
for uuid, mutableIPConfig := range service.PodIPConfigState {
119+
if mutableIPConfig.State == ipStateType {
104120
mutableIPConfig.State = cns.PendingRelease
105121
service.PodIPConfigState[uuid] = mutableIPConfig
106122
pendingReleaseIPs[uuid] = mutableIPConfig
107-
markedIPCount++
108-
if markedIPCount == numberToMark {
109-
return pendingReleaseIPs, nil
123+
if len(pendingReleaseIPs) == numberToMark {
124+
return pendingReleaseIPs
110125
}
111126
}
112127
}
113-
114-
return nil, fmt.Errorf("Failed to mark %d IP's as pending, only marked %d IP's", numberToMark, len(pendingReleaseIPs))
128+
return pendingReleaseIPs
115129
}
116130

117131
// MarkIpsAsAvailableUntransacted will update pending programming IPs to available if NMAgent side's programmed nc version keep up with nc version.

cns/restserver/ipam_test.go

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/json"
88
"fmt"
99
"reflect"
10+
"strconv"
1011
"testing"
1112

1213
"github.com/Azure/azure-container-networking/cns"
@@ -37,6 +38,9 @@ var (
3738
PodName: "testpod3",
3839
PodNamespace: "testpod3namespace",
3940
}
41+
42+
testIP4 = "10.0.0.4"
43+
testPod4GUID = "718e04ac-5a13-4dce-84b3-040accaa9b42"
4044
)
4145

4246
func getTestService() *HTTPRestService {
@@ -550,7 +554,7 @@ func TestIPAMMarkIPCountAsPending(t *testing.T) {
550554
}
551555

552556
// Release Test Pod 1
553-
ips, err := svc.MarkIPsAsPending(1)
557+
ips, err := svc.MarkIPAsPendingRelease(1)
554558
if err != nil {
555559
t.Fatalf("Unexpected failure releasing IP: %+v", err)
556560
}
@@ -575,6 +579,93 @@ func TestIPAMMarkIPCountAsPending(t *testing.T) {
575579
if err != nil {
576580
t.Fatalf("Unexpected failure releasing IP: %+v", err)
577581
}
582+
583+
// Try to release IP when no IP can be released. It should return error and ips will be nil
584+
ips, err = svc.MarkIPAsPendingRelease(1)
585+
if err == nil || ips != nil {
586+
t.Fatalf("We are expecting err and ips should be nil, however, return these IP %v", ips)
587+
}
588+
}
589+
590+
func TestIPAMMarkIPAsPendingWithPendingProgrammingIPs(t *testing.T) {
591+
svc := getTestService()
592+
593+
secondaryIPConfigs := make(map[string]cns.SecondaryIPConfig)
594+
// Default Programmed NC version is -1, set nc version as 0 will result in pending programming state.
595+
constructSecondaryIPConfigs(testIP1, testPod1GUID, 0, secondaryIPConfigs)
596+
constructSecondaryIPConfigs(testIP3, testPod3GUID, 0, secondaryIPConfigs)
597+
// Default Programmed NC version is -1, set nc version as -1 will result in available state.
598+
constructSecondaryIPConfigs(testIP2, testPod2GUID, -1, secondaryIPConfigs)
599+
constructSecondaryIPConfigs(testIP4, testPod4GUID, -1, secondaryIPConfigs)
600+
601+
// createNCRequest with NC version 0
602+
req := generateNetworkContainerRequest(secondaryIPConfigs, testNCID, strconv.Itoa(0))
603+
returnCode := svc.CreateOrUpdateNetworkContainerInternal(req, fakes.NewFakeScalar(releasePercent, requestPercent, batchSize), fakes.NewFakeNodeNetworkConfigSpec(initPoolSize))
604+
if returnCode != 0 {
605+
t.Fatalf("Failed to createNetworkContainerRequest, req: %+v, err: %d", req, returnCode)
606+
}
607+
608+
// Release pending programming IPs
609+
ips, err := svc.MarkIPAsPendingRelease(2)
610+
if err != nil {
611+
t.Fatalf("Unexpected failure releasing IP: %+v", err)
612+
}
613+
// Check returning released IPs are from pod 1 and 3
614+
if _, exists := ips[testPod1GUID]; !exists {
615+
t.Fatalf("Expected ID not marked as pending: %+v, ips is %s", err, ips)
616+
}
617+
if _, exists := ips[testPod3GUID]; !exists {
618+
t.Fatalf("Expected ID not marked as pending: %+v, ips is %s", err, ips)
619+
}
620+
621+
pendingRelease := svc.GetPendingReleaseIPConfigs()
622+
if len(pendingRelease) != 2 {
623+
t.Fatalf("Expected 2 pending release IPs but got %d pending release IP", len(pendingRelease))
624+
}
625+
// Check pending release IDs are from pod 1 and 3
626+
for _, config := range pendingRelease {
627+
if config.ID != testPod1GUID && config.ID != testPod3GUID {
628+
t.Fatalf("Expected pending release ID is either from pod 1 or pod 3 but got ID as %s ", config.ID)
629+
}
630+
}
631+
632+
available := svc.GetAvailableIPConfigs()
633+
if len(available) != 2 {
634+
t.Fatalf("Expected 1 available IP with test pod 2 but got available %d IP", len(available))
635+
}
636+
637+
// Call release again, should be fine
638+
err = svc.releaseIPConfig(testPod1Info)
639+
if err != nil {
640+
t.Fatalf("Unexpected failure releasing IP: %+v", err)
641+
}
642+
643+
// Release 2 more IPs
644+
ips, err = svc.MarkIPAsPendingRelease(2)
645+
if err != nil {
646+
t.Fatalf("Unexpected failure releasing IP: %+v", err)
647+
}
648+
// Make sure newly released IPs are from pod 2 and pod 4
649+
if _, exists := ips[testPod2GUID]; !exists {
650+
t.Fatalf("Expected ID not marked as pending: %+v, ips is %s", err, ips)
651+
}
652+
if _, exists := ips[testPod4GUID]; !exists {
653+
t.Fatalf("Expected ID not marked as pending: %+v, ips is %s", err, ips)
654+
}
655+
656+
// Get all pending release IPs and check total number is 4
657+
pendingRelease = svc.GetPendingReleaseIPConfigs()
658+
if len(pendingRelease) != 4 {
659+
t.Fatalf("Expected 4 pending release IPs but got %d pending release IP", len(pendingRelease))
660+
}
661+
}
662+
663+
func constructSecondaryIPConfigs(ipAddress, uuid string, ncVersion int, secondaryIPConfigs map[string]cns.SecondaryIPConfig) {
664+
secIpConfig := cns.SecondaryIPConfig{
665+
IPAddress: ipAddress,
666+
NCVersion: ncVersion,
667+
}
668+
secondaryIPConfigs[uuid] = secIpConfig
578669
}
579670

580671
func TestIPAMMarkExistingIPConfigAsPending(t *testing.T) {

cns/restserver/util.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,6 @@ func (service *HTTPRestService) updateIpConfigsStateUntransacted(req cns.CreateN
258258
// If the IP is already added then it will be an idempotent call. Also note, caller will
259259
// acquire/release the service lock.
260260
func (service *HTTPRestService) addIPConfigStateUntransacted(ncId string, hostVersion int, ipconfigs, existingSecondaryIPConfigs map[string]cns.SecondaryIPConfig) {
261-
newIPCNSStatus := cns.Available
262261
// add ipconfigs to state
263262
for ipId, ipconfig := range ipconfigs {
264263
// New secondary IP configs has new NC version however, CNS don't want to override existing IPs'with new NC version
@@ -274,8 +273,11 @@ func (service *HTTPRestService) addIPConfigStateUntransacted(ncId string, hostVe
274273
}
275274
// Using the updated NC version attached with IP to compare with latest nmagent version and determine IP statues.
276275
// When reconcile, service.PodIPConfigState doens't exist, rebuild it with the help of NC version attached with IP.
276+
var newIPCNSStatus string
277277
if hostVersion < ipconfig.NCVersion {
278278
newIPCNSStatus = cns.PendingProgramming
279+
} else {
280+
newIPCNSStatus = cns.Available
279281
}
280282
// add the new State
281283
ipconfigStatus := cns.IPConfigurationStatus{

0 commit comments

Comments
 (0)