From 9fa364c80a232ab80b967deb97f1f4bb0e25f099 Mon Sep 17 00:00:00 2001 From: v-xuxin Date: Wed, 17 Jun 2020 04:01:40 +0000 Subject: [PATCH] Enrich the unit tests for azure_standard --- .../azure/azure_standard_test.go | 998 ++++++++++++++++++ 1 file changed, 998 insertions(+) diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/azure_standard_test.go b/staging/src/k8s.io/legacy-cloud-providers/azure/azure_standard_test.go index ef468e7198c..5b83b546207 100644 --- a/staging/src/k8s.io/legacy-cloud-providers/azure/azure_standard_test.go +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/azure_standard_test.go @@ -20,14 +20,23 @@ package azure import ( "fmt" + "net/http" "strconv" "testing" + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute" + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network" + "github.com/Azure/go-autorest/autorest/to" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + cloudprovider "k8s.io/cloud-provider" + "k8s.io/legacy-cloud-providers/azure/clients/interfaceclient/mockinterfaceclient" + "k8s.io/legacy-cloud-providers/azure/clients/vmclient/mockvmclient" + "k8s.io/legacy-cloud-providers/azure/retry" ) const ( @@ -206,8 +215,16 @@ func TestGetAzureLoadBalancerName(t *testing.T) { isInternal bool useStandardLB bool clusterName string + lbName string expected string }{ + { + description: "prefix of loadBalancerName should be az.LoadBalancerName if az.LoadBalancerName is not nil", + vmSet: "primary", + clusterName: "azure", + lbName: "azurelb", + expected: "azurelb", + }, { description: "default external LB should get primary vmset", vmSet: "primary", @@ -272,6 +289,7 @@ func TestGetAzureLoadBalancerName(t *testing.T) { } else { az.Config.LoadBalancerSku = loadBalancerSkuBasic } + az.Config.LoadBalancerName = c.lbName loadbalancerName := az.getAzureLoadBalancerName(c.clusterName, c.vmSet, c.isInternal) assert.Equal(t, c.expected, loadbalancerName, c.description) } @@ -511,3 +529,983 @@ func testGetLoadBalancerSubResourceID( assert.Equal(t, c.expected, subResourceID, c.description) } } + +func TestGetProtocolsFromKubernetesProtocol(t *testing.T) { + testcases := []struct { + Name string + protocol v1.Protocol + expectedTransportProto network.TransportProtocol + expectedSecurityGroupProto network.SecurityRuleProtocol + expectedProbeProto network.ProbeProtocol + nilProbeProto bool + expectedErrMsg error + }{ + { + Name: "getProtocolsFromKubernetesProtocol should get TCP protocol", + protocol: v1.ProtocolTCP, + expectedTransportProto: network.TransportProtocolTCP, + expectedSecurityGroupProto: network.SecurityRuleProtocolTCP, + expectedProbeProto: network.ProbeProtocolTCP, + }, + { + Name: "getProtocolsFromKubernetesProtocol should get UDP protocol", + protocol: v1.ProtocolUDP, + expectedTransportProto: network.TransportProtocolUDP, + expectedSecurityGroupProto: network.SecurityRuleProtocolUDP, + nilProbeProto: true, + }, + { + Name: "getProtocolsFromKubernetesProtocol should report error", + protocol: v1.ProtocolSCTP, + expectedErrMsg: fmt.Errorf("only TCP and UDP are supported for Azure LoadBalancers"), + }, + } + + for _, test := range testcases { + transportProto, securityGroupProto, probeProto, err := getProtocolsFromKubernetesProtocol(test.protocol) + assert.Equal(t, test.expectedTransportProto, *transportProto, test.Name) + assert.Equal(t, test.expectedSecurityGroupProto, *securityGroupProto, test.Name) + if test.nilProbeProto { + assert.Nil(t, probeProto, test.Name) + } else { + assert.Equal(t, test.expectedProbeProto, *probeProto, test.Name) + } + assert.Equal(t, test.expectedErrMsg, err, test.Name) + } +} + +func TestGetStandardVMPrimaryInterfaceID(t *testing.T) { + testcases := []struct { + name string + vm compute.VirtualMachine + expectedNicID string + expectedErrMsg error + }{ + { + name: "GetPrimaryInterfaceID should get the only NIC ID", + vm: compute.VirtualMachine{ + Name: to.StringPtr("vm1"), + VirtualMachineProperties: &compute.VirtualMachineProperties{ + NetworkProfile: &compute.NetworkProfile{ + NetworkInterfaces: &[]compute.NetworkInterfaceReference{ + { + ID: to.StringPtr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic"), + }, + }, + }, + }, + }, + expectedNicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic", + }, + { + name: "GetPrimaryInterfaceID should get primary NIC ID", + vm: compute.VirtualMachine{ + Name: to.StringPtr("vm2"), + VirtualMachineProperties: &compute.VirtualMachineProperties{ + NetworkProfile: &compute.NetworkProfile{ + NetworkInterfaces: &[]compute.NetworkInterfaceReference{ + { + NetworkInterfaceReferenceProperties: &compute.NetworkInterfaceReferenceProperties{ + Primary: to.BoolPtr(true), + }, + ID: to.StringPtr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic1"), + }, + { + NetworkInterfaceReferenceProperties: &compute.NetworkInterfaceReferenceProperties{ + Primary: to.BoolPtr(false), + }, + ID: to.StringPtr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic2"), + }, + }, + }, + }, + }, + expectedNicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic1", + }, + { + name: "GetPrimaryInterfaceID should report error if node don't have primary NIC", + vm: compute.VirtualMachine{ + Name: to.StringPtr("vm3"), + VirtualMachineProperties: &compute.VirtualMachineProperties{ + NetworkProfile: &compute.NetworkProfile{ + NetworkInterfaces: &[]compute.NetworkInterfaceReference{ + { + NetworkInterfaceReferenceProperties: &compute.NetworkInterfaceReferenceProperties{ + Primary: to.BoolPtr(false), + }, + ID: to.StringPtr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic1"), + }, + { + NetworkInterfaceReferenceProperties: &compute.NetworkInterfaceReferenceProperties{ + Primary: to.BoolPtr(false), + }, + ID: to.StringPtr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic2"), + }, + }, + }, + }, + }, + expectedErrMsg: fmt.Errorf("failed to find a primary nic for the vm. vmname=%q", "vm3"), + }, + } + + for _, test := range testcases { + primaryNicID, err := getPrimaryInterfaceID(test.vm) + assert.Equal(t, test.expectedNicID, primaryNicID, test.name) + assert.Equal(t, test.expectedErrMsg, err, test.name) + } +} + +func TestGetPrimaryIPConfig(t *testing.T) { + testcases := []struct { + name string + nic network.Interface + expectedIPConfig *network.InterfaceIPConfiguration + expectedErrMsg error + }{ + + { + name: "GetPrimaryIPConfig should get the only IP configuration", + nic: network.Interface{ + Name: to.StringPtr("nic"), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ + IPConfigurations: &[]network.InterfaceIPConfiguration{ + { + Name: to.StringPtr("ipconfig1"), + }, + }, + }, + }, + expectedIPConfig: &network.InterfaceIPConfiguration{ + Name: to.StringPtr("ipconfig1"), + }, + }, + { + name: "GetPrimaryIPConfig should get the primary IP configuration", + nic: network.Interface{ + Name: to.StringPtr("nic"), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ + IPConfigurations: &[]network.InterfaceIPConfiguration{ + { + Name: to.StringPtr("ipconfig1"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + Primary: to.BoolPtr(true), + }, + }, + { + Name: to.StringPtr("ipconfig2"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + Primary: to.BoolPtr(false), + }, + }, + }, + }, + }, + expectedIPConfig: &network.InterfaceIPConfiguration{ + Name: to.StringPtr("ipconfig1"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + Primary: to.BoolPtr(true), + }, + }, + }, + { + name: "GetPrimaryIPConfig should report error if nic don't have IP configuration", + nic: network.Interface{ + Name: to.StringPtr("nic"), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{}, + }, + expectedErrMsg: fmt.Errorf("nic.IPConfigurations for nic (nicname=%q) is nil", "nic"), + }, + { + name: "GetPrimaryIPConfig should report error if node has more than one IP configuration and don't have primary IP configuration", + nic: network.Interface{ + Name: to.StringPtr("nic"), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ + IPConfigurations: &[]network.InterfaceIPConfiguration{ + { + Name: to.StringPtr("ipconfig1"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + Primary: to.BoolPtr(false), + }, + }, + { + Name: to.StringPtr("ipconfig2"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + Primary: to.BoolPtr(false), + }, + }, + }, + }, + }, + expectedErrMsg: fmt.Errorf("failed to determine the primary ipconfig. nicname=%q", "nic"), + }, + } + + for _, test := range testcases { + primaryIPConfig, err := getPrimaryIPConfig(test.nic) + assert.Equal(t, test.expectedIPConfig, primaryIPConfig, test.name) + assert.Equal(t, test.expectedErrMsg, err, test.name) + } +} + +func TestGetIPConfigByIPFamily(t *testing.T) { + ipv4IPconfig := network.InterfaceIPConfiguration{ + Name: to.StringPtr("ipconfig1"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddressVersion: network.IPv4, + PrivateIPAddress: to.StringPtr("10.10.0.12"), + }, + } + ipv6IPconfig := network.InterfaceIPConfiguration{ + Name: to.StringPtr("ipconfig2"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddressVersion: network.IPv6, + PrivateIPAddress: to.StringPtr("1111:11111:00:00:1111:1111:000:111"), + }, + } + testNic := network.Interface{ + Name: to.StringPtr("nic"), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ + IPConfigurations: &[]network.InterfaceIPConfiguration{ipv4IPconfig, ipv6IPconfig}, + }, + } + testcases := []struct { + name string + nic network.Interface + expectedIPConfig *network.InterfaceIPConfiguration + IPv6 bool + expectedErrMsg error + }{ + { + name: "GetIPConfigByIPFamily should get the IPv6 IP configuration if IPv6 is false", + nic: testNic, + expectedIPConfig: &ipv4IPconfig, + }, + { + name: "GetIPConfigByIPFamily should get the IPv4 IP configuration if IPv6 is true", + nic: testNic, + IPv6: true, + expectedIPConfig: &ipv6IPconfig, + }, + { + name: "GetIPConfigByIPFamily should report error if nic don't have IP configuration", + nic: network.Interface{ + Name: to.StringPtr("nic"), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{}, + }, + expectedErrMsg: fmt.Errorf("nic.IPConfigurations for nic (nicname=%q) is nil", "nic"), + }, + { + name: "GetIPConfigByIPFamily should report error if nic don't have IPv6 configuration when IPv6 is true", + nic: network.Interface{ + Name: to.StringPtr("nic"), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ + IPConfigurations: &[]network.InterfaceIPConfiguration{ipv4IPconfig}, + }, + }, + IPv6: true, + expectedErrMsg: fmt.Errorf("failed to determine the ipconfig(IPv6=%v). nicname=%q", true, "nic"), + }, + { + name: "GetIPConfigByIPFamily should report error if nic don't have PrivateIPAddress", + nic: network.Interface{ + Name: to.StringPtr("nic"), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ + IPConfigurations: &[]network.InterfaceIPConfiguration{ + { + Name: to.StringPtr("ipconfig1"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddressVersion: network.IPv4, + }, + }, + }, + }, + }, + expectedErrMsg: fmt.Errorf("failed to determine the ipconfig(IPv6=%v). nicname=%q", false, "nic"), + }, + } + + for _, test := range testcases { + ipConfig, err := getIPConfigByIPFamily(test.nic, test.IPv6) + assert.Equal(t, test.expectedIPConfig, ipConfig, test.name) + assert.Equal(t, test.expectedErrMsg, err, test.name) + } +} + +func TestGetBackendPoolName(t *testing.T) { + testcases := []struct { + name string + service v1.Service + clusterName string + expectedPoolName string + }{ + { + name: "GetBackendPoolName should return -IPv6", + service: getTestService("test1", v1.ProtocolTCP, nil, true, 80), + clusterName: "azure", + expectedPoolName: "azure-IPv6", + }, + { + name: "GetBackendPoolName should return ", + service: getTestService("test1", v1.ProtocolTCP, nil, false, 80), + clusterName: "azure", + expectedPoolName: "azure", + }, + } + for _, test := range testcases { + backPoolName := getBackendPoolName(test.clusterName, &test.service) + assert.Equal(t, test.expectedPoolName, backPoolName, test.name) + } +} + +func TestGetStandardInstanceIDByNodeName(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cloud := GetTestCloud(ctrl) + + expectedVM := compute.VirtualMachine{ + Name: to.StringPtr("vm1"), + ID: to.StringPtr("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm1"), + } + invalidResouceID := "/subscriptions/subscription/resourceGroups/rg/Microsoft.Compute/virtualMachines/vm4" + testcases := []struct { + name string + nodeName string + expectedID string + expectedErrMsg error + }{ + { + name: "GetInstanceIDByNodeName should get instanceID as expected", + nodeName: "vm1", + expectedID: "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm1", + }, + { + name: "GetInstanceIDByNodeName should report error if node don't exist", + nodeName: "vm2", + expectedErrMsg: fmt.Errorf("instance not found"), + }, + { + name: "GetInstanceIDByNodeName should report error if Error encountered when invoke mockVMClient.Get", + nodeName: "vm3", + expectedErrMsg: fmt.Errorf("Retriable: false, RetryAfter: 0s, HTTPStatusCode: 500, RawError: VMGet error"), + }, + { + name: "GetInstanceIDByNodeName should report error if ResourceID is invalid", + nodeName: "vm4", + expectedErrMsg: fmt.Errorf("%q isn't in Azure resource ID format %q", invalidResouceID, azureResourceGroupNameRE.String()), + }, + } + for _, test := range testcases { + mockVMClient := cloud.VirtualMachinesClient.(*mockvmclient.MockInterface) + mockVMClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, "vm1", gomock.Any()).Return(expectedVM, nil).AnyTimes() + mockVMClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, "vm2", gomock.Any()).Return(compute.VirtualMachine{}, &retry.Error{HTTPStatusCode: http.StatusNotFound, RawError: cloudprovider.InstanceNotFound}).AnyTimes() + mockVMClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, "vm3", gomock.Any()).Return(compute.VirtualMachine{}, &retry.Error{ + HTTPStatusCode: http.StatusInternalServerError, + RawError: fmt.Errorf("VMGet error"), + }).AnyTimes() + mockVMClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, "vm4", gomock.Any()).Return(compute.VirtualMachine{ + Name: to.StringPtr("vm4"), + ID: to.StringPtr(invalidResouceID), + }, nil).AnyTimes() + + instanceID, err := cloud.vmSet.GetInstanceIDByNodeName(test.nodeName) + assert.Equal(t, test.expectedErrMsg, err, test.name) + assert.Equal(t, test.expectedID, instanceID, test.name) + } +} + +func TestGetStandardVMPowerStatusByNodeName(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cloud := GetTestCloud(ctrl) + + testcases := []struct { + name string + nodeName string + vm compute.VirtualMachine + expectedStatus string + getErr *retry.Error + expectedErrMsg error + }{ + { + name: "GetPowerStatusByNodeName should report error if node don't exist", + nodeName: "vm1", + vm: compute.VirtualMachine{}, + getErr: &retry.Error{ + HTTPStatusCode: http.StatusNotFound, + RawError: cloudprovider.InstanceNotFound, + }, + expectedErrMsg: fmt.Errorf("instance not found"), + }, + { + name: "GetPowerStatusByNodeName should get power status as expected", + nodeName: "vm2", + vm: compute.VirtualMachine{ + Name: to.StringPtr("vm2"), + VirtualMachineProperties: &compute.VirtualMachineProperties{ + InstanceView: &compute.VirtualMachineInstanceView{ + Statuses: &[]compute.InstanceViewStatus{ + { + Code: to.StringPtr("PowerState/Running"), + }, + }, + }, + }, + }, + expectedStatus: "Running", + }, + { + name: "GetPowerStatusByNodeName should get vmPowerStateStopped if vm.InstanceView is nil", + nodeName: "vm3", + vm: compute.VirtualMachine{ + Name: to.StringPtr("vm3"), + VirtualMachineProperties: &compute.VirtualMachineProperties{}, + }, + expectedStatus: vmPowerStateStopped, + }, + { + name: "GetPowerStatusByNodeName should get vmPowerStateStopped if vm.InstanceView.statuses is nil", + nodeName: "vm4", + vm: compute.VirtualMachine{ + Name: to.StringPtr("vm4"), + VirtualMachineProperties: &compute.VirtualMachineProperties{ + InstanceView: &compute.VirtualMachineInstanceView{}, + }, + }, + expectedStatus: vmPowerStateStopped, + }, + } + for _, test := range testcases { + mockVMClient := cloud.VirtualMachinesClient.(*mockvmclient.MockInterface) + mockVMClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, test.nodeName, gomock.Any()).Return(test.vm, test.getErr).AnyTimes() + + powerState, err := cloud.vmSet.GetPowerStatusByNodeName(test.nodeName) + assert.Equal(t, test.expectedErrMsg, err, test.name) + assert.Equal(t, test.expectedStatus, powerState, test.name) + } +} + +func TestGetStandardVMZoneByNodeName(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cloud := GetTestCloud(ctrl) + + var faultDomain int32 = 3 + testcases := []struct { + name string + nodeName string + vm compute.VirtualMachine + expectedZone cloudprovider.Zone + getErr *retry.Error + expectedErrMsg error + }{ + { + name: "GetZoneByNodeName should report error if node don't exist", + nodeName: "vm1", + vm: compute.VirtualMachine{}, + getErr: &retry.Error{ + HTTPStatusCode: http.StatusNotFound, + RawError: cloudprovider.InstanceNotFound, + }, + expectedErrMsg: fmt.Errorf("instance not found"), + }, + { + name: "GetZoneByNodeName should get zone as expected", + nodeName: "vm2", + vm: compute.VirtualMachine{ + Name: to.StringPtr("vm2"), + Location: to.StringPtr("EASTUS"), + Zones: &[]string{"2"}, + VirtualMachineProperties: &compute.VirtualMachineProperties{ + InstanceView: &compute.VirtualMachineInstanceView{ + PlatformFaultDomain: &faultDomain, + }, + }, + }, + expectedZone: cloudprovider.Zone{ + FailureDomain: "eastus-2", + Region: "eastus", + }, + }, + { + name: "GetZoneByNodeName should get FailureDomain as zone if zone is not used for node", + nodeName: "vm3", + vm: compute.VirtualMachine{ + Name: to.StringPtr("vm3"), + Location: to.StringPtr("EASTUS"), + VirtualMachineProperties: &compute.VirtualMachineProperties{ + InstanceView: &compute.VirtualMachineInstanceView{ + PlatformFaultDomain: &faultDomain, + }, + }, + }, + expectedZone: cloudprovider.Zone{ + FailureDomain: "3", + Region: "eastus", + }, + }, + { + name: "GetZoneByNodeName should report error if zones is invalid", + nodeName: "vm4", + vm: compute.VirtualMachine{ + Name: to.StringPtr("vm4"), + Location: to.StringPtr("EASTUS"), + Zones: &[]string{"a"}, + VirtualMachineProperties: &compute.VirtualMachineProperties{ + InstanceView: &compute.VirtualMachineInstanceView{ + PlatformFaultDomain: &faultDomain, + }, + }, + }, + expectedErrMsg: fmt.Errorf("failed to parse zone %q: strconv.Atoi: parsing %q: invalid syntax", []string{"a"}, "a"), + }, + } + for _, test := range testcases { + mockVMClient := cloud.VirtualMachinesClient.(*mockvmclient.MockInterface) + mockVMClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, test.nodeName, gomock.Any()).Return(test.vm, test.getErr).AnyTimes() + + zone, err := cloud.vmSet.GetZoneByNodeName(test.nodeName) + assert.Equal(t, test.expectedErrMsg, err, test.name) + assert.Equal(t, test.expectedZone, zone, test.name) + } +} + +func TestGetStandardVMSetNames(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cloud := GetTestCloud(ctrl) + + asID := "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Compute/availabilitySets/myAvailabilitySet" + testVM := compute.VirtualMachine{ + Name: to.StringPtr("vm1"), + VirtualMachineProperties: &compute.VirtualMachineProperties{ + AvailabilitySet: &compute.SubResource{ID: to.StringPtr(asID)}, + }, + } + testVMWithoutAS := compute.VirtualMachine{ + Name: to.StringPtr("vm2"), + VirtualMachineProperties: &compute.VirtualMachineProperties{}, + } + testCases := []struct { + name string + vm []compute.VirtualMachine + service *v1.Service + nodes []*v1.Node + expectedVMSetNames *[]string + expectedErrMsg error + }{ + { + name: "GetVMSetNames should return the primary vm set name if the service has no mode annotation", + vm: []compute.VirtualMachine{testVM}, + service: &v1.Service{}, + expectedVMSetNames: &[]string{"as"}, + }, + { + name: "GetVMSetNames should return the correct as names if the service has auto mode annotation", + vm: []compute.VirtualMachine{testVM}, + service: &v1.Service{ + ObjectMeta: meta.ObjectMeta{Annotations: map[string]string{ServiceAnnotationLoadBalancerMode: ServiceAnnotationLoadBalancerAutoModeValue}}, + }, + nodes: []*v1.Node{ + { + ObjectMeta: meta.ObjectMeta{ + Name: "vm1", + }, + }, + }, + expectedVMSetNames: &[]string{"myavailabilityset"}, + }, + { + name: "GetVMSetNames should return the correct as names if node don't have availability set", + vm: []compute.VirtualMachine{testVMWithoutAS}, + service: &v1.Service{ + ObjectMeta: meta.ObjectMeta{Annotations: map[string]string{ServiceAnnotationLoadBalancerMode: ServiceAnnotationLoadBalancerAutoModeValue}}, + }, + nodes: []*v1.Node{ + { + ObjectMeta: meta.ObjectMeta{ + Name: "vm2", + }, + }, + }, + expectedErrMsg: fmt.Errorf("Node (vm2) - has no availability sets"), + }, + { + name: "GetVMSetNames should report the error if there's no such availability set", + vm: []compute.VirtualMachine{testVM}, + service: &v1.Service{ + ObjectMeta: meta.ObjectMeta{Annotations: map[string]string{ServiceAnnotationLoadBalancerMode: "vm2"}}, + }, + nodes: []*v1.Node{ + { + ObjectMeta: meta.ObjectMeta{ + Name: "vm1", + }, + }, + }, + expectedErrMsg: fmt.Errorf("availability set (vm2) - not found"), + }, + { + name: "GetVMSetNames should return the correct node name", + vm: []compute.VirtualMachine{testVM}, + service: &v1.Service{ + ObjectMeta: meta.ObjectMeta{Annotations: map[string]string{ServiceAnnotationLoadBalancerMode: "myAvailabilitySet"}}, + }, + nodes: []*v1.Node{ + { + ObjectMeta: meta.ObjectMeta{ + Name: "vm1", + }, + }, + }, + expectedVMSetNames: &[]string{"myavailabilityset"}, + }, + } + + for _, test := range testCases { + mockVMClient := cloud.VirtualMachinesClient.(*mockvmclient.MockInterface) + mockVMClient.EXPECT().List(gomock.Any(), cloud.ResourceGroup).Return(test.vm, nil).AnyTimes() + + vmSetNames, err := cloud.vmSet.GetVMSetNames(test.service, test.nodes) + assert.Equal(t, test.expectedErrMsg, err, test.name) + assert.Equal(t, test.expectedVMSetNames, vmSetNames, test.name) + } +} + +func TestExtractResourceGroupByNicID(t *testing.T) { + testCases := []struct { + name string + nicID string + expectedRG string + expectedErrMsg error + }{ + { + name: "ExtractResourceGroupByNicID should return correct resource group", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic", + expectedRG: "rg", + }, + { + name: "ExtractResourceGroupByNicID should report error if nicID is invalid", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Compute/networkInterfaces/nic", + expectedErrMsg: fmt.Errorf("error of extracting resourceGroup from nicID %q", "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Compute/networkInterfaces/nic"), + }, + } + + for _, test := range testCases { + rgName, err := extractResourceGroupByNicID(test.nicID) + assert.Equal(t, test.expectedErrMsg, err, test.name) + assert.Equal(t, test.expectedRG, rgName, test.name) + } +} + +func TestStandardEnsureHostInPool(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cloud := GetTestCloud(ctrl) + + availabilitySetID := "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Compute/availabilitySets/myAvailabilitySet" + backendAddressPoolID := "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb1-internal/backendAddressPools/backendpool-1" + + testCases := []struct { + name string + service *v1.Service + nodeName types.NodeName + backendPoolID string + nicName string + nicID string + vmSetName string + nicProvisionState string + isStandardLB bool + expectedErrMsg error + }{ + { + name: "EnsureHostInPool should return nil if node is not in VMSet", + service: &v1.Service{}, + nodeName: "vm1", + nicName: "nic1", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic1", + vmSetName: "availabilityset-1", + }, + { + name: "EnsureHostInPool should report error if last segment of nicID is nil", + service: &v1.Service{}, + nodeName: "vm2", + nicName: "nic2", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/", + vmSetName: "availabilityset-1", + expectedErrMsg: fmt.Errorf("resource name was missing from identifier"), + }, + { + name: "EnsureHostInPool should return nil if node's provisioning state is Failed", + service: &v1.Service{}, + nodeName: "vm3", + nicName: "nic3", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic3", + nicProvisionState: nicFailedState, + vmSetName: "myAvailabilitySet", + }, + { + name: "EnsureHostInPool should report error if service.Spec.ClusterIP is ipv6 but node don't have IPv6 address", + service: &v1.Service{Spec: v1.ServiceSpec{ClusterIP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334"}}, + nodeName: "vm4", + nicName: "nic4", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic4", + vmSetName: "myAvailabilitySet", + expectedErrMsg: fmt.Errorf("failed to determine the ipconfig(IPv6=true). nicname=%q", "nic4"), + }, + { + name: "EnsureHostInPool should return nil if there is matched backend pool", + service: &v1.Service{}, + backendPoolID: backendAddressPoolID, + nodeName: "vm5", + nicName: "nic5", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic5", + vmSetName: "myAvailabilitySet", + }, + { + name: "EnsureHostInPool should return nil if there isn't matched backend pool", + service: &v1.Service{}, + backendPoolID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb1-internal/backendAddressPools/backendpool-2", + nodeName: "vm6", + nicName: "nic6", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic6", + vmSetName: "myAvailabilitySet", + }, + { + name: "EnsureHostInPool should return nil if BackendPool is not on same LB", + service: &v1.Service{}, + isStandardLB: true, + backendPoolID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb2-internal/backendAddressPools/backendpool-3", + nodeName: "vm7", + nicName: "nic7", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic7", + vmSetName: "myAvailabilitySet", + }, + { + name: "EnsureHostInPool should report error if the format of backendPoolID is invalid", + service: &v1.Service{}, + isStandardLB: true, + backendPoolID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb2-internal/backendAddressPool/backendpool-3", + nodeName: "vm8", + nicName: "nic8", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic7", + vmSetName: "myAvailabilitySet", + expectedErrMsg: fmt.Errorf("new backendPoolID %q is in wrong format", "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb2-internal/backendAddressPool/backendpool-3"), + }, + } + + for _, test := range testCases { + if test.isStandardLB { + cloud.Config.LoadBalancerSku = loadBalancerSkuStandard + } + + testVM := compute.VirtualMachine{ + Name: to.StringPtr(string(test.nodeName)), + VirtualMachineProperties: &compute.VirtualMachineProperties{ + AvailabilitySet: &compute.SubResource{ID: to.StringPtr(availabilitySetID)}, + NetworkProfile: &compute.NetworkProfile{ + NetworkInterfaces: &[]compute.NetworkInterfaceReference{ + { + ID: to.StringPtr(test.nicID), + }, + }, + }, + }, + } + testNIC := network.Interface{ + Name: to.StringPtr(test.nicName), + ID: to.StringPtr(test.nicID), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ + ProvisioningState: to.StringPtr(test.nicProvisionState), + IPConfigurations: &[]network.InterfaceIPConfiguration{ + { + Name: to.StringPtr("ifconfig1"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + LoadBalancerBackendAddressPools: &[]network.BackendAddressPool{ + { + ID: to.StringPtr(backendAddressPoolID), + }, + }, + }, + }, + }, + }, + } + + mockVMClient := cloud.VirtualMachinesClient.(*mockvmclient.MockInterface) + mockVMClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, string(test.nodeName), gomock.Any()).Return(testVM, nil).AnyTimes() + + mockInterfaceClient := cloud.InterfacesClient.(*mockinterfaceclient.MockInterface) + mockInterfaceClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, test.nicName, gomock.Any()).Return(testNIC, nil).AnyTimes() + mockInterfaceClient.EXPECT().CreateOrUpdate(gomock.Any(), cloud.ResourceGroup, gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + + _, _, _, vm, err := cloud.vmSet.EnsureHostInPool(test.service, test.nodeName, test.backendPoolID, test.vmSetName, false) + assert.Equal(t, test.expectedErrMsg, err, test.name) + assert.Nil(t, vm, test.name) + } +} + +func TestStandardEnsureHostsInPool(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cloud := GetTestCloud(ctrl) + + availabilitySetID := "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Compute/availabilitySets/myAvailabilitySet" + backendAddressPoolID := "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb1-internal/backendAddressPools/backendpool-1" + + testCases := []struct { + name string + service *v1.Service + nodes []*v1.Node + nodeName string + backendPoolID string + nicName string + nicID string + vmSetName string + expectedErr bool + expectedErrMsg string + }{ + { + name: "EnsureHostsInPool should return nil if there's no error when invoke EnsureHostInPool", + service: &v1.Service{}, + nodeName: "vm1", + nodes: []*v1.Node{ + { + ObjectMeta: meta.ObjectMeta{ + Name: "vm1", + }, + }, + }, + nicName: "nic1", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic1", + backendPoolID: backendAddressPoolID, + vmSetName: "availabilityset-1", + }, + { + name: "EnsureHostsInPool should skip if node is master node", + service: &v1.Service{}, + nodeName: "vm2", + nodes: []*v1.Node{ + { + ObjectMeta: meta.ObjectMeta{ + Name: "vm2", + Labels: map[string]string{nodeLabelRole: "master"}, + }, + }, + }, + nicName: "nic2", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/", + vmSetName: "availabilityset-1", + }, + { + name: "EnsureHostsInPool should skip if node is in external resource group", + service: &v1.Service{}, + nodeName: "vm3", + nodes: []*v1.Node{ + { + ObjectMeta: meta.ObjectMeta{ + Name: "vm3", + Labels: map[string]string{externalResourceGroupLabel: "rg-external"}, + }, + }, + }, + nicName: "nic3", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic3", + vmSetName: "availabilityset-1", + }, + { + name: "EnsureHostsInPool should skip if node is unmanaged", + service: &v1.Service{}, + nodeName: "vm4", + nodes: []*v1.Node{ + { + ObjectMeta: meta.ObjectMeta{ + Name: "vm4", + Labels: map[string]string{managedByAzureLabel: "false"}, + }, + }, + }, + + nicName: "nic4", + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic4", + vmSetName: "availabilityset-1", + }, + { + name: "EnsureHostsInPool should report error if service.Spec.ClusterIP is ipv6 but node don't have IPv6 address", + service: &v1.Service{ + ObjectMeta: meta.ObjectMeta{ + Name: "svc", + Namespace: "default", + }, + Spec: v1.ServiceSpec{ + ClusterIP: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + }, + }, + nodeName: "vm5", + nodes: []*v1.Node{ + { + ObjectMeta: meta.ObjectMeta{ + Name: "vm5", + }, + }, + }, + nicName: "nic5", + backendPoolID: backendAddressPoolID, + nicID: "/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic5", + vmSetName: "myAvailabilitySet", + expectedErr: true, + expectedErrMsg: fmt.Sprintf("ensure(default/svc): backendPoolID(%s) - failed to ensure host in pool: %q", backendAddressPoolID, fmt.Errorf("failed to determine the ipconfig(IPv6=true). nicname=%q", "nic5")), + }, + } + + for _, test := range testCases { + cloud.Config.LoadBalancerSku = loadBalancerSkuStandard + cloud.Config.ExcludeMasterFromStandardLB = to.BoolPtr(true) + + testVM := compute.VirtualMachine{ + Name: to.StringPtr(string(test.nodeName)), + VirtualMachineProperties: &compute.VirtualMachineProperties{ + AvailabilitySet: &compute.SubResource{ID: to.StringPtr(availabilitySetID)}, + NetworkProfile: &compute.NetworkProfile{ + NetworkInterfaces: &[]compute.NetworkInterfaceReference{ + { + ID: to.StringPtr(test.nicID), + }, + }, + }, + }, + } + testNIC := network.Interface{ + Name: to.StringPtr(test.nicName), + ID: to.StringPtr(test.nicID), + InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ + IPConfigurations: &[]network.InterfaceIPConfiguration{ + { + Name: to.StringPtr("ifconfig1"), + InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ + LoadBalancerBackendAddressPools: &[]network.BackendAddressPool{ + { + ID: to.StringPtr(backendAddressPoolID), + }, + }, + }, + }, + }, + }, + } + + mockVMClient := cloud.VirtualMachinesClient.(*mockvmclient.MockInterface) + mockVMClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, test.nodeName, gomock.Any()).Return(testVM, nil).AnyTimes() + + mockInterfaceClient := cloud.InterfacesClient.(*mockinterfaceclient.MockInterface) + mockInterfaceClient.EXPECT().Get(gomock.Any(), cloud.ResourceGroup, test.nicName, gomock.Any()).Return(testNIC, nil).AnyTimes() + mockInterfaceClient.EXPECT().CreateOrUpdate(gomock.Any(), cloud.ResourceGroup, gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + + err := cloud.vmSet.EnsureHostsInPool(test.service, test.nodes, test.backendPoolID, test.vmSetName, false) + if test.expectedErr { + assert.Equal(t, test.expectedErrMsg, err.Error(), test.name) + } else { + assert.Nil(t, err, test.name) + } + } +}