/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package azure import ( "encoding/json" "fmt" "math" "net/http" "net/http/httptest" "reflect" "strings" "testing" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/flowcontrol" serviceapi "k8s.io/kubernetes/pkg/api/v1/service" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" "github.com/Azure/azure-sdk-for-go/arm/compute" "github.com/Azure/azure-sdk-for-go/arm/network" "github.com/Azure/go-autorest/autorest/to" ) var testClusterName = "testCluster" // Test additional of a new service/port. func TestAddPort(t *testing.T) { az := getTestCloud() svc := getTestService("servicea", v1.ProtocolTCP, 80) clusterResources := getClusterResources(az, 1, 1) svc.Spec.Ports = append(svc.Spec.Ports, v1.ServicePort{ Name: fmt.Sprintf("port-udp-%d", 1234), Protocol: v1.ProtocolUDP, Port: 1234, NodePort: getBackendPort(1234), }) lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } // ensure we got a frontend ip configuration if len(*lb.FrontendIPConfigurations) != 1 { t.Error("Expected the loadbalancer to have a frontend ip configuration") } validateLoadBalancer(t, lb, svc) } func TestLoadBalancerInternalServiceModeSelection(t *testing.T) { testLoadBalancerServiceDefaultModeSelection(t, true) testLoadBalancerServiceAutoModeSelection(t, true) testLoadBalancerServicesSpecifiedSelection(t, true) testLoadBalancerMaxRulesServices(t, true) testLoadBalancerServiceAutoModeDeleteSelection(t, true) } func TestLoadBalancerExternalServiceModeSelection(t *testing.T) { testLoadBalancerServiceDefaultModeSelection(t, false) testLoadBalancerServiceAutoModeSelection(t, false) testLoadBalancerServicesSpecifiedSelection(t, false) testLoadBalancerMaxRulesServices(t, false) testLoadBalancerServiceAutoModeDeleteSelection(t, false) } func testLoadBalancerServiceDefaultModeSelection(t *testing.T, isInternal bool) { az := getTestCloud() const vmCount = 8 const availabilitySetCount = 4 const serviceCount = 9 clusterResources := getClusterResources(az, vmCount, availabilitySetCount) getTestSecurityGroup(az) for index := 1; index <= serviceCount; index++ { svcName := fmt.Sprintf("service-%d", index) var svc v1.Service if isInternal { svc = getInternalTestService(svcName, 8081) addTestSubnet(t, az, &svc) } else { svc = getTestService(svcName, v1.ProtocolTCP, 8081) } lbStatus, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) if err != nil { t.Errorf("Unexpected error: %q", err) } if lbStatus == nil { t.Errorf("Unexpected error: %s", svcName) } expectedLBName := testClusterName if isInternal { expectedLBName = testClusterName + "-internal" } result, _ := az.LoadBalancerClient.List(az.Config.ResourceGroup) lb := (*result.Value)[0] lbCount := len(*result.Value) expectedNumOfLB := 1 if lbCount != expectedNumOfLB { t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLB, lbCount) } if !strings.EqualFold(*lb.Name, expectedLBName) { t.Errorf("lb name should be the default LB name Extected (%s) Fouund (%s)", expectedLBName, *lb.Name) } ruleCount := len(*lb.LoadBalancingRules) if ruleCount != index { t.Errorf("lb rule count should be equal to nuber of services deployed, expected (%d) Found (%d)", index, ruleCount) } } } // Validate even distribution of external services across load balancers // based on number of availability sets func testLoadBalancerServiceAutoModeSelection(t *testing.T, isInternal bool) { az := getTestCloud() const vmCount = 8 const availabilitySetCount = 4 const serviceCount = 9 clusterResources := getClusterResources(az, vmCount, availabilitySetCount) getTestSecurityGroup(az) for index := 1; index <= serviceCount; index++ { svcName := fmt.Sprintf("service-%d", index) var svc v1.Service if isInternal { svc = getInternalTestService(svcName, 8081) addTestSubnet(t, az, &svc) } else { svc = getTestService(svcName, v1.ProtocolTCP, 8081) } setLoadBalancerAutoModeAnnotation(&svc) lbStatus, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) if err != nil { t.Errorf("Unexpected error: %q", err) } if lbStatus == nil { t.Errorf("Unexpected error: %s", svcName) } // expected is MIN(index, availabilitySetCount) expectedNumOfLB := int(math.Min(float64(index), float64(availabilitySetCount))) result, _ := az.LoadBalancerClient.List(az.Config.ResourceGroup) lbCount := len(*result.Value) if lbCount != expectedNumOfLB { t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLB, lbCount) } maxRules := 0 minRules := serviceCount for _, lb := range *result.Value { ruleCount := len(*lb.LoadBalancingRules) if ruleCount < minRules { minRules = ruleCount } if ruleCount > maxRules { maxRules = ruleCount } } delta := maxRules - minRules if delta > 1 { t.Errorf("Unexpected min or max rule in LB's in resource group: Service Index (%d) Min (%d) Max(%d)", index, minRules, maxRules) } } } // Validate availability set selection of services across load balancers // based on provided availability sets through service annotation // The scenario is that there are 4 availability sets in the agent pool but the // services will be assigned load balancers that are part of the provided availability sets // specified in service annotation func testLoadBalancerServicesSpecifiedSelection(t *testing.T, isInternal bool) { az := getTestCloud() const vmCount = 8 const availabilitySetCount = 4 const serviceCount = 9 clusterResources := getClusterResources(az, vmCount, availabilitySetCount) getTestSecurityGroup(az) selectedAvailabilitySetName1 := getAvailabilitySetName(az, 1, availabilitySetCount) selectedAvailabilitySetName2 := getAvailabilitySetName(az, 2, availabilitySetCount) for index := 1; index <= serviceCount; index++ { svcName := fmt.Sprintf("service-%d", index) var svc v1.Service if isInternal { svc = getInternalTestService(svcName, 8081) addTestSubnet(t, az, &svc) } else { svc = getTestService(svcName, v1.ProtocolTCP, 8081) } lbMode := fmt.Sprintf("%s,%s", selectedAvailabilitySetName1, selectedAvailabilitySetName2) setLoadBalancerModeAnnotation(&svc, lbMode) lbStatus, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) if err != nil { t.Errorf("Unexpected error: %q", err) } if lbStatus == nil { t.Errorf("Unexpected error: %s", svcName) } // expected is MIN(index, 2) expectedNumOfLB := int(math.Min(float64(index), float64(2))) result, _ := az.LoadBalancerClient.List(az.Config.ResourceGroup) lbCount := len(*result.Value) if lbCount != expectedNumOfLB { t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLB, lbCount) } } } func testLoadBalancerMaxRulesServices(t *testing.T, isInternal bool) { az := getTestCloud() const vmCount = 1 const availabilitySetCount = 1 clusterResources := getClusterResources(az, vmCount, availabilitySetCount) getTestSecurityGroup(az) az.Config.MaximumLoadBalancerRuleCount = 1 for index := 1; index <= az.Config.MaximumLoadBalancerRuleCount; index++ { svcName := fmt.Sprintf("service-%d", index) var svc v1.Service if isInternal { svc = getInternalTestService(svcName, 8081) addTestSubnet(t, az, &svc) } else { svc = getTestService(svcName, v1.ProtocolTCP, 8081) } lbStatus, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) if err != nil { t.Errorf("Unexpected error: %q", err) } if lbStatus == nil { t.Errorf("Unexpected error: %s", svcName) } // expected is MIN(index, az.Config.MaximumLoadBalancerRuleCount) expectedNumOfLBRules := int(math.Min(float64(index), float64(az.Config.MaximumLoadBalancerRuleCount))) result, _ := az.LoadBalancerClient.List(az.Config.ResourceGroup) lbCount := len(*result.Value) if lbCount != expectedNumOfLBRules { t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLBRules, lbCount) } } // validate adding a new service fails since it will exceed the max limit on LB svcName := fmt.Sprintf("service-%d", az.Config.MaximumLoadBalancerRuleCount+1) var svc v1.Service if isInternal { svc = getInternalTestService(svcName, 8081) addTestSubnet(t, az, &svc) } else { svc = getTestService(svcName, v1.ProtocolTCP, 8081) } _, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) if err == nil { t.Errorf("Expect any new service to fail as max limit in lb has reached") } else { expectedErrMessageSubString := "all available load balancers have exceeded maximum rule limit" if !strings.Contains(err.Error(), expectedErrMessageSubString) { t.Errorf("Error message returned is not expected, expected sub string=%s, actual error message=%v", expectedErrMessageSubString, err) } } } // Validate service deletion in lb auto selection mode func testLoadBalancerServiceAutoModeDeleteSelection(t *testing.T, isInternal bool) { az := getTestCloud() const vmCount = 8 const availabilitySetCount = 4 const serviceCount = 9 clusterResources := getClusterResources(az, vmCount, availabilitySetCount) getTestSecurityGroup(az) for index := 1; index <= serviceCount; index++ { svcName := fmt.Sprintf("service-%d", index) var svc v1.Service if isInternal { svc = getInternalTestService(svcName, 8081) addTestSubnet(t, az, &svc) } else { svc = getTestService(svcName, v1.ProtocolTCP, 8081) } setLoadBalancerAutoModeAnnotation(&svc) lbStatus, err := az.EnsureLoadBalancer(testClusterName, &svc, clusterResources.nodes) if err != nil { t.Errorf("Unexpected error: %q", err) } if lbStatus == nil { t.Errorf("Unexpected error: %s", svcName) } } for index := serviceCount; index >= 1; index-- { svcName := fmt.Sprintf("service-%d", index) var svc v1.Service if isInternal { svc = getInternalTestService(svcName, 8081) addTestSubnet(t, az, &svc) } else { svc = getTestService(svcName, v1.ProtocolTCP, 8081) } setLoadBalancerAutoModeAnnotation(&svc) // expected is MIN(index, availabilitySetCount) expectedNumOfLB := int(math.Min(float64(index), float64(availabilitySetCount))) result, _ := az.LoadBalancerClient.List(az.Config.ResourceGroup) lbCount := len(*result.Value) if lbCount != expectedNumOfLB { t.Errorf("Unexpected number of LB's: Expected (%d) Found (%d)", expectedNumOfLB, lbCount) } err := az.EnsureLoadBalancerDeleted(testClusterName, &svc) if err != nil { t.Errorf("Unexpected error: %q", err) } } } // Test addition of a new service on an internal LB with a subnet. func TestReconcileLoadBalancerAddServiceOnInternalSubnet(t *testing.T) { az := getTestCloud() clusterResources := getClusterResources(az, 1, 1) svc := getInternalTestService("servicea", 80) addTestSubnet(t, az, &svc) lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } // ensure we got a frontend ip configuration if len(*lb.FrontendIPConfigurations) != 1 { t.Error("Expected the loadbalancer to have a frontend ip configuration") } validateLoadBalancer(t, lb, svc) } // Test addition of services on an internal LB using both default and explicit subnets. func TestReconcileLoadBalancerAddServicesOnMultipleSubnets(t *testing.T) { az := getTestCloud() clusterResources := getClusterResources(az, 1, 1) svc1 := getTestService("service1", v1.ProtocolTCP, 8081) svc2 := getInternalTestService("service2", 8081) // Internal and External service cannot reside on the same LB resource addTestSubnet(t, az, &svc2) // svc1 is using LB without "-internal" suffix lb, err := az.reconcileLoadBalancer(testClusterName, &svc1, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error reconciling svc1: %q", err) } // ensure we got a frontend ip configuration for each service if len(*lb.FrontendIPConfigurations) != 1 { t.Error("Expected the loadbalancer to have 1 frontend ip configurations") } validateLoadBalancer(t, lb, svc1) // svc2 is using LB with "-internal" suffix lb, err = az.reconcileLoadBalancer(testClusterName, &svc2, nil, true /* wantLb */) if err != nil { t.Errorf("Unexpected error reconciling svc2: %q", err) } // ensure we got a frontend ip configuration for each service if len(*lb.FrontendIPConfigurations) != 1 { t.Error("Expected the loadbalancer to have 1 frontend ip configurations") } validateLoadBalancer(t, lb, svc2) } // Test moving a service exposure from one subnet to another. func TestReconcileLoadBalancerEditServiceSubnet(t *testing.T) { az := getTestCloud() clusterResources := getClusterResources(az, 1, 1) svc := getInternalTestService("service1", 8081) addTestSubnet(t, az, &svc) lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error reconciling initial svc: %q", err) } validateLoadBalancer(t, lb, svc) svc.Annotations[ServiceAnnotationLoadBalancerInternalSubnet] = "NewSubnet" addTestSubnet(t, az, &svc) lb, err = az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error reconciling edits to svc: %q", err) } // ensure we got a frontend ip configuration for the service if len(*lb.FrontendIPConfigurations) != 1 { t.Error("Expected the loadbalancer to have 1 frontend ip configuration") } validateLoadBalancer(t, lb, svc) } func TestReconcileLoadBalancerNodeHealth(t *testing.T) { az := getTestCloud() clusterResources := getClusterResources(az, 1, 1) svc := getTestService("servicea", v1.ProtocolTCP, 80) svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal svc.Spec.HealthCheckNodePort = int32(32456) lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } // ensure we got a frontend ip configuration if len(*lb.FrontendIPConfigurations) != 1 { t.Error("Expected the loadbalancer to have a frontend ip configuration") } validateLoadBalancer(t, lb, svc) } // Test removing all services results in removing the frontend ip configuration func TestReconcileLoadBalancerRemoveService(t *testing.T) { az := getTestCloud() clusterResources := getClusterResources(az, 1, 1) svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } lb, err = az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, false /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } // ensure we abandoned the frontend ip configuration if len(*lb.FrontendIPConfigurations) != 0 { t.Error("Expected the loadbalancer to have no frontend ip configuration") } validateLoadBalancer(t, lb) } // Test removing all service ports results in removing the frontend ip configuration func TestReconcileLoadBalancerRemoveAllPortsRemovesFrontendConfig(t *testing.T) { az := getTestCloud() clusterResources := getClusterResources(az, 1, 1) svc := getTestService("servicea", v1.ProtocolTCP, 80) lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validateLoadBalancer(t, lb, svc) svcUpdated := getTestService("servicea", v1.ProtocolTCP) lb, err = az.reconcileLoadBalancer(testClusterName, &svcUpdated, clusterResources.nodes, false /* wantLb*/) if err != nil { t.Errorf("Unexpected error: %q", err) } // ensure we abandoned the frontend ip configuration if len(*lb.FrontendIPConfigurations) != 0 { t.Error("Expected the loadbalancer to have no frontend ip configuration") } validateLoadBalancer(t, lb, svcUpdated) } // Test removal of a port from an existing service. func TestReconcileLoadBalancerRemovesPort(t *testing.T) { az := getTestCloud() clusterResources := getClusterResources(az, 1, 1) svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) lb, err := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } svcUpdated := getTestService("servicea", v1.ProtocolTCP, 80) lb, err = az.reconcileLoadBalancer(testClusterName, &svcUpdated, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validateLoadBalancer(t, lb, svcUpdated) } // Test reconciliation of multiple services on same port func TestReconcileLoadBalancerMultipleServices(t *testing.T) { az := getTestCloud() clusterResources := getClusterResources(az, 1, 1) svc1 := getTestService("servicea", v1.ProtocolTCP, 80, 443) svc2 := getTestService("serviceb", v1.ProtocolTCP, 80) updatedLoadBalancer, err := az.reconcileLoadBalancer(testClusterName, &svc1, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } updatedLoadBalancer, err = az.reconcileLoadBalancer(testClusterName, &svc2, clusterResources.nodes, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validateLoadBalancer(t, updatedLoadBalancer, svc1, svc2) } func findLBRuleForPort(lbRules []network.LoadBalancingRule, port int32) (network.LoadBalancingRule, error) { for _, lbRule := range lbRules { if *lbRule.FrontendPort == port { return lbRule, nil } } return network.LoadBalancingRule{}, fmt.Errorf("Expected LB rule with port %d but none found", port) } func TestServiceDefaultsToNoSessionPersistence(t *testing.T) { az := getTestCloud() svc := getTestService("service-sa-omitted", v1.ProtocolTCP, 7170) configProperties := getTestPublicFipConfigurationProperties() lb := getTestLoadBalancer() nodes := []*v1.Node{} lb, _, err := az.reconcileLoadBalancer(lb, &configProperties, testClusterName, &svc, nodes) if err != nil { t.Errorf("Unexpected error reconciling svc1: %q", err) } validateLoadBalancer(t, lb, svc) lbRule, err := findLBRuleForPort(*lb.LoadBalancingRules, 7170) if err != nil { t.Error(err) } if lbRule.LoadDistribution != network.Default { t.Errorf("Expected LB rule to have default load distribution but was %s", lbRule.LoadDistribution) } } func TestServiceRespectsNoSessionAffinity(t *testing.T) { az := getTestCloud() svc := getTestService("service-sa-none", v1.ProtocolTCP, 7170) svc.Spec.SessionAffinity = v1.ServiceAffinityNone configProperties := getTestPublicFipConfigurationProperties() lb := getTestLoadBalancer() nodes := []*v1.Node{} lb, _, err := az.reconcileLoadBalancer(lb, &configProperties, testClusterName, &svc, nodes) if err != nil { t.Errorf("Unexpected error reconciling svc1: %q", err) } validateLoadBalancer(t, lb, svc) lbRule, err := findLBRuleForPort(*lb.LoadBalancingRules, 7170) if err != nil { t.Error(err) } if lbRule.LoadDistribution != network.Default { t.Errorf("Expected LB rule to have default load distribution but was %s", lbRule.LoadDistribution) } } func TestServiceRespectsClientIPSessionAffinity(t *testing.T) { az := getTestCloud() svc := getTestService("service-sa-clientip", v1.ProtocolTCP, 7170) svc.Spec.SessionAffinity = v1.ServiceAffinityClientIP configProperties := getTestPublicFipConfigurationProperties() lb := getTestLoadBalancer() nodes := []*v1.Node{} lb, _, err := az.reconcileLoadBalancer(lb, &configProperties, testClusterName, &svc, nodes) if err != nil { t.Errorf("Unexpected error reconciling svc1: %q", err) } validateLoadBalancer(t, lb, svc) lbRule, err := findLBRuleForPort(*lb.LoadBalancingRules, 7170) if err != nil { t.Error(err) } if lbRule.LoadDistribution != network.SourceIP { t.Errorf("Expected LB rule to have SourceIP load distribution but was %s", lbRule.LoadDistribution) } } func TestReconcileSecurityGroupNewServiceAddsPort(t *testing.T) { az := getTestCloud() getTestSecurityGroup(az) svc1 := getTestService("servicea", v1.ProtocolTCP, 80) clusterResources := getClusterResources(az, 1, 1) lb, _ := az.reconcileLoadBalancer(testClusterName, &svc1, clusterResources.nodes, true) lbStatus, _ := az.getServiceLoadBalancerStatus(&svc1, lb) sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, lbStatus, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validateSecurityGroup(t, sg, svc1) } func TestReconcileSecurityGroupNewInternalServiceAddsPort(t *testing.T) { az := getTestCloud() getTestSecurityGroup(az) svc1 := getInternalTestService("serviceea", 80) addTestSubnet(t, az, &svc1) clusterResources := getClusterResources(az, 1, 1) lb, _ := az.reconcileLoadBalancer(testClusterName, &svc1, clusterResources.nodes, true) lbStatus, _ := az.getServiceLoadBalancerStatus(&svc1, lb) sg, err := az.reconcileSecurityGroup(testClusterName, &svc1, lbStatus, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validateSecurityGroup(t, sg, svc1) } func TestReconcileSecurityGroupRemoveService(t *testing.T) { az := getTestCloud() service1 := getTestService("servicea", v1.ProtocolTCP, 81) service2 := getTestService("serviceb", v1.ProtocolTCP, 82) clusterResources := getClusterResources(az, 1, 1) lb, _ := az.reconcileLoadBalancer(testClusterName, &service1, clusterResources.nodes, true) az.reconcileLoadBalancer(testClusterName, &service2, clusterResources.nodes, true) lbStatus, _ := az.getServiceLoadBalancerStatus(&service1, lb) sg := getTestSecurityGroup(az, service1, service2) validateSecurityGroup(t, sg, service1, service2) sg, err := az.reconcileSecurityGroup(testClusterName, &service1, lbStatus, false /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validateSecurityGroup(t, sg, service2) } func TestReconcileSecurityGroupRemoveServiceRemovesPort(t *testing.T) { az := getTestCloud() svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) clusterResources := getClusterResources(az, 1, 1) sg := getTestSecurityGroup(az, svc) svcUpdated := getTestService("servicea", v1.ProtocolTCP, 80) lb, _ := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true) lbStatus, _ := az.getServiceLoadBalancerStatus(&svc, lb) sg, err := az.reconcileSecurityGroup(testClusterName, &svcUpdated, lbStatus, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validateSecurityGroup(t, sg, svcUpdated) } func TestReconcileSecurityWithSourceRanges(t *testing.T) { az := getTestCloud() svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) svc.Spec.LoadBalancerSourceRanges = []string{ "192.168.0.0/24", "10.0.0.0/32", } clusterResources := getClusterResources(az, 1, 1) sg := getTestSecurityGroup(az, svc) lb, _ := az.reconcileLoadBalancer(testClusterName, &svc, clusterResources.nodes, true) lbStatus, _ := az.getServiceLoadBalancerStatus(&svc, lb) sg, err := az.reconcileSecurityGroup(testClusterName, &svc, lbStatus, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validateSecurityGroup(t, sg, svc) } func TestReconcilePublicIPWithNewService(t *testing.T) { az := getTestCloud() svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) pip, err := az.reconcilePublicIP(testClusterName, &svc, true /* wantLb*/) if err != nil { t.Errorf("Unexpected error: %q", err) } validatePublicIP(t, pip, &svc, true) pip2, err := az.reconcilePublicIP(testClusterName, &svc, true /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validatePublicIP(t, pip2, &svc, true) if pip.Name != pip2.Name || pip.PublicIPAddressPropertiesFormat.IPAddress != pip2.PublicIPAddressPropertiesFormat.IPAddress { t.Errorf("We should get the exact same public ip resource after a second reconcile") } } func TestReconcilePublicIPRemoveService(t *testing.T) { az := getTestCloud() svc := getTestService("servicea", v1.ProtocolTCP, 80, 443) pip, err := az.reconcilePublicIP(testClusterName, &svc, true /* wantLb*/) if err != nil { t.Errorf("Unexpected error: %q", err) } validatePublicIP(t, pip, &svc, true) // Remove the service pip, err = az.reconcilePublicIP(testClusterName, &svc, false /* wantLb */) if err != nil { t.Errorf("Unexpected error: %q", err) } validatePublicIP(t, pip, &svc, false) } func TestReconcilePublicIPWithInternalService(t *testing.T) { az := getTestCloud() svc := getInternalTestService("servicea", 80, 443) pip, err := az.reconcilePublicIP(testClusterName, &svc, true /* wantLb*/) if err != nil { t.Errorf("Unexpected error: %q", err) } validatePublicIP(t, pip, &svc, true) } func TestReconcilePublicIPWithExternalAndInternalSwitch(t *testing.T) { az := getTestCloud() svc := getInternalTestService("servicea", 80, 443) pip, err := az.reconcilePublicIP(testClusterName, &svc, true /* wantLb*/) if err != nil { t.Errorf("Unexpected error: %q", err) } validatePublicIP(t, pip, &svc, true) // Update to external service svcUpdated := getTestService("servicea", v1.ProtocolTCP, 80) pip, err = az.reconcilePublicIP(testClusterName, &svcUpdated, true /* wantLb*/) if err != nil { t.Errorf("Unexpected error: %q", err) } validatePublicIP(t, pip, &svcUpdated, true) // Update to internal service again pip, err = az.reconcilePublicIP(testClusterName, &svc, true /* wantLb*/) if err != nil { t.Errorf("Unexpected error: %q", err) } validatePublicIP(t, pip, &svc, true) } func getTestCloud() (az *Cloud) { az = &Cloud{ Config: Config{ TenantID: "tenant", SubscriptionID: "subscription", ResourceGroup: "rg", VnetResourceGroup: "rg", Location: "westus", VnetName: "vnet", SubnetName: "subnet", SecurityGroupName: "nsg", RouteTableName: "rt", PrimaryAvailabilitySetName: "as", MaximumLoadBalancerRuleCount: 250, }, } az.operationPollRateLimiter = flowcontrol.NewTokenBucketRateLimiter(100, 100) az.LoadBalancerClient = newFakeAzureLBClient() az.PublicIPAddressesClient = newFakeAzurePIPClient(az.Config.SubscriptionID) az.SubnetsClient = newFakeAzureSubnetsClient() az.SecurityGroupsClient = newFakeAzureNSGClient() az.VirtualMachinesClient = newFakeAzureVirtualMachinesClient() az.InterfacesClient = newFakeAzureInterfacesClient() return az } const networkInterfacesIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkInterfaces/%s" const primaryIPConfigIDTemplate = "%s/ipConfigurations/ipconfig" // returns the full identifier of Network Interface. func getNetworkInterfaceID(subscriptionID string, resourceGroupName, nicName string) string { return fmt.Sprintf( networkInterfacesIDTemplate, subscriptionID, resourceGroupName, nicName) } // returns the full identifier of a private ipconfig of the nic func getPrimaryIPConfigID(nicID string) string { return fmt.Sprintf( primaryIPConfigIDTemplate, nicID) } const TestResourceNameFormat = "%s-%d" const TestVMResourceBaseName = "vm" const TestASResourceBaseName = "as" func getTestResourceName(resourceBaseName string, index int) string { return fmt.Sprintf(TestResourceNameFormat, resourceBaseName, index) } func getVMName(vmIndex int) string { return getTestResourceName(TestVMResourceBaseName, vmIndex) } func getAvailabilitySetName(az *Cloud, vmIndex int, numAS int) string { asIndex := vmIndex % numAS if asIndex == 0 { return az.Config.PrimaryAvailabilitySetName } return getTestResourceName(TestASResourceBaseName, asIndex) } // test supporting on 1 nic per vm // we really dont care about the name of the nic // just using the vm name for testing purposes func getNICName(vmIndex int) string { return getVMName(vmIndex) } type ClusterResources struct { nodes []*v1.Node availabilitySetNames []string } func getClusterResources(az *Cloud, vmCount int, availabilitySetCount int) (clusterResources *ClusterResources) { if vmCount < availabilitySetCount { return nil } clusterResources = &ClusterResources{} clusterResources.nodes = []*v1.Node{} clusterResources.availabilitySetNames = []string{} for vmIndex := 0; vmIndex < vmCount; vmIndex++ { vmName := getVMName(vmIndex) asName := getAvailabilitySetName(az, vmIndex, availabilitySetCount) clusterResources.availabilitySetNames = append(clusterResources.availabilitySetNames, asName) nicName := getNICName(vmIndex) nicID := getNetworkInterfaceID(az.Config.SubscriptionID, az.Config.ResourceGroup, nicName) primaryIPConfigID := getPrimaryIPConfigID(nicID) isPrimary := true newNIC := network.Interface{ ID: &nicID, Name: &nicName, InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ IPConfigurations: &[]network.InterfaceIPConfiguration{ { ID: &primaryIPConfigID, InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{ PrivateIPAddress: &nicName, Primary: &isPrimary, }, }, }, }, } az.InterfacesClient.CreateOrUpdate(az.Config.ResourceGroup, nicName, newNIC, nil) // create vm asID := az.getAvailabilitySetID(asName) newVM := compute.VirtualMachine{ Name: &vmName, Location: &az.Config.Location, VirtualMachineProperties: &compute.VirtualMachineProperties{ AvailabilitySet: &compute.SubResource{ ID: &asID, }, NetworkProfile: &compute.NetworkProfile{ NetworkInterfaces: &[]compute.NetworkInterfaceReference{ { ID: &nicID, }, }, }, }, } _, errChan := az.VirtualMachinesClient.CreateOrUpdate(az.Config.ResourceGroup, vmName, newVM, nil) if err := <-errChan; err != nil { } // add to kubernetes newNode := &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: vmName, Labels: map[string]string{ kubeletapis.LabelHostname: vmName, }, }, } clusterResources.nodes = append(clusterResources.nodes, newNode) } return clusterResources } func getBackendPort(port int32) int32 { return port + 10000 } func getTestPublicFipConfigurationProperties() network.FrontendIPConfigurationPropertiesFormat { return network.FrontendIPConfigurationPropertiesFormat{ PublicIPAddress: &network.PublicIPAddress{ID: to.StringPtr("/this/is/a/public/ip/address/id")}, } } func getTestInternalFipConfigurationProperties(expectedSubnetName *string) network.FrontendIPConfigurationPropertiesFormat { var expectedSubnet *network.Subnet if expectedSubnetName != nil { expectedSubnet = &network.Subnet{Name: expectedSubnetName} } return network.FrontendIPConfigurationPropertiesFormat{ PublicIPAddress: &network.PublicIPAddress{ID: to.StringPtr("/this/is/a/public/ip/address/id")}, Subnet: expectedSubnet, } } func getTestService(identifier string, proto v1.Protocol, requestedPorts ...int32) v1.Service { ports := []v1.ServicePort{} for _, port := range requestedPorts { ports = append(ports, v1.ServicePort{ Name: fmt.Sprintf("port-tcp-%d", port), Protocol: proto, Port: port, NodePort: getBackendPort(port), }) } svc := v1.Service{ Spec: v1.ServiceSpec{ Type: v1.ServiceTypeLoadBalancer, Ports: ports, }, } svc.Name = identifier svc.Namespace = "default" svc.UID = types.UID(identifier) svc.Annotations = make(map[string]string) return svc } func getInternalTestService(identifier string, requestedPorts ...int32) v1.Service { svc := getTestService(identifier, v1.ProtocolTCP, requestedPorts...) svc.Annotations[ServiceAnnotationLoadBalancerInternal] = "true" return svc } func setLoadBalancerModeAnnotation(service *v1.Service, lbMode string) { service.Annotations[ServiceAnnotationLoadBalancerMode] = lbMode } func setLoadBalancerAutoModeAnnotation(service *v1.Service) { setLoadBalancerModeAnnotation(service, ServiceAnnotationLoadBalancerAutoModeValue) } func getTestLoadBalancer(services ...v1.Service) network.LoadBalancer { rules := []network.LoadBalancingRule{} probes := []network.Probe{} for _, service := range services { for _, port := range service.Spec.Ports { ruleName := getLoadBalancerRuleName(&service, port, nil) rules = append(rules, network.LoadBalancingRule{ Name: to.StringPtr(ruleName), LoadBalancingRulePropertiesFormat: &network.LoadBalancingRulePropertiesFormat{ FrontendPort: to.Int32Ptr(port.Port), BackendPort: to.Int32Ptr(port.Port), }, }) probes = append(probes, network.Probe{ Name: to.StringPtr(ruleName), ProbePropertiesFormat: &network.ProbePropertiesFormat{ Port: to.Int32Ptr(port.NodePort), }, }) } } lb := network.LoadBalancer{ LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{ LoadBalancingRules: &rules, Probes: &probes, }, } return lb } func getServiceSourceRanges(service *v1.Service) []string { if len(service.Spec.LoadBalancerSourceRanges) == 0 { if !requiresInternalLoadBalancer(service) { return []string{"Internet"} } } return service.Spec.LoadBalancerSourceRanges } func getTestSecurityGroup(az *Cloud, services ...v1.Service) *network.SecurityGroup { rules := []network.SecurityRule{} for _, service := range services { for _, port := range service.Spec.Ports { sources := getServiceSourceRanges(&service) for _, src := range sources { ruleName := getSecurityRuleName(&service, port, src) rules = append(rules, network.SecurityRule{ Name: to.StringPtr(ruleName), SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{ SourceAddressPrefix: to.StringPtr(src), DestinationPortRange: to.StringPtr(fmt.Sprintf("%d", port.Port)), }, }) } } } sg := network.SecurityGroup{ Name: &az.SecurityGroupName, SecurityGroupPropertiesFormat: &network.SecurityGroupPropertiesFormat{ SecurityRules: &rules, }, } az.SecurityGroupsClient.CreateOrUpdate( az.ResourceGroup, az.SecurityGroupName, sg, nil) return &sg } func validateLoadBalancer(t *testing.T, loadBalancer *network.LoadBalancer, services ...v1.Service) { expectedRuleCount := 0 expectedFrontendIPCount := 0 expectedProbeCount := 0 expectedFrontendIPs := []ExpectedFrontendIPInfo{} for _, svc := range services { if len(svc.Spec.Ports) > 0 { expectedFrontendIPCount++ expectedFrontendIP := ExpectedFrontendIPInfo{ Name: getFrontendIPConfigName(&svc, subnet(&svc)), Subnet: subnet(&svc), } expectedFrontendIPs = append(expectedFrontendIPs, expectedFrontendIP) } for _, wantedRule := range svc.Spec.Ports { expectedRuleCount++ wantedRuleName := getLoadBalancerRuleName(&svc, wantedRule, subnet(&svc)) foundRule := false for _, actualRule := range *loadBalancer.LoadBalancingRules { if strings.EqualFold(*actualRule.Name, wantedRuleName) && *actualRule.FrontendPort == wantedRule.Port && *actualRule.BackendPort == wantedRule.Port { foundRule = true break } } if !foundRule { t.Errorf("Expected load balancer rule but didn't find it: %q", wantedRuleName) } // if UDP rule, there is no probe if wantedRule.Protocol == v1.ProtocolUDP { continue } expectedProbeCount++ foundProbe := false if serviceapi.NeedsHealthCheck(&svc) { path, port := serviceapi.GetServiceHealthCheckPathPort(&svc) for _, actualProbe := range *loadBalancer.Probes { if strings.EqualFold(*actualProbe.Name, wantedRuleName) && *actualProbe.Port == port && *actualProbe.RequestPath == path && actualProbe.Protocol == network.ProbeProtocolHTTP { foundProbe = true break } } } else { for _, actualProbe := range *loadBalancer.Probes { if strings.EqualFold(*actualProbe.Name, wantedRuleName) && *actualProbe.Port == wantedRule.NodePort { foundProbe = true break } } } if !foundProbe { for _, actualProbe := range *loadBalancer.Probes { t.Logf("Probe: %s %d", *actualProbe.Name, *actualProbe.Port) } t.Errorf("Expected loadbalancer probe but didn't find it: %q", wantedRuleName) } } } frontendIPCount := len(*loadBalancer.FrontendIPConfigurations) if frontendIPCount != expectedFrontendIPCount { t.Errorf("Expected the loadbalancer to have %d frontend IPs. Found %d.\n%v", expectedFrontendIPCount, frontendIPCount, loadBalancer.FrontendIPConfigurations) } frontendIPs := *loadBalancer.FrontendIPConfigurations for _, expectedFrontendIP := range expectedFrontendIPs { if !expectedFrontendIP.existsIn(frontendIPs) { t.Errorf("Expected the loadbalancer to have frontend IP %s/%s. Found %s", expectedFrontendIP.Name, to.String(expectedFrontendIP.Subnet), describeFIPs(frontendIPs)) } } lenRules := len(*loadBalancer.LoadBalancingRules) if lenRules != expectedRuleCount { t.Errorf("Expected the loadbalancer to have %d rules. Found %d.\n%v", expectedRuleCount, lenRules, loadBalancer.LoadBalancingRules) } lenProbes := len(*loadBalancer.Probes) if lenProbes != expectedProbeCount { t.Errorf("Expected the loadbalancer to have %d probes. Found %d.", expectedRuleCount, lenProbes) } } type ExpectedFrontendIPInfo struct { Name string Subnet *string } func (expected ExpectedFrontendIPInfo) matches(frontendIP network.FrontendIPConfiguration) bool { return strings.EqualFold(expected.Name, to.String(frontendIP.Name)) && strings.EqualFold(to.String(expected.Subnet), to.String(subnetName(frontendIP))) } func (expected ExpectedFrontendIPInfo) existsIn(frontendIPs []network.FrontendIPConfiguration) bool { for _, fip := range frontendIPs { if expected.matches(fip) { return true } } return false } func subnetName(frontendIP network.FrontendIPConfiguration) *string { if frontendIP.Subnet != nil { return frontendIP.Subnet.Name } return nil } func describeFIPs(frontendIPs []network.FrontendIPConfiguration) string { description := "" for _, actualFIP := range frontendIPs { actualSubnetName := "" if actualFIP.Subnet != nil { actualSubnetName = to.String(actualFIP.Subnet.Name) } actualFIPText := fmt.Sprintf("%s/%s ", to.String(actualFIP.Name), actualSubnetName) description = description + actualFIPText } return description } func validatePublicIP(t *testing.T, publicIP *network.PublicIPAddress, service *v1.Service, wantLb bool) { isInternal := requiresInternalLoadBalancer(service) if isInternal || !wantLb { if publicIP != nil { t.Errorf("Expected publicIP resource to be nil, when it is an internal service or doesn't want LB") } return } // For external service if publicIP == nil { t.Errorf("Expected publicIP resource exists, when it is not an internal service") } if publicIP.Tags == nil || (*publicIP.Tags)["service"] == nil { t.Errorf("Expected publicIP resource has tags[service]") } serviceName := getServiceName(service) if serviceName != *(*publicIP.Tags)["service"] { t.Errorf("Expected publicIP resource has matching tags[service]") } // We cannot use service.Spec.LoadBalancerIP to compare with // Public IP's IPAddress // Becuase service properties are updated outside of cloudprovider code } func validateSecurityGroup(t *testing.T, securityGroup *network.SecurityGroup, services ...v1.Service) { expectedRuleCount := 0 for _, svc := range services { for _, wantedRule := range svc.Spec.Ports { sources := getServiceSourceRanges(&svc) for _, source := range sources { wantedRuleName := getSecurityRuleName(&svc, wantedRule, source) expectedRuleCount++ foundRule := false for _, actualRule := range *securityGroup.SecurityRules { if strings.EqualFold(*actualRule.Name, wantedRuleName) && *actualRule.SourceAddressPrefix == source && *actualRule.DestinationPortRange == fmt.Sprintf("%d", wantedRule.Port) { foundRule = true break } } if !foundRule { t.Errorf("Expected security group rule but didn't find it: %q", wantedRuleName) } } } } lenRules := len(*securityGroup.SecurityRules) if lenRules != expectedRuleCount { t.Errorf("Expected the loadbalancer to have %d rules. Found %d.\n", expectedRuleCount, lenRules) } } func TestSecurityRulePriorityPicksNextAvailablePriority(t *testing.T) { rules := []network.SecurityRule{} var expectedPriority int32 = loadBalancerMinimumPriority + 50 var i int32 for i = loadBalancerMinimumPriority; i < expectedPriority; i++ { rules = append(rules, network.SecurityRule{ SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{ Priority: to.Int32Ptr(i), }, }) } priority, err := getNextAvailablePriority(rules) if err != nil { t.Errorf("Unexpectected error: %q", err) } if priority != expectedPriority { t.Errorf("Expected priority %d. Got priority %d.", expectedPriority, priority) } } func TestSecurityRulePriorityFailsIfExhausted(t *testing.T) { rules := []network.SecurityRule{} var i int32 for i = loadBalancerMinimumPriority; i < loadBalancerMaximumPriority; i++ { rules = append(rules, network.SecurityRule{ SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{ Priority: to.Int32Ptr(i), }, }) } _, err := getNextAvailablePriority(rules) if err == nil { t.Error("Expectected an error. There are no priority levels left.") } } func TestProtocolTranslationTCP(t *testing.T) { proto := v1.ProtocolTCP transportProto, securityGroupProto, probeProto, err := getProtocolsFromKubernetesProtocol(proto) if err != nil { t.Error(err) } if *transportProto != network.TransportProtocolTCP { t.Errorf("Expected TCP LoadBalancer Rule Protocol. Got %v", transportProto) } if *securityGroupProto != network.SecurityRuleProtocolTCP { t.Errorf("Expected TCP SecurityGroup Protocol. Got %v", transportProto) } if *probeProto != network.ProbeProtocolTCP { t.Errorf("Expected TCP LoadBalancer Probe Protocol. Got %v", transportProto) } } func TestProtocolTranslationUDP(t *testing.T) { proto := v1.ProtocolUDP transportProto, securityGroupProto, probeProto, _ := getProtocolsFromKubernetesProtocol(proto) if *transportProto != network.TransportProtocolUDP { t.Errorf("Expected UDP LoadBalancer Rule Protocol. Got %v", transportProto) } if *securityGroupProto != network.SecurityRuleProtocolUDP { t.Errorf("Expected UDP SecurityGroup Protocol. Got %v", transportProto) } if probeProto != nil { t.Errorf("Expected UDP LoadBalancer Probe Protocol. Got %v", transportProto) } } // Test Configuration deserialization (json) func TestNewCloudFromJSON(t *testing.T) { config := `{ "tenantId": "--tenant-id--", "subscriptionId": "--subscription-id--", "aadClientId": "--aad-client-id--", "aadClientSecret": "--aad-client-secret--", "aadClientCertPath": "--aad-client-cert-path--", "aadClientCertPassword": "--aad-client-cert-password--", "resourceGroup": "--resource-group--", "location": "--location--", "subnetName": "--subnet-name--", "securityGroupName": "--security-group-name--", "vnetName": "--vnet-name--", "routeTableName": "--route-table-name--", "primaryAvailabilitySetName": "--primary-availability-set-name--", "cloudProviderBackoff": true, "cloudProviderRatelimit": true, "cloudProviderRateLimitQPS": 0.5, "cloudProviderRateLimitBucket": 5 }` validateConfig(t, config) } // Test Backoff and Rate Limit defaults (json) func TestCloudDefaultConfigFromJSON(t *testing.T) { config := `{ "aadClientId": "--aad-client-id--", "aadClientSecret": "--aad-client-secret--" }` validateEmptyConfig(t, config) } // Test Backoff and Rate Limit defaults (yaml) func TestCloudDefaultConfigFromYAML(t *testing.T) { config := ` aadClientId: --aad-client-id-- aadClientSecret: --aad-client-secret-- ` validateEmptyConfig(t, config) } // Test Configuration deserialization (yaml) func TestNewCloudFromYAML(t *testing.T) { config := ` tenantId: --tenant-id-- subscriptionId: --subscription-id-- aadClientId: --aad-client-id-- aadClientSecret: --aad-client-secret-- aadClientCertPath: --aad-client-cert-path-- aadClientCertPassword: --aad-client-cert-password-- resourceGroup: --resource-group-- location: --location-- subnetName: --subnet-name-- securityGroupName: --security-group-name-- vnetName: --vnet-name-- routeTableName: --route-table-name-- primaryAvailabilitySetName: --primary-availability-set-name-- cloudProviderBackoff: true cloudProviderBackoffRetries: 6 cloudProviderBackoffExponent: 1.5 cloudProviderBackoffDuration: 5 cloudProviderBackoffJitter: 1.0 cloudProviderRatelimit: true cloudProviderRateLimitQPS: 0.5 cloudProviderRateLimitBucket: 5 ` validateConfig(t, config) } func validateConfig(t *testing.T, config string) { azureCloud := getCloudFromConfig(t, config) if azureCloud.TenantID != "--tenant-id--" { t.Errorf("got incorrect value for TenantID") } if azureCloud.SubscriptionID != "--subscription-id--" { t.Errorf("got incorrect value for SubscriptionID") } if azureCloud.AADClientID != "--aad-client-id--" { t.Errorf("got incorrect value for AADClientID") } if azureCloud.AADClientSecret != "--aad-client-secret--" { t.Errorf("got incorrect value for AADClientSecret") } if azureCloud.AADClientCertPath != "--aad-client-cert-path--" { t.Errorf("got incorrect value for AADClientCertPath") } if azureCloud.AADClientCertPassword != "--aad-client-cert-password--" { t.Errorf("got incorrect value for AADClientCertPassword") } if azureCloud.ResourceGroup != "--resource-group--" { t.Errorf("got incorrect value for ResourceGroup") } if azureCloud.Location != "--location--" { t.Errorf("got incorrect value for Location") } if azureCloud.SubnetName != "--subnet-name--" { t.Errorf("got incorrect value for SubnetName") } if azureCloud.SecurityGroupName != "--security-group-name--" { t.Errorf("got incorrect value for SecurityGroupName") } if azureCloud.VnetName != "--vnet-name--" { t.Errorf("got incorrect value for VnetName") } if azureCloud.RouteTableName != "--route-table-name--" { t.Errorf("got incorrect value for RouteTableName") } if azureCloud.PrimaryAvailabilitySetName != "--primary-availability-set-name--" { t.Errorf("got incorrect value for PrimaryAvailabilitySetName") } if azureCloud.CloudProviderBackoff != true { t.Errorf("got incorrect value for CloudProviderBackoff") } if azureCloud.CloudProviderBackoffRetries != 6 { t.Errorf("got incorrect value for CloudProviderBackoffRetries") } if azureCloud.CloudProviderBackoffExponent != 1.5 { t.Errorf("got incorrect value for CloudProviderBackoffExponent") } if azureCloud.CloudProviderBackoffDuration != 5 { t.Errorf("got incorrect value for CloudProviderBackoffDuration") } if azureCloud.CloudProviderBackoffJitter != 1.0 { t.Errorf("got incorrect value for CloudProviderBackoffJitter") } if azureCloud.CloudProviderRateLimit != true { t.Errorf("got incorrect value for CloudProviderRateLimit") } if azureCloud.CloudProviderRateLimitQPS != 0.5 { t.Errorf("got incorrect value for CloudProviderRateLimitQPS") } if azureCloud.CloudProviderRateLimitBucket != 5 { t.Errorf("got incorrect value for CloudProviderRateLimitBucket") } } func getCloudFromConfig(t *testing.T, config string) *Cloud { configReader := strings.NewReader(config) cloud, err := NewCloud(configReader) if err != nil { t.Error(err) } azureCloud, ok := cloud.(*Cloud) if !ok { t.Error("NewCloud returned incorrect type") } return azureCloud } // TODO include checks for other appropriate default config parameters func validateEmptyConfig(t *testing.T, config string) { azureCloud := getCloudFromConfig(t, config) // backoff should be disabled by default if not explicitly enabled in config if azureCloud.CloudProviderBackoff != false { t.Errorf("got incorrect value for CloudProviderBackoff") } // rate limits should be disabled by default if not explicitly enabled in config if azureCloud.CloudProviderRateLimit != false { t.Errorf("got incorrect value for CloudProviderRateLimit") } } func TestDecodeInstanceInfo(t *testing.T) { response := `{"ID":"_azdev","UD":"0","FD":"99"}` faultDomain, err := readFaultDomain(strings.NewReader(response)) if err != nil { t.Error("Unexpected error in ReadFaultDomain") } if faultDomain == nil { t.Error("Fault domain was unexpectedly nil") } if *faultDomain != "99" { t.Error("got incorrect fault domain") } } func TestSplitProviderID(t *testing.T) { providers := []struct { providerID string name types.NodeName fail bool }{ { providerID: CloudProviderName + ":///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroupName/providers/Microsoft.Compute/virtualMachines/k8s-agent-AAAAAAAA-0", name: "k8s-agent-AAAAAAAA-0", fail: false, }, { providerID: CloudProviderName + ":/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroupName/providers/Microsoft.Compute/virtualMachines/k8s-agent-AAAAAAAA-0", name: "", fail: true, }, { providerID: CloudProviderName + "://", name: "", fail: true, }, { providerID: ":///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroupName/providers/Microsoft.Compute/virtualMachines/k8s-agent-AAAAAAAA-0", name: "", fail: true, }, { providerID: "aws:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroupName/providers/Microsoft.Compute/virtualMachines/k8s-agent-AAAAAAAA-0", name: "", fail: true, }, } for _, test := range providers { name, err := splitProviderID(test.providerID) if (err != nil) != test.fail { t.Errorf("Expected to failt=%t, with pattern %v", test.fail, test) } if test.fail { continue } if name != test.name { t.Errorf("Expected %v, but got %v", test.name, name) } } } func TestMetadataURLGeneration(t *testing.T) { metadata := NewInstanceMetadata() fullPath := metadata.makeMetadataURL("some/path") if fullPath != "http://169.254.169.254/metadata/some/path" { t.Errorf("Expected http://169.254.169.254/metadata/some/path saw %s", fullPath) } } func TestMetadataParsing(t *testing.T) { data := ` { "interface": [ { "ipv4": { "ipAddress": [ { "privateIpAddress": "10.0.1.4", "publicIpAddress": "X.X.X.X" } ], "subnet": [ { "address": "10.0.1.0", "prefix": "24" } ] }, "ipv6": { "ipAddress": [ ] }, "macAddress": "002248020E1E" } ] } ` network := NetworkMetadata{} if err := json.Unmarshal([]byte(data), &network); err != nil { t.Errorf("Unexpected error: %v", err) } ip := network.Interface[0].IPV4.IPAddress[0].PrivateIP if ip != "10.0.1.4" { t.Errorf("Unexpected value: %s, expected 10.0.1.4", ip) } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, data) })) defer server.Close() metadata := &InstanceMetadata{ baseURL: server.URL, } networkJSON := NetworkMetadata{} if err := metadata.Object("/some/path", &networkJSON); err != nil { t.Errorf("Unexpected error: %v", err) } if !reflect.DeepEqual(network, networkJSON) { t.Errorf("Unexpected inequality:\n%#v\nvs\n%#v", network, networkJSON) } } func addTestSubnet(t *testing.T, az *Cloud, svc *v1.Service) { if svc.Annotations[ServiceAnnotationLoadBalancerInternal] != "true" { t.Error("Subnet added to non-internal service") } subName := svc.Annotations[ServiceAnnotationLoadBalancerInternalSubnet] if subName == "" { subName = az.SubnetName } subnetID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s", az.SubscriptionID, az.VnetResourceGroup, az.VnetName, subName) _, errChan := az.SubnetsClient.CreateOrUpdate(az.VnetResourceGroup, az.VnetName, subName, network.Subnet{ ID: &subnetID, Name: &subName, }, nil) if err := <-errChan; err != nil { t.Errorf("Subnet cannot be created or update, %v", err) } svc.Annotations[ServiceAnnotationLoadBalancerInternalSubnet] = subName }