From eec82e80b082a68e826d2ba64cb12cbb6a0ea2d0 Mon Sep 17 00:00:00 2001 From: AzureAhai Date: Thu, 24 Oct 2024 13:54:46 -0700 Subject: [PATCH 1/3] feat: adding stateless CNI support for ACI --- cns/restserver/ipam.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/cns/restserver/ipam.go b/cns/restserver/ipam.go index db5784c5fa..883d9aaef3 100644 --- a/cns/restserver/ipam.go +++ b/cns/restserver/ipam.go @@ -1260,20 +1260,25 @@ func (service *HTTPRestService) UpdateEndpointHelper(endpointID string, req map[ return ErrStoreEmpty } logger.Printf("[updateEndpoint] Updating endpoint state for infra container %s", endpointID) - if endpointInfo, ok := service.EndpointState[endpointID]; ok { - // Updating the InterfaceInfo map of endpoint states with the interfaceInfo map that is given by Stateless Azure CNI - for ifName, interfaceInfo := range req { - // updating the ipInfoMap - updateIPInfoMap(endpointInfo.IfnameToIPMap, interfaceInfo, ifName, endpointID) - } - err := service.EndpointStateStore.Write(EndpointStoreKey, service.EndpointState) - if err != nil { - return fmt.Errorf("[updateEndpoint] failed to write endpoint state to store for pod %s : %w", endpointInfo.PodName, err) - } - logger.Printf("[updateEndpoint] successfully write the state to the file %s", endpointID) - return nil + endpointInfo, endpointExist := service.EndpointState[endpointID] + // create a new entry in case the ednpoint does not exist in the statefile. + // this applies to the ACI scenario when the endpoint is not added to the statefile when the goalstate is sent to CNI + if !endpointExist { + logger.Printf("[updateEndpoint] endpoint could not be found in the statefile %s, new entry is being added", endpointID) + endpointInfo = &EndpointInfo{PodName: "", PodNamespace: "", IfnameToIPMap: make(map[string]*IPInfo)} + service.EndpointState[endpointID] = endpointInfo + } + // updating the InterfaceInfo map of endpoint states with the interfaceInfo map that is given by Stateless Azure CNI + for ifName, interfaceInfo := range req { + // updating the ipInfoMap + updateIPInfoMap(endpointInfo.IfnameToIPMap, interfaceInfo, ifName, endpointID) + } + err := service.EndpointStateStore.Write(EndpointStoreKey, service.EndpointState) + if err != nil { + return fmt.Errorf("[updateEndpoint] failed to write endpoint state to store for pod %s : %w", endpointInfo.PodName, err) } - return errors.New("[updateEndpoint] endpoint could not be found in the statefile") + logger.Printf("[updateEndpoint] successfully write the state to the file %s", endpointID) + return nil } // updateIPInfoMap updates the IfnameToIPMap of endpoint states with the interfaceInfo map that is given by Stateless Azure CNI From a7c18447db52a7aa7c7790f0e95d35f943296f87 Mon Sep 17 00:00:00 2001 From: AzureAhai Date: Thu, 7 Nov 2024 16:57:41 -0800 Subject: [PATCH 2/3] fix:adding unit test --- cns/restserver/ipam_test.go | 125 ++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/cns/restserver/ipam_test.go b/cns/restserver/ipam_test.go index 7220f4d413..3b161832d0 100644 --- a/cns/restserver/ipam_test.go +++ b/cns/restserver/ipam_test.go @@ -2154,3 +2154,128 @@ func createAndSaveMockNCRequest(t *testing.T, svc *HTTPRestService, ncID string, require.Equal(t, types.Success, returnCode) require.Empty(t, returnMessage) } + +// Validate Statefile in Stateless CNI scenarios +func TestStatelessCNIStateFile(t *testing.T) { + svc := getTestService(cns.KubernetesCRD) + svc.EndpointStateStore = store.NewMockStore("") + // test Case 1 - AKS SIngleTenancy + endpointInfo1ContainerID := "0a4917617e15d24dc495e407d8eb5c88e4406e58fa209e4eb75a2c2fb7045eea" + endpointInfo1 := &EndpointInfo{IfnameToIPMap: make(map[string]*IPInfo)} + endpointInfo1.IfnameToIPMap["eth0"] = &IPInfo{IPv4: []net.IPNet{{IP: net.IPv4(10, 0, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 0)}}} + req1 := make(map[string]*IPInfo) + req1["eth0"] = &IPInfo{IPv4: []net.IPNet{{IP: net.IPv4(10, 0, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 0)}}, HnsEndpointID: "5c15cccc-830a-4dff-81f3-4b1e55cb7dcb", NICType: cns.InfraNIC} + testPod1Info = cns.NewPodInfo(endpointInfo1ContainerID, endpointInfo1ContainerID, "pod1", "default") + req := cns.IPConfigsRequest{ + PodInterfaceID: testPod1Info.InterfaceID(), + InfraContainerID: testPod1Info.InfraContainerID(), + } + // test Case 2 - ACI + endpointInfo2ContainerID := "1b4917617e15d24dc495e407d8eb5c88e4406e58fa209e4eb75a2c2fb7045eea" + endpointInfo2 := &EndpointInfo{IfnameToIPMap: make(map[string]*IPInfo)} + endpointInfo2.IfnameToIPMap["eth2"] = &IPInfo{ + IPv4: nil, + NICType: cns.DelegatedVMNIC, + HnsEndpointID: "5c15cccc-830a-4dff-81f3-4b1e55cb7dcb", + HnsNetworkID: "5c0712cd-824c-4898-b1c0-2fcb16ede4fb", + MacAddress: "7c:1e:52:06:d3:4b", + } + // test cases + tests := []struct { + name string + endpointID string + req map[string]*IPInfo + store store.KeyValueStore + want *EndpointInfo + wantErr bool + }{ + { + name: "good", + endpointID: endpointInfo1ContainerID, + req: req1, + store: svc.EndpointStateStore, + want: &EndpointInfo{ + PodName: "pod1", PodNamespace: "default", IfnameToIPMap: map[string]*IPInfo{ + "eth0": { + IPv4: []net.IPNet{{IP: net.IPv4(10, 0, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 0)}}, + HnsEndpointID: "5c15cccc-830a-4dff-81f3-4b1e55cb7dcb", + NICType: cns.InfraNIC, + }, + }, + }, + wantErr: false, + }, + { + name: "good with ACI endpoint", + endpointID: endpointInfo2ContainerID, + req: endpointInfo2.IfnameToIPMap, + store: svc.EndpointStateStore, + want: endpointInfo2, + wantErr: false, + }, + } + ncStates := []ncState{ + { + ncID: testNCID, + ips: []string{ + testIP1, + }, + }, + } + + ipconfigs := make(map[string]cns.IPConfigurationStatus, 0) + for i := range ncStates { + state := NewPodState(ncStates[i].ips[0], ipIDs[i][0], ncStates[i].ncID, types.Available, 0) + ipconfigs[state.ID] = state + err := UpdatePodIPConfigState(t, svc, ipconfigs, ncStates[i].ncID) + if err != nil { + t.Fatalf("Expected to not fail update service with config: %+v", err) + } + } + t.Log(ipconfigs) + b, _ := testPod1Info.OrchestratorContext() + req.OrchestratorContext = b + req.Ifname = "eth0" + podIPInfo, err := requestIPConfigsHelper(svc, req) + if err != nil { + t.Fatalf("Expected to not fail getting pod ip info: %+v", err) + } + + ipInfo := &IPInfo{} + for i := range podIPInfo { + ip, ipnet, errIP := net.ParseCIDR(podIPInfo[i].PodIPConfig.IPAddress + "/" + strconv.FormatUint(uint64(podIPInfo[i].PodIPConfig.PrefixLength), 10)) + if errIP != nil { + t.Fatalf("failed to parse pod ip address: %+v", errIP) + } + ipconfig := net.IPNet{IP: ip, Mask: ipnet.Mask} + if ip.To4() == nil { // is an ipv6 address + ipInfo.IPv6 = append(ipInfo.IPv6, ipconfig) + } else { + ipInfo.IPv4 = append(ipInfo.IPv4, ipconfig) + } + } + + // add goalState + err = svc.updateEndpointState(req, testPod1Info, podIPInfo) + if err != nil { + t.Fatalf("Expected to not fail updating endpoint state: %+v", err) + } + // update State + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err := svc.UpdateEndpointHelper(tt.endpointID, tt.req) + if tt.wantErr { + assert.Error(t, err) + return + } + got, err := svc.GetEndpointHelper(tt.endpointID) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} From b0d5a100b624ed2e4983448e14ff5d1ee0c1437d Mon Sep 17 00:00:00 2001 From: AzureAhai Date: Fri, 8 Nov 2024 09:16:25 -0800 Subject: [PATCH 3/3] addressing the comments --- cns/restserver/ipam_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cns/restserver/ipam_test.go b/cns/restserver/ipam_test.go index 3b161832d0..b70b439de1 100644 --- a/cns/restserver/ipam_test.go +++ b/cns/restserver/ipam_test.go @@ -2190,7 +2190,7 @@ func TestStatelessCNIStateFile(t *testing.T) { wantErr bool }{ { - name: "good", + name: "single-tenancy: update endpoint without error", endpointID: endpointInfo1ContainerID, req: req1, store: svc.EndpointStateStore, @@ -2206,7 +2206,7 @@ func TestStatelessCNIStateFile(t *testing.T) { wantErr: false, }, { - name: "good with ACI endpoint", + name: "ACI: update and create absent endpoint without error", endpointID: endpointInfo2ContainerID, req: endpointInfo2.IfnameToIPMap, store: svc.EndpointStateStore,