Skip to content
Open
1 change: 1 addition & 0 deletions cns/NetworkContainerContract.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ type CreateNetworkContainerRequest struct {
SkipDefaultRoutes bool
EndpointPolicies []NetworkContainerRequestPolicies
NCStatus v1alpha.NCStatus
SwiftV2PrefixOnNic bool // Indicates if is swiftv2 nc, PrefixOnNic scenario (isSwiftV2 && nc.Type == VNETBlock)
NetworkInterfaceInfo NetworkInterfaceInfo //nolint // introducing new field for backendnic, to be used later by cni code
}

Expand Down
3 changes: 2 additions & 1 deletion cns/kubecontroller/nodenetworkconfig/conversion_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ func createNCRequestFromStaticNCHelper(nc v1alpha.NetworkContainer, primaryIPPre
GatewayIPAddress: nc.DefaultGateway,
GatewayIPv6Address: nc.DefaultGatewayV6,
},
NCStatus: nc.Status,
NCStatus: nc.Status,
SwiftV2PrefixOnNic: isSwiftV2 && nc.Type == v1alpha.VNETBlock,
NetworkInterfaceInfo: cns.NetworkInterfaceInfo{
MACAddress: nc.MacAddress,
},
Expand Down
3 changes: 2 additions & 1 deletion cns/kubecontroller/nodenetworkconfig/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,8 @@ func TestCreateNCRequestFromStaticNCWithConfig(t *testing.T) {
SecondaryIPConfigs: map[string]cns.SecondaryIPConfig{
// No IPs from primary prefix
},
NCStatus: "Available",
SwiftV2PrefixOnNic: true,
NCStatus: "Available",
},
wantErr: false,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func createNCRequestFromStaticNCHelper(nc v1alpha.NetworkContainer, primaryIPPre
IPSubnet: subnet,
GatewayIPAddress: nc.DefaultGateway,
},
NCStatus: nc.Status,
NCStatus: nc.Status,
SwiftV2PrefixOnNic: isSwiftV2 && nc.Type == v1alpha.VNETBlock,
}, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var validOverlayRequest = &cns.CreateNetworkContainerRequest{
NCVersion: 0,
},
},
SwiftV2PrefixOnNic: false,
}

var validVNETBlockRequest = &cns.CreateNetworkContainerRequest{
Expand Down Expand Up @@ -79,4 +80,5 @@ var validVNETBlockRequest = &cns.CreateNetworkContainerRequest{
NCVersion: version,
},
},
SwiftV2PrefixOnNic: true,
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 check conversion_test.go passes when OS=windows-- this request is the expected for one of the tests but in the test we set swiftv2 to false

}
40 changes: 27 additions & 13 deletions cns/restserver/internalapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,12 @@ func (service *HTTPRestService) syncHostNCVersion(ctx context.Context, channelMo
return len(programmedNCs), errors.Wrap(err, "failed to get nc version list from nmagent")
}

// Get IMDS NC versions for delegated NIC scenarios
imdsNCVersions, err := service.GetIMDSNCs(ctx)
if err != nil {
// If any of the NMA API check calls, imds calls fails assume that nma build doesn't have the latest changes and create empty map
imdsNCVersions = make(map[string]string)
}
// Get IMDS NC versions for delegated NIC scenarios.
imdsNCVersions, infraNicMacAddress := service.getIMDSNCs(ctx)

// Process Windows registry keys with the retrieved MAC address (empty if not found). It is required for HNS team to configure cilium routes sepcific to windows nodes.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: cilium routes "specific" to windows nodes and nit: isPrefixOnNicSwiftV2() (capital "O" for camel case or isPrefixOnNICSwiftV2() for capitalized acronyms)

isPrefixOnNic := service.isPrefixonNicSwiftV2()
service.processWindowsRegistryKeys(isPrefixOnNic, infraNicMacAddress)
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 confirm that it's intentional that this runs even for non-prefix on nic scenarios?


nmaNCs := map[string]string{}
for _, nc := range ncVersionListResp.Containers {
Expand Down Expand Up @@ -691,31 +691,31 @@ func (service *HTTPRestService) isNCDetailsAPIExists(ctx context.Context) bool {
return false
}

// GetIMDSNCs gets NC versions from IMDS and returns them as a map
func (service *HTTPRestService) GetIMDSNCs(ctx context.Context) (map[string]string, error) {
// GetIMDSNCs gets NC versions from IMDS and returns them as a map along with the MAC address
func (service *HTTPRestService) getIMDSNCs(ctx context.Context) (ncs map[string]string, infraNicMacAddress string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

since we're also getting the mac of the infra nic, maybe this should be something like getIMDSInfo now?

imdsClient := service.imdsClient
if imdsClient == nil {
//nolint:staticcheck // SA1019: suppress deprecated logger.Printf usage. Todo: legacy logger usage is consistent in cns repo. Migrates when all logger usage is migrated
logger.Errorf("IMDS client is not available")
return make(map[string]string), nil
return make(map[string]string), ""
}
// Check NC version support
if !service.isNCDetailsAPIExists(ctx) {
//nolint:staticcheck // SA1019: suppress deprecated logger.Printf usage. Todo: legacy logger usage is consistent in cns repo. Migrates when all logger usage is migrated
logger.Errorf("IMDS does not support NC details API")
return make(map[string]string), nil
return make(map[string]string), ""
}

// Get all network interfaces from IMDS
networkInterfaces, err := imdsClient.GetNetworkInterfaces(ctx)
if err != nil {
//nolint:staticcheck // SA1019: suppress deprecated logger.Printf usage. Todo: legacy logger usage is consistent in cns repo. Migrates when all logger usage is migrated
logger.Errorf("Failed to get network interfaces from IMDS: %v", err)
return make(map[string]string), nil
return make(map[string]string), ""
}

// Build ncs map from the network interfaces
ncs := make(map[string]string)
ncs = make(map[string]string)
for _, iface := range networkInterfaces {
//nolint:staticcheck // SA1019: suppress deprecated logger.Debugf usage. Todo: legacy logger usage is consistent in cns repo. Migrates when all logger usage is migrated
logger.Debugf("Nc id: %s and mac address: %s from IMDS call", iface.InterfaceCompartmentID, iface.MacAddress.String())
Expand All @@ -724,10 +724,24 @@ func (service *HTTPRestService) GetIMDSNCs(ctx context.Context) (map[string]stri

if ncID != "" {
ncs[ncID] = PrefixOnNicNCVersion // for prefix on nic version scenario nc version is 1
} else {
infraNicMacAddress = iface.MacAddress.String()
Copy link
Contributor

Choose a reason for hiding this comment

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

what does having an empty ncID mean and when does this happen? can this be added as a comment for the function? can we also clarify in the comment what mac address we are getting? will the infra nic mac address ever be empty if all the network interfaces have nc ids?

}
}

return ncs, nil
return ncs, infraNicMacAddress
}

// Check whether NC is SwiftV2 NIC associated NC and prefix on nic is enabled
func (service *HTTPRestService) isPrefixonNicSwiftV2() bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

For the comment it seems more like "check whether any NC is a swiftv2 nic with prefix on nic enabled" rather than any particular NC-- when this is enabled is there typically only 1 NC (or is it multitenant?)?

for i := range service.state.ContainerStatus {
req := service.state.ContainerStatus[i].CreateNetworkContainerRequest

if req.SwiftV2PrefixOnNic {
return true
}
}
return false
}

// IsCIDRSuperset returns true if newCIDR is a superset of oldCIDR (i.e., all IPs in oldCIDR are contained in newCIDR).
Expand Down
38 changes: 38 additions & 0 deletions cns/restserver/internalapi_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,44 @@ func (c *iptablesLegacy) Delete(table, chain string, rulespec ...string) error {
return errors.Wrap(exec.Command("iptables-legacy", cmd...).Run(), "iptables legacy failed delete")
}

// processWindowsRegistryKeys is a no-op on Linux.
func (service *HTTPRestService) processWindowsRegistryKeys(_ bool, _ string) {
// No-op on Linux
}

// linuxRegistryClient is a no-op implementation of windowsRegistryClient for Linux.
type linuxRegistryClient struct{}

// SetPrefixOnNicEnabled is a no-op on Linux.
func (*linuxRegistryClient) SetPrefixOnNicEnabled(_ bool) error {
return nil
}

// SetInfraNicMacAddress is a no-op on Linux.
func (*linuxRegistryClient) SetInfraNicMacAddress(_ string) error {
return nil
}

// SetInfraNicIfName is a no-op on Linux.
func (*linuxRegistryClient) SetInfraNicIfName(_ string) error {
return nil
}

// SetEnableSNAT is a no-op on Linux.
func (*linuxRegistryClient) SetEnableSNAT(_ bool) error {
return nil
}

// newLinuxRegistryClient creates a no-op registry client for Linux.
func newLinuxRegistryClient() windowsRegistryClient {
return &linuxRegistryClient{}
Copy link
Contributor

Choose a reason for hiding this comment

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

newLinuxRegistryClient() seems to be called only from newRegistryClient()-- can we just have newRegistryClient() return &linuxRegistryClient directly?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@NihaNallappagari I don't think you have understood my earlier feedback on OS abstraction/separations.

A func called newLinuxRegistryClient returning struct linuxRegistryClient as interface windowsRegistryClient is nonsensical. Stubbing Windows only behavior in a Linux struct so that the special Windows code compiles on Linux is bad. This abstraction is wrong.

If you need to make stubs like this, the place that you are calling these stubs is OS specific code already. THAT is what should be refactored in to Windows and Linux versions.

}

// newRegistryClient creates the OS-specific registry client (Linux implementation).
func newRegistryClient() windowsRegistryClient {
return newLinuxRegistryClient()
}

// nolint
func (service *HTTPRestService) programSNATRules(req *cns.CreateNetworkContainerRequest) (types.ResponseCode, string) {
service.Lock()
Expand Down
54 changes: 54 additions & 0 deletions cns/restserver/internalapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,7 @@ func TestCNIConflistGenerationNewNC(t *testing.T) {
mockgen := &mockCNIConflistGenerator{}
service := &HTTPRestService{
cniConflistGenerator: mockgen,
windowsRegistry: newRegistryClient(),
state: &httpRestServiceState{
ContainerStatus: map[string]containerstatus{
ncID: {
Expand Down Expand Up @@ -1392,6 +1393,7 @@ func TestCNIConflistGenerationExistingNC(t *testing.T) {
mockgen := &mockCNIConflistGenerator{}
service := &HTTPRestService{
cniConflistGenerator: mockgen,
windowsRegistry: newRegistryClient(),
state: &httpRestServiceState{
ContainerStatus: map[string]containerstatus{
ncID: {
Expand Down Expand Up @@ -1434,6 +1436,7 @@ func TestCNIConflistGenerationNewNCTwice(t *testing.T) {
mockgen := &mockCNIConflistGenerator{}
service := &HTTPRestService{
cniConflistGenerator: mockgen,
windowsRegistry: newRegistryClient(),
state: &httpRestServiceState{
ContainerStatus: map[string]containerstatus{
ncID: {
Expand Down Expand Up @@ -1480,6 +1483,7 @@ func TestCNIConflistNotGenerated(t *testing.T) {
mockgen := &mockCNIConflistGenerator{}
service := &HTTPRestService{
cniConflistGenerator: mockgen,
windowsRegistry: newRegistryClient(),
state: &httpRestServiceState{
ContainerStatus: map[string]containerstatus{
newNCID: {
Expand Down Expand Up @@ -1516,6 +1520,7 @@ func TestCNIConflistGenerationOnNMAError(t *testing.T) {
mockgen := &mockCNIConflistGenerator{}
service := &HTTPRestService{
cniConflistGenerator: mockgen,
windowsRegistry: newRegistryClient(),
state: &httpRestServiceState{
ContainerStatus: map[string]containerstatus{
newNCID: {
Expand Down Expand Up @@ -1758,3 +1763,52 @@ func setupIMDSMockAPIsWithCustomIDs(svc *HTTPRestService, interfaceIDs []string)
// Return cleanup function
return func() { svc.imdsClient = originalIMDS }
}

func TestGetIMDSNCsWithANDWITHOUTNCID(t *testing.T) {
testSvc := getTestService(cns.Kubernetes)

// Set up mock IMDS client with one interface with NC ID, one without
mac1, _ := net.ParseMAC("AA:BB:CC:DD:EE:FF")
mac2, _ := net.ParseMAC("11:22:33:44:55:66")

mockIMDS := &mockIMDSAdapter{
mock: &struct {
networkInterfaces func(_ context.Context) ([]imds.NetworkInterface, error)
imdsVersions func(_ context.Context) (*imds.APIVersionsResponse, error)
}{
networkInterfaces: func(_ context.Context) ([]imds.NetworkInterface, error) {
return []imds.NetworkInterface{
{
InterfaceCompartmentID: "nc-id-1",
MacAddress: imds.HardwareAddr(mac1),
},
{
InterfaceCompartmentID: "",
MacAddress: imds.HardwareAddr(mac2),
},
}, nil
},
imdsVersions: func(_ context.Context) (*imds.APIVersionsResponse, error) {
return &imds.APIVersionsResponse{
APIVersions: []string{expectedIMDSAPIVersion},
}, nil
},
},
}

// Replace the IMDS client
originalIMDS := testSvc.imdsClient
testSvc.imdsClient = mockIMDS
defer func() { testSvc.imdsClient = originalIMDS }()

ctx := context.Background()
imdsNCs, infraNicMacAddress := testSvc.getIMDSNCs(ctx)

// Verify that NC with compartment ID is returned
assert.Equal(t, PrefixOnNicNCVersion, imdsNCs["nc-id-1"], "NC should have expected version")
assert.Len(t, imdsNCs, 1, "Only one NC should be returned (the one with NC ID)")

// Verify that MAC address is returned for interface without NC ID
assert.Equal(t, mac2.String(), infraNicMacAddress, "MAC address should be returned for interface without NC ID")
assert.NotEmpty(t, infraNicMacAddress, "MAC address should not be empty when an interface has no NC ID")
}
Loading
Loading