Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions cni/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"net"
"regexp"
"slices"
"strconv"
"time"

Expand Down Expand Up @@ -640,13 +641,41 @@ func (plugin *NetPlugin) Add(args *cniSkel.CmdArgs) error {
}
}()

// Sort epInfos so InfraNIC is processed first. In stateless mode, ExternalInterfaces is empty
// on every ADD, so whichever NIC is processed first determines which interface the network
// gets bound to. If DelegatedVMNIC wins the race, the network gets created with eth1 as extIf.
// SecondaryEndpointClient then moves eth1 into the pod namespace, and the subsequent InfraNIC
// iteration finds the network bound to that now-gone interface, causing
// TransparentEndpointClient.AddEndpoints to fail with "no such network interface".
sortInfraNICFirst(epInfos)

err = plugin.nm.EndpointCreate(cnsclient, epInfos)
if err != nil {
return errors.Wrap(err, "failed to create endpoint") // behavior can change if you don't assign to err prior to returning
}
return nil
}

// sortInfraNICFirst sorts endpoint infos so that InfraNIC (or legacy empty NICType)
// entries come before all other NIC types, preserving relative order among equals.
func sortInfraNICFirst(epInfos []*network.EndpointInfo) {
slices.SortStableFunc(epInfos, func(a, b *network.EndpointInfo) int {
if isInfraOrLegacyNICType(a.NICType) && !isInfraOrLegacyNICType(b.NICType) {
return -1
}
if !isInfraOrLegacyNICType(a.NICType) && isInfraOrLegacyNICType(b.NICType) {
return 1
}
return 0
})
}

// isInfraOrLegacyNICType returns true if the NIC type is InfraNIC or empty (legacy).
// Empty NICType is treated as infra for backward compatibility with older CNS responses.
func isInfraOrLegacyNICType(nicType cns.NICType) bool {
return nicType == cns.InfraNIC || nicType == ""
}

func (plugin *NetPlugin) findMasterInterface(opt *createEpInfoOpt) string {
switch opt.ifInfo.NICType {
case cns.InfraNIC:
Expand Down Expand Up @@ -1155,8 +1184,7 @@ func (plugin *NetPlugin) Delete(args *cniSkel.CmdArgs) error {
zap.String("endpointID", epInfo.EndpointID))
telemetryClient.SendEvent("Deleting endpoint: " + epInfo.EndpointID)

isInfraOrLegacyNIC := epInfo.NICType == cns.InfraNIC || epInfo.NICType == ""
if !nwCfg.MultiTenancy && isInfraOrLegacyNIC {
if !nwCfg.MultiTenancy && isInfraOrLegacyNICType(epInfo.NICType) {
// Call into IPAM plugin to release the endpoint's addresses.
for i := range epInfo.IPAddresses {
logger.Info("Release ip", zap.String("ip", epInfo.IPAddresses[i].IP.String()))
Expand Down
40 changes: 40 additions & 0 deletions cni/network/network_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -794,3 +794,43 @@ func (m *mockInterfaceGetter) GetNetworkInterfaces() ([]net.Interface, error) {
func (m *mockInterfaceGetter) GetNetworkInterfaceAddrs(_ *net.Interface) ([]net.Addr, error) {
return nil, errNotImplemented
}

func TestSortInfraNICFirst(t *testing.T) {
tests := []struct {
name string
input []cns.NICType
wantFirst cns.NICType
}{
{
name: "infra already first",
input: []cns.NICType{cns.InfraNIC, cns.NodeNetworkInterfaceFrontendNIC, cns.NodeNetworkInterfaceFrontendNIC},
wantFirst: cns.InfraNIC,
},
{
name: "infra last among several frontends",
input: []cns.NICType{cns.NodeNetworkInterfaceFrontendNIC, cns.NodeNetworkInterfaceFrontendNIC, cns.NodeNetworkInterfaceFrontendNIC, cns.InfraNIC},
wantFirst: cns.InfraNIC,
},
{
name: "infra in the middle",
input: []cns.NICType{cns.DelegatedVMNIC, cns.InfraNIC, cns.NodeNetworkInterfaceFrontendNIC},
wantFirst: cns.InfraNIC,
},
{
name: "empty NICType treated as infra",
input: []cns.NICType{cns.NodeNetworkInterfaceFrontendNIC, ""},
wantFirst: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
epInfos := make([]*network.EndpointInfo, len(tt.input))
for i, nic := range tt.input {
epInfos[i] = &network.EndpointInfo{NICType: nic}
}
sortInfraNICFirst(epInfos)
assert.Equal(t, tt.wantFirst, epInfos[0].NICType, "infra/legacy NIC should be sorted first")
})
}
}
Loading