Allow DNC to create NCs on a node with duplicate orchestrator context -- CNI & CNS (#1625)
This commit is contained in:
Родитель
162643ad9b
Коммит
1c854b470d
|
@ -9,5 +9,6 @@ import (
|
|||
type cnsclient interface {
|
||||
RequestIPAddress(ctx context.Context, ipconfig cns.IPConfigRequest) (*cns.IPConfigResponse, error)
|
||||
ReleaseIPAddress(ctx context.Context, ipconfig cns.IPConfigRequest) error
|
||||
GetNetworkConfiguration(ctx context.Context, orchestratorContext []byte) (*cns.GetNetworkContainerResponse, error)
|
||||
GetNetworkContainer(ctx context.Context, orchestratorContext []byte) (*cns.GetNetworkContainerResponse, error)
|
||||
GetAllNetworkContainers(ctx context.Context, orchestratorContext []byte) ([]cns.GetNetworkContainerResponse, error)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/Azure/azure-container-networking/cni"
|
||||
"github.com/Azure/azure-container-networking/cns"
|
||||
"github.com/Azure/azure-container-networking/cns/client"
|
||||
"github.com/Azure/azure-container-networking/common"
|
||||
"github.com/Azure/azure-container-networking/log"
|
||||
"github.com/Azure/azure-container-networking/network"
|
||||
|
@ -37,11 +38,12 @@ type MultitenancyClient interface {
|
|||
DetermineSnatFeatureOnHost(
|
||||
snatFile string,
|
||||
nmAgentSupportedApisURL string) (bool, bool, error)
|
||||
GetContainerNetworkConfiguration(
|
||||
GetAllNetworkContainers(
|
||||
ctx context.Context,
|
||||
nwCfg *cni.NetworkConfig,
|
||||
podName string,
|
||||
podNamespace string) (*cns.GetNetworkContainerResponse, net.IPNet, error)
|
||||
podNamespace string,
|
||||
ifName string) ([]IPAMAddResult, error)
|
||||
Init(cnsclient cnsclient, netioshim netioshim)
|
||||
}
|
||||
|
||||
|
@ -180,9 +182,10 @@ func (m *Multitenancy) SetupRoutingForMultitenancy(
|
|||
setupInfraVnetRoutingForMultitenancy(nwCfg, azIpamResult, epInfo, result)
|
||||
}
|
||||
|
||||
func (m *Multitenancy) GetContainerNetworkConfiguration(
|
||||
ctx context.Context, nwCfg *cni.NetworkConfig, podName, podNamespace string,
|
||||
) (*cns.GetNetworkContainerResponse, net.IPNet, error) {
|
||||
// get all network container configuration(s) for given orchestratorContext
|
||||
func (m *Multitenancy) GetAllNetworkContainers(
|
||||
ctx context.Context, nwCfg *cni.NetworkConfig, podName, podNamespace, ifName string,
|
||||
) ([]IPAMAddResult, error) {
|
||||
var podNameWithoutSuffix string
|
||||
|
||||
if !nwCfg.EnableExactMatchForPodName {
|
||||
|
@ -192,20 +195,36 @@ func (m *Multitenancy) GetContainerNetworkConfiguration(
|
|||
}
|
||||
|
||||
log.Printf("Podname without suffix %v", podNameWithoutSuffix)
|
||||
ncResponse, hostSubnetPrefix, err := m.getContainerNetworkConfigurationInternal(ctx, podNamespace, podNameWithoutSuffix)
|
||||
if nwCfg.EnableSnatOnHost {
|
||||
if ncResponse.LocalIPConfiguration.IPSubnet.IPAddress == "" {
|
||||
log.Printf("Snat IP is not populated. Got empty string")
|
||||
return nil, net.IPNet{}, errSnatIP
|
||||
|
||||
ncResponses, hostSubnetPrefixes, err := m.getNetworkContainersInternal(ctx, podNamespace, podNameWithoutSuffix)
|
||||
if err != nil {
|
||||
return []IPAMAddResult{}, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(ncResponses); i++ {
|
||||
if nwCfg.EnableSnatOnHost {
|
||||
if ncResponses[i].LocalIPConfiguration.IPSubnet.IPAddress == "" {
|
||||
log.Printf("Snat IP is not populated for ncs %+v. Got empty string", ncResponses)
|
||||
return []IPAMAddResult{}, errSnatIP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ncResponse, hostSubnetPrefix, err
|
||||
ipamResults := make([]IPAMAddResult, len(ncResponses))
|
||||
|
||||
for i := 0; i < len(ncResponses); i++ {
|
||||
ipamResults[i].ncResponse = &ncResponses[i]
|
||||
ipamResults[i].hostSubnetPrefix = hostSubnetPrefixes[i]
|
||||
ipamResults[i].ipv4Result = convertToCniResult(ipamResults[i].ncResponse, ifName)
|
||||
}
|
||||
|
||||
return ipamResults, err
|
||||
}
|
||||
|
||||
func (m *Multitenancy) getContainerNetworkConfigurationInternal(
|
||||
// get all network containers configuration for given orchestratorContext
|
||||
func (m *Multitenancy) getNetworkContainersInternal(
|
||||
ctx context.Context, namespace, podName string,
|
||||
) (*cns.GetNetworkContainerResponse, net.IPNet, error) {
|
||||
) ([]cns.GetNetworkContainerResponse, []net.IPNet, error) {
|
||||
podInfo := cns.KubernetesPodInfo{
|
||||
PodName: podName,
|
||||
PodNamespace: namespace,
|
||||
|
@ -214,25 +233,35 @@ func (m *Multitenancy) getContainerNetworkConfigurationInternal(
|
|||
orchestratorContext, err := json.Marshal(podInfo)
|
||||
if err != nil {
|
||||
log.Printf("Marshalling KubernetesPodInfo failed with %v", err)
|
||||
return nil, net.IPNet{}, fmt.Errorf("%w", err)
|
||||
return nil, []net.IPNet{}, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
networkConfig, err := m.cnsclient.GetNetworkConfiguration(ctx, orchestratorContext)
|
||||
if err != nil {
|
||||
log.Printf("GetNetworkConfiguration failed with %v", err)
|
||||
return nil, net.IPNet{}, fmt.Errorf("%w", err)
|
||||
// First try the new CNS API that returns slice of nc responses. If CNS doesn't support the new API, an error will be returned and as a result
|
||||
// try using the old CNS API that returns single nc response.
|
||||
ncConfigs, err := m.cnsclient.GetAllNetworkContainers(ctx, orchestratorContext)
|
||||
if err != nil && client.IsUnsupportedAPI(err) {
|
||||
ncConfig, errGetNC := m.cnsclient.GetNetworkContainer(ctx, orchestratorContext)
|
||||
if errGetNC != nil {
|
||||
return nil, []net.IPNet{}, fmt.Errorf("%w", err)
|
||||
}
|
||||
ncConfigs = append(ncConfigs, *ncConfig)
|
||||
} else if err != nil {
|
||||
return nil, []net.IPNet{}, fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
log.Printf("Network config received from cns %+v", networkConfig)
|
||||
log.Printf("Network config received from cns %+v", ncConfigs)
|
||||
|
||||
subnetPrefix := m.netioshim.GetInterfaceSubnetWithSpecificIP(networkConfig.PrimaryInterfaceIdentifier)
|
||||
if subnetPrefix == nil {
|
||||
errBuf := fmt.Errorf("%w %s", errIfaceNotFound, networkConfig.PrimaryInterfaceIdentifier)
|
||||
log.Printf(errBuf.Error())
|
||||
return nil, net.IPNet{}, errBuf
|
||||
subnetPrefixes := []net.IPNet{}
|
||||
for i := 0; i < len(ncConfigs); i++ {
|
||||
subnetPrefix := m.netioshim.GetInterfaceSubnetWithSpecificIP(ncConfigs[i].PrimaryInterfaceIdentifier)
|
||||
if subnetPrefix == nil {
|
||||
log.Printf("%w %s", errIfaceNotFound, ncConfigs[i].PrimaryInterfaceIdentifier)
|
||||
return nil, []net.IPNet{}, errIfaceNotFound
|
||||
}
|
||||
subnetPrefixes = append(subnetPrefixes, *subnetPrefix)
|
||||
}
|
||||
|
||||
return networkConfig, *subnetPrefix, nil
|
||||
return ncConfigs, subnetPrefixes, nil
|
||||
}
|
||||
|
||||
func convertToCniResult(networkConfig *cns.GetNetworkContainerResponse, ifName string) *cniTypesCurr.Result {
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"github.com/Azure/azure-container-networking/cni"
|
||||
"github.com/Azure/azure-container-networking/cns"
|
||||
|
@ -16,8 +17,10 @@ type MockMultitenancy struct {
|
|||
}
|
||||
|
||||
const (
|
||||
ipPrefixLen = 24
|
||||
localIPPrefixLen = 17
|
||||
ipPrefixLen = 24
|
||||
localIPPrefixLen = 17
|
||||
multiTenancyVlan1 = 1
|
||||
multiTenancyVlan2 = 2
|
||||
)
|
||||
|
||||
var errMockMulAdd = errors.New("multitenancy fail")
|
||||
|
@ -42,7 +45,7 @@ func (m *MockMultitenancy) DetermineSnatFeatureOnHost(snatFile, nmAgentSupported
|
|||
return true, true, nil
|
||||
}
|
||||
|
||||
func (m *MockMultitenancy) GetContainerNetworkConfiguration(
|
||||
func (m *MockMultitenancy) GetNetworkContainer(
|
||||
ctx context.Context,
|
||||
nwCfg *cni.NetworkConfig,
|
||||
podName string,
|
||||
|
@ -78,3 +81,85 @@ func (m *MockMultitenancy) GetContainerNetworkConfiguration(
|
|||
|
||||
return cnsResponse, *ipnet, nil
|
||||
}
|
||||
|
||||
func (m *MockMultitenancy) GetAllNetworkContainers(
|
||||
ctx context.Context,
|
||||
nwCfg *cni.NetworkConfig,
|
||||
podName string,
|
||||
podNamespace string,
|
||||
ifName string,
|
||||
) ([]IPAMAddResult, error) {
|
||||
if m.fail {
|
||||
return nil, errMockMulAdd
|
||||
}
|
||||
|
||||
var cnsResponses []cns.GetNetworkContainerResponse
|
||||
var ipNets []net.IPNet
|
||||
|
||||
cnsResponseOne := &cns.GetNetworkContainerResponse{
|
||||
IPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "20.0.0.10",
|
||||
PrefixLength: ipPrefixLen,
|
||||
},
|
||||
GatewayIPAddress: "20.0.0.1",
|
||||
},
|
||||
LocalIPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "168.254.0.4",
|
||||
PrefixLength: localIPPrefixLen,
|
||||
},
|
||||
GatewayIPAddress: "168.254.0.1",
|
||||
},
|
||||
|
||||
PrimaryInterfaceIdentifier: "20.240.0.4/24",
|
||||
MultiTenancyInfo: cns.MultiTenancyInfo{
|
||||
EncapType: cns.Vlan,
|
||||
ID: multiTenancyVlan1,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: add dual nic test cases for windows
|
||||
if runtime.GOOS == "windows" {
|
||||
cnsResponseTwo := &cns.GetNetworkContainerResponse{
|
||||
IPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "10.0.0.10",
|
||||
PrefixLength: ipPrefixLen,
|
||||
},
|
||||
GatewayIPAddress: "10.0.0.1",
|
||||
},
|
||||
LocalIPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "169.254.0.4",
|
||||
PrefixLength: localIPPrefixLen,
|
||||
},
|
||||
GatewayIPAddress: "169.254.0.1",
|
||||
},
|
||||
|
||||
PrimaryInterfaceIdentifier: "10.240.0.4/24",
|
||||
MultiTenancyInfo: cns.MultiTenancyInfo{
|
||||
EncapType: cns.Vlan,
|
||||
ID: multiTenancyVlan2,
|
||||
},
|
||||
}
|
||||
|
||||
_, secondIPnet, _ := net.ParseCIDR(cnsResponseTwo.PrimaryInterfaceIdentifier)
|
||||
ipNets = append(ipNets, *secondIPnet)
|
||||
cnsResponses = append(cnsResponses, *cnsResponseTwo)
|
||||
}
|
||||
|
||||
_, firstIPnet, _ := net.ParseCIDR(cnsResponseOne.PrimaryInterfaceIdentifier)
|
||||
|
||||
ipNets = append(ipNets, *firstIPnet)
|
||||
cnsResponses = append(cnsResponses, *cnsResponseOne)
|
||||
|
||||
ipamResults := make([]IPAMAddResult, len(cnsResponses))
|
||||
for i := 0; i < len(cnsResponses); i++ {
|
||||
ipamResults[i].ncResponse = &cnsResponses[i]
|
||||
ipamResults[i].hostSubnetPrefix = ipNets[i]
|
||||
ipamResults[i].ipv4Result = convertToCniResult(ipamResults[i].ncResponse, ifName)
|
||||
}
|
||||
|
||||
return ipamResults, nil
|
||||
}
|
||||
|
|
|
@ -29,17 +29,25 @@ type releaseIPAddressHandler struct {
|
|||
err error
|
||||
}
|
||||
|
||||
type getNetworkConfigurationHandler struct {
|
||||
type getNetworkContainerConfigurationHandler struct {
|
||||
orchestratorContext []byte
|
||||
returnResponse *cns.GetNetworkContainerResponse
|
||||
err error
|
||||
}
|
||||
|
||||
// this is to get all the NCs for testing with given orchestratorContext
|
||||
type getAllNetworkContainersConfigurationHandler struct {
|
||||
orchestratorContext []byte
|
||||
returnResponse []cns.GetNetworkContainerResponse
|
||||
err error
|
||||
}
|
||||
|
||||
type MockCNSClient struct {
|
||||
require *require.Assertions
|
||||
request requestIPAddressHandler
|
||||
release releaseIPAddressHandler
|
||||
getNetworkConfiguration getNetworkConfigurationHandler
|
||||
require *require.Assertions
|
||||
request requestIPAddressHandler
|
||||
release releaseIPAddressHandler
|
||||
getNetworkContainerConfiguration getNetworkContainerConfigurationHandler
|
||||
getAllNetworkContainersConfiguration getAllNetworkContainersConfigurationHandler
|
||||
}
|
||||
|
||||
func (c *MockCNSClient) RequestIPAddress(_ context.Context, ipconfig cns.IPConfigRequest) (*cns.IPConfigResponse, error) {
|
||||
|
@ -52,9 +60,14 @@ func (c *MockCNSClient) ReleaseIPAddress(_ context.Context, ipconfig cns.IPConfi
|
|||
return c.release.err
|
||||
}
|
||||
|
||||
func (c *MockCNSClient) GetNetworkConfiguration(ctx context.Context, orchestratorContext []byte) (*cns.GetNetworkContainerResponse, error) {
|
||||
c.require.Exactly(c.getNetworkConfiguration.orchestratorContext, orchestratorContext)
|
||||
return c.getNetworkConfiguration.returnResponse, c.getNetworkConfiguration.err
|
||||
func (c *MockCNSClient) GetNetworkContainer(ctx context.Context, orchestratorContext []byte) (*cns.GetNetworkContainerResponse, error) {
|
||||
c.require.Exactly(c.getNetworkContainerConfiguration.orchestratorContext, orchestratorContext)
|
||||
return c.getNetworkContainerConfiguration.returnResponse, c.getNetworkContainerConfiguration.err
|
||||
}
|
||||
|
||||
func (c *MockCNSClient) GetAllNetworkContainers(ctx context.Context, orchestratorContext []byte) ([]cns.GetNetworkContainerResponse, error) {
|
||||
c.require.Exactly(c.getAllNetworkContainersConfiguration.orchestratorContext, orchestratorContext)
|
||||
return c.getAllNetworkContainersConfiguration.returnResponse, c.getAllNetworkContainersConfiguration.err
|
||||
}
|
||||
|
||||
func defaultIPNet() *net.IPNet {
|
||||
|
@ -217,6 +230,71 @@ func TestCleanupMultitenancyResources(t *testing.T) {
|
|||
|
||||
func TestGetMultiTenancyCNIResult(t *testing.T) {
|
||||
require := require.New(t) //nolint:gocritic
|
||||
|
||||
var ncResponses []cns.GetNetworkContainerResponse
|
||||
ncResponseOne := cns.GetNetworkContainerResponse{
|
||||
PrimaryInterfaceIdentifier: "10.0.0.0/16",
|
||||
LocalIPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "10.0.0.5",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
GatewayIPAddress: "",
|
||||
},
|
||||
CnetAddressSpace: []cns.IPSubnet{
|
||||
{
|
||||
IPAddress: "10.1.0.0",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
},
|
||||
IPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "10.1.0.5",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
DNSServers: nil,
|
||||
GatewayIPAddress: "10.1.0.1",
|
||||
},
|
||||
Routes: []cns.Route{
|
||||
{
|
||||
IPAddress: "10.1.0.0/16",
|
||||
GatewayIPAddress: "10.1.0.1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ncResponseTwo := cns.GetNetworkContainerResponse{
|
||||
PrimaryInterfaceIdentifier: "20.0.0.0/16",
|
||||
LocalIPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "20.0.0.5",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
GatewayIPAddress: "",
|
||||
},
|
||||
CnetAddressSpace: []cns.IPSubnet{
|
||||
{
|
||||
IPAddress: "20.1.0.0",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
},
|
||||
IPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "20.1.0.5",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
DNSServers: nil,
|
||||
GatewayIPAddress: "20.1.0.1",
|
||||
},
|
||||
Routes: []cns.Route{
|
||||
{
|
||||
IPAddress: "20.1.0.0/16",
|
||||
GatewayIPAddress: "20.1.0.1",
|
||||
},
|
||||
},
|
||||
}
|
||||
ncResponses = append(ncResponses, ncResponseOne, ncResponseTwo)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
enableInfraVnet bool
|
||||
|
@ -226,13 +304,16 @@ func TestGetMultiTenancyCNIResult(t *testing.T) {
|
|||
k8sNamespace string
|
||||
ifName string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *cniTypesCurr.Result
|
||||
want1 *cns.GetNetworkContainerResponse
|
||||
want2 net.IPNet
|
||||
want3 *cniTypesCurr.Result
|
||||
want2 *cns.GetNetworkContainerResponse
|
||||
want3 net.IPNet
|
||||
want4 *cniTypesCurr.Result
|
||||
want5 []cns.GetNetworkContainerResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
|
@ -252,41 +333,12 @@ func TestGetMultiTenancyCNIResult(t *testing.T) {
|
|||
netioshim: &mockNetIOShim{},
|
||||
cnsclient: &MockCNSClient{
|
||||
require: require,
|
||||
getNetworkConfiguration: getNetworkConfigurationHandler{
|
||||
getAllNetworkContainersConfiguration: getAllNetworkContainersConfigurationHandler{
|
||||
orchestratorContext: marshallPodInfo(cns.KubernetesPodInfo{
|
||||
PodName: "testpod",
|
||||
PodNamespace: "testnamespace",
|
||||
}),
|
||||
returnResponse: &cns.GetNetworkContainerResponse{
|
||||
PrimaryInterfaceIdentifier: "10.0.0.0/16",
|
||||
LocalIPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "10.0.0.5",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
GatewayIPAddress: "",
|
||||
},
|
||||
CnetAddressSpace: []cns.IPSubnet{
|
||||
{
|
||||
IPAddress: "10.1.0.0",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
},
|
||||
IPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "10.1.0.5",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
DNSServers: nil,
|
||||
GatewayIPAddress: "10.1.0.1",
|
||||
},
|
||||
Routes: []cns.Route{
|
||||
{
|
||||
IPAddress: "10.1.0.0/16",
|
||||
GatewayIPAddress: "10.1.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
returnResponse: ncResponses,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -295,7 +347,6 @@ func TestGetMultiTenancyCNIResult(t *testing.T) {
|
|||
k8sNamespace: "testnamespace",
|
||||
ifName: "eth0",
|
||||
},
|
||||
|
||||
want: &cniTypesCurr.Result{
|
||||
Interfaces: []*cniTypesCurr.Interface{
|
||||
{
|
||||
|
@ -349,8 +400,38 @@ func TestGetMultiTenancyCNIResult(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want2: *getCIDRNotationForAddress("10.0.0.0/16"),
|
||||
want3: &cniTypesCurr.Result{
|
||||
want2: &cns.GetNetworkContainerResponse{
|
||||
PrimaryInterfaceIdentifier: "20.0.0.0/16",
|
||||
LocalIPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "20.0.0.5",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
GatewayIPAddress: "",
|
||||
},
|
||||
CnetAddressSpace: []cns.IPSubnet{
|
||||
{
|
||||
IPAddress: "20.1.0.0",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
},
|
||||
IPConfiguration: cns.IPConfiguration{
|
||||
IPSubnet: cns.IPSubnet{
|
||||
IPAddress: "20.1.0.5",
|
||||
PrefixLength: 16,
|
||||
},
|
||||
DNSServers: nil,
|
||||
GatewayIPAddress: "20.1.0.1",
|
||||
},
|
||||
Routes: []cns.Route{
|
||||
{
|
||||
IPAddress: "20.1.0.0/16",
|
||||
GatewayIPAddress: "20.1.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
want3: *getCIDRNotationForAddress("10.0.0.0/16"),
|
||||
want4: &cniTypesCurr.Result{
|
||||
IPs: []*cniTypesCurr.IPConfig{
|
||||
{
|
||||
Address: net.IPNet{
|
||||
|
@ -368,11 +449,12 @@ func TestGetMultiTenancyCNIResult(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got1, got2, err := tt.args.plugin.multitenancyClient.GetContainerNetworkConfiguration(
|
||||
got, err := tt.args.plugin.multitenancyClient.GetAllNetworkContainers(
|
||||
tt.args.ctx,
|
||||
tt.args.nwCfg,
|
||||
tt.args.k8sPodName,
|
||||
tt.args.k8sNamespace)
|
||||
tt.args.k8sNamespace,
|
||||
tt.args.ifName)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetContainerNetworkConfiguration() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
@ -381,8 +463,13 @@ func TestGetMultiTenancyCNIResult(t *testing.T) {
|
|||
require.Error(err)
|
||||
}
|
||||
require.NoError(err)
|
||||
require.Exactly(tt.want1, got1)
|
||||
require.Exactly(tt.want2, got2)
|
||||
require.Exactly(tt.want1, got[0].ncResponse)
|
||||
require.Exactly(tt.want2, got[1].ncResponse)
|
||||
require.Exactly(tt.want3, got[0].hostSubnetPrefix)
|
||||
|
||||
// check multiple responses
|
||||
tt.want5 = append(tt.want5, *tt.want1, *tt.want2)
|
||||
require.Exactly(tt.want5, ncResponses)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -316,15 +316,14 @@ func addNatIPV6SubnetInfo(nwCfg *cni.NetworkConfig,
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// CNI implementation
|
||||
// https://github.com/containernetworking/cni/blob/master/SPEC.md
|
||||
//
|
||||
|
||||
// Add handles CNI add commands.
|
||||
func (plugin *NetPlugin) Add(args *cniSkel.CmdArgs) error {
|
||||
var (
|
||||
ipamAddResult IPAMAddResult
|
||||
ipamAddResults []IPAMAddResult
|
||||
azIpamResult *cniTypesCurr.Result
|
||||
enableInfraVnet bool
|
||||
enableSnatForDNS bool
|
||||
|
@ -431,9 +430,9 @@ func (plugin *NetPlugin) Add(args *cniSkel.CmdArgs) error {
|
|||
}
|
||||
}
|
||||
|
||||
cnsClient, er := cnscli.New(nwCfg.CNSUrl, defaultRequestTimeout)
|
||||
if er != nil {
|
||||
return fmt.Errorf("failed to create cns client with error: %w", er)
|
||||
cnsClient, err := cnscli.New(nwCfg.CNSUrl, defaultRequestTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create cns client with error: %w", err)
|
||||
}
|
||||
|
||||
if nwCfg.MultiTenancy {
|
||||
|
@ -446,123 +445,128 @@ func (plugin *NetPlugin) Add(args *cniSkel.CmdArgs) error {
|
|||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
ipamAddResult.ncResponse, ipamAddResult.hostSubnetPrefix, er = plugin.multitenancyClient.GetContainerNetworkConfiguration(
|
||||
context.TODO(), nwCfg, k8sPodName, k8sNamespace)
|
||||
if er != nil {
|
||||
er = errors.Wrapf(er, "GetContainerNetworkConfiguration failed for podname %v namespace %v", k8sPodName, k8sNamespace)
|
||||
log.Printf("%+v", er)
|
||||
return er
|
||||
}
|
||||
|
||||
ipamAddResult.ipv4Result = convertToCniResult(ipamAddResult.ncResponse, args.IfName)
|
||||
|
||||
log.Printf("PrimaryInterfaceIdentifier: %v", ipamAddResult.hostSubnetPrefix.IP.String())
|
||||
}
|
||||
|
||||
// Initialize values from network config.
|
||||
networkID, err := plugin.getNetworkName(args.Netns, &ipamAddResult, nwCfg)
|
||||
if err != nil {
|
||||
log.Printf("[cni-net] Failed to extract network name from network config. error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
endpointID := GetEndpointID(args)
|
||||
policies := cni.GetPoliciesFromNwCfg(nwCfg.AdditionalArgs)
|
||||
|
||||
options := make(map[string]interface{})
|
||||
// Check whether the network already exists.
|
||||
nwInfo, nwInfoErr := plugin.nm.GetNetworkInfo(networkID)
|
||||
/* Handle consecutive ADD calls for infrastructure containers.
|
||||
* This is a temporary work around for issue #57253 of Kubernetes.
|
||||
* We can delete this if statement once they fix it.
|
||||
* Issue link: https://github.com/kubernetes/kubernetes/issues/57253
|
||||
*/
|
||||
if nwInfoErr == nil {
|
||||
log.Printf("[cni-net] Found network %v with subnet %v.", networkID, nwInfo.Subnets[0].Prefix.String())
|
||||
nwInfo.IPAMType = nwCfg.IPAM.Type
|
||||
options = nwInfo.Options
|
||||
|
||||
var resultSecondAdd *cniTypesCurr.Result
|
||||
resultSecondAdd, err = plugin.handleConsecutiveAdd(args, endpointID, networkID, &nwInfo, nwCfg)
|
||||
ipamAddResults, err = plugin.multitenancyClient.GetAllNetworkContainers(context.TODO(), nwCfg, k8sPodName, k8sNamespace, args.IfName)
|
||||
if err != nil {
|
||||
log.Printf("handleConsecutiveAdd failed with error %v", err)
|
||||
err = fmt.Errorf("GetAllNetworkContainers failed for podname %s namespace %s. error: %w", k8sPodName, k8sNamespace, err)
|
||||
log.Printf("%+v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if resultSecondAdd != nil {
|
||||
ipamAddResult.ipv4Result = resultSecondAdd
|
||||
return nil
|
||||
if len(ipamAddResults) > 1 && !plugin.isDualNicFeatureSupported(args.Netns) {
|
||||
errMsg := fmt.Sprintf("received multiple NC results %+v from CNS while dualnic feature is not supported", ipamAddResults)
|
||||
log.Printf(errMsg)
|
||||
return plugin.Errorf(errMsg)
|
||||
}
|
||||
} else {
|
||||
// TODO: refactor this code for simplification
|
||||
// Add dummy ipamAddResult nil object for single tenancy mode
|
||||
// this will be used for: ipamAddResult, err = plugin.ipamInvoker.Add(ipamAddConfig)
|
||||
ipamAddResults = append(ipamAddResults, ipamAddResult)
|
||||
}
|
||||
|
||||
// Initialize azureipam/cns ipam
|
||||
if plugin.ipamInvoker == nil {
|
||||
switch nwCfg.IPAM.Type {
|
||||
case network.AzureCNS:
|
||||
plugin.ipamInvoker = NewCNSInvoker(k8sPodName, k8sNamespace, cnsClient, util.ExecutionMode(nwCfg.ExecutionMode), util.IpamMode(nwCfg.IPAM.Mode))
|
||||
// iterate ipamAddResults and program the endpoint
|
||||
for i := 0; i < len(ipamAddResults); i++ {
|
||||
ipamAddResult = ipamAddResults[i]
|
||||
|
||||
default:
|
||||
plugin.ipamInvoker = NewAzureIpamInvoker(plugin, &nwInfo)
|
||||
options := make(map[string]any)
|
||||
networkID, err := plugin.getNetworkName(args.Netns, &ipamAddResult, nwCfg)
|
||||
|
||||
endpointID := GetEndpointID(args)
|
||||
policies := cni.GetPoliciesFromNwCfg(nwCfg.AdditionalArgs)
|
||||
|
||||
// Check whether the network already exists.
|
||||
nwInfo, nwInfoErr := plugin.nm.GetNetworkInfo(networkID)
|
||||
// Handle consecutive ADD calls for infrastructure containers.
|
||||
// This is a temporary work around for issue #57253 of Kubernetes.
|
||||
// We can delete this if statement once they fix it.
|
||||
// Issue link: https://github.com/kubernetes/kubernetes/issues/57253
|
||||
|
||||
if nwInfoErr == nil {
|
||||
log.Printf("[cni-net] Found network %v with subnet %v.", networkID, nwInfo.Subnets[0].Prefix.String())
|
||||
nwInfo.IPAMType = nwCfg.IPAM.Type
|
||||
options = nwInfo.Options
|
||||
|
||||
var resultSecondAdd *cniTypesCurr.Result
|
||||
resultSecondAdd, err = plugin.handleConsecutiveAdd(args, endpointID, networkID, &nwInfo, nwCfg)
|
||||
if err != nil {
|
||||
log.Printf("handleConsecutiveAdd failed with error %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if resultSecondAdd != nil {
|
||||
ipamAddResult.ipv4Result = resultSecondAdd
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipamAddConfig := IPAMAddConfig{nwCfg: nwCfg, args: args, options: options}
|
||||
// No need to call Add if we already got IPAMAddResult in multitenancy section via GetContainerNetworkConfiguration
|
||||
if !nwCfg.MultiTenancy {
|
||||
ipamAddResult, err = plugin.ipamInvoker.Add(ipamAddConfig)
|
||||
// Initialize azureipam/cns ipam
|
||||
if plugin.ipamInvoker == nil {
|
||||
switch nwCfg.IPAM.Type {
|
||||
case network.AzureCNS:
|
||||
plugin.ipamInvoker = NewCNSInvoker(k8sPodName, k8sNamespace, cnsClient, util.ExecutionMode(nwCfg.ExecutionMode), util.IpamMode(nwCfg.IPAM.Mode))
|
||||
|
||||
default:
|
||||
plugin.ipamInvoker = NewAzureIpamInvoker(plugin, &nwInfo)
|
||||
}
|
||||
}
|
||||
|
||||
ipamAddConfig := IPAMAddConfig{nwCfg: nwCfg, args: args, options: options}
|
||||
if !nwCfg.MultiTenancy {
|
||||
ipamAddResult, err = plugin.ipamInvoker.Add(ipamAddConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("IPAM Invoker Add failed with error: %w", err)
|
||||
}
|
||||
sendEvent(plugin, fmt.Sprintf("Allocated IPAddress from ipam:%+v v6:%+v", ipamAddResult.ipv4Result, ipamAddResult.ipv6Result))
|
||||
}
|
||||
|
||||
defer func() { //nolint:gocritic
|
||||
if err != nil {
|
||||
plugin.cleanupAllocationOnError(ipamAddResult.ipv4Result, ipamAddResult.ipv6Result, nwCfg, args, options)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create network
|
||||
if nwInfoErr != nil {
|
||||
// Network does not exist.
|
||||
logAndSendEvent(plugin, fmt.Sprintf("[cni-net] Creating network %v.", networkID))
|
||||
// opts map needs to get passed in here
|
||||
if nwInfo, err = plugin.createNetworkInternal(networkID, policies, ipamAddConfig, ipamAddResult); err != nil {
|
||||
log.Errorf("Create network failed: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logAndSendEvent(plugin, fmt.Sprintf("[cni-net] Created network %v with subnet %v.", networkID, ipamAddResult.hostSubnetPrefix.String()))
|
||||
}
|
||||
|
||||
natInfo := getNATInfo(nwCfg.ExecutionMode, options[network.SNATIPKey], nwCfg.MultiTenancy, enableSnatForDNS)
|
||||
|
||||
createEndpointInternalOpt := createEndpointInternalOpt{
|
||||
nwCfg: nwCfg,
|
||||
cnsNetworkConfig: ipamAddResult.ncResponse,
|
||||
result: ipamAddResult.ipv4Result,
|
||||
resultV6: ipamAddResult.ipv6Result,
|
||||
azIpamResult: azIpamResult,
|
||||
args: args,
|
||||
nwInfo: &nwInfo,
|
||||
policies: policies,
|
||||
endpointID: endpointID,
|
||||
k8sPodName: k8sPodName,
|
||||
k8sNamespace: k8sNamespace,
|
||||
enableInfraVnet: enableInfraVnet,
|
||||
enableSnatForDNS: enableSnatForDNS,
|
||||
natInfo: natInfo,
|
||||
}
|
||||
|
||||
epInfo, err := plugin.createEndpointInternal(&createEndpointInternalOpt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("IPAM Invoker Add failed with error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
sendEvent(plugin, fmt.Sprintf("Allocated IPAddress from ipam:%+v v6:%+v", ipamAddResult.ipv4Result, ipamAddResult.ipv6Result))
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
plugin.cleanupAllocationOnError(ipamAddResult.ipv4Result, ipamAddResult.ipv6Result, nwCfg, args, options)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create network
|
||||
if nwInfoErr != nil {
|
||||
// Network does not exist.
|
||||
logAndSendEvent(plugin, fmt.Sprintf("[cni-net] Creating network %v.", networkID))
|
||||
// opts map needs to get passed in here
|
||||
if nwInfo, err = plugin.createNetworkInternal(networkID, policies, ipamAddConfig, ipamAddResult); err != nil {
|
||||
log.Errorf("Create network failed: %w", err)
|
||||
log.Errorf("Endpoint creation failed:%w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logAndSendEvent(plugin, fmt.Sprintf("[cni-net] Created network %v with subnet %v.", networkID, ipamAddResult.hostSubnetPrefix.String()))
|
||||
sendEvent(plugin, fmt.Sprintf("CNI ADD succeeded : IP:%+v, VlanID: %v, podname %v, namespace %v numendpoints:%d",
|
||||
ipamAddResult.ipv4Result.IPs, epInfo.Data[network.VlanIDKey], k8sPodName, k8sNamespace, plugin.nm.GetNumberOfEndpoints("", nwCfg.Name)))
|
||||
}
|
||||
|
||||
natInfo := getNATInfo(nwCfg.ExecutionMode, options[network.SNATIPKey], nwCfg.MultiTenancy, enableSnatForDNS)
|
||||
|
||||
createEndpointInternalOpt := createEndpointInternalOpt{
|
||||
nwCfg: nwCfg,
|
||||
cnsNetworkConfig: ipamAddResult.ncResponse,
|
||||
result: ipamAddResult.ipv4Result,
|
||||
resultV6: ipamAddResult.ipv6Result,
|
||||
azIpamResult: azIpamResult,
|
||||
args: args,
|
||||
nwInfo: &nwInfo,
|
||||
policies: policies,
|
||||
endpointID: endpointID,
|
||||
k8sPodName: k8sPodName,
|
||||
k8sNamespace: k8sNamespace,
|
||||
enableInfraVnet: enableInfraVnet,
|
||||
enableSnatForDNS: enableSnatForDNS,
|
||||
natInfo: natInfo,
|
||||
}
|
||||
epInfo, err := plugin.createEndpointInternal(&createEndpointInternalOpt)
|
||||
if err != nil {
|
||||
log.Errorf("Endpoint creation failed:%w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
sendEvent(plugin, fmt.Sprintf("CNI ADD succeeded : IP:%+v, VlanID: %v, podname %v, namespace %v numendpoints:%d",
|
||||
ipamAddResult.ipv4Result.IPs, epInfo.Data[network.VlanIDKey], k8sPodName, k8sNamespace, plugin.nm.GetNumberOfEndpoints("", nwCfg.Name)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -947,6 +951,7 @@ func (plugin *NetPlugin) Delete(args *cniSkel.CmdArgs) error {
|
|||
// with CNI SPEC as mentioned below.
|
||||
|
||||
numEndpointsToDelete := 1
|
||||
// only get number of endpoints if it's multitenancy mode
|
||||
if nwCfg.MultiTenancy {
|
||||
numEndpointsToDelete = plugin.nm.GetNumEndpointsInNetNs(args.Netns)
|
||||
}
|
||||
|
@ -1148,8 +1153,8 @@ func (plugin *NetPlugin) Update(args *cniSkel.CmdArgs) error {
|
|||
return plugin.Errorf(err.Error())
|
||||
}
|
||||
|
||||
if targetNetworkConfig, err = cnsclient.GetNetworkConfiguration(context.TODO(), orchestratorContext); err != nil {
|
||||
log.Printf("GetNetworkConfiguration failed with %v", err)
|
||||
if targetNetworkConfig, err = cnsclient.GetNetworkContainer(context.TODO(), orchestratorContext); err != nil {
|
||||
log.Printf("GetNetworkContainer failed with %v", err)
|
||||
return plugin.Errorf(err.Error())
|
||||
}
|
||||
|
||||
|
|
|
@ -141,3 +141,8 @@ func getNATInfo(_ string, _ interface{}, _, _ bool) (natInfo []policy.NATInfo) {
|
|||
}
|
||||
|
||||
func platformInit(cniConfig *cni.NetworkConfig) {}
|
||||
|
||||
// isDualNicFeatureSupported returns if the dual nic feature is supported. Currently it's only supported for windows hnsv2 path
|
||||
func (plugin *NetPlugin) isDualNicFeatureSupported(netNs string) bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -605,6 +605,7 @@ func TestPluginMultitenancyAdd(t *testing.T) {
|
|||
report: &telemetry.CNIReport{},
|
||||
multitenancyClient: NewMockMultitenancy(false),
|
||||
},
|
||||
|
||||
args: &cniSkel.CmdArgs{
|
||||
StdinData: localNwCfg.Serialize(),
|
||||
ContainerID: "test-container",
|
||||
|
@ -706,7 +707,7 @@ func TestPluginMultitenancyDelete(t *testing.T) {
|
|||
}
|
||||
|
||||
/*
|
||||
Baremetal scenarios
|
||||
Baremetal scenarios
|
||||
*/
|
||||
func TestPluginBaremetalAdd(t *testing.T) {
|
||||
plugin, _ := cni.NewPlugin("test", "0.3.0")
|
||||
|
@ -835,7 +836,7 @@ func TestPluginBaremetalDelete(t *testing.T) {
|
|||
}
|
||||
|
||||
/*
|
||||
AKS-Swift scenario
|
||||
AKS-Swift scenario
|
||||
*/
|
||||
func TestPluginAKSSwiftAdd(t *testing.T) {
|
||||
plugin := GetTestResources()
|
||||
|
|
|
@ -171,8 +171,8 @@ func (plugin *NetPlugin) getNetworkName(netNs string, ipamAddResult *IPAMAddResu
|
|||
// This will happen during DEL call
|
||||
networkName, err := plugin.nm.FindNetworkIDFromNetNs(netNs)
|
||||
if err != nil {
|
||||
log.Printf("Error getting network name from state: %v.", err)
|
||||
return "", fmt.Errorf("error getting network name from state: %w", err)
|
||||
log.Printf("No endpoint available with netNs: %s: %v.", netNs, err)
|
||||
return "", fmt.Errorf("No endpoint available with netNs: %s: %w", netNs, err)
|
||||
}
|
||||
|
||||
return networkName, nil
|
||||
|
@ -400,3 +400,13 @@ func platformInit(cniConfig *cni.NetworkConfig) {
|
|||
network.EnableHnsV2Timeout(cniConfig.WindowsSettings.HnsTimeoutDurationInSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
// isDualNicFeatureSupported returns if the dual nic feature is supported. Currently it's only supported for windows hnsv2 path
|
||||
func (plugin *NetPlugin) isDualNicFeatureSupported(netNs string) bool {
|
||||
useHnsV2, err := network.UseHnsV2(netNs)
|
||||
if useHnsV2 && err == nil {
|
||||
return true
|
||||
}
|
||||
log.Errorf("DualNicFeature is not supported")
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ const (
|
|||
UnpublishNetworkContainer = "/network/unpublishnetworkcontainer"
|
||||
GetInterfaceForContainer = "/network/getinterfaceforcontainer"
|
||||
GetNetworkContainerByOrchestratorContext = "/network/getnetworkcontainerbyorchestratorcontext"
|
||||
GetAllNetworkContainers = "/network/getAllNetworkContainers"
|
||||
NetworkContainersURLPath = "/network/networkcontainers"
|
||||
AttachContainerToNetwork = "/network/attachcontainertonetwork"
|
||||
DetachContainerFromNetwork = "/network/detachcontainerfromnetwork"
|
||||
|
|
|
@ -26,6 +26,7 @@ const (
|
|||
|
||||
var clientPaths = []string{
|
||||
cns.GetNetworkContainerByOrchestratorContext,
|
||||
cns.GetAllNetworkContainers,
|
||||
cns.CreateHostNCApipaEndpointPath,
|
||||
cns.DeleteHostNCApipaEndpointPath,
|
||||
cns.RequestIPConfig,
|
||||
|
@ -93,8 +94,68 @@ func buildRoutes(baseURL string, paths []string) (map[string]url.URL, error) {
|
|||
return routes, nil
|
||||
}
|
||||
|
||||
// GetNetworkConfiguration Request to get network config.
|
||||
func (c *Client) GetNetworkConfiguration(ctx context.Context, orchestratorContext []byte) (*cns.GetNetworkContainerResponse, error) {
|
||||
// GetAllNetworkContainers Request to get network container configs.
|
||||
func (c *Client) GetAllNetworkContainers(ctx context.Context, orchestratorContext []byte) ([]cns.GetNetworkContainerResponse, error) {
|
||||
payload := cns.GetNetworkContainerRequest{
|
||||
OrchestratorContext: orchestratorContext,
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
if err := json.NewEncoder(&body).Encode(payload); err != nil {
|
||||
return nil, &CNSClientError{
|
||||
Code: types.UnexpectedError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
u := c.routes[cns.GetAllNetworkContainers]
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to build request")
|
||||
}
|
||||
req.Header.Set(headerContentType, contentTypeJSON)
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "http request failed")
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// investigate 404 orchestratorContext which is invalid and make sure this is addressed
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return nil, &CNSClientError{
|
||||
Code: types.UnsupportedAPI,
|
||||
Err: errors.Errorf("Unsupported API"),
|
||||
}
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, &CNSClientError{
|
||||
Code: types.UnexpectedError,
|
||||
Err: errors.Errorf("http response %d", res.StatusCode),
|
||||
}
|
||||
}
|
||||
|
||||
var resp cns.GetAllNetworkContainersResponse
|
||||
err = json.NewDecoder(res.Body).Decode(&resp)
|
||||
if err != nil {
|
||||
return nil, &CNSClientError{
|
||||
Code: types.UnexpectedError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if resp.Response.ReturnCode != 0 {
|
||||
return nil, &CNSClientError{
|
||||
Code: resp.Response.ReturnCode,
|
||||
Err: errors.New(resp.Response.Message),
|
||||
}
|
||||
}
|
||||
|
||||
return resp.NetworkContainers, nil
|
||||
}
|
||||
|
||||
// GetNetworkContainer Request to get network container config.
|
||||
func (c *Client) GetNetworkContainer(ctx context.Context, orchestratorContext []byte) (*cns.GetNetworkContainerResponse, error) {
|
||||
payload := cns.GetNetworkContainerRequest{
|
||||
OrchestratorContext: orchestratorContext,
|
||||
}
|
||||
|
|
|
@ -519,6 +519,137 @@ func TestBuildRoutes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetAllNetworkContainers(t *testing.T) {
|
||||
emptyRoutes, _ := buildRoutes(defaultBaseURL, clientPaths)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
podInfo cns.KubernetesPodInfo
|
||||
mockdo *mockdo
|
||||
routes map[string]url.URL
|
||||
want []cns.GetNetworkContainerResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "bad request 404",
|
||||
ctx: context.TODO(),
|
||||
podInfo: cns.KubernetesPodInfo{
|
||||
PodName: "testpodname",
|
||||
PodNamespace: "podNamespace",
|
||||
},
|
||||
mockdo: &mockdo{
|
||||
errToReturn: nil,
|
||||
objToReturn: cns.GetNetworkContainerResponse{},
|
||||
httpStatusCodeToReturn: http.StatusNotFound,
|
||||
},
|
||||
routes: emptyRoutes,
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "bad request",
|
||||
ctx: context.TODO(),
|
||||
podInfo: cns.KubernetesPodInfo{
|
||||
PodName: "testpodname",
|
||||
PodNamespace: "podNamespace",
|
||||
},
|
||||
mockdo: &mockdo{
|
||||
errToReturn: errBadRequest,
|
||||
objToReturn: nil,
|
||||
httpStatusCodeToReturn: http.StatusBadRequest,
|
||||
},
|
||||
routes: emptyRoutes,
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "bad decoding",
|
||||
ctx: context.TODO(),
|
||||
podInfo: cns.KubernetesPodInfo{
|
||||
PodName: "testpodname",
|
||||
PodNamespace: "podNamespace",
|
||||
},
|
||||
mockdo: &mockdo{
|
||||
errToReturn: nil,
|
||||
objToReturn: []cns.GetNetworkContainerResponse{},
|
||||
httpStatusCodeToReturn: http.StatusOK,
|
||||
},
|
||||
routes: emptyRoutes,
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "http status not ok",
|
||||
ctx: context.TODO(),
|
||||
podInfo: cns.KubernetesPodInfo{
|
||||
PodName: "testpodname",
|
||||
PodNamespace: "podNamespace",
|
||||
},
|
||||
mockdo: &mockdo{
|
||||
errToReturn: nil,
|
||||
objToReturn: nil,
|
||||
httpStatusCodeToReturn: http.StatusInternalServerError,
|
||||
},
|
||||
routes: emptyRoutes,
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "cns return code not zero",
|
||||
ctx: context.TODO(),
|
||||
podInfo: cns.KubernetesPodInfo{
|
||||
PodName: "testpodname",
|
||||
PodNamespace: "podNamespace",
|
||||
},
|
||||
mockdo: &mockdo{
|
||||
errToReturn: nil,
|
||||
objToReturn: &cns.GetNetworkContainerResponse{
|
||||
Response: cns.Response{
|
||||
ReturnCode: types.UnsupportedNetworkType,
|
||||
},
|
||||
},
|
||||
httpStatusCodeToReturn: http.StatusOK,
|
||||
},
|
||||
routes: emptyRoutes,
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "nil context",
|
||||
ctx: nil,
|
||||
podInfo: cns.KubernetesPodInfo{
|
||||
PodName: "testpodname",
|
||||
PodNamespace: "podNamespace",
|
||||
},
|
||||
mockdo: &mockdo{},
|
||||
routes: emptyRoutes,
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
client := Client{
|
||||
client: tt.mockdo,
|
||||
routes: tt.routes,
|
||||
}
|
||||
|
||||
orchestratorContext, err := json.Marshal(tt.podInfo)
|
||||
assert.NoError(t, err, "marshaling orchestrator context failed")
|
||||
|
||||
got, err := client.GetAllNetworkContainers(tt.ctx, orchestratorContext)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNetworkConfiguration(t *testing.T) {
|
||||
emptyRoutes, _ := buildRoutes(defaultBaseURL, clientPaths)
|
||||
tests := []struct {
|
||||
|
@ -639,7 +770,7 @@ func TestGetNetworkConfiguration(t *testing.T) {
|
|||
orchestratorContext, err := json.Marshal(tt.podInfo)
|
||||
assert.NoError(t, err, "marshaling orchestrator context failed")
|
||||
|
||||
got, err := client.GetNetworkConfiguration(tt.ctx, orchestratorContext)
|
||||
got, err := client.GetNetworkContainer(tt.ctx, orchestratorContext)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
|
|
|
@ -34,3 +34,10 @@ func IsNotFound(err error) bool {
|
|||
e := &CNSClientError{}
|
||||
return errors.As(err, &e) && (e.Code == types.UnknownContainerID)
|
||||
}
|
||||
|
||||
// IsUnsupportedAPI tests if the provided error is of type CNSClientError and then
|
||||
// further tests if the error code is of type UnsupportedAPI
|
||||
func IsUnsupportedAPI(err error) bool {
|
||||
e := &CNSClientError{}
|
||||
return errors.As(err, &e) && (e.Code == types.UnsupportedAPI)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"net/url"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-container-networking/cns"
|
||||
"github.com/Azure/azure-container-networking/cns/hnsclient"
|
||||
|
@ -727,19 +728,7 @@ func (service *HTTPRestService) setOrchestratorType(w http.ResponseWriter, r *ht
|
|||
|
||||
if nodeID == "" || nodeID == req.NodeID || !service.areNCsPresent() {
|
||||
switch req.OrchestratorType {
|
||||
case cns.ServiceFabric:
|
||||
fallthrough
|
||||
case cns.Kubernetes:
|
||||
fallthrough
|
||||
case cns.KubernetesCRD:
|
||||
fallthrough
|
||||
case cns.WebApps:
|
||||
fallthrough
|
||||
case cns.Batch:
|
||||
fallthrough
|
||||
case cns.DBforPostgreSQL:
|
||||
fallthrough
|
||||
case cns.AzureFirstParty:
|
||||
case cns.ServiceFabric, cns.Kubernetes, cns.KubernetesCRD, cns.WebApps, cns.Batch, cns.DBforPostgreSQL, cns.AzureFirstParty:
|
||||
service.state.OrchestratorType = req.OrchestratorType
|
||||
service.state.NodeID = req.NodeID
|
||||
logger.SetContextDetails(req.OrchestratorType, req.NodeID)
|
||||
|
@ -872,6 +861,44 @@ func (service *HTTPRestService) getNetworkContainerByID(w http.ResponseWriter, r
|
|||
logger.Response(service.Name, reserveResp, resp.ReturnCode, err)
|
||||
}
|
||||
|
||||
// the function is to get all network containers based on given OrchestratorContext
|
||||
func (service *HTTPRestService) getAllNetworkContainers(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Printf("[Azure CNS] getAllNetworkContainers")
|
||||
|
||||
var req cns.GetNetworkContainerRequest
|
||||
|
||||
err := service.Listener.Decode(w, r, &req)
|
||||
logger.Request(service.Name, &req, err)
|
||||
if err != nil {
|
||||
logger.Errorf("[Azure CNS] failed to decode cns request with req %+v due to %+v", req, err)
|
||||
return
|
||||
}
|
||||
|
||||
getAllNetworkContainerResponses := service.getAllNetworkContainerResponses(req) // nolint
|
||||
|
||||
var resp cns.GetAllNetworkContainersResponse
|
||||
|
||||
failedNCs := make([]string, 0)
|
||||
for i := 0; i < len(getAllNetworkContainerResponses); i++ {
|
||||
if getAllNetworkContainerResponses[i].Response.ReturnCode != types.Success {
|
||||
failedNCs = append(failedNCs, getAllNetworkContainerResponses[i].NetworkContainerID)
|
||||
}
|
||||
}
|
||||
|
||||
resp.NetworkContainers = getAllNetworkContainerResponses
|
||||
|
||||
if len(failedNCs) > 0 {
|
||||
resp.Response.ReturnCode = types.UnexpectedError
|
||||
resp.Response.Message = fmt.Sprintf("Failed to get NCs %s", strings.Join(failedNCs, ","))
|
||||
} else {
|
||||
resp.Response.ReturnCode = types.Success
|
||||
resp.Response.Message = "Successfully retrieved NCs"
|
||||
}
|
||||
|
||||
err = service.Listener.Encode(w, &resp)
|
||||
logger.Response(service.Name, resp, resp.Response.ReturnCode, err)
|
||||
}
|
||||
|
||||
func (service *HTTPRestService) getNetworkContainerByOrchestratorContext(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Printf("[Azure CNS] getNetworkContainerByOrchestratorContext")
|
||||
|
||||
|
@ -883,10 +910,9 @@ func (service *HTTPRestService) getNetworkContainerByOrchestratorContext(w http.
|
|||
return
|
||||
}
|
||||
|
||||
getNetworkContainerResponse := service.getNetworkContainerResponse(req)
|
||||
returnCode := getNetworkContainerResponse.Response.ReturnCode
|
||||
err = service.Listener.Encode(w, &getNetworkContainerResponse)
|
||||
logger.Response(service.Name, getNetworkContainerResponse, returnCode, err)
|
||||
getNetworkContainerResponses := service.getAllNetworkContainerResponses(req) // nolint
|
||||
err = service.Listener.Encode(w, &getNetworkContainerResponses[0])
|
||||
logger.Response(service.Name, getNetworkContainerResponses[0], getNetworkContainerResponses[0].Response.ReturnCode, err)
|
||||
}
|
||||
|
||||
// getOrRefreshNetworkContainers is to check whether refresh association is needed.
|
||||
|
@ -921,7 +947,8 @@ func (service *HTTPRestService) deleteNetworkContainer(w http.ResponseWriter, r
|
|||
return
|
||||
}
|
||||
|
||||
if req.NetworkContainerid == "" {
|
||||
ncid := req.NetworkContainerid
|
||||
if ncid == "" {
|
||||
returnCode = types.NetworkContainerNotSpecified
|
||||
returnMessage = "[Azure CNS] Error. NetworkContainerid is empty"
|
||||
}
|
||||
|
@ -931,17 +958,17 @@ func (service *HTTPRestService) deleteNetworkContainer(w http.ResponseWriter, r
|
|||
var containerStatus containerstatus
|
||||
var ok bool
|
||||
|
||||
containerStatus, ok = service.getNetworkContainerDetails(req.NetworkContainerid)
|
||||
containerStatus, ok = service.getNetworkContainerDetails(ncid)
|
||||
|
||||
if !ok {
|
||||
logger.Printf("Not able to retrieve network container details for this container id %v", req.NetworkContainerid)
|
||||
logger.Printf("Not able to retrieve network container details for this container id %v", ncid)
|
||||
break
|
||||
}
|
||||
|
||||
if containerStatus.CreateNetworkContainerRequest.NetworkContainerType == cns.WebApps {
|
||||
nc := service.networkContainer
|
||||
if err := nc.Delete(req.NetworkContainerid); err != nil {
|
||||
returnMessage = fmt.Sprintf("[Azure CNS] Error. DeleteNetworkContainer failed %v", err.Error())
|
||||
if deleteErr := nc.Delete(ncid); deleteErr != nil { // nolint:gocritic
|
||||
returnMessage = fmt.Sprintf("[Azure CNS] Error. DeleteNetworkContainer failed %v", deleteErr.Error())
|
||||
returnCode = types.UnexpectedError
|
||||
break
|
||||
}
|
||||
|
@ -951,14 +978,17 @@ func (service *HTTPRestService) deleteNetworkContainer(w http.ResponseWriter, r
|
|||
defer service.Unlock()
|
||||
|
||||
if service.state.ContainerStatus != nil {
|
||||
delete(service.state.ContainerStatus, req.NetworkContainerid)
|
||||
delete(service.state.ContainerStatus, ncid)
|
||||
}
|
||||
|
||||
if service.state.ContainerIDByOrchestratorContext != nil {
|
||||
for orchestratorContext, networkContainerID := range service.state.ContainerIDByOrchestratorContext {
|
||||
if networkContainerID == req.NetworkContainerid {
|
||||
delete(service.state.ContainerIDByOrchestratorContext, orchestratorContext)
|
||||
break
|
||||
for orchestratorContext, networkContainerIDs := range service.state.ContainerIDByOrchestratorContext { //nolint:gocritic // copy is ok
|
||||
if networkContainerIDs.Contains(ncid) {
|
||||
networkContainerIDs.Delete(ncid)
|
||||
if *networkContainerIDs == "" {
|
||||
delete(service.state.ContainerIDByOrchestratorContext, orchestratorContext)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,24 @@ var (
|
|||
}
|
||||
ncParams = []createOrUpdateNetworkContainerParams{nc1, nc2}
|
||||
errMismatchedNCs = errors.New("GetNetworkContainers failed because NCs not matched")
|
||||
|
||||
nc3 = createOrUpdateNetworkContainerParams{
|
||||
ncID: "1abc",
|
||||
ncIP: "10.0.0.5",
|
||||
ncType: cns.AzureContainerInstance,
|
||||
ncVersion: "0",
|
||||
podName: "testpod",
|
||||
podNamespace: "testpodnamespace",
|
||||
}
|
||||
nc4 = createOrUpdateNetworkContainerParams{
|
||||
ncID: "2abc",
|
||||
ncIP: "20.0.0.5",
|
||||
ncType: cns.AzureContainerInstance,
|
||||
ncVersion: "0",
|
||||
podName: "testpod",
|
||||
podNamespace: "testpodnamespace",
|
||||
}
|
||||
ncDualNicParams = []createOrUpdateNetworkContainerParams{nc3, nc4}
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -231,6 +249,7 @@ func FirstRequest(req *http.Request, err error) *http.Request {
|
|||
}
|
||||
|
||||
func TestSetOrchestratorType_NCsPresent(t *testing.T) {
|
||||
present := ncList("present")
|
||||
tests := []struct {
|
||||
name string
|
||||
service *HTTPRestService
|
||||
|
@ -247,8 +266,8 @@ func TestSetOrchestratorType_NCsPresent(t *testing.T) {
|
|||
ContainerStatus: map[string]containerstatus{
|
||||
"nc1": {},
|
||||
},
|
||||
ContainerIDByOrchestratorContext: map[string]string{
|
||||
"nc1": "present",
|
||||
ContainerIDByOrchestratorContext: map[string]*ncList{
|
||||
"nc1": &present,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -312,6 +331,112 @@ func TestSetOrchestratorType_NCsPresent(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDeleteContainerIDByOrchestratorContext(t *testing.T) {
|
||||
fmt.Println("Test: TestDeleteContainerIDByOrchestratorContext")
|
||||
|
||||
setEnv(t)
|
||||
err := setOrchestratorType(t, cns.Kubernetes)
|
||||
if err != nil {
|
||||
t.Fatalf("TestDeleteContainerIDByOrchestratorContext failed with error:%+v", err)
|
||||
}
|
||||
|
||||
// create two network containers
|
||||
for i := 0; i < len(ncDualNicParams); i++ {
|
||||
err = createOrUpdateNetworkContainerWithParams(ncDualNicParams[i])
|
||||
if err != nil {
|
||||
t.Fatalf("createOrUpdateNetworkContainerWithParams failed with error:%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = getAllNetworkContainers(t, ncDualNicParams)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGetAllNetworkContainers failed with error:%+v", err)
|
||||
}
|
||||
|
||||
svc = service.(*HTTPRestService)
|
||||
|
||||
// get ncList based on orchestratorContext
|
||||
orchestratorContext := ncDualNicParams[0].podName + ncDualNicParams[0].podNamespace
|
||||
// i.e, ncs is {"testpodtestpodnamespace": "Swift_1abc,Swift_2abc"}
|
||||
ncs := svc.state.ContainerIDByOrchestratorContext[orchestratorContext]
|
||||
|
||||
// delete "Swift_1abc" NC first:
|
||||
err = deleteNetworkContainerWithParams(ncDualNicParams[0])
|
||||
if err != nil {
|
||||
t.Fatalf("createOrUpdateNetworkContainerWithParams failed with error:%+v", err)
|
||||
}
|
||||
|
||||
expectedNC := "Swift_" + ncDualNicParams[1].ncID
|
||||
if ncList(expectedNC) != *ncs {
|
||||
t.Fatalf("failed to delete first NCID %s", ncDualNicParams[0].ncID)
|
||||
}
|
||||
|
||||
// delete second NC "Swift_2abc", the svc.state.ContainerIDByOrchestratorContext map should be empty
|
||||
err = deleteNetworkContainerWithParams(ncDualNicParams[1])
|
||||
if err != nil {
|
||||
t.Fatalf("createOrUpdateNetworkContainerWithParams failed with error:%+v", err)
|
||||
}
|
||||
|
||||
if *ncs != "" || len(svc.state.ContainerIDByOrchestratorContext) != 0 {
|
||||
t.Fatal("failed to delete all NCs and ContainerIDByOrchestratorContext object")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteNetworkContainers(t *testing.T) {
|
||||
fmt.Println("Test: TestDeleteNetworkContainers")
|
||||
|
||||
setEnv(t)
|
||||
err := setOrchestratorType(t, cns.Kubernetes)
|
||||
if err != nil {
|
||||
t.Fatalf("TestDeleteNetworkContainer failed with error:%+v", err)
|
||||
}
|
||||
|
||||
// create two network containers
|
||||
for i := 0; i < len(ncDualNicParams); i++ {
|
||||
err = createOrUpdateNetworkContainerWithParams(ncDualNicParams[i])
|
||||
if err != nil {
|
||||
t.Fatalf("createOrUpdateNetworkContainerWithParams failed with error:%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
ncResponses, err := getAllNetworkContainers(t, ncDualNicParams)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGetAllNetworkContainers failed with error:%+v", err)
|
||||
}
|
||||
|
||||
if len(ncResponses.NetworkContainers) != 2 {
|
||||
t.Fatalf("TestGetAllNetworkContainers failed to create dual network containers")
|
||||
}
|
||||
|
||||
// delete one NC(nc3, 10.0.0.5)
|
||||
err = deleteNetworkContainerWithParams(ncDualNicParams[0])
|
||||
if err != nil {
|
||||
t.Fatalf("createOrUpdateNetworkContainerWithParams failed with error:%+v", err)
|
||||
}
|
||||
|
||||
// get second NC info and check if it's equal to "Swift" + NCID
|
||||
ncResponse, err := getNetworkContainerByContext(ncDualNicParams[1])
|
||||
if err != nil {
|
||||
t.Errorf("TestGetNetworkContainerByOrchestratorContext failed Err:%+v", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ncResponse.NetworkContainerID != "Swift_2abc" {
|
||||
t.Fatal("failed to check second nc")
|
||||
}
|
||||
|
||||
// delete second one
|
||||
err = deleteNetworkContainerWithParams(ncDualNicParams[1])
|
||||
if err != nil {
|
||||
t.Fatalf("createOrUpdateNetworkContainerWithParams failed with error:%+v", err)
|
||||
}
|
||||
|
||||
ncResponses, err = getAllNetworkContainers(t, ncDualNicParams)
|
||||
if len(ncResponses.NetworkContainers) != 0 && err != nil {
|
||||
t.Fatalf("failed to remove all network containers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateNetworkContainer(t *testing.T) {
|
||||
// requires more than 30 seconds to run
|
||||
fmt.Println("Test: TestCreateNetworkContainer")
|
||||
|
@ -1167,11 +1292,11 @@ func TestCreateHostNCApipaEndpoint(t *testing.T) {
|
|||
fmt.Printf("createHostNCApipaEndpoint Responded with %+v\n", createHostNCApipaEndpointResponse)
|
||||
}
|
||||
|
||||
func TestGetNetworkContainers(t *testing.T) {
|
||||
func TestGetAllNetworkContainers(t *testing.T) {
|
||||
setEnv(t)
|
||||
err := setOrchestratorType(t, cns.Kubernetes)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGetNetworkContainers failed with error:%+v", err)
|
||||
t.Fatalf("TestGetAllNetworkContainers failed with error:%+v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(ncParams); i++ {
|
||||
|
@ -1181,9 +1306,9 @@ func TestGetNetworkContainers(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
err = getAllNetworkContainers(t, ncParams)
|
||||
_, err = getAllNetworkContainers(t, ncParams)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGetNetworkContainers failed with error:%+v", err)
|
||||
t.Fatalf("TestGetAllNetworkContainers failed with error:%+v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(ncParams); i++ {
|
||||
|
@ -1194,10 +1319,10 @@ func TestGetNetworkContainers(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func getAllNetworkContainers(t *testing.T, ncParams []createOrUpdateNetworkContainerParams) error {
|
||||
func getAllNetworkContainers(t *testing.T, ncParams []createOrUpdateNetworkContainerParams) (ncResponses cns.GetAllNetworkContainersResponse, err error) {
|
||||
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, cns.NetworkContainersURLPath, http.NoBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetNetworkContainers failed with error: %w", err)
|
||||
return cns.GetAllNetworkContainersResponse{}, fmt.Errorf("GetNetworkContainers failed with error: %w", err)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
@ -1206,18 +1331,18 @@ func getAllNetworkContainers(t *testing.T, ncParams []createOrUpdateNetworkConta
|
|||
var resp cns.GetAllNetworkContainersResponse
|
||||
err = decodeResponse(w, &resp)
|
||||
if err != nil || resp.Response.ReturnCode != types.Success || len(resp.NetworkContainers) != len(ncParams) {
|
||||
return fmt.Errorf("GetNetworkContainers failed with response %+v Err: %w", resp, err)
|
||||
return cns.GetAllNetworkContainersResponse{}, fmt.Errorf("GetNetworkContainers failed with response %+v Err: %w", resp, err)
|
||||
}
|
||||
|
||||
// If any NC in response is not found in ncParams, it means get all NCs failed
|
||||
for i := 0; i < len(ncParams); i++ {
|
||||
if !contains(resp.NetworkContainers, cns.SwiftPrefix+ncParams[i].ncID) {
|
||||
return errMismatchedNCs
|
||||
return cns.GetAllNetworkContainersResponse{}, errMismatchedNCs
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("GetNetworkContainers succeeded with response: %+v", resp)
|
||||
return nil
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func TestPostNetworkContainers(t *testing.T) {
|
||||
|
@ -1232,7 +1357,7 @@ func TestPostNetworkContainers(t *testing.T) {
|
|||
t.Fatalf("Failed to save all network containers due to error: %+v", err)
|
||||
}
|
||||
|
||||
err = getAllNetworkContainers(t, ncParams)
|
||||
_, err = getAllNetworkContainers(t, ncParams)
|
||||
if err != nil {
|
||||
t.Fatalf("TestPostNetworkContainers failed with error:%+v", err)
|
||||
}
|
||||
|
|
|
@ -312,32 +312,35 @@ func (service *HTTPRestService) ReconcileNCState(ncRequest *cns.CreateNetworkCon
|
|||
func (service *HTTPRestService) GetNetworkContainerInternal(
|
||||
req cns.GetNetworkContainerRequest,
|
||||
) (cns.GetNetworkContainerResponse, types.ResponseCode) {
|
||||
getNetworkContainerResponse := service.getNetworkContainerResponse(req)
|
||||
returnCode := getNetworkContainerResponse.Response.ReturnCode
|
||||
return getNetworkContainerResponse, returnCode
|
||||
getNetworkContainerResponses := service.getAllNetworkContainerResponses(req)
|
||||
return getNetworkContainerResponses[0], getNetworkContainerResponses[0].Response.ReturnCode
|
||||
}
|
||||
|
||||
// DeleteNetworkContainerInternal deletes a network container.
|
||||
func (service *HTTPRestService) DeleteNetworkContainerInternal(
|
||||
req cns.DeleteNetworkContainerRequest,
|
||||
) types.ResponseCode {
|
||||
_, exist := service.getNetworkContainerDetails(req.NetworkContainerid)
|
||||
ncid := req.NetworkContainerid
|
||||
_, exist := service.getNetworkContainerDetails(ncid)
|
||||
if !exist {
|
||||
logger.Printf("network container for id %v doesn't exist", req.NetworkContainerid)
|
||||
logger.Printf("network container for id %v doesn't exist", ncid)
|
||||
return types.Success
|
||||
}
|
||||
|
||||
service.Lock()
|
||||
defer service.Unlock()
|
||||
if service.state.ContainerStatus != nil {
|
||||
delete(service.state.ContainerStatus, req.NetworkContainerid)
|
||||
delete(service.state.ContainerStatus, ncid)
|
||||
}
|
||||
|
||||
if service.state.ContainerIDByOrchestratorContext != nil {
|
||||
for orchestratorContext, networkContainerID := range service.state.ContainerIDByOrchestratorContext {
|
||||
if networkContainerID == req.NetworkContainerid {
|
||||
delete(service.state.ContainerIDByOrchestratorContext, orchestratorContext)
|
||||
break
|
||||
for orchestratorContext, networkContainerIDs := range service.state.ContainerIDByOrchestratorContext { //nolint:gocritic // copy is ok
|
||||
if networkContainerIDs.Contains(ncid) {
|
||||
networkContainerIDs.Delete(ncid)
|
||||
if *networkContainerIDs == "" {
|
||||
delete(service.state.ContainerIDByOrchestratorContext, orchestratorContext)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ type httpRestServiceState struct {
|
|||
OrchestratorType string
|
||||
NodeID string
|
||||
Initialized bool
|
||||
ContainerIDByOrchestratorContext map[string]string // OrchestratorContext is key and value is NetworkContainerID.
|
||||
ContainerIDByOrchestratorContext map[string]*ncList // OrchestratorContext is the key and value is a list of NetworkContainerIDs separated by comma
|
||||
ContainerStatus map[string]containerstatus // NetworkContainerID is key.
|
||||
Networks map[string]*networkInfo
|
||||
TimeStamp time.Time
|
||||
|
@ -243,6 +243,7 @@ func (service *HTTPRestService) Init(config *common.ServiceConfig) error {
|
|||
listener.AddHandler(cns.GetInterfaceForContainer, service.getInterfaceForContainer)
|
||||
listener.AddHandler(cns.SetOrchestratorType, service.setOrchestratorType)
|
||||
listener.AddHandler(cns.GetNetworkContainerByOrchestratorContext, service.getNetworkContainerByOrchestratorContext)
|
||||
listener.AddHandler(cns.GetAllNetworkContainers, service.getAllNetworkContainers)
|
||||
listener.AddHandler(cns.AttachContainerToNetwork, service.attachNetworkContainerToNetwork)
|
||||
listener.AddHandler(cns.DetachContainerFromNetwork, service.detachNetworkContainerFromNetwork)
|
||||
listener.AddHandler(cns.CreateHnsNetworkPath, service.createHnsNetwork)
|
||||
|
@ -275,6 +276,7 @@ func (service *HTTPRestService) Init(config *common.ServiceConfig) error {
|
|||
listener.AddHandler(cns.V2Prefix+cns.GetInterfaceForContainer, service.getInterfaceForContainer)
|
||||
listener.AddHandler(cns.V2Prefix+cns.SetOrchestratorType, service.setOrchestratorType)
|
||||
listener.AddHandler(cns.V2Prefix+cns.GetNetworkContainerByOrchestratorContext, service.getNetworkContainerByOrchestratorContext)
|
||||
listener.AddHandler(cns.V2Prefix+cns.GetAllNetworkContainers, service.getAllNetworkContainers)
|
||||
listener.AddHandler(cns.V2Prefix+cns.AttachContainerToNetwork, service.attachNetworkContainerToNetwork)
|
||||
listener.AddHandler(cns.V2Prefix+cns.DetachContainerFromNetwork, service.detachNetworkContainerFromNetwork)
|
||||
listener.AddHandler(cns.V2Prefix+cns.CreateHnsNetworkPath, service.createHnsNetwork)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-container-networking/aitelemetry"
|
||||
|
@ -183,13 +184,20 @@ func (service *HTTPRestService) saveNetworkContainerGoalState(
|
|||
return types.UnexpectedError, errBuf
|
||||
}
|
||||
|
||||
logger.Printf("Pod info %v", podInfo)
|
||||
orchestratorContext := podInfo.Name() + podInfo.Namespace()
|
||||
|
||||
if service.state.ContainerIDByOrchestratorContext == nil {
|
||||
service.state.ContainerIDByOrchestratorContext = make(map[string]string)
|
||||
service.state.ContainerIDByOrchestratorContext = make(map[string]*ncList)
|
||||
}
|
||||
|
||||
service.state.ContainerIDByOrchestratorContext[podInfo.Name()+podInfo.Namespace()] = req.NetworkContainerid
|
||||
if _, ok := service.state.ContainerIDByOrchestratorContext[orchestratorContext]; !ok {
|
||||
service.state.ContainerIDByOrchestratorContext[orchestratorContext] = new(ncList)
|
||||
}
|
||||
|
||||
ncs := service.state.ContainerIDByOrchestratorContext[orchestratorContext]
|
||||
ncs.Add(req.NetworkContainerid)
|
||||
|
||||
logger.Printf("service.state.ContainerIDByOrchestratorContext[%s] is %+v", orchestratorContext, *service.state.ContainerIDByOrchestratorContext[orchestratorContext])
|
||||
|
||||
case cns.KubernetesCRD:
|
||||
// Validate and Update the SecondaryIpConfig state
|
||||
|
@ -349,78 +357,94 @@ func (service *HTTPRestService) removeToBeDeletedIPStateUntransacted(
|
|||
return 0, ""
|
||||
}
|
||||
|
||||
func (service *HTTPRestService) getNetworkContainerResponse(
|
||||
func (service *HTTPRestService) getAllNetworkContainerResponses(
|
||||
req cns.GetNetworkContainerRequest,
|
||||
) cns.GetNetworkContainerResponse {
|
||||
) []cns.GetNetworkContainerResponse {
|
||||
var (
|
||||
containerID string
|
||||
getNetworkContainerResponse cns.GetNetworkContainerResponse
|
||||
exists bool
|
||||
waitingForUpdate bool
|
||||
ncs []string
|
||||
skipNCVersionCheck = false
|
||||
)
|
||||
|
||||
service.Lock()
|
||||
defer service.Unlock()
|
||||
|
||||
switch service.state.OrchestratorType {
|
||||
case cns.Kubernetes:
|
||||
fallthrough
|
||||
case cns.ServiceFabric:
|
||||
fallthrough
|
||||
case cns.Batch:
|
||||
fallthrough
|
||||
case cns.DBforPostgreSQL:
|
||||
fallthrough
|
||||
case cns.AzureFirstParty:
|
||||
case cns.Kubernetes, cns.ServiceFabric, cns.Batch, cns.DBforPostgreSQL, cns.AzureFirstParty:
|
||||
podInfo, err := cns.UnmarshalPodInfo(req.OrchestratorContext)
|
||||
getNetworkContainersResponse := []cns.GetNetworkContainerResponse{}
|
||||
|
||||
if err != nil {
|
||||
getNetworkContainerResponse.Response.ReturnCode = types.UnexpectedError
|
||||
getNetworkContainerResponse.Response.Message = fmt.Sprintf("Unmarshalling orchestrator context failed with error %v", err)
|
||||
return getNetworkContainerResponse
|
||||
response := cns.Response{
|
||||
ReturnCode: types.UnexpectedError,
|
||||
Message: fmt.Sprintf("Unmarshalling orchestrator context failed with error %v", err),
|
||||
}
|
||||
|
||||
getNetworkContainerResponse.Response = response
|
||||
getNetworkContainersResponse = append(getNetworkContainersResponse, getNetworkContainerResponse)
|
||||
return getNetworkContainersResponse
|
||||
}
|
||||
|
||||
logger.Printf("pod info %+v", podInfo)
|
||||
// get networkContainerIDs as string, "nc1, nc2"
|
||||
orchestratorContext := podInfo.Name() + podInfo.Namespace()
|
||||
if service.state.ContainerIDByOrchestratorContext[orchestratorContext] != nil {
|
||||
ncs = strings.Split(string(*service.state.ContainerIDByOrchestratorContext[orchestratorContext]), ",")
|
||||
}
|
||||
|
||||
containerID, exists = service.state.ContainerIDByOrchestratorContext[podInfo.Name()+podInfo.Namespace()]
|
||||
// This indicates that there are no ncs for the given orchestrator context
|
||||
if len(ncs) == 0 {
|
||||
response := cns.Response{
|
||||
ReturnCode: types.UnknownContainerID,
|
||||
Message: fmt.Sprintf("Failed to find networkContainerID for orchestratorContext %s", orchestratorContext),
|
||||
}
|
||||
|
||||
getNetworkContainerResponse.Response = response
|
||||
getNetworkContainersResponse = append(getNetworkContainersResponse, getNetworkContainerResponse)
|
||||
return getNetworkContainersResponse
|
||||
}
|
||||
|
||||
skipNCVersionCheck := false
|
||||
ctx, cancel := context.WithTimeout(context.Background(), nmaAPICallTimeout)
|
||||
defer cancel()
|
||||
ncVersionListResp, err := service.nma.GetNCVersionList(ctx)
|
||||
if err != nil {
|
||||
skipNCVersionCheck = true
|
||||
logger.Errorf("failed to get nc version list from nmagent")
|
||||
// TODO: Add telemetry as this has potential to have containers in the running state w/o datapath working
|
||||
}
|
||||
nmaNCs := map[string]string{}
|
||||
for _, nc := range ncVersionListResp.Containers {
|
||||
nmaNCs[cns.SwiftPrefix+nc.NetworkContainerID] = nc.Version
|
||||
}
|
||||
if exists && !skipNCVersionCheck {
|
||||
// If the goal state is available with CNS, check if the NC is pending VFP programming
|
||||
waitingForUpdate, getNetworkContainerResponse.Response.ReturnCode, getNetworkContainerResponse.Response.Message = service.isNCWaitingForUpdate(
|
||||
service.state.ContainerStatus[containerID].CreateNetworkContainerRequest.Version, containerID, nmaNCs)
|
||||
// If the return code is not success, return the error to the caller
|
||||
if getNetworkContainerResponse.Response.ReturnCode == types.NetworkContainerVfpProgramPending {
|
||||
logger.Errorf("[Azure-CNS] isNCWaitingForUpdate failed for NC: %s with error: %s",
|
||||
containerID, getNetworkContainerResponse.Response.Message)
|
||||
return getNetworkContainerResponse
|
||||
}
|
||||
|
||||
vfpUpdateComplete := !waitingForUpdate
|
||||
ncstatus := service.state.ContainerStatus[containerID]
|
||||
// Update the container status if-
|
||||
// 1. VfpUpdateCompleted successfully
|
||||
// 2. VfpUpdateComplete changed to false
|
||||
if (getNetworkContainerResponse.Response.ReturnCode == types.NetworkContainerVfpProgramComplete &&
|
||||
vfpUpdateComplete && ncstatus.VfpUpdateComplete != vfpUpdateComplete) ||
|
||||
(!vfpUpdateComplete && ncstatus.VfpUpdateComplete != vfpUpdateComplete) {
|
||||
logger.Printf("[Azure-CNS] Setting VfpUpdateComplete to %t for NC: %s", vfpUpdateComplete, containerID)
|
||||
ncstatus.VfpUpdateComplete = vfpUpdateComplete
|
||||
service.state.ContainerStatus[containerID] = ncstatus
|
||||
service.saveState()
|
||||
}
|
||||
if !skipNCVersionCheck {
|
||||
for _, ncid := range ncs {
|
||||
waitingForUpdate := false
|
||||
// If the goal state is available with CNS, check if the NC is pending VFP programming
|
||||
waitingForUpdate, getNetworkContainerResponse.Response.ReturnCode, getNetworkContainerResponse.Response.Message = service.isNCWaitingForUpdate(service.state.ContainerStatus[ncid].CreateNetworkContainerRequest.Version, ncid, nmaNCs) //nolint:lll // bad code
|
||||
// If the return code is not success, return the error to the caller
|
||||
if getNetworkContainerResponse.Response.ReturnCode == types.NetworkContainerVfpProgramPending {
|
||||
logger.Errorf("[Azure-CNS] isNCWaitingForUpdate failed for NCID: %s", ncid)
|
||||
}
|
||||
|
||||
} else if service.ChannelMode == cns.Managed {
|
||||
vfpUpdateComplete := !waitingForUpdate
|
||||
ncstatus := service.state.ContainerStatus[ncid]
|
||||
// Update the container status if-
|
||||
// 1. VfpUpdateCompleted successfully
|
||||
// 2. VfpUpdateComplete changed to false
|
||||
if (getNetworkContainerResponse.Response.ReturnCode == types.NetworkContainerVfpProgramComplete &&
|
||||
vfpUpdateComplete && ncstatus.VfpUpdateComplete != vfpUpdateComplete) ||
|
||||
(!vfpUpdateComplete && ncstatus.VfpUpdateComplete != vfpUpdateComplete) {
|
||||
logger.Printf("[Azure-CNS] Setting VfpUpdateComplete to %t for NCID: %s", vfpUpdateComplete, ncid)
|
||||
ncstatus.VfpUpdateComplete = vfpUpdateComplete
|
||||
service.state.ContainerStatus[ncid] = ncstatus
|
||||
if err = service.saveState(); err != nil {
|
||||
logger.Errorf("Failed to save goal states for nc %+v due to %s", getNetworkContainerResponse, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if service.ChannelMode == cns.Managed {
|
||||
// If the NC goal state doesn't exist in CNS running in managed mode, call DNC to retrieve the goal state
|
||||
var (
|
||||
dncEP = service.GetOption(acn.OptPrivateEndpoint).(string)
|
||||
|
@ -432,42 +456,66 @@ func (service *HTTPRestService) getNetworkContainerResponse(
|
|||
getNetworkContainerResponse.Response.ReturnCode, getNetworkContainerResponse.Response.Message = service.SyncNodeStatus(dncEP, infraVnet, nodeID, req.OrchestratorContext)
|
||||
service.Lock()
|
||||
if getNetworkContainerResponse.Response.ReturnCode == types.NotFound {
|
||||
return getNetworkContainerResponse
|
||||
getNetworkContainersResponse = append(getNetworkContainersResponse, getNetworkContainerResponse)
|
||||
return getNetworkContainersResponse
|
||||
}
|
||||
|
||||
containerID = service.state.ContainerIDByOrchestratorContext[podInfo.Name()+podInfo.Namespace()]
|
||||
}
|
||||
default:
|
||||
getNetworkContainersResponse := []cns.GetNetworkContainerResponse{}
|
||||
response := cns.Response{
|
||||
ReturnCode: types.UnsupportedOrchestratorType,
|
||||
Message: fmt.Sprintf("Invalid orchestrator type %v", service.state.OrchestratorType),
|
||||
}
|
||||
|
||||
logger.Printf("containerid %v", containerID)
|
||||
|
||||
default:
|
||||
getNetworkContainerResponse.Response.ReturnCode = types.UnsupportedOrchestratorType
|
||||
getNetworkContainerResponse.Response.Message = fmt.Sprintf("Invalid orchestrator type %v", service.state.OrchestratorType)
|
||||
return getNetworkContainerResponse
|
||||
getNetworkContainerResponse.Response = response
|
||||
getNetworkContainersResponse = append(getNetworkContainersResponse, getNetworkContainerResponse)
|
||||
return getNetworkContainersResponse
|
||||
}
|
||||
|
||||
containerStatus := service.state.ContainerStatus
|
||||
containerDetails, ok := containerStatus[containerID]
|
||||
if !ok {
|
||||
getNetworkContainerResponse.Response.ReturnCode = types.UnknownContainerID
|
||||
getNetworkContainerResponse.Response.Message = "NetworkContainer doesn't exist."
|
||||
return getNetworkContainerResponse
|
||||
getNetworkContainersResponse := []cns.GetNetworkContainerResponse{}
|
||||
|
||||
for _, ncid := range ncs {
|
||||
containerStatus := service.state.ContainerStatus
|
||||
containerDetails, ok := containerStatus[ncid]
|
||||
if !ok {
|
||||
response := cns.Response{
|
||||
ReturnCode: types.UnknownContainerID,
|
||||
Message: "NetworkContainer doesn't exist.",
|
||||
}
|
||||
|
||||
getNetworkContainerResponse.Response = response
|
||||
getNetworkContainersResponse = append(getNetworkContainersResponse, getNetworkContainerResponse)
|
||||
continue
|
||||
}
|
||||
|
||||
savedReq := containerDetails.CreateNetworkContainerRequest
|
||||
getNetworkContainerResponse = cns.GetNetworkContainerResponse{
|
||||
NetworkContainerID: savedReq.NetworkContainerid,
|
||||
IPConfiguration: savedReq.IPConfiguration,
|
||||
Routes: savedReq.Routes,
|
||||
CnetAddressSpace: savedReq.CnetAddressSpace,
|
||||
MultiTenancyInfo: savedReq.MultiTenancyInfo,
|
||||
PrimaryInterfaceIdentifier: savedReq.PrimaryInterfaceIdentifier,
|
||||
LocalIPConfiguration: savedReq.LocalIPConfiguration,
|
||||
AllowHostToNCCommunication: savedReq.AllowHostToNCCommunication,
|
||||
AllowNCToHostCommunication: savedReq.AllowNCToHostCommunication,
|
||||
}
|
||||
|
||||
// If the NC version check wasn't skipped, take into account the VFP programming status when returning the response
|
||||
if !skipNCVersionCheck {
|
||||
if !containerDetails.VfpUpdateComplete {
|
||||
getNetworkContainerResponse.Response = cns.Response{
|
||||
ReturnCode: types.NetworkContainerVfpProgramPending,
|
||||
Message: "NetworkContainer VFP programming is pending",
|
||||
}
|
||||
}
|
||||
}
|
||||
getNetworkContainersResponse = append(getNetworkContainersResponse, getNetworkContainerResponse)
|
||||
}
|
||||
|
||||
savedReq := containerDetails.CreateNetworkContainerRequest
|
||||
getNetworkContainerResponse = cns.GetNetworkContainerResponse{
|
||||
NetworkContainerID: savedReq.NetworkContainerid,
|
||||
IPConfiguration: savedReq.IPConfiguration,
|
||||
Routes: savedReq.Routes,
|
||||
CnetAddressSpace: savedReq.CnetAddressSpace,
|
||||
MultiTenancyInfo: savedReq.MultiTenancyInfo,
|
||||
PrimaryInterfaceIdentifier: savedReq.PrimaryInterfaceIdentifier,
|
||||
LocalIPConfiguration: savedReq.LocalIPConfiguration,
|
||||
AllowHostToNCCommunication: savedReq.AllowHostToNCCommunication,
|
||||
AllowNCToHostCommunication: savedReq.AllowNCToHostCommunication,
|
||||
}
|
||||
logger.Printf("getNetworkContainersResponses are %+v", getNetworkContainersResponse)
|
||||
|
||||
return getNetworkContainerResponse
|
||||
return getNetworkContainersResponse
|
||||
}
|
||||
|
||||
// restoreNetworkState restores Network state that existed before reboot.
|
||||
|
@ -932,3 +980,42 @@ func (service *HTTPRestService) setResponse(w http.ResponseWriter, returnCode ty
|
|||
serviceErr := service.Listener.Encode(w, &response)
|
||||
logger.Response(service.Name, response, returnCode, serviceErr)
|
||||
}
|
||||
|
||||
// ncList contains comma-separated list of unique NCs
|
||||
type ncList string
|
||||
|
||||
// only add unique NC to ncList
|
||||
func (n *ncList) Add(nc string) {
|
||||
var ncs []string
|
||||
if len(*n) > 0 {
|
||||
ncs = strings.Split(string(*n), ",")
|
||||
}
|
||||
for _, v := range ncs {
|
||||
// if NC is already present in ncList, do not add it
|
||||
if nc == v {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ncs = append(ncs, nc)
|
||||
*n = ncList(strings.Join(ncs, ","))
|
||||
}
|
||||
|
||||
// delete nc from ncList
|
||||
// split the slice around the index that contains the NC to delete so that neigher of two resulting nc slices cotnains this NC
|
||||
// use append menthod to join the new NC slices
|
||||
func (n *ncList) Delete(nc string) {
|
||||
ncs := strings.Split(string(*n), ",")
|
||||
for i, v := range ncs {
|
||||
if nc == v {
|
||||
ncs = append((ncs)[:i], (ncs)[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
*n = ncList(strings.Join(ncs, ","))
|
||||
}
|
||||
|
||||
// check if ncList contains NC
|
||||
func (n *ncList) Contains(nc string) bool {
|
||||
return strings.Contains(string(*n), nc)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
)
|
||||
|
||||
func TestAreNCsPresent(t *testing.T) {
|
||||
present := ncList("present")
|
||||
tests := []struct {
|
||||
name string
|
||||
service HTTPRestService
|
||||
|
@ -27,8 +28,8 @@ func TestAreNCsPresent(t *testing.T) {
|
|||
name: "containerIDByOrchestorContext present",
|
||||
service: HTTPRestService{
|
||||
state: &httpRestServiceState{
|
||||
ContainerIDByOrchestratorContext: map[string]string{
|
||||
"nc1": "present",
|
||||
ContainerIDByOrchestratorContext: map[string]*ncList{
|
||||
"nc1": &present,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -50,3 +51,111 @@ func TestAreNCsPresent(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// test to add unique nc to ncList for Add() method
|
||||
func TestAddNCs(t *testing.T) {
|
||||
var ncs ncList
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
want ncList
|
||||
}{
|
||||
{
|
||||
name: "test add NCs",
|
||||
want: "swift_1abc,swift_2abc,swift_3abc",
|
||||
},
|
||||
{
|
||||
name: "test add duplicated NCs",
|
||||
want: "swift_1abc,swift_2abc,swift_3abc",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ncs.Add("swift_1abc")
|
||||
ncs.Add("swift_2abc")
|
||||
ncs.Add("swift_3abc")
|
||||
// test if added nc will be combined to one string with "," separated
|
||||
assert.Equal(t, tt.want, ncs)
|
||||
|
||||
// test if duplicated nc("swift_3abc") cannot be added to ncList
|
||||
ncs.Add("swift_3abc")
|
||||
assert.Equal(t, tt.want, ncs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// test to check if ncList contains specific NC for Containers() method
|
||||
func TestContainsNC(t *testing.T) {
|
||||
var ncs ncList
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
want1 bool
|
||||
want2 bool
|
||||
}{
|
||||
{
|
||||
name: "test NC is in ncList",
|
||||
want1: true,
|
||||
want2: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ncs.Add("swift_1abc")
|
||||
ncs.Add("swift_2abc")
|
||||
assert.Equal(t, tt.want1, ncs.Contains("swift_1abc"))
|
||||
assert.Equal(t, tt.want2, ncs.Contains("swift_3abc"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// test to check if nc can be deleted from ncList for Delete() method
|
||||
func TestDeleteNCs(t *testing.T) {
|
||||
var ncs ncList
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
want1 ncList
|
||||
want2 ncList
|
||||
want3 ncList
|
||||
want4 ncList
|
||||
}{
|
||||
{
|
||||
name: "test to delete NC from ncList",
|
||||
want1: "swift_1abc,swift_3abc,swift_4abc",
|
||||
want2: "swift_3abc,swift_4abc",
|
||||
want3: "swift_3abc",
|
||||
want4: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ncs.Add("swift_1abc")
|
||||
ncs.Add("swift_2abc")
|
||||
ncs.Add("swift_3abc")
|
||||
ncs.Add("swift_4abc")
|
||||
|
||||
// remove "swift_2abc" from ncList
|
||||
ncs.Delete("swift_2abc")
|
||||
assert.Equal(t, tt.want1, ncs)
|
||||
|
||||
// remove "swift_1abc" from ncList
|
||||
ncs.Delete("swift_1abc")
|
||||
assert.Equal(t, tt.want2, ncs)
|
||||
|
||||
// remove "swift_4abc" from ncList
|
||||
ncs.Delete("swift_4abc")
|
||||
assert.Equal(t, tt.want3, ncs)
|
||||
|
||||
// remove "swift_3abc" from ncList and check if ncList become ""
|
||||
ncs.Delete("swift_3abc")
|
||||
assert.Equal(t, tt.want4, ncs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ const (
|
|||
NilEndpointStateStore ResponseCode = 40
|
||||
NmAgentInternalServerError ResponseCode = 41
|
||||
StatusUnauthorized ResponseCode = 42
|
||||
UnsupportedAPI ResponseCode = 43
|
||||
UnexpectedError ResponseCode = 99
|
||||
)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче