From 7cc0432703df6a33e19eeff6f66f69d0b11979da Mon Sep 17 00:00:00 2001 From: l1b0k Date: Wed, 4 Sep 2024 14:27:58 +0800 Subject: [PATCH] update tests Signed-off-by: l1b0k --- pkg/controller/multi-ip/pod/pod_test.go | 124 ++++++++++++++ pkg/controller/multi-ip/pod/suite_test.go | 88 ++++++++++ pkg/controller/webhook/validate.go | 10 +- pkg/controller/webhook/validate_test.go | 198 ++++++++++++++++++++++ types/help_test.go | 65 +++++++ 5 files changed, 479 insertions(+), 6 deletions(-) create mode 100644 pkg/controller/multi-ip/pod/pod_test.go create mode 100644 pkg/controller/multi-ip/pod/suite_test.go create mode 100644 pkg/controller/webhook/validate_test.go create mode 100644 types/help_test.go diff --git a/pkg/controller/multi-ip/pod/pod_test.go b/pkg/controller/multi-ip/pod/pod_test.go new file mode 100644 index 00000000..ba0b512e --- /dev/null +++ b/pkg/controller/multi-ip/pod/pod_test.go @@ -0,0 +1,124 @@ +package pod + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Pod Controller", func() { + ctx := context.Background() + + BeforeEach(func() { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "default"}, + Spec: corev1.PodSpec{ + NodeName: "node-az-1", + Containers: []corev1.Container{ + { + Name: "foo", + Image: "busybox", + }, + }, + }, + } + err := k8sClient.Create(ctx, pod) + Expect(err).NotTo(HaveOccurred()) + }) + AfterEach(func() { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "default"}, + } + err := k8sClient.Delete(ctx, pod) + Expect(err).NotTo(HaveOccurred()) + }) + + Context("Reconcile", func() { + It("Reconcile should succeed", func() { + n := &ReconcilePod{ + client: k8sClient, + } + _, err := n.Reconcile(ctx, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "pod", + }, + }) + + Expect(err).To(BeNil()) + }) + }) +}) + +var _ = Describe("NeedProcess Function", func() { + var pod *corev1.Pod + + BeforeEach(func() { + pod = &corev1.Pod{ + Spec: corev1.PodSpec{ + NodeName: "node1", + }, + } + }) + + Context("When object is not a Pod", func() { + It("returns false", func() { + obj := &corev1.Service{} + result := needProcess(obj) + Expect(result).To(BeFalse()) + }) + }) + + Context("When Pod has no NodeName", func() { + It("returns false", func() { + pod.Spec.NodeName = "" + result := needProcess(pod) + Expect(result).To(BeFalse()) + }) + }) + + Context("When Pod uses HostNetwork", func() { + It("returns false", func() { + pod.Spec.HostNetwork = true + result := needProcess(pod) + Expect(result).To(BeFalse()) + }) + }) + + Context("When Pod is ignored by Terway", func() { + It("returns false", func() { + pod.ObjectMeta.Labels = map[string]string{"k8s.aliyun.com/ignore-by-terway": "true"} + result := needProcess(pod) + Expect(result).To(BeFalse()) + }) + }) + + Context("When Pod uses ENI", func() { + It("returns false", func() { + pod.ObjectMeta.Annotations = map[string]string{"k8s.aliyun.com/pod-eni": "true"} + result := needProcess(pod) + Expect(result).To(BeFalse()) + }) + }) + + Context("When Pod sandbox not exited and has PodIP", func() { + It("returns false", func() { + pod.Status.PodIP = "192.168.1.1" + result := needProcess(pod) + Expect(result).To(BeFalse()) + }) + }) + + Context("When Pod is ready to process", func() { + It("returns true", func() { + result := needProcess(pod) + Expect(result).To(BeTrue()) + }) + }) +}) diff --git a/pkg/controller/multi-ip/pod/suite_test.go b/pkg/controller/multi-ip/pod/suite_test.go new file mode 100644 index 00000000..81710c14 --- /dev/null +++ b/pkg/controller/multi-ip/pod/suite_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2024. + +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 pod + +import ( + "fmt" + "path/filepath" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + networkv1beta1 "github.com/AliyunContainerService/terway/pkg/apis/network.alibabacloud.com/v1beta1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "apis", "crds")}, + ErrorIfCRDPathMissing: true, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", + fmt.Sprintf("1.29.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = networkv1beta1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/pkg/controller/webhook/validate.go b/pkg/controller/webhook/validate.go index 40001d4c..e152ee16 100644 --- a/pkg/controller/webhook/validate.go +++ b/pkg/controller/webhook/validate.go @@ -47,12 +47,10 @@ func ValidateHook() *webhook.Admission { return admission.Denied("security group can not more than 5") } - if podNetworking.Spec.AllocationType.ReleaseStrategy == v1beta1.IPAllocTypeFixed { - if podNetworking.Spec.AllocationType.ReleaseStrategy == v1beta1.ReleaseStrategyTTL { - _, err = time.ParseDuration(podNetworking.Spec.AllocationType.ReleaseAfter) - if err != nil { - return webhook.Denied(fmt.Sprintf("invalid releaseAfter %s", podNetworking.Spec.AllocationType.ReleaseAfter)) - } + if podNetworking.Spec.AllocationType.ReleaseStrategy == v1beta1.ReleaseStrategyTTL { + _, err = time.ParseDuration(podNetworking.Spec.AllocationType.ReleaseAfter) + if err != nil { + return webhook.Denied(fmt.Sprintf("invalid releaseAfter %s", podNetworking.Spec.AllocationType.ReleaseAfter)) } } return webhook.Allowed("checked") diff --git a/pkg/controller/webhook/validate_test.go b/pkg/controller/webhook/validate_test.go new file mode 100644 index 00000000..f6a4c0f1 --- /dev/null +++ b/pkg/controller/webhook/validate_test.go @@ -0,0 +1,198 @@ +package webhook + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + "github.com/AliyunContainerService/terway/pkg/apis/network.alibabacloud.com/v1beta1" +) + +func TestValidateHookAllowsWhenKindIsNotPodNetworking(t *testing.T) { + req := webhook.AdmissionRequest{ + AdmissionRequest: v1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{ + Group: "", + Version: "", + Kind: "Foo", + }, + }, + } + resp := ValidateHook().Handle(context.Background(), req) + assert.True(t, resp.Allowed) + assert.Equal(t, "not care", resp.Result.Message) +} + +func TestValidateHookDeniesWhenPodSelectorAndNamespaceSelectorAreNil(t *testing.T) { + podNetworking := &v1beta1.PodNetworking{ + Spec: v1beta1.PodNetworkingSpec{ + Selector: v1beta1.Selector{}, + }, + } + raw, _ := json.Marshal(podNetworking) + req := webhook.AdmissionRequest{ + AdmissionRequest: v1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{ + Group: "", + Version: "", + Kind: "PodNetworking", + }, + Object: runtime.RawExtension{ + Raw: raw, + }, + }, + } + resp := ValidateHook().Handle(context.Background(), req) + assert.False(t, resp.Allowed) + assert.Equal(t, "neither the PodSelector nor the NamespaceSelector is set", resp.Result.Message) +} + +func TestValidateHookDeniesWhenVSwitchOptionsIsEmpty(t *testing.T) { + podNetworking := &v1beta1.PodNetworking{ + Spec: v1beta1.PodNetworkingSpec{ + Selector: v1beta1.Selector{ + PodSelector: &metav1.LabelSelector{}, + }, + }, + } + raw, _ := json.Marshal(podNetworking) + req := webhook.AdmissionRequest{ + AdmissionRequest: v1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{ + Group: "", + Version: "", + Kind: "PodNetworking", + }, + Object: runtime.RawExtension{ + Raw: raw, + }, + }, + } + resp := ValidateHook().Handle(context.Background(), req) + assert.False(t, resp.Allowed) + assert.Equal(t, "vSwitchOptions is not set", resp.Result.Message) +} + +func TestValidateHookDeniesWhenSecurityGroupIDsIsEmpty(t *testing.T) { + podNetworking := &v1beta1.PodNetworking{ + Spec: v1beta1.PodNetworkingSpec{ + Selector: v1beta1.Selector{ + PodSelector: &metav1.LabelSelector{}, + }, + VSwitchOptions: []string{"vsw-123"}, + }, + } + raw, _ := json.Marshal(podNetworking) + req := webhook.AdmissionRequest{ + AdmissionRequest: v1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{ + Group: "", + Version: "", + Kind: "PodNetworking", + }, + Object: runtime.RawExtension{ + Raw: raw, + }, + }, + } + resp := ValidateHook().Handle(context.Background(), req) + assert.False(t, resp.Allowed) + assert.Equal(t, "security group is not set", resp.Result.Message) +} + +func TestValidateHookDeniesWhenSecurityGroupIDsExceedsLimit(t *testing.T) { + podNetworking := &v1beta1.PodNetworking{ + Spec: v1beta1.PodNetworkingSpec{ + Selector: v1beta1.Selector{ + PodSelector: &metav1.LabelSelector{}, + }, + VSwitchOptions: []string{"vsw-123"}, + SecurityGroupIDs: []string{"sg-1", "sg-2", "sg-3", "sg-4", "sg-5", "sg-6"}, + }, + } + raw, _ := json.Marshal(podNetworking) + req := webhook.AdmissionRequest{ + AdmissionRequest: v1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{ + Group: "", + Version: "", + Kind: "PodNetworking", + }, + Object: runtime.RawExtension{ + Raw: raw, + }, + }, + } + resp := ValidateHook().Handle(context.Background(), req) + assert.False(t, resp.Allowed) + assert.Equal(t, "security group can not more than 5", resp.Result.Message) +} + +func TestValidateHookDeniesWhenReleaseAfterIsInvalid(t *testing.T) { + podNetworking := &v1beta1.PodNetworking{ + Spec: v1beta1.PodNetworkingSpec{ + Selector: v1beta1.Selector{ + PodSelector: &metav1.LabelSelector{}, + }, + VSwitchOptions: []string{"vsw-123"}, + SecurityGroupIDs: []string{"sg-1"}, + AllocationType: v1beta1.AllocationType{ + ReleaseStrategy: v1beta1.ReleaseStrategyTTL, + ReleaseAfter: "invalid-duration", + }, + }, + } + raw, _ := json.Marshal(podNetworking) + req := webhook.AdmissionRequest{ + AdmissionRequest: v1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{ + Group: "", + Version: "", + Kind: "PodNetworking", + }, + Object: runtime.RawExtension{ + Raw: raw, + }, + }, + } + resp := ValidateHook().Handle(context.Background(), req) + assert.False(t, resp.Allowed) +} + +func TestValidateHookAllowsWhenAllConditionsAreMet(t *testing.T) { + podNetworking := &v1beta1.PodNetworking{ + Spec: v1beta1.PodNetworkingSpec{ + Selector: v1beta1.Selector{ + PodSelector: &metav1.LabelSelector{}, + }, + VSwitchOptions: []string{"vsw-123"}, + SecurityGroupIDs: []string{"sg-1"}, + AllocationType: v1beta1.AllocationType{ + ReleaseStrategy: v1beta1.ReleaseStrategyTTL, + ReleaseAfter: "1h", + }, + }, + } + raw, _ := json.Marshal(podNetworking) + req := webhook.AdmissionRequest{ + AdmissionRequest: v1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{ + Group: "", + Version: "", + Kind: "PodNetworking", + }, + Object: runtime.RawExtension{ + Raw: raw, + }, + }, + } + resp := ValidateHook().Handle(context.Background(), req) + assert.True(t, resp.Allowed) + assert.Equal(t, "checked", resp.Result.Message) +} diff --git a/types/help_test.go b/types/help_test.go new file mode 100644 index 00000000..24d112b7 --- /dev/null +++ b/types/help_test.go @@ -0,0 +1,65 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/AliyunContainerService/terway/rpc" +) + +func TestBuildIPNetReturnsEmptyWhenIPAndSubnetAreNil(t *testing.T) { + ipnet, err := BuildIPNet(nil, nil) + assert.NoError(t, err) + assert.NotNil(t, ipnet) + assert.Nil(t, ipnet.IPv4) + assert.Nil(t, ipnet.IPv6) +} + +func TestBuildIPNetReturnsErrorWhenIPv4IsInvalid(t *testing.T) { + ip := &rpc.IPSet{IPv4: "invalid-ip"} + subnet := &rpc.IPSet{IPv4: "192.168.0.0/24"} + _, err := BuildIPNet(ip, subnet) + assert.Error(t, err) +} + +func TestBuildIPNetReturnsErrorWhenIPv6IsInvalid(t *testing.T) { + ip := &rpc.IPSet{IPv6: "invalid-ip"} + subnet := &rpc.IPSet{IPv6: "fd00::/64"} + _, err := BuildIPNet(ip, subnet) + assert.Error(t, err) +} + +func TestBuildIPNetReturnsErrorWhenSubnetIPv4IsInvalid(t *testing.T) { + ip := &rpc.IPSet{IPv4: "192.168.0.1"} + subnet := &rpc.IPSet{IPv4: "invalid-subnet"} + _, err := BuildIPNet(ip, subnet) + assert.Error(t, err) +} + +func TestBuildIPNetReturnsErrorWhenSubnetIPv6IsInvalid(t *testing.T) { + ip := &rpc.IPSet{IPv6: "fd00::1"} + subnet := &rpc.IPSet{IPv6: "invalid-subnet"} + _, err := BuildIPNet(ip, subnet) + assert.Error(t, err) +} + +func TestBuildIPNetReturnsCorrectIPNetForValidIPv4(t *testing.T) { + ip := &rpc.IPSet{IPv4: "192.168.0.1"} + subnet := &rpc.IPSet{IPv4: "192.168.0.0/24"} + ipnet, err := BuildIPNet(ip, subnet) + assert.NoError(t, err) + assert.NotNil(t, ipnet.IPv4) + assert.Equal(t, "192.168.0.1/24", ipnet.IPv4.String()) + assert.Nil(t, ipnet.IPv6) +} + +func TestBuildIPNetReturnsCorrectIPNetForValidIPv6(t *testing.T) { + ip := &rpc.IPSet{IPv6: "fd00::1"} + subnet := &rpc.IPSet{IPv6: "fd00::/64"} + ipnet, err := BuildIPNet(ip, subnet) + assert.NoError(t, err) + assert.NotNil(t, ipnet.IPv6) + assert.Equal(t, "fd00::1/64", ipnet.IPv6.String()) + assert.Nil(t, ipnet.IPv4) +}