From c241ccf19d6d1cdf089e32d51c021085c17a1d6c Mon Sep 17 00:00:00 2001 From: Tsukilc <153273766+Tsukilc@users.noreply.github.com> Date: Thu, 24 Apr 2025 20:03:57 +0800 Subject: [PATCH] test: add test for /pkg/ingress/kube/common (#2123) --- pkg/ingress/kube/common/model_test.go | 97 +++++ pkg/ingress/kube/common/tool_test.go | 512 ++++++++++++++++++++++++++ 2 files changed, 609 insertions(+) create mode 100644 pkg/ingress/kube/common/model_test.go diff --git a/pkg/ingress/kube/common/model_test.go b/pkg/ingress/kube/common/model_test.go new file mode 100644 index 000000000..68157426a --- /dev/null +++ b/pkg/ingress/kube/common/model_test.go @@ -0,0 +1,97 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// 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 common + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config" +) + +func TestIngressDomainCache(t *testing.T) { + cache := NewIngressDomainCache() + assert.NotNil(t, cache) + assert.NotNil(t, cache.Valid) + assert.Empty(t, cache.Invalid) + + cache.Valid["example.com"] = &IngressDomainBuilder{ + Host: "example.com", + Protocol: HTTP, + ClusterId: "cluster-1", + Ingress: &config.Config{ + Meta: config.Meta{ + Name: "test-ingress", + Namespace: "default", + }, + }, + } + + cache.Invalid = append(cache.Invalid, model.IngressDomain{ + Host: "invalid.com", + Error: "invalid domain", + }) + + result := cache.Extract() + assert.Equal(t, 1, len(result.Valid)) + assert.Equal(t, "example.com", result.Valid[0].Host) + assert.Equal(t, string(HTTP), result.Valid[0].Protocol) + + assert.Equal(t, 1, len(result.Invalid)) + assert.Equal(t, "invalid.com", result.Invalid[0].Host) +} + +func TestIngressDomainBuilder(t *testing.T) { + builder := &IngressDomainBuilder{ + Host: "example.com", + Protocol: HTTP, + ClusterId: "cluster-1", + Ingress: &config.Config{ + Meta: config.Meta{ + Name: "test-ingress", + Namespace: "default", + }, + }, + } + + domain := builder.Build() + assert.Equal(t, "example.com", domain.Host) + assert.Equal(t, string(HTTP), domain.Protocol) + + builder.Event = MissingSecret + eventDomain := builder.Build() + assert.Contains(t, eventDomain.Error, "misses secret") + + builder.Event = DuplicatedTls + builder.PreIngress = &config.Config{ + Meta: config.Meta{ + Name: "pre-ingress", + Namespace: "default", + }, + } + builder.PreIngress.Meta.Annotations = map[string]string{ + ClusterIdAnnotation: "pre-cluster", + } + dupDomain := builder.Build() + assert.Contains(t, dupDomain.Error, "conflicted with ingress") + + builder.Protocol = HTTPS + builder.SecretName = "test-secret" + builder.Event = "" + httpsDomain := builder.Build() + assert.Equal(t, string(HTTPS), httpsDomain.Protocol) + assert.Equal(t, "test-secret", httpsDomain.SecretName) +} diff --git a/pkg/ingress/kube/common/tool_test.go b/pkg/ingress/kube/common/tool_test.go index d2f92009e..60ad4f747 100644 --- a/pkg/ingress/kube/common/tool_test.go +++ b/pkg/ingress/kube/common/tool_test.go @@ -18,6 +18,7 @@ import ( "testing" networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/model" "istio.io/istio/pkg/config" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -556,3 +557,514 @@ func TestSortHTTPRoutesWithMoreRules(t *testing.T) { } } } + +func TestValidateBackendResource(t *testing.T) { + groupStr := "networking.higress.io" + testCases := []struct { + name string + resource *v1.TypedLocalObjectReference + expected bool + }{ + { + name: "nil resource", + resource: nil, + expected: false, + }, + { + name: "nil APIGroup", + resource: &v1.TypedLocalObjectReference{ + APIGroup: nil, + Kind: "McpBridge", + Name: "default", + }, + expected: false, + }, + { + name: "wrong APIGroup", + resource: &v1.TypedLocalObjectReference{ + APIGroup: &groupStr, + Kind: "McpBridge", + Name: "wrong-name", + }, + expected: false, + }, + { + name: "wrong Kind", + resource: &v1.TypedLocalObjectReference{ + APIGroup: &groupStr, + Kind: "WrongKind", + Name: "default", + }, + expected: false, + }, + { + name: "valid resource", + resource: &v1.TypedLocalObjectReference{ + APIGroup: &groupStr, + Kind: "McpBridge", + Name: "default", + }, + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := ValidateBackendResource(tc.resource) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestCreateOrUpdateAnnotations(t *testing.T) { + testCases := []struct { + name string + annotations map[string]string + options Options + expected map[string]string + }{ + { + name: "empty annotations", + annotations: map[string]string{}, + options: Options{ + ClusterId: "test-cluster", + RawClusterId: "raw-test-cluster", + }, + expected: map[string]string{ + ClusterIdAnnotation: "test-cluster", + RawClusterIdAnnotation: "raw-test-cluster", + }, + }, + { + name: "existing annotations", + annotations: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + options: Options{ + ClusterId: "test-cluster", + RawClusterId: "raw-test-cluster", + }, + expected: map[string]string{ + "key1": "value1", + "key2": "value2", + ClusterIdAnnotation: "test-cluster", + RawClusterIdAnnotation: "raw-test-cluster", + }, + }, + { + name: "overwrite existing cluster annotations", + annotations: map[string]string{ + ClusterIdAnnotation: "old-cluster", + RawClusterIdAnnotation: "old-raw-cluster", + "key1": "value1", + }, + options: Options{ + ClusterId: "new-cluster", + RawClusterId: "new-raw-cluster", + }, + expected: map[string]string{ + ClusterIdAnnotation: "new-cluster", + RawClusterIdAnnotation: "new-raw-cluster", + "key1": "value1", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := CreateOrUpdateAnnotations(tc.annotations, tc.options) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestGetClusterId(t *testing.T) { + testCases := []struct { + name string + annotations map[string]string + expected string + }{ + { + name: "nil annotations", + annotations: nil, + expected: "", + }, + { + name: "empty annotations", + annotations: map[string]string{}, + expected: "", + }, + { + name: "with cluster id", + annotations: map[string]string{ + ClusterIdAnnotation: "test-cluster", + }, + expected: "test-cluster", + }, + { + name: "with other annotations", + annotations: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + expected: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := GetClusterId(tc.annotations) + assert.Equal(t, tc.expected, string(result)) + }) + } +} + +func TestConvertToDNSLabelValidAndCleanHost(t *testing.T) { + testCases := []struct { + name string + input string + }{ + { + name: "simple host", + input: "example.com", + }, + { + name: "wildcard host", + input: "*.example.com", + }, + { + name: "long host", + input: "very-long-subdomain.example-service.my-namespace.svc.cluster.local", + }, + { + name: "empty host", + input: "", + }, + { + name: "ip address", + input: "192.168.1.1", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Test internal convertToDNSLabelValid function (through CleanHost) + result := CleanHost(tc.input) + + // Validate result + assert.NotEmpty(t, result) + assert.Equal(t, 16, len(result)) // MD5 hash format is fixed length of 16 bytes + + // Consistency check - same input should produce same output + result2 := CleanHost(tc.input) + assert.Equal(t, result, result2) + }) + } +} + +func TestSplitServiceFQDN(t *testing.T) { + testCases := []struct { + name string + fqdn string + expectedSvc string + expectedNs string + expectedValid bool + }{ + { + name: "simple fqdn", + fqdn: "service.namespace", + expectedSvc: "service", + expectedNs: "namespace", + expectedValid: true, + }, + { + name: "full k8s fqdn", + fqdn: "service.namespace.svc.cluster.local", + expectedSvc: "service", + expectedNs: "namespace", + expectedValid: true, + }, + { + name: "just service name", + fqdn: "service", + expectedSvc: "", + expectedNs: "", + expectedValid: false, + }, + { + name: "empty string", + fqdn: "", + expectedSvc: "", + expectedNs: "", + expectedValid: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + svc, ns, valid := SplitServiceFQDN(tc.fqdn) + assert.Equal(t, tc.expectedSvc, svc) + assert.Equal(t, tc.expectedNs, ns) + assert.Equal(t, tc.expectedValid, valid) + }) + } +} + +func TestConvertBackendService(t *testing.T) { + testCases := []struct { + name string + dest *networking.HTTPRouteDestination + expected model.BackendService + }{ + { + name: "simple service", + dest: &networking.HTTPRouteDestination{ + Destination: &networking.Destination{ + Host: "service.namespace", + Port: &networking.PortSelector{ + Number: 80, + }, + }, + Weight: 100, + }, + expected: model.BackendService{ + Name: "service", + Namespace: "namespace", + Port: 80, + Weight: 100, + }, + }, + { + name: "full k8s FQDN", + dest: &networking.HTTPRouteDestination{ + Destination: &networking.Destination{ + Host: "service.namespace.svc.cluster.local", + Port: &networking.PortSelector{ + Number: 8080, + }, + }, + Weight: 50, + }, + expected: model.BackendService{ + Name: "service", + Namespace: "namespace", + Port: 8080, + Weight: 50, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := ConvertBackendService(tc.dest) + assert.Equal(t, tc.expected.Name, result.Name) + assert.Equal(t, tc.expected.Namespace, result.Namespace) + assert.Equal(t, tc.expected.Port, result.Port) + assert.Equal(t, tc.expected.Weight, result.Weight) + }) + } +} + +func TestCreateConvertedName(t *testing.T) { + testCases := []struct { + name string + items []string + expected string + }{ + { + name: "empty slice", + items: []string{}, + expected: "", + }, + { + name: "single item", + items: []string{"example"}, + expected: "example", + }, + { + name: "multiple items", + items: []string{"part1", "part2", "part3"}, + expected: "part1-part2-part3", + }, + { + name: "with empty strings", + items: []string{"part1", "", "part3"}, + expected: "part1-part3", + }, + { + name: "all empty strings", + items: []string{"", "", ""}, + expected: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := CreateConvertedName(tc.items...) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestSortIngressByCreationTime(t *testing.T) { + configs := []config.Config{ + { + Meta: config.Meta{ + Name: "c-ingress", + Namespace: "ns1", + }, + }, + { + Meta: config.Meta{ + Name: "a-ingress", + Namespace: "ns1", + }, + }, + { + Meta: config.Meta{ + Name: "b-ingress", + Namespace: "ns1", + }, + }, + } + + expected := []string{"a-ingress", "b-ingress", "c-ingress"} + + SortIngressByCreationTime(configs) + + var actual []string + for _, cfg := range configs { + actual = append(actual, cfg.Name) + } + + assert.Equal(t, expected, actual, "When the timestamps are the same, the configuration should be sorted by name") + + sameNamespaceConfigs := []config.Config{ + { + Meta: config.Meta{ + Name: "same-name", + Namespace: "c-ns", + }, + }, + { + Meta: config.Meta{ + Name: "same-name", + Namespace: "a-ns", + }, + }, + { + Meta: config.Meta{ + Name: "same-name", + Namespace: "b-ns", + }, + }, + } + + expectedNamespace := []string{"a-ns", "b-ns", "c-ns"} + + SortIngressByCreationTime(sameNamespaceConfigs) + + var actualNamespace []string + for _, cfg := range sameNamespaceConfigs { + actualNamespace = append(actualNamespace, cfg.Namespace) + } + + assert.Equal(t, expectedNamespace, actualNamespace, "When the names are the same, the configuration should be sorted by namespace") +} + +func TestPartMd5(t *testing.T) { + testCases := []struct { + name string + input string + length int + }{ + { + name: "empty string", + input: "", + length: 8, + }, + { + name: "simple string", + input: "test", + length: 8, + }, + { + name: "complex string", + input: "this-is-a-long-string-with-special-chars-!@#$%^&*()", + length: 8, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := partMd5(tc.input) + + // Check result format + assert.Equal(t, tc.length, len(result), "MD5 hash excerpt should be 8 characters") + + // Run twice to ensure deterministic output + result2 := partMd5(tc.input) + assert.Equal(t, result, result2, "partMd5 function should be deterministic") + }) + } +} + +func TestGetLbStatusListV1AndV1Beta1(t *testing.T) { + clusterPrefix = "gw-123-" + svcName := clusterPrefix + svcList := []*v1.Service{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: svcName, + }, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeLoadBalancer, + }, + Status: v1.ServiceStatus{ + LoadBalancer: v1.LoadBalancerStatus{ + Ingress: []v1.LoadBalancerIngress{ + { + IP: "2.2.2.2", + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: svcName, + }, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeLoadBalancer, + }, + Status: v1.ServiceStatus{ + LoadBalancer: v1.LoadBalancerStatus{ + Ingress: []v1.LoadBalancerIngress{ + { + Hostname: "1.1.1.1" + SvcHostNameSuffix, + }, + }, + }, + }, + }, + } + + // Test the V1 version + t.Run("GetLbStatusListV1", func(t *testing.T) { + lbiList := GetLbStatusListV1(svcList) + + assert.Equal(t, 2, len(lbiList), "There should be 2 entry points") + assert.Equal(t, "1.1.1.1", lbiList[0].IP, "The first IP should be 1.1.1.1") + assert.Equal(t, "2.2.2.2", lbiList[1].IP, "The second IP should be 2.2.2.2") + }) + + // Test the V1Beta1 version + t.Run("GetLbStatusListV1Beta1", func(t *testing.T) { + lbiList := GetLbStatusListV1Beta1(svcList) + + assert.Equal(t, 2, len(lbiList), "There should be 2 entry points") + assert.Equal(t, "1.1.1.1", lbiList[0].IP, "The first IP should be 1.1.1.1") + assert.Equal(t, "2.2.2.2", lbiList[1].IP, "The second IP should be 2.2.2.2") + }) +}