Skip to content

Commit b1a63a9

Browse files
authored
adding CNS fix for requesting IPs with 0 NCs (#2063)
* adding fix for requesting IPs with 0 NCs * addressing comments * error change * fixing comments
1 parent 0f3cd58 commit b1a63a9

File tree

2 files changed

+73
-11
lines changed

2 files changed

+73
-11
lines changed

cns/restserver/ipam.go

+39-11
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
var (
2121
ErrStoreEmpty = errors.New("empty endpoint state store")
2222
ErrParsePodIPFailed = errors.New("failed to parse pod's ip")
23+
ErrNoNCs = errors.New("No NCs found in the CNS internal state")
2324
)
2425

2526
// requestIPConfigHandlerHelper validates the request, assigns IPs, and returns a response
@@ -88,13 +89,13 @@ func (service *HTTPRestService) requestIPConfigHandler(w http.ResponseWriter, r
8889
return
8990
}
9091

91-
// This method can only return 1 IP. If we have more than one NC then we expect to need to return one IP per NC
92-
if len(service.state.ContainerStatus) > 1 {
92+
// This method can only return EXACTLY 1 IP. If we have more than one NC then we expect to need to return one IP per NC
93+
if len(service.state.ContainerStatus) != 1 {
9394
// we send a response back saying that this API won't be able to return the amount of IPs needed to fulfill the request
9495
reserveResp := &cns.IPConfigResponse{
9596
Response: cns.Response{
9697
ReturnCode: types.InvalidRequest,
97-
Message: fmt.Sprintf("Called API that can only return 1 IP when expecting %d", len(service.state.ContainerStatus)),
98+
Message: fmt.Sprintf("Expected 1 NC when calling this API but found %d NCs", len(service.state.ContainerStatus)),
9899
},
99100
}
100101
w.Header().Set(cnsReturnCode, reserveResp.Response.ReturnCode.String())
@@ -128,6 +129,21 @@ func (service *HTTPRestService) requestIPConfigHandler(w http.ResponseWriter, r
128129
return
129130
}
130131

132+
// Checks to make sure we return exactly 1 IP
133+
// If IPAM assigned more than 1 IP then we need to raise an error since this API can only return one IP and IPAM may have assigned more than one
134+
if len(ipConfigsResp.PodIPInfo) != 1 {
135+
// we send a response back saying that this API won't be able to return the amount of IPs needed to fulfill the request
136+
reserveResp := &cns.IPConfigResponse{
137+
Response: cns.Response{
138+
ReturnCode: types.UnexpectedError,
139+
Message: fmt.Sprintf("request returned incorrect number of IPs. Expected 1 and returned %d", len(ipConfigsResp.PodIPInfo)),
140+
},
141+
}
142+
w.Header().Set(cnsReturnCode, reserveResp.Response.ReturnCode.String())
143+
err = service.Listener.Encode(w, &reserveResp)
144+
logger.ResponseEx(service.Name+operationName, ipconfigRequest, reserveResp, reserveResp.Response.ReturnCode, err)
145+
return
146+
}
131147
// As this API is expected to return IPConfigResponse, generate it from the IPConfigsResponse returned above.
132148
reserveResp := &cns.IPConfigResponse{
133149
Response: ipConfigsResp.Response,
@@ -273,12 +289,12 @@ func (service *HTTPRestService) releaseIPConfigHandler(w http.ResponseWriter, r
273289
return
274290
}
275291

276-
// check to make sure there aren't multiple NCs
277-
if len(service.state.ContainerStatus) > 1 {
292+
// check to make sure there is only one NC
293+
if len(service.state.ContainerStatus) != 1 {
278294
reserveResp := &cns.IPConfigResponse{
279295
Response: cns.Response{
280296
ReturnCode: types.InvalidRequest,
281-
Message: fmt.Sprintf("Called API that can only return 1 IP when expecting %d", len(service.state.ContainerStatus)),
297+
Message: fmt.Sprintf("Expected 1 NC when calling this API but found %d NCs", len(service.state.ContainerStatus)),
282298
},
283299
}
284300
w.Header().Set(cnsReturnCode, reserveResp.Response.ReturnCode.String())
@@ -661,7 +677,15 @@ func (service *HTTPRestService) GetExistingIPConfig(podInfo cns.PodInfo) ([]cns.
661677

662678
// Assigns a pod with all IPs desired
663679
func (service *HTTPRestService) AssignDesiredIPConfigs(podInfo cns.PodInfo, desiredIPAddresses []string) ([]cns.PodIpInfo, error) {
680+
// Gets the number of NCs which will determine the number of IPs given to a pod
681+
numOfNCs := len(service.state.ContainerStatus)
682+
// checks to make sure we have NCs before trying to get IPs
683+
if numOfNCs == 0 {
684+
return nil, ErrNoNCs
685+
}
686+
// Sets the number of desired IPs equal to the number of desired IPs passed in
664687
numDesiredIPAddresses := len(desiredIPAddresses)
688+
// Creates a slice of PodIpInfo with the size as number of NCs to hold the result for assigned IP configs
665689
podIPInfo := make([]cns.PodIpInfo, numDesiredIPAddresses)
666690
// creating a map for the loop to check to see if the IP in the pool is one of the desired IPs
667691
desiredIPMap := make(map[string]struct{})
@@ -756,12 +780,16 @@ func (service *HTTPRestService) AssignDesiredIPConfigs(podInfo cns.PodInfo, desi
756780
// Assigns an available IP from each NC on the NNC. If there is one NC then we expect to only have one IP return
757781
// In the case of dualstack we would expect to have one IPv6 from one NC and one IPv4 from a second NC
758782
func (service *HTTPRestService) AssignAvailableIPConfigs(podInfo cns.PodInfo) ([]cns.PodIpInfo, error) {
783+
// Gets the number of NCs which will determine the number of IPs given to a pod
784+
numOfNCs := len(service.state.ContainerStatus)
785+
// if there are no NCs on the NNC there will be no IPs in the pool so return error
786+
if numOfNCs == 0 {
787+
return nil, ErrNoNCs
788+
}
759789
service.Lock()
760790
defer service.Unlock()
761-
// Sets the number of IPs needed equal to the number of NCs so that we can get one IP per NC
762-
numIPsNeeded := len(service.state.ContainerStatus)
763791
// Creates a slice of PodIpInfo with the size as number of NCs to hold the result for assigned IP configs
764-
podIPInfo := make([]cns.PodIpInfo, numIPsNeeded)
792+
podIPInfo := make([]cns.PodIpInfo, numOfNCs)
765793
// This map is used to store whether or not we have found an available IP from an NC when looping through the pool
766794
ipsToAssign := make(map[string]cns.IPConfigurationStatus)
767795

@@ -777,13 +805,13 @@ func (service *HTTPRestService) AssignAvailableIPConfigs(podInfo cns.PodInfo) ([
777805
}
778806
ipsToAssign[ipState.NCID] = ipState
779807
// Once one IP per container is found break out of the loop and stop searching
780-
if len(ipsToAssign) == numIPsNeeded {
808+
if len(ipsToAssign) == numOfNCs {
781809
break
782810
}
783811
}
784812

785813
// Checks to make sure we found one IP for each NC
786-
if len(ipsToAssign) != numIPsNeeded {
814+
if len(ipsToAssign) != numOfNCs {
787815
//nolint:goerr113 // return error
788816
return podIPInfo, fmt.Errorf("not enough IPs available, waiting on Azure CNS to allocate more")
789817
}

cns/restserver/ipam_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,40 @@ func IPAMMarkExistingIPConfigAsPending(t *testing.T, ncStates []ncState) {
13601360
}
13611361
}
13621362

1363+
func TestIPAMFailToRequestIPsWithNoNCsSpecificIP(t *testing.T) {
1364+
svc := getTestService()
1365+
req := cns.IPConfigsRequest{
1366+
PodInterfaceID: testPod1Info.InterfaceID(),
1367+
InfraContainerID: testPod1Info.InfraContainerID(),
1368+
}
1369+
b, _ := testPod1Info.OrchestratorContext()
1370+
req.OrchestratorContext = b
1371+
req.DesiredIPAddresses = make([]string, 1)
1372+
req.DesiredIPAddresses[0] = testIP1
1373+
1374+
_, err := requestIPConfigsHelper(svc, req)
1375+
if err == nil {
1376+
t.Fatalf("Expected error. Should not be able to request IPs when there are no NCs")
1377+
}
1378+
assert.ErrorIs(t, err, ErrNoNCs)
1379+
}
1380+
1381+
func TestIPAMFailToRequestIPsWithNoNCsAnyIP(t *testing.T) {
1382+
svc := getTestService()
1383+
req := cns.IPConfigsRequest{
1384+
PodInterfaceID: testPod1Info.InterfaceID(),
1385+
InfraContainerID: testPod1Info.InfraContainerID(),
1386+
}
1387+
b, _ := testPod1Info.OrchestratorContext()
1388+
req.OrchestratorContext = b
1389+
1390+
_, err := requestIPConfigsHelper(svc, req)
1391+
if err == nil {
1392+
t.Fatalf("Expected error. Should not be able to request IPs when there are no NCs")
1393+
}
1394+
assert.ErrorIs(t, err, ErrNoNCs)
1395+
}
1396+
13631397
func TestIPAMReleaseOneIPWhenExpectedToHaveTwo(t *testing.T) {
13641398
svc := getTestService()
13651399

0 commit comments

Comments
 (0)