Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
19 changes: 19 additions & 0 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,6 +641,24 @@ 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".
slices.SortStableFunc(epInfos, func(a, b *network.EndpointInfo) int {
aIsInfra := a.NICType == cns.InfraNIC || a.NICType == ""
Comment thread
QxBytes marked this conversation as resolved.
Outdated
bIsInfra := b.NICType == cns.InfraNIC || b.NICType == ""
if aIsInfra && !bIsInfra {
return -1
}
if !aIsInfra && bIsInfra {
return 1
}
return 0
})

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
Expand Down
39 changes: 39 additions & 0 deletions cni/network/network_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"net"
"regexp"
"slices"
"testing"

"github.com/Azure/azure-container-networking/cni"
Expand Down Expand Up @@ -794,3 +795,41 @@ func (m *mockInterfaceGetter) GetNetworkInterfaces() ([]net.Interface, error) {
func (m *mockInterfaceGetter) GetNetworkInterfaceAddrs(_ *net.Interface) ([]net.Addr, error) {
return nil, errNotImplemented
}

func TestSortEpInfosInfraNICFirst(t *testing.T) {
Comment thread
QxBytes marked this conversation as resolved.
Outdated
// Replicate the sort logic used before EndpointCreate to verify InfraNIC
// is always processed first regardless of input order.
epInfos := []*network.EndpointInfo{
{
NICType: cns.DelegatedVMNIC,
EndpointID: "delegated-ep",
NetworkID: network.DefaultNetworkID,
},
{
NICType: cns.InfraNIC,
EndpointID: "infra-ep",
NetworkID: network.DefaultNetworkID,
},
{
NICType: cns.NodeNetworkInterfaceFrontendNIC,
EndpointID: "frontend-ep",
NetworkID: "other",
},
}

slices.SortStableFunc(epInfos, func(a, b *network.EndpointInfo) int {
aIsInfra := a.NICType == cns.InfraNIC || a.NICType == ""
bIsInfra := b.NICType == cns.InfraNIC || b.NICType == ""
if aIsInfra && !bIsInfra {
return -1
}
if !aIsInfra && bIsInfra {
return 1
}
return 0
})

assert.Equal(t, cns.InfraNIC, epInfos[0].NICType, "InfraNIC should be sorted first")
assert.Equal(t, cns.DelegatedVMNIC, epInfos[1].NICType, "DelegatedVMNIC should come after InfraNIC")
assert.Equal(t, cns.NodeNetworkInterfaceFrontendNIC, epInfos[2].NICType, "FrontendNIC should come last")
Comment thread
behzad-mir marked this conversation as resolved.
Outdated
}
Loading