Skip to content

Conversation

behzad-mir
Copy link
Contributor

@behzad-mir behzad-mir commented Aug 27, 2025

A number of changes is made to stateless CNI to fully support SWiftV2 in Linux:

  1. For Stateless CNI, the EndpointID should include ifName when the NicType is Delegated. This ensures it can be distinguished from the InfraNIC endpoint. The reason for this behavior is that Stateless CNI uses only the ContainerID as the endpoint ID when saving the state file in CNS, to maintain consistency with Cilium. However, within CNI’s in-memory representation, the EndpointID must uniquely identify each NIC. As a result:
    • We always pass the ContainerID to CNS as the endpoint state key.
    • Internally, CNI uses EndpointID (which includes ifName for delegated NICs) to differentiate endpoints.
  2. Delete flow has been revised and NetNSPath has been added to the statefile since it is needed by the TransparentClient for Frontend NIC.
  3. The Transparent mode used by statefull CNI for SWiftV1 and V2 and stateless CNI should follow the same. TransparentVlan which is the original value seems to be a mistake.
  4. Async delete file creation in Stateless CNI has been moved to CNS_invoker to be consistent with Stateful CNI.
  5. Removing secondary interface from pod namespace in SwiftV2 Linux in case of asynchronous delete. This is needed to be covered for stateless CNI since if CNS is down, CNI does not have the secondary interface name. A dedicated function has been added to list the interface in pod namespace and remove the secondary.

For validating the scenario ADD/Delete calls have been issues on SwiftV2 cluster and and logs and satefile has been analyzed to make sure it is consistent with Stateful CNI and also nothing gets leaked.

Requirements:

Notes:

@Copilot Copilot AI review requested due to automatic review settings August 27, 2025 07:19
@behzad-mir behzad-mir requested review from a team as code owners August 27, 2025 07:19
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes issues with Stateless CNI delete operations in SwiftV2 scenarios by modifying endpoint ID generation and improving the delete flow. The changes ensure proper distinction between different NIC types and provide necessary context for transparent client operations.

  • Modifies GetEndpointID to accept a NICType parameter and append interface name for delegated NICs
  • Updates delete flow to use proper network manager clients and adds NetNsPath to state information
  • Adds NetworkNameSpace field to CNS REST server structures for frontend NIC support

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
network/manager.go Core logic changes for endpoint ID generation and delete flow improvements
network/manager_mock.go Mock implementation updated to match new GetEndpointID signature
cns/restserver/restserver.go Added NetworkNameSpace field to IPInfo struct
cns/restserver/ipam.go Updated validation and state management for NetworkNameSpace field
cni/network/network.go Updated callers to pass NICType parameter to GetEndpointID

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@santhoshmprabhu santhoshmprabhu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a lot of context on the need for these changes. The changes look fine, but we should improve the PR a bit -

  1. Update the description to cover why the opMode change
  2. Address other copilot comments (one about NICType in particular seems serious)
  3. Add a description of what validation steps have been carried out. Include screenshots/logs if necessary.
  4. Add tests.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@QxBytes QxBytes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preliminary suggestions-- could you also add how you tested and what happens without each of these changes?

UpdateEndpoint(networkID string, existingEpInfo *EndpointInfo, targetEpInfo *EndpointInfo) error
GetNumberOfEndpoints(ifName string, networkID string) int
GetEndpointID(containerID, ifName string) string
GetEndpointIDByNicType(containerID, ifName string, nicType cns.NICType) string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: GetEndpointIDByNICType as nic is an acronym

@behzad-mir behzad-mir force-pushed the swiftv2-stateless branch 5 times, most recently from 4b7c752 to 5c01db3 Compare September 25, 2025 17:30
@behzad-mir behzad-mir force-pushed the swiftv2-stateless branch 4 times, most recently from b342517 to 0118a49 Compare September 29, 2025 05:04
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Member

@tamilmani1989 tamilmani1989 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@behzad-mir lets discuss offline on these changes. i feel it can be done in another way

// The delegated NIC (SR-IOV VF) used by SwiftV2 for multitenant pods remains tied to the pod namespace,
// triggering hot-unplug/re-register events and leaving the node in an unhealthy state.
// This workaround mitigates the issue by removing the secondary NIC from the pod netns when CNS is unreachable during DEL to provide the endpoint state.
if err = plugin.nm.RemoveSecondaryEndpointFromPodNetNS(args.IfName, args.Netns); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should still create file for cns to release ip allocated for pod

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit

@behzad-mir behzad-mir force-pushed the swiftv2-stateless branch 5 times, most recently from b364575 to b27f6af Compare October 3, 2025 01:51
@behzad-mir behzad-mir force-pushed the swiftv2-stateless branch 3 times, most recently from 1f5326e to 6b96a7c Compare October 6, 2025 17:56
Copy link
Contributor

@QxBytes QxBytes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I'm reading this it seems like a lot of the code is geared around accessing FetchInterfacesFromNetnsPath in secondary_endpoint_client_linux.go, requiring us to drill down through cni/network/network.go > network/manager.go > network/endpoint_linux.go > secondary_endpoint_client_linux.go, adding methods to interfaces which are only specific to swiftv2/secondary endpoint client.

What is it from secondary endpoint client that we actually need? At GetEndpoint() > nm.generateEndpointLocally, there is no guarantee we are even in swiftv2 right? Why can't we just make a helper function (doesn't even need to be part of a client) where generateEndpointLocally is called that does the following:

  1. Enters the pod's netns
  2. Searches for any swiftv2/secondary interfaces if they exist
  3. Creates endpoint infos and returns them if found
    Instead of going through the trouble of creating a secondary endpoint client etc.

// for when the endpoint is not created, but the ips are already allocated (only works if single network, single infra)
// this block is not applied to stateless CNI
// for Stateful CNI when the endpoint is not created, but the ips are already allocated (only works if single network, single infra)
// this block is applied to stateless CNI only if there was a connection failure in previous block and asynchronous delete by CNS will remover the endpoint from state file
Copy link
Contributor

@QxBytes QxBytes Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No description provided.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you clarify the comment (1109-1110) to:
the following block applies to:

  • stateful when ...
  • stateless when ...

}
} else {
epInfos = plugin.nm.GetEndpointInfosFromContainerID(args.ContainerID)
epInfos, err := plugin.nm.GetEndpoint(networkID, args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you rename this-- it currently isn't getting an endpoint object-- it's getting a slice of endpoint infos. Also please add a comment to that function

if err != nil {
switch {
case errors.Is(err, ErrConnectionFailure):
logger.Error("Failed to connect to CNS", zap.Error(err))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we combine these logs-- they are related

// This workaround mitigates the issue by generating a minimal endpointInfo via containerd args and netlink APIs that can be then passed to DeleteEndpoint API.
epInfos, err = nm.generateEndpointLocally(args)
if err != nil {
logger.Error("Failed to fetch secondary endpoint from pod netns", zap.String("netns", args.Netns), zap.Error(err))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like to avoid logging and returning the error here since we should already log the error in the main Delete call


func (nm *networkManager) GetEndpoint(networkID string, args *cniSkel.CmdArgs) ([]*EndpointInfo, error) {
if nm.IsStatelessCNIMode() {
logger.Info("calling cns getEndpoint API")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

capitalize log messages

NetworkNameSpace: args.Netns,
IfName: args.IfName, // TODO: For stateless cni linux populate IfName here to use in deletion in secondary endpoint client
}
epInfo, err := nm.getEndpointInfoByIfNameImpl(ep)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you confirm what happens on windows? If the error is nil, I would prefer returning an empty slice over "nil" in the windows implementation. I believe in windows this leads to GetEndpoint returning empty/nil, we going into the len(epInfos)==0 block, calling ipamInvoker.Delete-- please confirm this is intended

Also can we make the function/method names more consistent? If it returns a slice of endpoint infos, we getEndpointInfos (plural)

The method name is also a bit confusing since we're using more than just the endpoint's ifname

}
ret := []*EndpointInfo{}
ret = append(ret, epInfo)
logger.Info("Fetching Secondary Endpoint from", zap.String("NetworkNameSpace: ", ep.NetworkNameSpace))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to put the colon in "NetworkNameSpace: "-- it should automatically format

if strings.Contains(err.Error(), errFileNotExist.Error()) {
// clear SecondaryInterfaces map since network namespace doesn't exist anymore
ep.SecondaryInterfaces = make(map[string]*InterfaceInfo)
// clear SecondaryInterfaces map since network namespace doesn't exist anymore, Specific to legacy code in stateful cni
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you explain these clearings of the secondary interface map? This function should only deal with moving into or out of a network namespace based on its naming-- this is a hidden side effect that modifies the endpoint client and should be moved out of this function if possible.

errV6SnatRuleNotSet = errors.New("ipv6 snat rule not set. Might be VM ipv6 address missing") // nolint
ErrEndpointStateNotFound = errors.New("endpoint state could not be found in the statefile")
ErrConnectionFailure = errors.New("couldn't connect to CNS")
ErrEndpointRemovalFailure = errors.New("Failed to remove endpoint")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know some of these are capitalized already but new error messages shouldn't be capitalized: https://go.dev/wiki/CodeReviewComments#error-strings

// This is an special case for stateless CNI when Asychronous DEL to CNS will take place
// At this point the endpoint is already deleted in previous block and CNS will release the IP whenever it is up
if epInfo.IPAddresses == nil && plugin.nm.IsStatelessCNIMode() {
logger.Warn("Release ip Asynchronously by CNS",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why releaseip only for stateless cni and not for stateful cni case? current code calling release for both cases

Comment on lines +125 to +127
GetEndpoint(networkID string, args *cniSkel.CmdArgs) ([]*EndpointInfo, error)
GetEndpointInfosFromContainerID(containerID string) []*EndpointInfo
GetEndpointState(networkID, containerID string) ([]*EndpointInfo, error)
GetEndpointState(networkID, containerID, netns string) ([]*EndpointInfo, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need 2 apis? can we merge to 1 api, otherwise this is confusing and not sure about purpose of 2 apis

// GetEndpointIDByNicType returns a unique endpoint ID based on the CNI mode and NIC type.
func (nm *networkManager) GetEndpointIDByNicType(containerID, ifName string, nicType cns.NICType) string {
// For stateless CNI, secondary NICs use containerID-ifName as endpointID.
if nm.IsStatelessCNIMode() && nicType != cns.InfraNIC {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for stateful cni, this is not an issue? what's the impact if we remove statelesscnimode check here?

}
return epInfos, nil
}
return nm.GetEndpointInfosFromContainerID(args.ContainerID), nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this api is called only for stateful cni, can we keep in else to avoid any error in future

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants