Allow DNC to create NCs on a node with duplicate orchestrator context -- CNI & CNS (#1625)

This commit is contained in:
Paul Yu 2023-03-03 22:51:12 -05:00 коммит произвёл GitHub
Родитель 162643ad9b
Коммит 1c854b470d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 1099 добавлений и 319 удалений

Просмотреть файл

@ -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
)