Skip to content

Commit bd3f491

Browse files
feature: Adding apipa nic support for swiftv2 windows (#4012)
* refactor: Update apipa nic as separate entry in podIPInfo This PR updates both CNS and CNI code to construct apipa nic as separate entry in podIpInfo if either of allowhostonc or allownctohost set. This allows CNI to treat this as separate endpoint and align with current cni design/model of 1 nic per endpoint info. CNI then iterates through endpoint info and creates one nic at a time. Signed-off-by: Tamilmani <tamanoha@microsoft.com> * Introduced NetworkContainerID, AllowHostToNC, AllowNCToHost fields in podipinfo to support apipa endpoint create request. persist networkcontainer id in cns state to support delete apipa endpoint when called as endpoint name based on networkcontainerid. Signed-off-by: Tamilmani <tamanoha@microsoft.com> * dummy create network for apipa nic as like backend nic Signed-off-by: Tamilmani <tamanoha@microsoft.com> * Added DeleteEndpointState handler for removing endpoint state from CNS in stateless cni case Signed-off-by: Tamilmani <tamanoha@microsoft.com> * add config for disabling async delete Signed-off-by: Tamilmani <tamanoha@microsoft.com> * lint fixes and address comment Signed-off-by: Tamilmani <tamanoha@microsoft.com> * Address review comments Signed-off-by: Tamilmani <tamanoha@microsoft.com> --------- Signed-off-by: Tamilmani <tamanoha@microsoft.com>
1 parent b9f406a commit bd3f491

18 files changed

+632
-57
lines changed

cni/netconfig.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type NetworkConfig struct {
7272
EnableExactMatchForPodName bool `json:"enableExactMatchForPodName,omitempty"`
7373
DisableHairpinOnHostInterface bool `json:"disableHairpinOnHostInterface,omitempty"`
7474
DisableIPTableLock bool `json:"disableIPTableLock,omitempty"`
75+
DisableAsyncDelete bool `json:"disableAsyncDelete,omitempty"`
7576
CNSUrl string `json:"cnsurl,omitempty"`
7677
ExecutionMode string `json:"executionMode,omitempty"`
7778
IPAM IPAM `json:"ipam,omitempty"`

cni/network/invoker_cns.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func (invoker *CNSIPAMInvoker) Add(addConfig IPAMAddConfig) (IPAMAddResult, erro
165165
}
166166

167167
logger.Info("Received info for pod",
168-
zap.Any("ipInfo", info),
168+
zap.Any("ipInfo", response.PodIPInfo[i]),
169169
zap.Any("podInfo", podInfo))
170170

171171
//nolint:exhaustive // ignore exhaustive types check
@@ -192,6 +192,11 @@ func (invoker *CNSIPAMInvoker) Add(addConfig IPAMAddConfig) (IPAMAddResult, erro
192192
if err := addBackendNICToResult(&info, &addResult, key); err != nil {
193193
return IPAMAddResult{}, err
194194
}
195+
case cns.ApipaNIC:
196+
if err := configureApipaAddResult(&addResult, &response.PodIPInfo[i], key); err != nil {
197+
return IPAMAddResult{}, err
198+
}
199+
195200
case cns.InfraNIC, "":
196201
// if we change from legacy cns, the nicType will be empty, so we assume it is infra nic
197202
info.nicType = cns.InfraNIC
@@ -508,6 +513,32 @@ func configureSecondaryAddResult(info *IPResultInfo, addResult *IPAMAddResult, p
508513
return nil
509514
}
510515

516+
func configureApipaAddResult(addResult *IPAMAddResult, info *cns.PodIpInfo, key string) error {
517+
ip, ipnet, err := info.PodIPConfig.GetIPNet()
518+
if ip == nil {
519+
return errors.Wrap(err, "GetIPNet failed while configuring apipa AddResult")
520+
}
521+
522+
addResult.interfaceInfo[key] = network.InterfaceInfo{
523+
IPConfigs: []*network.IPConfig{
524+
{
525+
Address: net.IPNet{
526+
IP: ip,
527+
Mask: ipnet.Mask,
528+
},
529+
Gateway: net.ParseIP(info.NetworkContainerPrimaryIPConfig.GatewayIPAddress),
530+
},
531+
},
532+
NICType: info.NICType,
533+
SkipDefaultRoutes: true,
534+
NetworkContainerID: info.NetworkContainerID,
535+
AllowHostToNCCommunication: info.AllowHostToNCCommunication,
536+
AllowNCToHostCommunication: info.AllowNCToHostCommunication,
537+
}
538+
539+
return nil
540+
}
541+
511542
func addBackendNICToResult(info *IPResultInfo, addResult *IPAMAddResult, key string) error {
512543
macAddress, err := net.ParseMAC(info.macAddress)
513544
if err != nil {

cni/network/invoker_cns_test.go

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2298,3 +2298,315 @@ func TestMultipleIBNICsToResult(t *testing.T) {
22982298
})
22992299
}
23002300
}
2301+
2302+
func TestCNSIPAMInvoker_Add_ApipaNIC(t *testing.T) {
2303+
require := require.New(t)
2304+
2305+
type fields struct {
2306+
podName string
2307+
podNamespace string
2308+
cnsClient cnsclient
2309+
ipamMode util.IpamMode
2310+
}
2311+
type args struct {
2312+
nwCfg *cni.NetworkConfig
2313+
args *cniSkel.CmdArgs
2314+
options map[string]interface{}
2315+
}
2316+
2317+
tests := []struct {
2318+
name string
2319+
fields fields
2320+
args args
2321+
wantApipaResult network.InterfaceInfo
2322+
wantErr bool
2323+
wantErrMsg string
2324+
}{
2325+
{
2326+
name: "Test CNI Add with InfraNIC + ApipaNIC",
2327+
fields: fields{
2328+
podName: testPodInfo.PodName,
2329+
podNamespace: testPodInfo.PodNamespace,
2330+
cnsClient: &MockCNSClient{
2331+
require: require,
2332+
requestIPs: requestIPsHandler{
2333+
ipconfigArgument: cns.IPConfigsRequest{
2334+
PodInterfaceID: "testcont-testifname",
2335+
InfraContainerID: "testcontainerid",
2336+
OrchestratorContext: marshallPodInfo(testPodInfo),
2337+
},
2338+
result: &cns.IPConfigsResponse{
2339+
PodIPInfo: []cns.PodIpInfo{
2340+
{
2341+
PodIPConfig: cns.IPSubnet{
2342+
IPAddress: "10.0.1.10",
2343+
PrefixLength: 24,
2344+
},
2345+
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
2346+
IPSubnet: cns.IPSubnet{
2347+
IPAddress: "10.0.1.0",
2348+
PrefixLength: 24,
2349+
},
2350+
GatewayIPAddress: "10.0.1.1",
2351+
},
2352+
HostPrimaryIPInfo: cns.HostIPInfo{
2353+
Gateway: "10.0.0.1",
2354+
PrimaryIP: "10.0.0.1",
2355+
Subnet: "10.0.0.0/24",
2356+
},
2357+
NICType: cns.InfraNIC,
2358+
},
2359+
{
2360+
PodIPConfig: cns.IPSubnet{
2361+
IPAddress: "169.254.128.10",
2362+
PrefixLength: 17,
2363+
},
2364+
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
2365+
GatewayIPAddress: "169.254.128.1",
2366+
},
2367+
NICType: cns.ApipaNIC,
2368+
NetworkContainerID: "test-nc-id",
2369+
AllowHostToNCCommunication: true,
2370+
AllowNCToHostCommunication: false,
2371+
},
2372+
},
2373+
Response: cns.Response{
2374+
ReturnCode: 0,
2375+
Message: "",
2376+
},
2377+
},
2378+
err: nil,
2379+
},
2380+
},
2381+
},
2382+
args: args{
2383+
nwCfg: &cni.NetworkConfig{},
2384+
args: &cniSkel.CmdArgs{
2385+
ContainerID: "testcontainerid",
2386+
Netns: "testnetns",
2387+
IfName: "testifname",
2388+
},
2389+
options: map[string]interface{}{},
2390+
},
2391+
wantApipaResult: network.InterfaceInfo{
2392+
IPConfigs: []*network.IPConfig{
2393+
{
2394+
Address: net.IPNet{
2395+
IP: net.ParseIP("169.254.128.10"),
2396+
Mask: net.CIDRMask(17, 32),
2397+
},
2398+
Gateway: net.ParseIP("169.254.128.1"),
2399+
},
2400+
},
2401+
NICType: cns.ApipaNIC,
2402+
SkipDefaultRoutes: true,
2403+
NetworkContainerID: "test-nc-id",
2404+
AllowHostToNCCommunication: true,
2405+
AllowNCToHostCommunication: false,
2406+
},
2407+
wantErr: false,
2408+
},
2409+
{
2410+
name: "Test CNI add with Frontend Nic + ApipaNIC",
2411+
fields: fields{
2412+
podName: testPodInfo.PodName,
2413+
podNamespace: testPodInfo.PodNamespace,
2414+
cnsClient: &MockCNSClient{
2415+
require: require,
2416+
requestIPs: requestIPsHandler{
2417+
ipconfigArgument: cns.IPConfigsRequest{
2418+
PodInterfaceID: "testcont-testifname",
2419+
InfraContainerID: "testcontainerid",
2420+
OrchestratorContext: marshallPodInfo(testPodInfo),
2421+
},
2422+
result: &cns.IPConfigsResponse{
2423+
PodIPInfo: []cns.PodIpInfo{
2424+
{
2425+
PodIPConfig: cns.IPSubnet{
2426+
IPAddress: "10.0.1.10",
2427+
PrefixLength: 24,
2428+
},
2429+
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
2430+
IPSubnet: cns.IPSubnet{
2431+
IPAddress: "10.0.1.0",
2432+
PrefixLength: 24,
2433+
},
2434+
GatewayIPAddress: "10.0.1.1",
2435+
},
2436+
HostPrimaryIPInfo: cns.HostIPInfo{
2437+
Gateway: "10.0.0.1",
2438+
PrimaryIP: "10.0.0.1",
2439+
Subnet: "10.0.0.0/24",
2440+
},
2441+
MacAddress: "bc:9a:78:56:34:12",
2442+
NICType: cns.NodeNetworkInterfaceFrontendNIC,
2443+
},
2444+
{
2445+
PodIPConfig: cns.IPSubnet{
2446+
IPAddress: "169.254.5.50",
2447+
PrefixLength: 16,
2448+
},
2449+
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
2450+
GatewayIPAddress: "169.254.5.1",
2451+
},
2452+
NICType: cns.ApipaNIC,
2453+
NetworkContainerID: "mixed-nc-id",
2454+
AllowHostToNCCommunication: true,
2455+
AllowNCToHostCommunication: false,
2456+
},
2457+
},
2458+
Response: cns.Response{
2459+
ReturnCode: 0,
2460+
Message: "",
2461+
},
2462+
},
2463+
err: nil,
2464+
},
2465+
},
2466+
},
2467+
args: args{
2468+
nwCfg: &cni.NetworkConfig{},
2469+
args: &cniSkel.CmdArgs{
2470+
ContainerID: "testcontainerid",
2471+
Netns: "testnetns",
2472+
IfName: "testifname",
2473+
},
2474+
options: map[string]interface{}{},
2475+
},
2476+
wantApipaResult: network.InterfaceInfo{
2477+
IPConfigs: []*network.IPConfig{
2478+
{
2479+
Address: net.IPNet{
2480+
IP: net.ParseIP("169.254.5.50"),
2481+
Mask: net.CIDRMask(16, 32),
2482+
},
2483+
Gateway: net.ParseIP("169.254.5.1"),
2484+
},
2485+
},
2486+
NICType: cns.ApipaNIC,
2487+
SkipDefaultRoutes: true,
2488+
NetworkContainerID: "mixed-nc-id",
2489+
AllowHostToNCCommunication: true,
2490+
AllowNCToHostCommunication: false,
2491+
},
2492+
wantErr: false,
2493+
},
2494+
{
2495+
name: "Test CNI add with ApipaNIC fails when GetIPNet fails",
2496+
fields: fields{
2497+
podName: testPodInfo.PodName,
2498+
podNamespace: testPodInfo.PodNamespace,
2499+
cnsClient: &MockCNSClient{
2500+
require: require,
2501+
requestIPs: requestIPsHandler{
2502+
ipconfigArgument: cns.IPConfigsRequest{
2503+
PodInterfaceID: "testcont-testifname",
2504+
InfraContainerID: "testcontainerid",
2505+
OrchestratorContext: marshallPodInfo(testPodInfo),
2506+
},
2507+
result: &cns.IPConfigsResponse{
2508+
PodIPInfo: []cns.PodIpInfo{
2509+
{
2510+
PodIPConfig: cns.IPSubnet{
2511+
IPAddress: "invalid-ip-address",
2512+
PrefixLength: 16,
2513+
},
2514+
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
2515+
GatewayIPAddress: "169.254.1.1",
2516+
},
2517+
NICType: cns.ApipaNIC,
2518+
NetworkContainerID: "failed-nc-id",
2519+
AllowHostToNCCommunication: false,
2520+
AllowNCToHostCommunication: false,
2521+
},
2522+
},
2523+
Response: cns.Response{
2524+
ReturnCode: 0,
2525+
Message: "",
2526+
},
2527+
},
2528+
err: nil,
2529+
},
2530+
},
2531+
},
2532+
args: args{
2533+
nwCfg: &cni.NetworkConfig{},
2534+
args: &cniSkel.CmdArgs{
2535+
ContainerID: "testcontainerid",
2536+
Netns: "testnetns",
2537+
IfName: "testifname",
2538+
},
2539+
options: map[string]interface{}{},
2540+
},
2541+
wantErr: true,
2542+
},
2543+
}
2544+
2545+
for _, tt := range tests {
2546+
t.Run(tt.name, func(_ *testing.T) {
2547+
invoker := &CNSIPAMInvoker{
2548+
podName: tt.fields.podName,
2549+
podNamespace: tt.fields.podNamespace,
2550+
cnsClient: tt.fields.cnsClient,
2551+
}
2552+
if tt.fields.ipamMode != "" {
2553+
invoker.ipamMode = tt.fields.ipamMode
2554+
}
2555+
2556+
ipamAddResult, err := invoker.Add(IPAMAddConfig{
2557+
nwCfg: tt.args.nwCfg,
2558+
args: tt.args.args,
2559+
options: tt.args.options,
2560+
})
2561+
2562+
if tt.wantErr {
2563+
require.Error(err)
2564+
return
2565+
}
2566+
2567+
require.NoError(err)
2568+
2569+
// Find the ApipaNIC interface in the result
2570+
var apipaInterfaceFound bool
2571+
var actualApipaResult network.InterfaceInfo
2572+
2573+
for _, ifInfo := range ipamAddResult.interfaceInfo {
2574+
if ifInfo.NICType == cns.ApipaNIC {
2575+
apipaInterfaceFound = true
2576+
actualApipaResult = ifInfo
2577+
break
2578+
}
2579+
}
2580+
2581+
require.True(apipaInterfaceFound, "ApipaNIC interface should be found in the result")
2582+
2583+
// Verify the ApipaNIC interface info
2584+
// Lines around 2586-2590 should be:
2585+
require.Equal(string(tt.wantApipaResult.NICType), string(actualApipaResult.NICType), "NICType should match expected value")
2586+
require.Equal(tt.wantApipaResult.SkipDefaultRoutes, actualApipaResult.SkipDefaultRoutes)
2587+
require.Equal(tt.wantApipaResult.NetworkContainerID, actualApipaResult.NetworkContainerID)
2588+
require.Equal(tt.wantApipaResult.AllowHostToNCCommunication, actualApipaResult.AllowHostToNCCommunication)
2589+
require.Equal(tt.wantApipaResult.AllowNCToHostCommunication, actualApipaResult.AllowNCToHostCommunication)
2590+
2591+
// Verify IP configs
2592+
require.Len(actualApipaResult.IPConfigs, 1, "Should have exactly one IP config for ApipaNIC")
2593+
actualIPConfig := actualApipaResult.IPConfigs[0]
2594+
expectedIPConfig := tt.wantApipaResult.IPConfigs[0]
2595+
2596+
require.True(actualIPConfig.Address.IP.Equal(expectedIPConfig.Address.IP),
2597+
"IP addresses should match: expected %s, got %s",
2598+
expectedIPConfig.Address.IP, actualIPConfig.Address.IP)
2599+
require.Equal(expectedIPConfig.Address.Mask, actualIPConfig.Address.Mask,
2600+
"IP masks should match")
2601+
2602+
if expectedIPConfig.Gateway != nil {
2603+
require.NotNil(actualIPConfig.Gateway, "Gateway should not be nil")
2604+
require.True(actualIPConfig.Gateway.Equal(expectedIPConfig.Gateway),
2605+
"Gateway IPs should match: expected %s, got %s",
2606+
expectedIPConfig.Gateway, actualIPConfig.Gateway)
2607+
} else {
2608+
require.Nil(actualIPConfig.Gateway, "Gateway should be nil")
2609+
}
2610+
})
2611+
}
2612+
}

0 commit comments

Comments
 (0)