From 4b123b1306d18ec99c3aee50083e78903cb9ca16 Mon Sep 17 00:00:00 2001 From: l1b0k Date: Fri, 3 Jan 2025 15:40:17 +0800 Subject: [PATCH] update ut Signed-off-by: l1b0k --- pkg/aliyun/client/errors/errors_test.go | 7 + pkg/controller/pod-eni/eni_controller_test.go | 283 ++++++++++++++++++ pkg/controller/pod/pod_controller_test.go | 2 - pkg/controller/pod/suite_test.go | 2 - pkg/eni/crdv2_test.go | 62 ++++ pkg/eni/remote_test.go | 67 ++++- pkg/ip/ip_test.go | 23 ++ pkg/k8s/k8s_test.go | 226 ++++++++++++++ types/controlplane/annotations_test.go | 83 +++++ types/controlplane/config_test.go | 38 +++ types/daemon/config_test.go | 62 ++++ types/daemon/dynamicconfig_test.go | 56 ++++ types/daemon/res_test.go | 50 ++++ types/helper_test.go | 134 +++++++++ types/secret/secret_test.go | 32 ++ types/types_test.go | 145 +++++++++ 16 files changed, 1260 insertions(+), 12 deletions(-) create mode 100644 pkg/controller/pod-eni/eni_controller_test.go create mode 100644 pkg/k8s/k8s_test.go create mode 100644 types/controlplane/annotations_test.go create mode 100644 types/daemon/dynamicconfig_test.go create mode 100644 types/daemon/res_test.go create mode 100644 types/helper_test.go create mode 100644 types/secret/secret_test.go diff --git a/pkg/aliyun/client/errors/errors_test.go b/pkg/aliyun/client/errors/errors_test.go index 9353ad40..d42cdd2a 100644 --- a/pkg/aliyun/client/errors/errors_test.go +++ b/pkg/aliyun/client/errors/errors_test.go @@ -118,3 +118,10 @@ func TestErrorIs(t *testing.T) { // Test case 3: Check if no check functions are provided assert.False(t, ErrorIs(err)) } + +func TestErrorIsReturnsFalseWhenNoCheckErrMatches(t *testing.T) { + err := apiErr.NewServerError(403, "{\"Code\": \"err\"}", "") + checkFunc1 := WarpFn("anotherErr") + checkFunc2 := WarpFn("yetAnotherErr") + assert.False(t, ErrorIs(err, checkFunc1, checkFunc2)) +} diff --git a/pkg/controller/pod-eni/eni_controller_test.go b/pkg/controller/pod-eni/eni_controller_test.go new file mode 100644 index 00000000..ee63d01a --- /dev/null +++ b/pkg/controller/pod-eni/eni_controller_test.go @@ -0,0 +1,283 @@ +package podeni + +import ( + "context" + "testing" + + "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + aliyunClient "github.com/AliyunContainerService/terway/pkg/aliyun/client" + "github.com/AliyunContainerService/terway/pkg/apis/network.alibabacloud.com/v1beta1" + register "github.com/AliyunContainerService/terway/pkg/controller" + "github.com/AliyunContainerService/terway/pkg/controller/mocks" +) + +func TestReconcilePodENI_podRequirePodENI(t *testing.T) { + type fields struct { + client client.Client + scheme *runtime.Scheme + aliyun register.Interface + record record.EventRecorder + trunkMode bool + crdMode bool + } + type args struct { + ctx context.Context + pod *corev1.Pod + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "normal pod", + fields: fields{ + client: func() client.Client { + nodeReader := fake.NewClientBuilder() + nodeReader.WithObjects(&corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: corev1.NodeSpec{}, + Status: corev1.NodeStatus{}, + }) + return nodeReader.Build() + }(), + scheme: nil, + aliyun: nil, + record: nil, + trunkMode: false, + crdMode: false, + }, + args: args{ + ctx: context.Background(), + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + NodeName: "foo", + }, + Status: corev1.PodStatus{}, + }, + }, + want: false, + }, + { + name: "trunk pod", + fields: fields{ + client: func() client.Client { + nodeReader := fake.NewClientBuilder() + nodeReader.WithObjects(&corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: corev1.NodeSpec{}, + Status: corev1.NodeStatus{}, + }) + return nodeReader.Build() + }(), + scheme: nil, + aliyun: nil, + record: nil, + trunkMode: false, + crdMode: false, + }, + args: args{ + ctx: context.Background(), + pod: &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + "k8s.aliyun.com/pod-eni": "true", + }, + }, + Spec: corev1.PodSpec{ + NodeName: "foo", + }, + Status: corev1.PodStatus{}, + }, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &ReconcilePodENI{ + client: tt.fields.client, + scheme: tt.fields.scheme, + aliyun: tt.fields.aliyun, + record: tt.fields.record, + trunkMode: tt.fields.trunkMode, + crdMode: tt.fields.crdMode, + } + if got := m.podRequirePodENI(tt.args.ctx, tt.args.pod); got != tt.want { + t.Errorf("podRequirePodENI() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_eniFilter(t *testing.T) { + type args struct { + eni *aliyunClient.NetworkInterface + filter map[string]string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "match", + args: args{ + eni: &aliyunClient.NetworkInterface{ + Tags: []ecs.Tag{ + { + TagKey: "foo", + TagValue: "bar", + }, + }, + }, + filter: map[string]string{ + "foo": "bar", + }, + }, + want: true, + }, + { + name: "not match", + args: args{ + eni: &aliyunClient.NetworkInterface{ + Tags: []ecs.Tag{ + { + TagKey: "foo", + TagValue: "bar", + }, + }, + }, + filter: map[string]string{ + "foo": "bar", + "foo1": "bar1", + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := eniFilter(tt.args.eni, tt.args.filter); got != tt.want { + t.Errorf("eniFilter() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReconcilePodENI_deleteMemberENI(t *testing.T) { + m := mocks.NewInterface(t) + m.On("DeleteNetworkInterface", mock.Anything, "foo").Return(nil) + + c := &ReconcilePodENI{ + client: fake.NewClientBuilder().Build(), + aliyun: m, + record: record.NewFakeRecorder(10), + } + err := c.deleteMemberENI(context.Background(), &v1beta1.PodENI{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: v1beta1.PodENISpec{ + Allocations: []v1beta1.Allocation{ + { + ENI: v1beta1.ENI{ID: "foo"}, + }, + }, + Zone: "", + }, + }) + assert.NoError(t, err) +} + +func TestReconcilePodENI_detachMemberENI(t *testing.T) { + m := mocks.NewInterface(t) + m.On("WaitForNetworkInterface", mock.Anything, "foo", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) + m.On("DetachNetworkInterface", mock.Anything, "foo", "i-x", "eni-x").Return(nil) + + c := &ReconcilePodENI{ + client: fake.NewClientBuilder().Build(), + aliyun: m, + record: record.NewFakeRecorder(10), + } + err := c.detachMemberENI(context.Background(), &v1beta1.PodENI{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: v1beta1.PodENISpec{ + Allocations: []v1beta1.Allocation{ + { + ENI: v1beta1.ENI{ID: "foo"}, + }, + }, + Zone: "", + }, + Status: v1beta1.PodENIStatus{ + Phase: v1beta1.ENIPhaseBind, + InstanceID: "i-x", + TrunkENIID: "eni-x", + Msg: "", + PodLastSeen: metav1.Time{}, + ENIInfos: nil, + }, + }) + assert.NoError(t, err) +} + +func TestReconcilePodENI_attachENI(t *testing.T) { + m := mocks.NewInterface(t) + m.On("WaitForNetworkInterface", mock.Anything, "foo", mock.Anything, mock.Anything, mock.Anything).Return(&aliyunClient.NetworkInterface{ + NetworkInterfaceID: "foo", + }, nil) + m.On("AttachNetworkInterface", mock.Anything, "foo", "i-x", "eni-x").Return(nil) + + c := &ReconcilePodENI{ + client: fake.NewClientBuilder().Build(), + aliyun: m, + record: record.NewFakeRecorder(10), + } + podENI := &v1beta1.PodENI{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: v1beta1.PodENISpec{ + Allocations: []v1beta1.Allocation{ + { + ENI: v1beta1.ENI{ID: "foo"}, + }, + }, + Zone: "", + }, + Status: v1beta1.PodENIStatus{ + InstanceID: "i-x", + TrunkENIID: "eni-x", + Msg: "", + PodLastSeen: metav1.Time{}, + ENIInfos: nil, + }, + } + err := c.attachENI(context.Background(), podENI) + assert.NoError(t, err) + + assert.Equal(t, v1beta1.ENIStatusBind, podENI.Status.ENIInfos["foo"].Status) +} diff --git a/pkg/controller/pod/pod_controller_test.go b/pkg/controller/pod/pod_controller_test.go index 1c2123de..0bf87507 100644 --- a/pkg/controller/pod/pod_controller_test.go +++ b/pkg/controller/pod/pod_controller_test.go @@ -1,5 +1,3 @@ -//go:build test_env - package pod import ( diff --git a/pkg/controller/pod/suite_test.go b/pkg/controller/pod/suite_test.go index c60ebc65..15a7cd38 100644 --- a/pkg/controller/pod/suite_test.go +++ b/pkg/controller/pod/suite_test.go @@ -1,5 +1,3 @@ -//go:build test_env - package pod import ( diff --git a/pkg/eni/crdv2_test.go b/pkg/eni/crdv2_test.go index f9c599df..36ee7a09 100644 --- a/pkg/eni/crdv2_test.go +++ b/pkg/eni/crdv2_test.go @@ -10,6 +10,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" + pkgclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" networkv1beta1 "github.com/AliyunContainerService/terway/pkg/apis/network.alibabacloud.com/v1beta1" @@ -470,3 +471,64 @@ func Test_removeDeleted(t *testing.T) { }) } } + +func TestSyncNodeRuntimeReturnsNilWhenNoDeletedPods(t *testing.T) { + r := &CRDV2{ + deletedPods: make(map[string]*networkv1beta1.RuntimePodStatus), + } + err := r.syncNodeRuntime(context.Background()) + assert.NoError(t, err) +} + +func TestSyncNodeRuntimeReturnsErrorWhenGetRuntimeNodeFails(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(types.Scheme).Build() + r := &CRDV2{ + client: client, + deletedPods: map[string]*networkv1beta1.RuntimePodStatus{"pod-1": {PodID: "pod-1"}}, + } + err := r.syncNodeRuntime(context.Background()) + assert.Error(t, err) +} + +func TestSyncNodeRuntimeUpdatesDeletedPods(t *testing.T) { + n := &networkv1beta1.NodeRuntime{ + ObjectMeta: metav1.ObjectMeta{Name: "node1"}, + Spec: networkv1beta1.NodeRuntimeSpec{}, + Status: networkv1beta1.NodeRuntimeStatus{Pods: map[string]*networkv1beta1.RuntimePodStatus{}}, + } + client := fake.NewClientBuilder().WithScheme(types.Scheme). + WithStatusSubresource(n). + WithObjects(n).Build() + r := &CRDV2{ + client: client, + nodeName: "node1", + deletedPods: map[string]*networkv1beta1.RuntimePodStatus{"pod-1": {PodID: "pod-1"}}, + } + + err := r.syncNodeRuntime(context.Background()) + assert.NoError(t, err) + + nodeRuntime := &networkv1beta1.NodeRuntime{} + err = client.Get(context.Background(), pkgclient.ObjectKey{Name: "node1"}, nodeRuntime) + assert.NoError(t, err) + assert.Contains(t, nodeRuntime.Status.Pods, "pod-1") + assert.Contains(t, nodeRuntime.Status.Pods["pod-1"].Status, networkv1beta1.CNIStatusDeleted) +} + +func TestSyncNodeRuntimeClearsDeletedPods(t *testing.T) { + n := &networkv1beta1.NodeRuntime{ + ObjectMeta: metav1.ObjectMeta{Name: "node1"}, + Spec: networkv1beta1.NodeRuntimeSpec{}, + Status: networkv1beta1.NodeRuntimeStatus{Pods: map[string]*networkv1beta1.RuntimePodStatus{}}, + } + + client := fake.NewClientBuilder().WithScheme(types.Scheme).WithStatusSubresource(n).WithObjects(n).Build() + r := &CRDV2{ + client: client, + nodeName: "node1", + deletedPods: map[string]*networkv1beta1.RuntimePodStatus{"pod-1": {PodID: "pod-1"}}, + } + err := r.syncNodeRuntime(context.Background()) + assert.NoError(t, err) + assert.Empty(t, r.deletedPods) +} diff --git a/pkg/eni/remote_test.go b/pkg/eni/remote_test.go index 934b796f..0e6e3aeb 100644 --- a/pkg/eni/remote_test.go +++ b/pkg/eni/remote_test.go @@ -1,46 +1,49 @@ package eni import ( + "context" "net" "testing" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/AliyunContainerService/terway/types" "github.com/AliyunContainerService/terway/types/daemon" - podENITypes "github.com/AliyunContainerService/terway/pkg/apis/network.alibabacloud.com/v1beta1" + networkv1beta1 "github.com/AliyunContainerService/terway/pkg/apis/network.alibabacloud.com/v1beta1" ) func TestToRPC(t *testing.T) { t.Run("test with valid IPv4 and IPv6 allocations", func(t *testing.T) { l := &RemoteIPResource{ - podENI: podENITypes.PodENI{ - Spec: podENITypes.PodENISpec{ - Allocations: []podENITypes.Allocation{ + podENI: networkv1beta1.PodENI{ + Spec: networkv1beta1.PodENISpec{ + Allocations: []networkv1beta1.Allocation{ { IPv4: "192.168.1.1", IPv4CIDR: "192.168.1.0/24", IPv6: "fd00:db8::1", IPv6CIDR: "fd00:db8::/64", - ENI: podENITypes.ENI{ + ENI: networkv1beta1.ENI{ ID: "eni-11", MAC: "00:00:00:00:00:00", }, Interface: "eth0", - ExtraRoutes: []podENITypes.Route{}, + ExtraRoutes: []networkv1beta1.Route{}, DefaultRoute: true, }, }, }, - Status: podENITypes.PodENIStatus{ + Status: networkv1beta1.PodENIStatus{ Phase: "", InstanceID: "i-123456", TrunkENIID: "eni-12345678", Msg: "", PodLastSeen: metav1.Time{}, - ENIInfos: map[string]podENITypes.ENIInfo{ + ENIInfos: map[string]networkv1beta1.ENIInfo{ "eni-11": {}, }, }, @@ -68,3 +71,51 @@ func TestToRPC(t *testing.T) { assert.Equal(t, true, result[0].DefaultRoute) }) } + +func TestAllocateReturnsErrorWhenResourceTypeMismatch(t *testing.T) { + r := &Remote{} + resp, traces := r.Allocate(context.Background(), &daemon.CNI{}, &LocalIPResource{}) + assert.Nil(t, resp) + assert.Equal(t, ResourceTypeMismatch, traces[0].Condition) +} + +func TestAllocateReturnsNetworkResourcesWhenPodENIReady(t *testing.T) { + scheme := runtime.NewScheme() + _ = networkv1beta1.AddToScheme(scheme) + // Build the fake client with scheme and objects + client := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(&networkv1beta1.PodENI{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + }, + Spec: networkv1beta1.PodENISpec{ + Allocations: []networkv1beta1.Allocation{ + { + IPv4: "192.168.1.1", + IPv4CIDR: "192.168.1.0/24", + ENI: networkv1beta1.ENI{ + ID: "eni-1", + MAC: "00:00:00:00:00:00", + }, + Interface: "eth0", + DefaultRoute: true, + }, + }, + }, + Status: networkv1beta1.PodENIStatus{ + Phase: networkv1beta1.ENIPhaseBind, + InstanceID: "i-123456", + }, + }). + Build() + + r := NewRemote(client, nil) + cni := &daemon.CNI{PodNamespace: "default", PodName: "pod-1"} + resp, _ := r.Allocate(context.Background(), cni, &RemoteIPRequest{}) + result := <-resp + assert.NoError(t, result.Err) + assert.NotNil(t, result.NetworkConfigs) + assert.Equal(t, "192.168.1.1", result.NetworkConfigs[0].ToRPC()[0].BasicInfo.PodIP.IPv4) +} diff --git a/pkg/ip/ip_test.go b/pkg/ip/ip_test.go index 9eb494e3..4c6e3951 100644 --- a/pkg/ip/ip_test.go +++ b/pkg/ip/ip_test.go @@ -2,7 +2,10 @@ package ip import ( "net" + "net/netip" "testing" + + "github.com/stretchr/testify/assert" ) func Test_ipIntersect(t *testing.T) { @@ -53,3 +56,23 @@ func Test_ipIntersect(t *testing.T) { }) } } + +func TestIPAddrs2str_MultipleValidIPs_ReturnsCorrectStrings(t *testing.T) { + ip1, _ := netip.ParseAddr("192.0.2.1") + ip2, _ := netip.ParseAddr("192.0.2.2") + input := []netip.Addr{ip1, ip2} + expected := []string{"192.0.2.1", "192.0.2.2"} + result := IPAddrs2str(input) + if len(result) != len(expected) { + t.Errorf("Expected %v, got %v", expected, result) + } + for i := range expected { + if result[i] != expected[i] { + t.Errorf("Expected %v, got %v", expected, result) + } + } +} + +func TestDeriveGatewayIP(t *testing.T) { + assert.Equal(t, "192.168.0.253", DeriveGatewayIP("192.168.0.0/24")) +} diff --git a/pkg/k8s/k8s_test.go b/pkg/k8s/k8s_test.go new file mode 100644 index 00000000..342f4aa2 --- /dev/null +++ b/pkg/k8s/k8s_test.go @@ -0,0 +1,226 @@ +package k8s + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/AliyunContainerService/terway/deviceplugin" + "github.com/AliyunContainerService/terway/types" + "github.com/AliyunContainerService/terway/types/daemon" +) + +func TestGetNodeReturnsNodeWhenExists(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(&corev1.Node{ + ObjectMeta: metav1.ObjectMeta{Name: "node1"}, + }).Build() + node, err := getNode(context.Background(), client, "node1") + assert.NoError(t, err) + assert.NotNil(t, node) + assert.Equal(t, "node1", node.Name) +} + +func TestGetPodReturnsPodWhenExists(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(&corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "default"}, + }).Build() + pod, err := getPod(context.Background(), client, "default", "pod1", true) + assert.NoError(t, err) + assert.NotNil(t, pod) + assert.Equal(t, "pod1", pod.Name) +} + +func TestGetCMReturnsConfigMapWhenExists(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "cm1", Namespace: "default"}, + }).Build() + cm, err := getCM(context.Background(), client, "default", "cm1") + assert.NoError(t, err) + assert.NotNil(t, cm) + assert.Equal(t, "cm1", cm.Name) +} + +func TestConvertPodReturnsPodInfoWithCorrectNetworkType(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "default"}, + Status: corev1.PodStatus{ + PodIP: "192.168.1.1", + PodIPs: []corev1.PodIP{ + {IP: "192.168.1.1"}, + }, + }, + } + result := convertPod(daemon.ModeENIMultiIP, false, sets.New[string](), pod) + assert.Equal(t, daemon.PodNetworkTypeENIMultiIP, result.PodNetworkType) +} + +func TestConvertPodSetsIngressAndEgressBandwidth(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + Annotations: map[string]string{ + podIngressBandwidth: "1M", + podEgressBandwidth: "2M", + }, + }, + } + result := convertPod(daemon.ModeENIMultiIP, false, sets.New[string](), pod) + assert.Equal(t, uint64(1*MEGABYTE), result.TcIngress) + assert.Equal(t, uint64(2*MEGABYTE), result.TcEgress) +} + +func TestConvertPodHandlesInvalidBandwidthAnnotations(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + Annotations: map[string]string{ + podIngressBandwidth: "invalid", + podEgressBandwidth: "invalid", + }, + }, + } + result := convertPod(daemon.ModeENIMultiIP, false, sets.New[string](), pod) + assert.Equal(t, uint64(0), result.TcIngress) + assert.Equal(t, uint64(0), result.TcEgress) +} + +func TestConvertPodSetsPodENI(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + Annotations: map[string]string{ + types.PodENI: "true", + }, + }, + } + result := convertPod(daemon.ModeENIMultiIP, false, sets.New[string](), pod) + assert.True(t, result.PodENI) +} + +func TestConvertPodHandlesInvalidPodENIAnnotation(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + Annotations: map[string]string{ + types.PodENI: "invalid", + }, + }, + } + result := convertPod(daemon.ModeENIMultiIP, false, sets.New[string](), pod) + assert.False(t, result.PodENI) +} + +func TestConvertPodSetsNetworkPriority(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + Annotations: map[string]string{ + types.NetworkPriority: string(types.NetworkPrioGuaranteed), + }, + }, + } + result := convertPod(daemon.ModeENIMultiIP, false, sets.New[string](), pod) + assert.Equal(t, string(types.NetworkPrioGuaranteed), result.NetworkPriority) +} + +func TestConvertPodHandlesInvalidNetworkPriorityAnnotation(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + Annotations: map[string]string{ + types.NetworkPriority: "invalid", + }, + }, + } + result := convertPod(daemon.ModeENIMultiIP, false, sets.New[string](), pod) + assert.Empty(t, result.NetworkPriority) +} + +func TestConvertPodSetsERdmaWhenEnabled(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "default"}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + deviceplugin.ERDMAResName: resource.MustParse("1"), + }, + }, + }, + }, + }, + } + result := convertPod(daemon.ModeENIMultiIP, true, sets.New[string](), pod) + assert.True(t, result.ERdma) +} + +func TestConvertPodSetsIPStickTimeForStatefulWorkload(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + Annotations: map[string]string{ + types.PodIPReservation: "true", + }, + }, + } + result := convertPod(daemon.ModeENIMultiIP, false, sets.New[string](), pod) + assert.Equal(t, defaultStickTimeForSts, result.IPStickTime) +} + +func TestServiceCidrFromAPIServerReturnsErrorWhenConfigMapNotFound(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() + _, err := serviceCidrFromAPIServer(client) + assert.Error(t, err) +} + +func TestServiceCidrFromAPIServerReturnsErrorWhenConfigMapDataMissing(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: k8sKubeadmConfigmap, Namespace: k8sSystemNamespace}, + }).Build() + _, err := serviceCidrFromAPIServer(client) + assert.Error(t, err) +} + +func TestServiceCidrFromAPIServerReturnsErrorWhenUnmarshalFails(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: k8sKubeadmConfigmap, Namespace: k8sSystemNamespace}, + Data: map[string]string{k8sKubeadmConfigmapNetworking: "invalid-yaml"}, + }).Build() + _, err := serviceCidrFromAPIServer(client) + assert.Error(t, err) +} + +func TestServiceCidrFromAPIServerReturnsErrorWhenServiceSubnetNotFound(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: k8sKubeadmConfigmap, Namespace: k8sSystemNamespace}, + Data: map[string]string{k8sKubeadmConfigmapNetworking: "networking: {}"}, + }).Build() + _, err := serviceCidrFromAPIServer(client) + assert.Error(t, err) +} + +func TestServiceCidrFromAPIServerReturnsParsedCidr(t *testing.T) { + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: k8sKubeadmConfigmap, Namespace: k8sSystemNamespace}, + Data: map[string]string{k8sKubeadmConfigmapNetworking: "networking:\n serviceSubnet: 10.96.0.0/12"}, + }).Build() + cidr, err := serviceCidrFromAPIServer(client) + assert.NoError(t, err) + assert.NotNil(t, cidr) + assert.Equal(t, "10.96.0.0/12", cidr.String()) +} diff --git a/types/controlplane/annotations_test.go b/types/controlplane/annotations_test.go new file mode 100644 index 00000000..a17f4fbf --- /dev/null +++ b/types/controlplane/annotations_test.go @@ -0,0 +1,83 @@ +package controlplane + +import ( + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/AliyunContainerService/terway/pkg/apis/network.alibabacloud.com/v1beta1" +) + +func TestParsePodNetworksFromAnnotation(t *testing.T) { + tests := []struct { + name string + podAnnotations map[string]string + expectedResult *PodNetworksAnnotation + expectedError string + }{ + { + name: "AnnotationDoesNotExist", + podAnnotations: map[string]string{}, + expectedResult: &PodNetworksAnnotation{}, + expectedError: "", + }, + { + name: "AnnotationExistsAndValid", + podAnnotations: map[string]string{ + "k8s.aliyun.com/pod-networks": `{ "podNetworks": [{"vSwitchOptions": [ + "vsw-a","vsw-b","vsw-c" + ], "interface": "eth0", + "securityGroupIDs": [ + "sg-1" + ]}]}`, + }, + expectedResult: &PodNetworksAnnotation{ + PodNetworks: []PodNetworks{ + { + Interface: "eth0", + VSwitchOptions: []string{ + "vsw-a", "vsw-b", "vsw-c", + }, + SecurityGroupIDs: []string{ + "sg-1", + }, + }, + }, + }, + expectedError: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: test.podAnnotations, + }, + } + + result, err := ParsePodNetworksFromAnnotation(pod) + if test.expectedError != "" { + assert.Error(t, err) + assert.Contains(t, err.Error(), test.expectedError) + } else { + assert.NoError(t, err) + assert.Equal(t, test.expectedResult, result) + } + }) + } +} + +func TestReturnsParsedAllocTypeWhenStringIsValidJSON(t *testing.T) { + input := `{"Type": "Fixed", "ReleaseStrategy": "Never", "ReleaseAfter": "5m"}` + expected := &v1beta1.AllocationType{ + Type: v1beta1.IPAllocTypeFixed, + ReleaseStrategy: v1beta1.ReleaseStrategyNever, + ReleaseAfter: "5m", + } + result, err := ParsePodIPType(input) + assert.NoError(t, err) + assert.Equal(t, expected, result) +} diff --git a/types/controlplane/config_test.go b/types/controlplane/config_test.go index 7b78c551..6864465d 100644 --- a/types/controlplane/config_test.go +++ b/types/controlplane/config_test.go @@ -17,9 +17,11 @@ limitations under the License. package controlplane import ( + "os" "testing" "github.com/go-playground/validator/v10" + "github.com/stretchr/testify/assert" ) func TestParseAndValidateCredential(t *testing.T) { @@ -146,3 +148,39 @@ func TestIsControllerEnabled(t *testing.T) { }) } } + +func TestParseAndValidate(t *testing.T) { + configFile, err := os.CreateTemp("", "") + assert.NoError(t, err) + defer os.Remove(configFile.Name()) + + err = os.WriteFile(configFile.Name(), []byte(`disableWebhook: true +regionID: "cn-hangzhou" +leaseLockName: "terway-controller-lock" +leaseLockNamespace: "kube-system" +controllerNamespace: "kube-system" +controllerName: "terway-controlplane" +metricsBindAddress: "127.0.0.1:9999" +healthzBindAddress: "0.0.0.0:8080" +clusterDomain: "cluster.local" +clusterID: foo +vpcID: bar +disableWebhook: true +webhookURLMode: true +leaderElection: true +webhookPort: 4443`), os.ModeType) + assert.NoError(t, err) + + credentialFilePath, err := os.CreateTemp("", "") + assert.NoError(t, err) + defer os.Remove(credentialFilePath.Name()) + + err = os.WriteFile(credentialFilePath.Name(), []byte(`accessKey: foo +accessSecret: bar`), os.ModeType) + assert.NoError(t, err) + + cfg, err := ParseAndValidate(configFile.Name(), credentialFilePath.Name()) + assert.NoError(t, err) + + assert.Equal(t, "cn-hangzhou", cfg.RegionID) +} diff --git a/types/daemon/config_test.go b/types/daemon/config_test.go index d2b8a6a3..58818472 100644 --- a/types/daemon/config_test.go +++ b/types/daemon/config_test.go @@ -6,6 +6,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/AliyunContainerService/terway/types" ) func Test_MergeConfigAndUnmarshal(t *testing.T) { @@ -105,3 +107,63 @@ func TestGetAddonSecret(t *testing.T) { assert.Equal(t, "key", ak) assert.Equal(t, "secret", sk) } + +func TestGetVSwitchIDsReturnsAllVSwitchIDs(t *testing.T) { + cfg := &Config{ + VSwitches: map[string][]string{ + "zone-a": {"vsw-1", "vsw-2"}, + "zone-b": {"vsw-3"}, + }, + } + vsws := cfg.GetVSwitchIDs() + assert.ElementsMatch(t, []string{"vsw-1", "vsw-2", "vsw-3"}, vsws) +} + +func TestGetVSwitchIDsReturnsEmptyWhenNoVSwitches(t *testing.T) { + cfg := &Config{ + VSwitches: map[string][]string{}, + } + vsws := cfg.GetVSwitchIDs() + assert.Empty(t, vsws) +} + +func TestGetExtraRoutesReturnsAllRoutes(t *testing.T) { + cfg := &Config{ + VSwitches: map[string][]string{ + "zone-a": {"vsw-1", "vsw-2"}, + "zone-b": {"vsw-3"}, + }, + } + routes := cfg.GetExtraRoutes() + assert.ElementsMatch(t, []string{"vsw-1", "vsw-2", "vsw-3"}, routes) +} + +func TestGetExtraRoutesReturnsEmptyWhenNoRoutes(t *testing.T) { + cfg := &Config{ + VSwitches: map[string][]string{}, + } + routes := cfg.GetExtraRoutes() + assert.Empty(t, routes) +} + +func TestPopulateSetsDefaultValues(t *testing.T) { + cfg := &Config{} + cfg.Populate() + assert.Equal(t, 1.0, cfg.EniCapRatio) + assert.Equal(t, VSwitchSelectionPolicyRandom, cfg.VSwitchSelectionPolicy) + assert.Equal(t, string(types.IPStackIPv4), cfg.IPStack) +} + +func TestPopulateDoesNotOverrideExistingValues(t *testing.T) { + cfg := &Config{ + EniCapRatio: 0.5, + VSwitchSelectionPolicy: "custom", + IPStack: string(types.IPStackDual), + } + cfg.Populate() + err := cfg.Validate() + assert.NoError(t, err) + assert.Equal(t, 0.5, cfg.EniCapRatio) + assert.Equal(t, "custom", cfg.VSwitchSelectionPolicy) + assert.Equal(t, string(types.IPStackDual), cfg.IPStack) +} diff --git a/types/daemon/dynamicconfig_test.go b/types/daemon/dynamicconfig_test.go new file mode 100644 index 00000000..43259de8 --- /dev/null +++ b/types/daemon/dynamicconfig_test.go @@ -0,0 +1,56 @@ +package daemon + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestConfigFromConfigMapReturnsErrorWhenBaseConfigMapNotFound(t *testing.T) { + client := fake.NewFakeClient() + _, err := ConfigFromConfigMap(context.Background(), client, "") + assert.Error(t, err) +} + +func TestConfigFromConfigMapReturnsErrorWhenBaseConfigIsEmpty(t *testing.T) { + client := fake.NewFakeClient(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "eni-config", + Namespace: "kube-system", + }, + Data: map[string]string{"eni_conf": ""}, + }) + _, err := ConfigFromConfigMap(context.Background(), client, "") + assert.Error(t, err) +} + +func TestConfigFromConfigMapReturnsConfigWhenNodeNameIsNotEmpty(t *testing.T) { + client := fake.NewFakeClient( + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "eni-config", + Namespace: "kube-system", + }, + Data: map[string]string{"eni_conf": `{"version": "1"}`}, + }, + ) + cfg, err := ConfigFromConfigMap(context.Background(), client, "node-1") + assert.NoError(t, err) + assert.Equal(t, "1", cfg.Version) +} + +func TestConfigFromConfigMapReturnsErrorWhenSecurityGroupsExceedLimit(t *testing.T) { + client := fake.NewFakeClient(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "eni-config", + Namespace: "kube-system", + }, + Data: map[string]string{"eni_conf": `{"security_groups": ["sg-1", "sg-2", "sg-3", "sg-4", "sg-5", "sg-6"]}`}, + }) + _, err := ConfigFromConfigMap(context.Background(), client, "") + assert.Error(t, err) +} diff --git a/types/daemon/res_test.go b/types/daemon/res_test.go new file mode 100644 index 00000000..5ce52836 --- /dev/null +++ b/types/daemon/res_test.go @@ -0,0 +1,50 @@ +package daemon + +import ( + "reflect" + "testing" +) + +// TestGetResourceItemByType tests the GetResourceItemByType method of PodResources. +func TestGetResourceItemByType(t *testing.T) { + tests := []struct { + name string + resType string + res []ResourceItem + expected []ResourceItem + }{ + { + name: "MatchingType", + resType: "network", + res: []ResourceItem{ + {Type: "network", ID: "1", ENIID: "eni-1", ENIMAC: "02:12:34:56:78:90", IPv4: "10.0.0.1", IPv6: "2001:0db8:85a3:0000:0000:8a2e:0700:7344"}, + {Type: "storage", ID: "2", ENIID: "eni-2", ENIMAC: "02:12:34:56:78:91", IPv4: "10.0.0.2", IPv6: "2001:0db8:85a3:0000:0000:8a2e:0700:7345"}, + }, + expected: []ResourceItem{ + {Type: "network", ID: "1", ENIID: "eni-1", ENIMAC: "02:12:34:56:78:90", IPv4: "10.0.0.1", IPv6: "2001:0db8:85a3:0000:0000:8a2e:0700:7344"}, + }, + }, + { + name: "MultipleMatchingTypes", + resType: "network", + res: []ResourceItem{ + {Type: "network", ID: "1", ENIID: "eni-1", ENIMAC: "02:12:34:56:78:90", IPv4: "10.0.0.1", IPv6: "2001:0db8:85a3:0000:0000:8a2e:0700:7344"}, + {Type: "network", ID: "2", ENIID: "eni-2", ENIMAC: "02:12:34:56:78:91", IPv4: "10.0.0.2", IPv6: "2001:0db8:85a3:0000:0000:8a2e:0700:7345"}, + }, + expected: []ResourceItem{ + {Type: "network", ID: "1", ENIID: "eni-1", ENIMAC: "02:12:34:56:78:90", IPv4: "10.0.0.1", IPv6: "2001:0db8:85a3:0000:0000:8a2e:0700:7344"}, + {Type: "network", ID: "2", ENIID: "eni-2", ENIMAC: "02:12:34:56:78:91", IPv4: "10.0.0.2", IPv6: "2001:0db8:85a3:0000:0000:8a2e:0700:7345"}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + podResources := PodResources{Resources: test.res} + result := podResources.GetResourceItemByType(test.resType) + if !reflect.DeepEqual(result, test.expected) { + t.Errorf("GetResourceItemByType(%s) = %v, want %v", test.resType, result, test.expected) + } + }) + } +} diff --git a/types/helper_test.go b/types/helper_test.go new file mode 100644 index 00000000..d3210ca9 --- /dev/null +++ b/types/helper_test.go @@ -0,0 +1,134 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/AliyunContainerService/terway/rpc" +) + +func TestBuildIPNet_EmptyInputs_ReturnsEmptyIPNetSet(t *testing.T) { + ipNetSet, err := BuildIPNet(nil, nil) + assert.NoError(t, err) + assert.NotNil(t, ipNetSet) + assert.Nil(t, ipNetSet.IPv4) + assert.Nil(t, ipNetSet.IPv6) +} + +func TestBuildIPNet_PartiallyEmptyInputs_ReturnsEmptyIPNetSet(t *testing.T) { + ip := &rpc.IPSet{IPv4: "192.168.1.1", IPv6: "2001:db8::1"} + ipNetSet, err := BuildIPNet(ip, nil) + assert.NoError(t, err) + assert.NotNil(t, ipNetSet) + assert.Nil(t, ipNetSet.IPv4) + assert.Nil(t, ipNetSet.IPv6) + + subnet := &rpc.IPSet{IPv4: "192.168.1.0/24", IPv6: "2001:db8::/64"} + ipNetSet, err = BuildIPNet(nil, subnet) + assert.NoError(t, err) + assert.NotNil(t, ipNetSet) + assert.Nil(t, ipNetSet.IPv4) + assert.Nil(t, ipNetSet.IPv6) +} + +func TestBuildIPNet_ValidInputs_ReturnsCorrectIPNetSet(t *testing.T) { + ip := &rpc.IPSet{IPv4: "192.168.1.1", IPv6: "2001:db8::1"} + subnet := &rpc.IPSet{IPv4: "192.168.1.0/24", IPv6: "2001:db8::/64"} + ipNetSet, err := BuildIPNet(ip, subnet) + assert.NoError(t, err) + assert.NotNil(t, ipNetSet) + assert.NotNil(t, ipNetSet.IPv4) + assert.NotNil(t, ipNetSet.IPv6) + assert.Equal(t, "192.168.1.1/24", ipNetSet.IPv4.String()) + assert.Equal(t, "2001:db8::1/64", ipNetSet.IPv6.String()) +} + +func TestBuildIPNet_InvalidIP_ReturnsError(t *testing.T) { + ip := &rpc.IPSet{IPv4: "invalid", IPv6: "2001:db8::1"} + subnet := &rpc.IPSet{IPv4: "192.168.1.0/24", IPv6: "2001:db8::/64"} + ipNetSet, err := BuildIPNet(ip, subnet) + assert.Error(t, err) + assert.Nil(t, ipNetSet) +} + +func TestBuildIPNet_InvalidSubnet_ReturnsError(t *testing.T) { + ip := &rpc.IPSet{IPv4: "192.168.1.1", IPv6: "2001:db8::1"} + subnet := &rpc.IPSet{IPv4: "invalid", IPv6: "2001:db8::/64"} + ipNetSet, err := BuildIPNet(ip, subnet) + assert.Error(t, err) + assert.Nil(t, ipNetSet) +} + +func TestBuildIPNet_OnlyIPv4_ReturnsCorrectIPNetSet(t *testing.T) { + ip := &rpc.IPSet{IPv4: "192.168.1.1"} + subnet := &rpc.IPSet{IPv4: "192.168.1.0/24"} + ipNetSet, err := BuildIPNet(ip, subnet) + assert.NoError(t, err) + assert.NotNil(t, ipNetSet) + assert.NotNil(t, ipNetSet.IPv4) + assert.Nil(t, ipNetSet.IPv6) + assert.Equal(t, "192.168.1.1/24", ipNetSet.IPv4.String()) +} + +func TestBuildIPNet_OnlyIPv6_ReturnsCorrectIPNetSet(t *testing.T) { + ip := &rpc.IPSet{IPv6: "2001:db8::1"} + subnet := &rpc.IPSet{IPv6: "2001:db8::/64"} + ipNetSet, err := BuildIPNet(ip, subnet) + assert.NoError(t, err) + assert.NotNil(t, ipNetSet) + assert.Nil(t, ipNetSet.IPv4) + assert.NotNil(t, ipNetSet.IPv6) + assert.Equal(t, "2001:db8::1/64", ipNetSet.IPv6.String()) +} + +func TestToIPNetSetReturnsErrorWhenIPIsNil(t *testing.T) { + ipNetSet, err := ToIPNetSet(nil) + assert.Error(t, err) + assert.Nil(t, ipNetSet) +} + +func TestToIPNetSetReturnsCorrectIPNetSetWhenValidIPv4(t *testing.T) { + ip := &rpc.IPSet{IPv4: "192.168.1.0/24"} + ipNetSet, err := ToIPNetSet(ip) + assert.NoError(t, err) + assert.NotNil(t, ipNetSet) + assert.NotNil(t, ipNetSet.IPv4) + assert.Nil(t, ipNetSet.IPv6) + assert.Equal(t, "192.168.1.0/24", ipNetSet.IPv4.String()) +} + +func TestToIPNetSetReturnsCorrectIPNetSetWhenValidIPv6(t *testing.T) { + ip := &rpc.IPSet{IPv6: "2001:db8::/64"} + ipNetSet, err := ToIPNetSet(ip) + assert.NoError(t, err) + assert.NotNil(t, ipNetSet) + assert.Nil(t, ipNetSet.IPv4) + assert.NotNil(t, ipNetSet.IPv6) + assert.Equal(t, "2001:db8::/64", ipNetSet.IPv6.String()) +} + +func TestToIPNetSetReturnsCorrectIPNetSetWhenValidIPv4AndIPv6(t *testing.T) { + ip := &rpc.IPSet{IPv4: "192.168.1.0/24", IPv6: "2001:db8::/64"} + ipNetSet, err := ToIPNetSet(ip) + assert.NoError(t, err) + assert.NotNil(t, ipNetSet) + assert.NotNil(t, ipNetSet.IPv4) + assert.NotNil(t, ipNetSet.IPv6) + assert.Equal(t, "192.168.1.0/24", ipNetSet.IPv4.String()) + assert.Equal(t, "2001:db8::/64", ipNetSet.IPv6.String()) +} + +func TestToIPNetSetReturnsErrorWhenInvalidIPv4(t *testing.T) { + ip := &rpc.IPSet{IPv4: "invalid"} + ipNetSet, err := ToIPNetSet(ip) + assert.Error(t, err) + assert.Nil(t, ipNetSet) +} + +func TestToIPNetSetReturnsErrorWhenInvalidIPv6(t *testing.T) { + ip := &rpc.IPSet{IPv6: "invalid"} + ipNetSet, err := ToIPNetSet(ip) + assert.Error(t, err) + assert.Nil(t, ipNetSet) +} diff --git a/types/secret/secret_test.go b/types/secret/secret_test.go new file mode 100644 index 00000000..7a2813af --- /dev/null +++ b/types/secret/secret_test.go @@ -0,0 +1,32 @@ +package secret + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStringReturnsMaskedValue(t *testing.T) { + s := Secret("mysecret") + assert.Equal(t, "******", s.String()) + assert.Equal(t, "mysecret", string((s))) +} + +func TestGoStringReturnsMaskedValue(t *testing.T) { + s := Secret("mysecret") + assert.Equal(t, "******", s.GoString()) +} + +func TestMarshalJSONReturnsMaskedValue(t *testing.T) { + s := Secret("mysecret") + json, err := s.MarshalJSON() + assert.NoError(t, err) + assert.Equal(t, `"******"`, string(json)) +} + +func TestMarshalJSONHandlesEmptySecret(t *testing.T) { + s := Secret("") + json, err := s.MarshalJSON() + assert.NoError(t, err) + assert.Equal(t, `"******"`, string(json)) +} diff --git a/types/types_test.go b/types/types_test.go index b97c457b..2a5e44ee 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -2,10 +2,12 @@ package types_test import ( "fmt" + "net/netip" "testing" "github.com/stretchr/testify/assert" + "github.com/AliyunContainerService/terway/rpc" "github.com/AliyunContainerService/terway/types" ) @@ -59,3 +61,146 @@ func TestErrorUnwrapReturnsNilWhenNoUnderlyingError(t *testing.T) { assert.Nil(t, err.Unwrap()) } + +func TestIPSet2_String(t *testing.T) { + tests := []struct { + name string + ipset2 types.IPSet2 + expected string + }{ + { + name: "IPv4 and IPv6 valid", + ipset2: types.IPSet2{IPv4: netip.MustParseAddr("192.0.2.1"), IPv6: netip.MustParseAddr("2001:db8::1")}, + expected: "192.0.2.1-2001:db8::1", + }, + { + name: "Only IPv4 valid", + ipset2: types.IPSet2{IPv4: netip.MustParseAddr("192.0.2.1")}, + expected: "192.0.2.1", + }, + { + name: "Only IPv6 valid", + ipset2: types.IPSet2{IPv6: netip.MustParseAddr("2001:db8::1")}, + expected: "2001:db8::1", + }, + { + name: "Both invalid", + ipset2: types.IPSet2{}, + expected: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := test.ipset2.String() + assert.Equal(t, test.expected, result) + }) + } +} + +func TestIPSet2_ToRPC(t *testing.T) { + tests := []struct { + name string + ipset2 types.IPSet2 + expected *rpc.IPSet + }{ + { + name: "IPv4 and IPv6 valid", + ipset2: types.IPSet2{ + IPv4: netip.MustParseAddr("192.0.2.1"), + IPv6: netip.MustParseAddr("2001:db8::1"), + }, + expected: &rpc.IPSet{ + IPv4: "192.0.2.1", + IPv6: "2001:db8::1", + }, + }, + { + name: "Only IPv4 valid", + ipset2: types.IPSet2{ + IPv4: netip.MustParseAddr("192.0.2.1"), + }, + expected: &rpc.IPSet{ + IPv4: "192.0.2.1", + IPv6: "", + }, + }, + { + name: "Only IPv6 valid", + ipset2: types.IPSet2{ + IPv6: netip.MustParseAddr("2001:db8::1"), + }, + expected: &rpc.IPSet{ + IPv4: "", + IPv6: "2001:db8::1", + }, + }, + { + name: "Both invalid", + ipset2: types.IPSet2{}, + expected: &rpc.IPSet{ + IPv4: "", + IPv6: "", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := test.ipset2.ToRPC() + assert.Equal(t, test.expected, result) + }) + } +} + +func TestIPSet2_GetIPv4(t *testing.T) { + tests := []struct { + name string + ipset2 types.IPSet2 + expected string + }{ + { + name: "IPv4 valid", + ipset2: types.IPSet2{IPv4: netip.MustParseAddr("192.0.2.1")}, + expected: "192.0.2.1", + }, + { + name: "IPv4 invalid", + ipset2: types.IPSet2{}, + expected: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := test.ipset2.GetIPv4() + assert.Equal(t, test.expected, result) + }) + } +} + +func TestIPSet2_GetIPv6(t *testing.T) { + tests := []struct { + name string + ipset2 types.IPSet2 + expected string + }{ + { + name: "IPv6 valid", + ipset2: types.IPSet2{IPv6: netip.MustParseAddr("2001:db8::1")}, + expected: "2001:db8::1", + }, + { + name: "IPv6 invalid", + ipset2: types.IPSet2{}, + expected: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := test.ipset2.GetIPv6() + assert.Equal(t, test.expected, result) + }) + } +}