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
2 changes: 1 addition & 1 deletion cni/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ARG OS_VERSION
ARG OS

# mcr.microsoft.com/oss/go/microsoft/golang:1.24-azurelinux3.0
FROM --platform=linux/${ARCH} mcr.microsoft.com/oss/go/microsoft/golang@sha256:281d086598c336073a59d32cd6fc614a892e90c0c0b881e5051d014859739f0e AS go
FROM --platform=linux/${ARCH} mcr.microsoft.com/oss/go/microsoft/golang@sha256:7bbbda682ce4a462855bd8a61c5efdc1e79ab89d9e32c2610f41e6f9502e1cf4 AS go

# mcr.microsoft.com/azurelinux/base/core:3.0
FROM --platform=linux/${ARCH} mcr.microsoft.com/azurelinux/base/core@sha256:833693619d523c23b1fe4d9c1f64a6c697e2a82f7a6ee26e1564897c3fe3fa02 AS mariner-core
Expand Down
2 changes: 1 addition & 1 deletion cns/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ARG OS_VERSION
ARG OS

# mcr.microsoft.com/oss/go/microsoft/golang:1.24-azurelinux3.0
FROM --platform=linux/${ARCH} mcr.microsoft.com/oss/go/microsoft/golang@sha256:281d086598c336073a59d32cd6fc614a892e90c0c0b881e5051d014859739f0e AS go
FROM --platform=linux/${ARCH} mcr.microsoft.com/oss/go/microsoft/golang@sha256:7bbbda682ce4a462855bd8a61c5efdc1e79ab89d9e32c2610f41e6f9502e1cf4 AS go

# mcr.microsoft.com/azurelinux/base/core:3.0
FROM mcr.microsoft.com/azurelinux/base/core@sha256:833693619d523c23b1fe4d9c1f64a6c697e2a82f7a6ee26e1564897c3fe3fa02 AS mariner-core
Expand Down
24 changes: 2 additions & 22 deletions cns/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,6 @@ const (
initialIBNICCount = 0
)

// ErrHomeAzNotAvailable indicates that HomeAZ information is not available from CNS
var ErrHomeAzNotAvailable = errors.New("home AZ not available from CNS")

type cniConflistScenario string

const (
Expand Down Expand Up @@ -1694,31 +1691,15 @@ func getPodInfoByIPProvider(
return podInfoByIPProvider, nil
}

// createOrUpdateNodeInfoCRD polls IMDS to learn the VM Unique ID and CNS to get the HomeAZ,
// then creates or updates the NodeInfo CRD with that information
// createOrUpdateNodeInfoCRD polls imds to learn the VM Unique ID and then creates or updates the NodeInfo CRD
// with that vm unique ID
func createOrUpdateNodeInfoCRD(ctx context.Context, restConfig *rest.Config, node *corev1.Node) error {
imdsCli := imds.NewClient()
vmUniqueID, err := imdsCli.GetVMUniqueID(ctx)
if err != nil {
return errors.Wrap(err, "error getting vm unique ID from imds")
}

cnsClient, err := cnsclient.New("", cnsReqTimeout)
if err != nil {
return errors.Wrap(err, "error creating CNS client")
}
var homeAZ string
homeAzResponse, err := cnsClient.GetHomeAz(ctx)
if err != nil {
return errors.Wrap(err, "error getting home AZ from CNS")
}
if homeAzResponse.Response.ReturnCode == cnstypes.Success && homeAzResponse.HomeAzResponse.IsSupported {
homeAZ = fmt.Sprintf("AZ%02d", homeAzResponse.HomeAzResponse.HomeAz)
} else {
return errors.Wrapf(ErrHomeAzNotAvailable, "ReturnCode=%d (expected=%d), IsSupported=%t",
homeAzResponse.Response.ReturnCode, cnstypes.Success, homeAzResponse.HomeAzResponse.IsSupported)
}

directcli, err := client.New(restConfig, client.Options{Scheme: multitenancy.Scheme})
if err != nil {
return errors.Wrap(err, "failed to create ctrl client")
Expand All @@ -1734,7 +1715,6 @@ func createOrUpdateNodeInfoCRD(ctx context.Context, restConfig *rest.Config, nod
},
Spec: mtv1alpha1.NodeInfoSpec{
VMUniqueID: vmUniqueID,
HomeAZ: homeAZ,
},
}

Expand Down
191 changes: 0 additions & 191 deletions cns/service/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,14 @@ package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/Azure/azure-container-networking/cns"
"github.com/Azure/azure-container-networking/cns/fakes"
"github.com/Azure/azure-container-networking/cns/logger"
"github.com/Azure/azure-container-networking/crd/multitenancy/api/v1alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
)

// MockHTTPClient is a mock implementation of HTTPClient
Expand Down Expand Up @@ -79,184 +69,3 @@ func TestSendRegisterNodeRequest_StatusAccepted(t *testing.T) {

assert.Error(t, sendRegisterNodeRequest(ctx, mockClient, httpServiceFake, nodeRegisterReq, url))
}

func TestCreateOrUpdateNodeInfoCRD_PopulatesHomeAZ(t *testing.T) {
vmID := "test-vm-unique-id-12345"
homeAZ := uint(2)
HomeAZStr := fmt.Sprintf("AZ0%d", homeAZ)

// Create mock IMDS server
mockIMDSServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/metadata/instance/compute") {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
response := map[string]interface{}{
"vmId": vmID,
"name": "test-vm",
"resourceGroupName": "test-rg",
}
_ = json.NewEncoder(w).Encode(response)
return
}
w.WriteHeader(http.StatusNotFound)
}))
defer mockIMDSServer.Close()

// Create mock CNS server
mockCNSServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/homeaz") || strings.Contains(r.URL.Path, "homeaz") {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
response := map[string]interface{}{
"ReturnCode": 0,
"Message": "",
"HomeAzResponse": map[string]interface{}{
"IsSupported": true,
"HomeAz": homeAZ,
},
}
_ = json.NewEncoder(w).Encode(response)
return
}
w.WriteHeader(http.StatusNotFound)
}))
defer mockCNSServer.Close()

// Set up HTTP transport to mock IMDS and CNS
originalTransport := http.DefaultTransport
defer func() { http.DefaultTransport = originalTransport }()

http.DefaultTransport = &mockTransport{
imdsServer: mockIMDSServer,
cnsServer: mockCNSServer,
original: originalTransport,
}

// Create a mock Kubernetes server that captures the NodeInfo being created
var capturedNodeInfo *v1alpha1.NodeInfo

mockK8sServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Handle specific API group discovery - multitenancy.acn.azure.com
if r.URL.Path == "/apis/multitenancy.acn.azure.com/v1alpha1" && r.Method == "GET" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"kind": "APIResourceList",
"groupVersion": "multitenancy.acn.azure.com/v1alpha1",
"resources": []map[string]interface{}{
{
"name": "nodeinfos",
"singularName": "nodeinfo",
"namespaced": false,
"kind": "NodeInfo",
"verbs": []string{"create", "delete", "get", "list", "patch", "update", "watch"},
},
},
})
return
}

// Handle NodeInfo resource requests
if strings.Contains(r.URL.Path, "nodeinfos") || strings.Contains(r.URL.Path, "multitenancy") {
if r.Method == "POST" || r.Method == "PATCH" || r.Method == "PUT" {
body, _ := io.ReadAll(r.Body)

// Try to parse the NodeInfo from the request
var nodeInfo v1alpha1.NodeInfo
if err := json.Unmarshal(body, &nodeInfo); err == nil {
capturedNodeInfo = &nodeInfo
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
// Return the created NodeInfo
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"apiVersion": "multitenancy.acn.azure.com/v1alpha1",
"kind": "NodeInfo",
"metadata": map[string]interface{}{
"name": "test-node",
},
"spec": map[string]interface{}{
"vmUniqueID": vmID,
"homeAZ": HomeAZStr,
},
})
return
}
}

// Default success response for any other API calls
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"kind": "Status",
"status": "Success",
})
}))
defer mockK8sServer.Close()

// Test the function with mocked dependencies
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Point to our mock Kubernetes server
restConfig := &rest.Config{
Host: mockK8sServer.URL,
}

node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "test-node"},
}

// Call the createOrUpdateNodeInfoCRD function
err := createOrUpdateNodeInfoCRD(ctx, restConfig, node)

// Verify the function succeeded
require.NoError(t, err, "Function should succeed with mocked dependencies")

// Verify the captured values
assert.NotNil(t, capturedNodeInfo, "NodeInfo should have been captured from K8s API call")
if capturedNodeInfo != nil {
assert.Equal(t, vmID, capturedNodeInfo.Spec.VMUniqueID, "VMUniqueID should be from IMDS")
assert.Equal(t, HomeAZStr, capturedNodeInfo.Spec.HomeAZ, "HomeAZ should be formatted from CNS response")
}
}

// mockTransport redirects HTTP requests to mock servers for testing.
// It intercepts requests to IMDS and CNS endpoints and routes them to local test servers.
type mockTransport struct {
imdsServer *httptest.Server
cnsServer *httptest.Server
original http.RoundTripper
}

func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// Redirect IMDS calls to mock IMDS server
if req.URL.Host == "169.254.169.254" {
req.URL.Scheme = "http"
req.URL.Host = strings.TrimPrefix(m.imdsServer.URL, "http://")
resp, err := m.original.RoundTrip(req)
if err != nil {
return nil, fmt.Errorf("IMDS mock transport failed: %w", err)
}
return resp, nil
}

// Redirect CNS calls to mock CNS server
if req.URL.Host == "localhost:10090" || strings.Contains(req.URL.Host, "10090") {
req.URL.Scheme = "http"
req.URL.Host = strings.TrimPrefix(m.cnsServer.URL, "http://")
resp, err := m.original.RoundTrip(req)
if err != nil {
return nil, fmt.Errorf("CNS mock transport failed: %w", err)
}
return resp, nil
}

// All other calls go through original transport
resp, err := m.original.RoundTrip(req)
if err != nil {
return nil, fmt.Errorf("mock transport failed: %w", err)
}
return resp, nil
}
Loading