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
45 changes: 38 additions & 7 deletions cns/restserver/internalapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net"
"net/http"
"net/http/httptest"
"net/netip"
"reflect"
"strconv"
"strings"
Expand Down Expand Up @@ -630,13 +631,17 @@ func (service *HTTPRestService) CreateOrUpdateNetworkContainerInternal(req *cns.
if ok {
existingReq := existingNCInfo.CreateNetworkContainerRequest
if !reflect.DeepEqual(existingReq.IPConfiguration.IPSubnet, req.IPConfiguration.IPSubnet) {
logger.Errorf("[Azure CNS] Error. PrimaryCA is not same, NCId %s, old CA %s/%d, new CA %s/%d",
req.NetworkContainerid,
existingReq.IPConfiguration.IPSubnet.IPAddress,
existingReq.IPConfiguration.IPSubnet.PrefixLength,
req.IPConfiguration.IPSubnet.IPAddress,
req.IPConfiguration.IPSubnet.PrefixLength)
return types.PrimaryCANotSame
// check for potential overlay subnet expansion - checking if new subnet is a superset of old subnet
isCIDRSuperset := validateCIDRSuperset(req.IPConfiguration.IPSubnet.IPAddress, existingReq.IPConfiguration.IPSubnet.IPAddress)
if !isCIDRSuperset {
logger.Errorf("[Azure CNS] Error. PrimaryCA is not same, NCId %s, old CA %s/%d, new CA %s/%d", //nolint:staticcheck // Suppress SA1019: logger.Errorf is deprecated
req.NetworkContainerid,
existingReq.IPConfiguration.IPSubnet.IPAddress,
existingReq.IPConfiguration.IPSubnet.PrefixLength,
req.IPConfiguration.IPSubnet.IPAddress,
req.IPConfiguration.IPSubnet.PrefixLength)
return types.PrimaryCANotSame
}
}
}

Expand Down Expand Up @@ -722,3 +727,29 @@ func (service *HTTPRestService) GetIMDSNCs(ctx context.Context) (map[string]stri

return ncs, nil
}

// IsCIDRSuperset returns true if newCIDR is a superset of oldCIDR (i.e., all IPs in oldCIDR are contained in newCIDR).
func validateCIDRSuperset(newCIDR, oldCIDR string) bool {
// Parse newCIDR and oldCIDR into netip.Prefix
newPrefix, err := netip.ParsePrefix(newCIDR)
if err != nil {
return false
}

oldPrefix, err := netip.ParsePrefix(oldCIDR)
if err != nil {
return false
}

// Condition 1: Check if the new prefix length is smaller (larger range) than the old prefix length
if newPrefix.Bits() >= oldPrefix.Bits() {
return false
}

// Condition 2: Check for Overlap - this will also ensure containment
if !newPrefix.Overlaps(oldPrefix) {
return false
}

return true
}
124 changes: 99 additions & 25 deletions cns/restserver/internalapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,39 +66,113 @@ func TestReconcileNCStatePrimaryIPChangeShouldFail(t *testing.T) {
setOrchestratorTypeInternal(cns.KubernetesCRD)
svc.state.ContainerStatus = make(map[string]containerstatus)

// start with a NC in state
ncID := "555ac5c9-89f2-4b5d-b8d0-616894d6d151"
svc.state.ContainerStatus[ncID] = containerstatus{
ID: ncID,
VMVersion: "0",
HostVersion: "0",
CreateNetworkContainerRequest: cns.CreateNetworkContainerRequest{
NetworkContainerid: ncID,
IPConfiguration: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: "10.0.1.0",
PrefixLength: 24,
testCases := []struct {
existingIPAddress string
requestIPAddress string
}{
{"", "10.240.0.0/16"},
{"10.240.0.0", "2001:db8::/64"},
{"2001:db8::/64", "10.240.0.0/16"},
{"10.0.1.0/22", "10.0.2.0/24"},
{"10.0.1.0/21", "10.0.1.0/23"},
{"10.0.1.0", "10.0.0.0/15"},
{"10.0.1.0/15", "10.0.0.0"},
}

// Run test cases
for _, tc := range testCases {
// start with a NC in state
ncID := "555ac5c9-89f2-4b5d-b8d0-616894d6d150"
svc.state.ContainerStatus[ncID] = containerstatus{
ID: ncID,
VMVersion: "0",
HostVersion: "0",
CreateNetworkContainerRequest: cns.CreateNetworkContainerRequest{
NetworkContainerid: ncID,
IPConfiguration: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: tc.existingIPAddress,
PrefixLength: 24,
},
},
},
},
}
}

ncReqs := []*cns.CreateNetworkContainerRequest{
{
NetworkContainerid: ncID,
IPConfiguration: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: "10.0.2.0", // note this IP has changed
PrefixLength: 24,
ncReqs := []*cns.CreateNetworkContainerRequest{
{
NetworkContainerid: ncID,
IPConfiguration: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: tc.requestIPAddress,
PrefixLength: 24,
},
},
},
},
}

// now try to reconcile the state where the NC primary IP has changed
resp := svc.ReconcileIPAMStateForSwift(ncReqs, map[string]cns.PodInfo{}, &v1alpha.NodeNetworkConfig{})

assert.Equal(t, types.PrimaryCANotSame, resp)
}

// now try to reconcile the state where the NC primary IP has changed
resp := svc.ReconcileIPAMStateForSwift(ncReqs, map[string]cns.PodInfo{}, &v1alpha.NodeNetworkConfig{})
}

// TestReconcileNCStatePrimaryIPChangeShouldNotFail tests that reconciling NC state with
// a NC whose IP has changed should not fail if new IP is superset of old IP
func TestReconcileNCStatePrimaryIPChangeShouldNotFail(t *testing.T) {
restartService()
setEnv(t)
setOrchestratorTypeInternal(cns.KubernetesCRD)
svc.state.ContainerStatus = make(map[string]containerstatus)

testCases := []struct {
existingIPAddress string
requestIPAddress string
}{
{"10.0.1.0/24", "10.0.2.0/22"},
{"10.0.1.0/20", "10.0.1.0/18"},
{"10.0.1.0/19", "10.0.0.0/15"},
{"10.0.1.0/18", "10.0.1.0/18"},
}

// Run test cases
for _, tc := range testCases {
// start with a NC in state
ncID := "555ac5c9-89f2-4b5d-b8d0-616894d6d150"
svc.state.ContainerStatus[ncID] = containerstatus{
ID: ncID,
VMVersion: "0",
HostVersion: "0",
CreateNetworkContainerRequest: cns.CreateNetworkContainerRequest{
NetworkContainerid: ncID,
IPConfiguration: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: tc.existingIPAddress,
PrefixLength: 24,
},
},
},
}

ncReqs := []*cns.CreateNetworkContainerRequest{
{
NetworkContainerid: ncID,
IPConfiguration: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: tc.requestIPAddress,
PrefixLength: 24,
},
},
NetworkContainerType: cns.Kubernetes,
},
}

assert.Equal(t, types.PrimaryCANotSame, resp)
// now try to reconcile the state where the NC primary IP has changed
resp := svc.ReconcileIPAMStateForSwift(ncReqs, map[string]cns.PodInfo{}, &v1alpha.NodeNetworkConfig{})

assert.Equal(t, types.Success, resp)
}
}

// TestReconcileNCStateGatewayChange tests that NC state gets updated when reconciled
Expand Down
Loading