package kubernetes import ( "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" ) func TestString(t *testing.T) { pv1 := newPercentageValueFromFloat64(0.5) pv2 := newPercentageValueFromFloat64(0.2) pv3 := newPercentageValueFromFloat64(0.3) f := fractionalWeightAllocator( map[ingressService]int{ { host: "host2", path: "path2", service: "service2", }: int(pv2), { host: "host3", path: "path3", service: "service3", }: int(pv3), { host: "host1", path: "path1", service: "service1", }: int(pv1), }, ) expected := fmt.Sprintf("[service1: %s service2: %s service3: %s]", pv1, pv2, pv3) actual := f.String() assert.Equal(t, expected, actual) } func TestGetServicesPercentageWeights(t *testing.T) { testCases := []struct { desc string annotationValue string expectError bool expectedWeights map[string]percentageValue }{ { desc: "empty annotation", annotationValue: ``, expectedWeights: map[string]percentageValue{}, }, { desc: "50% fraction", annotationValue: ` service1: 10% service2: 20% service3: 20% `, expectedWeights: map[string]percentageValue{ "service1": newPercentageValueFromFloat64(0.1), "service2": newPercentageValueFromFloat64(0.2), "service3": newPercentageValueFromFloat64(0.2), }, }, { desc: "50% fraction with empty fraction", annotationValue: ` service1: 10% service2: 20% service3: 20% service4: `, expectError: true, }, { desc: "50% fraction float form", annotationValue: ` service1: 0.1 service2: 0.2 service3: 0.2 `, expectedWeights: map[string]percentageValue{ "service1": newPercentageValueFromFloat64(0.001), "service2": newPercentageValueFromFloat64(0.002), "service3": newPercentageValueFromFloat64(0.002), }, }, { desc: "no fraction", annotationValue: ` service1: 10% service2: 90% `, expectedWeights: map[string]percentageValue{ "service1": newPercentageValueFromFloat64(0.1), "service2": newPercentageValueFromFloat64(0.9), }, }, { desc: "extra weight specification", annotationValue: ` service1: 90% service5: 90% `, expectedWeights: map[string]percentageValue{ "service1": newPercentageValueFromFloat64(0.9), "service5": newPercentageValueFromFloat64(0.9), }, }, { desc: "malformed annotation", annotationValue: ` service1- 90% service5- 90% `, expectError: true, expectedWeights: nil, }, { desc: "more than one hundred percentaged service", annotationValue: ` service1: 100% service2: 1% `, expectedWeights: map[string]percentageValue{ "service1": newPercentageValueFromFloat64(1), "service2": newPercentageValueFromFloat64(0.01), }, }, { desc: "incorrect percentage value", annotationValue: ` service1: 1000% `, expectedWeights: map[string]percentageValue{ "service1": newPercentageValueFromFloat64(10), }, }, } for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() ingress := &extensionsv1beta1.Ingress{ ObjectMeta: v1.ObjectMeta{ Annotations: map[string]string{ annotationKubernetesServiceWeights: test.annotationValue, }, }, } weights, err := getServicesPercentageWeights(ingress) if test.expectError { require.Error(t, err) } else { require.NoError(t, err) assert.Equal(t, test.expectedWeights, weights) } }) } } func TestComputeServiceWeights(t *testing.T) { client := clientMock{ services: []*corev1.Service{ buildService( sName("service1"), sNamespace("testing"), ), buildService( sName("service2"), sNamespace("testing"), ), buildService( sName("service3"), sNamespace("testing"), ), buildService( sName("service4"), sNamespace("testing"), ), }, endpoints: []*corev1.Endpoints{ buildEndpoint( eNamespace("testing"), eName("service1"), eUID("1"), subset( eAddresses(eAddress("10.10.0.1")), ePorts(ePort(8080, ""))), subset( eAddresses(eAddress("10.21.0.2")), ePorts(ePort(8080, ""))), ), buildEndpoint( eNamespace("testing"), eName("service2"), eUID("2"), subset( eAddresses(eAddress("10.10.0.3")), ePorts(ePort(8080, ""))), ), buildEndpoint( eNamespace("testing"), eName("service3"), eUID("3"), subset( eAddresses(eAddress("10.10.0.4")), ePorts(ePort(8080, ""))), subset( eAddresses(eAddress("10.21.0.5")), ePorts(ePort(8080, ""))), subset( eAddresses(eAddress("10.21.0.6")), ePorts(ePort(8080, ""))), subset( eAddresses(eAddress("10.21.0.7")), ePorts(ePort(8080, ""))), ), buildEndpoint( eNamespace("testing"), eName("service4"), eUID("4"), subset( eAddresses(eAddress("10.10.0.7")), ePorts(ePort(8080, ""))), ), }, } testCases := []struct { desc string ingress *extensionsv1beta1.Ingress expectError bool expectedWeights map[ingressService]percentageValue }{ { desc: "1 path 2 service", ingress: buildIngress( iNamespace("testing"), iAnnotation(annotationKubernetesServiceWeights, ` service1: 10% `), iRules( iRule(iHost("foo.test"), iPaths( onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))), onePath(iPath("/foo"), iBackend("service2", intstr.FromInt(8080))), )), ), ), expectError: false, expectedWeights: map[ingressService]percentageValue{ { host: "foo.test", path: "/foo", service: "service1", }: newPercentageValueFromFloat64(0.05), { host: "foo.test", path: "/foo", service: "service2", }: newPercentageValueFromFloat64(0.90), }, }, { desc: "2 path 2 service", ingress: buildIngress( iNamespace("testing"), iAnnotation(annotationKubernetesServiceWeights, ` service1: 60% `), iRules( iRule(iHost("foo.test"), iPaths( onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))), onePath(iPath("/foo"), iBackend("service2", intstr.FromInt(8080))), onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(8080))), onePath(iPath("/bar"), iBackend("service3", intstr.FromInt(8080))), )), ), ), expectError: false, expectedWeights: map[ingressService]percentageValue{ { host: "foo.test", path: "/foo", service: "service1", }: newPercentageValueFromFloat64(0.30), { host: "foo.test", path: "/foo", service: "service2", }: newPercentageValueFromFloat64(0.40), { host: "foo.test", path: "/bar", service: "service1", }: newPercentageValueFromFloat64(0.30), { host: "foo.test", path: "/bar", service: "service3", }: newPercentageValueFromFloat64(0.10), }, }, { desc: "2 path 3 service", ingress: buildIngress( iNamespace("testing"), iAnnotation(annotationKubernetesServiceWeights, ` service1: 20% service3: 20% `), iRules( iRule(iHost("foo.test"), iPaths( onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))), onePath(iPath("/foo"), iBackend("service2", intstr.FromInt(8080))), onePath(iPath("/bar"), iBackend("service2", intstr.FromInt(8080))), onePath(iPath("/bar"), iBackend("service3", intstr.FromInt(8080))), )), ), ), expectError: false, expectedWeights: map[ingressService]percentageValue{ { host: "foo.test", path: "/foo", service: "service1", }: newPercentageValueFromFloat64(0.10), { host: "foo.test", path: "/foo", service: "service2", }: newPercentageValueFromFloat64(0.80), { host: "foo.test", path: "/bar", service: "service3", }: newPercentageValueFromFloat64(0.05), { host: "foo.test", path: "/bar", service: "service2", }: newPercentageValueFromFloat64(0.80), }, }, { desc: "1 path 4 service", ingress: buildIngress( iNamespace("testing"), iAnnotation(annotationKubernetesServiceWeights, ` service1: 20% service2: 40% service3: 40% `), iRules( iRule(iHost("foo.test"), iPaths( onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))), onePath(iPath("/foo"), iBackend("service2", intstr.FromInt(8080))), onePath(iPath("/foo"), iBackend("service3", intstr.FromInt(8080))), onePath(iPath("/foo"), iBackend("service4", intstr.FromInt(8080))), )), ), ), expectError: false, expectedWeights: map[ingressService]percentageValue{ { host: "foo.test", path: "/foo", service: "service1", }: newPercentageValueFromFloat64(0.10), { host: "foo.test", path: "/foo", service: "service2", }: newPercentageValueFromFloat64(0.40), { host: "foo.test", path: "/foo", service: "service3", }: newPercentageValueFromFloat64(0.10), { host: "foo.test", path: "/foo", service: "service4", }: newPercentageValueFromFloat64(0.00), }, }, { desc: "2 path no service", ingress: buildIngress( iNamespace("testing"), iAnnotation(annotationKubernetesServiceWeights, ` service1: 20% service2: 40% service3: 40% `), iRules( iRule(iHost("foo.test"), iPaths( onePath(iPath("/foo"), iBackend("noservice", intstr.FromInt(8080))), onePath(iPath("/bar"), iBackend("noservice", intstr.FromInt(8080))), )), ), ), expectError: true, }, { desc: "2 path without weight", ingress: buildIngress( iNamespace("testing"), iAnnotation(annotationKubernetesServiceWeights, ``), iRules( iRule(iHost("foo.test"), iPaths( onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))), onePath(iPath("/bar"), iBackend("service2", intstr.FromInt(8080))), )), ), ), expectError: false, expectedWeights: map[ingressService]percentageValue{ { host: "foo.test", path: "/foo", service: "service1", }: newPercentageValueFromFloat64(0.50), { host: "foo.test", path: "/bar", service: "service2", }: newPercentageValueFromFloat64(1.00), }, }, { desc: "2 path overflow", ingress: buildIngress( iNamespace("testing"), iAnnotation(annotationKubernetesServiceWeights, ` service1: 70% service2: 80% `), iRules( iRule(iHost("foo.test"), iPaths( onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))), onePath(iPath("/foo"), iBackend("service2", intstr.FromInt(8080))), )), ), ), expectError: true, }, } for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() weightAllocator, err := newFractionalWeightAllocator(test.ingress, client) if test.expectError { require.Error(t, err) } else { if err != nil { t.Errorf("%v failed: %v", test.desc, err) } else { for ingSvc, percentage := range test.expectedWeights { assert.Equal(t, int(percentage), weightAllocator.getWeight(ingSvc.host, ingSvc.path, ingSvc.service)) } } } }) } }