From 235c337853f27ef8dc81a6220f81e5df692a1a65 Mon Sep 17 00:00:00 2001 From: Jian Qiu Date: Sat, 6 Jun 2020 10:07:41 +0800 Subject: [PATCH] Fix issue when externalserver is not set --- go.sum | 1 + .../klusterlet-registration-deployment.yaml | 2 + pkg/operators/klusterlet/bindata/bindata.go | 2 + pkg/operators/klusterlet/controller.go | 26 +- pkg/operators/klusterlet/controller_test.go | 164 +++++++++++++ .../{hub_test.go => clustermanager_test.go} | 4 +- test/integration/klusterlet_test.go | 225 ++++++++++++++++++ test/integration/util/assertion.go | 37 +++ test/integration/util/util.go | 20 ++ 9 files changed, 469 insertions(+), 12 deletions(-) rename test/integration/{hub_test.go => clustermanager_test.go} (97%) create mode 100644 test/integration/klusterlet_test.go create mode 100644 test/integration/util/assertion.go diff --git a/go.sum b/go.sum index dcce5366b..10c843388 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,7 @@ github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE= github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= diff --git a/manifests/klusterlet/klusterlet-registration-deployment.yaml b/manifests/klusterlet/klusterlet-registration-deployment.yaml index a88f36741..424cb1897 100644 --- a/manifests/klusterlet/klusterlet-registration-deployment.yaml +++ b/manifests/klusterlet/klusterlet-registration-deployment.yaml @@ -25,7 +25,9 @@ spec: - "agent" - "--cluster-name={{ .ClusterName }}" - "--bootstrap-kubeconfig=/spoke/bootstrap/kubeconfig" + {{if .ExternalServerURL}} - "--spoke-external-server-urls={{ .ExternalServerURL }}" + {{end}} volumeMounts: - name: bootstrap-secret mountPath: "/spoke/bootstrap" diff --git a/pkg/operators/klusterlet/bindata/bindata.go b/pkg/operators/klusterlet/bindata/bindata.go index 762810751..3b23b6ba5 100644 --- a/pkg/operators/klusterlet/bindata/bindata.go +++ b/pkg/operators/klusterlet/bindata/bindata.go @@ -150,7 +150,9 @@ spec: - "agent" - "--cluster-name={{ .ClusterName }}" - "--bootstrap-kubeconfig=/spoke/bootstrap/kubeconfig" + {{if .ExternalServerURL}} - "--spoke-external-server-urls={{ .ExternalServerURL }}" + {{end}} volumeMounts: - name: bootstrap-secret mountPath: "/spoke/bootstrap" diff --git a/pkg/operators/klusterlet/controller.go b/pkg/operators/klusterlet/controller.go index dd6cac3bf..36274d481 100644 --- a/pkg/operators/klusterlet/controller.go +++ b/pkg/operators/klusterlet/controller.go @@ -251,6 +251,14 @@ func (n *klusterletController) sync(ctx context.Context, controllerContext facto // TODO store this in the status of the klusterlet itself n.registrationGeneration = generation + // If cluster name is empty, read cluster name from hub config secret + if config.ClusterName == "" { + clusterName := hubSecret.Data["cluster-name"] + if clusterName != nil { + config.ClusterName = string(clusterName) + } + } + // Deploy work agent generation, err = helpers.ApplyDeployment( n.kubeClient, @@ -281,24 +289,20 @@ func (n *klusterletController) sync(ctx context.Context, controllerContext facto // TODO this should be moved into a separate loop since it is independent of the application of the eventually consistent // resources above - // If cluster name is empty, read cluster name from hub config secret + // If cluster name is empty, return err if config.ClusterName == "" { - clusterName := hubSecret.Data["cluster-name"] - if clusterName == nil { - helpers.UpdateKlusterletStatus(ctx, n.klusterletClient, klusterletName, helpers.UpdateKlusterletConditionFn(operatorapiv1.StatusCondition{ - Type: klusterletRegistrationDegraded, Status: metav1.ConditionTrue, Reason: "ClusterNameMissing", - Message: fmt.Sprintf("Failed to get cluster name from `kubectl get secret -n %q %q -ojsonpath='{.data.cluster-name}`. This is set by the klusterlet registration deployment.", hubSecret.Namespace, hubSecret.Name), - })) - return fmt.Errorf("Failed to get cluster name") - } - config.ClusterName = string(clusterName) + helpers.UpdateKlusterletStatus(ctx, n.klusterletClient, klusterletName, helpers.UpdateKlusterletConditionFn(operatorapiv1.StatusCondition{ + Type: klusterletRegistrationDegraded, Status: metav1.ConditionTrue, Reason: "ClusterNameMissing", + Message: fmt.Sprintf("Failed to get cluster name from `kubectl get secret -n %q %q -ojsonpath='{.data.cluster-name}`. This is set by the klusterlet registration deployment.", hubSecret.Namespace, hubSecret.Name), + })) + return fmt.Errorf("Failed to get cluster name") } // If hub kubeconfig does not exist, return err. if hubSecret.Data["kubeconfig"] == nil { helpers.UpdateKlusterletStatus(ctx, n.klusterletClient, klusterletName, helpers.UpdateKlusterletConditionFn(operatorapiv1.StatusCondition{ Type: klusterletRegistrationDegraded, Status: metav1.ConditionTrue, Reason: "HubKubeconfigMissing", - Message: fmt.Sprintf("Failed to get cluster name from `kubectl get secret -n %q %q -ojsonpath='{.data.kubeconfig}`. This is set by the klusterlet registration deployment, but the CSR must be approved by the cluster-admin on the hub.", hubSecret.Namespace, hubSecret.Name), + Message: fmt.Sprintf("Failed to get kubeconfig from `kubectl get secret -n %q %q -ojsonpath='{.data.kubeconfig}`. This is set by the klusterlet registration deployment, but the CSR must be approved by the cluster-admin on the hub.", hubSecret.Namespace, hubSecret.Name), })) return fmt.Errorf("Failed to get kubeconfig from hub kubeconfig secret") } diff --git a/pkg/operators/klusterlet/controller_test.go b/pkg/operators/klusterlet/controller_test.go index 45cc5eb5d..80e27b0ce 100644 --- a/pkg/operators/klusterlet/controller_test.go +++ b/pkg/operators/klusterlet/controller_test.go @@ -22,6 +22,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" fakekube "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" + "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" ) @@ -29,6 +30,7 @@ type testController struct { controller *klusterletController kubeClient *fakekube.Clientset operatorClient *fakeoperatorclient.Clientset + operatorStore cache.Store } type fakeSyncContext struct { @@ -101,6 +103,7 @@ func newTestController(klusterlet *opratorapiv1.Klusterlet, objects ...runtime.O controller: hubController, kubeClient: fakeKubeClient, operatorClient: fakeOperatorClient, + operatorStore: store, } } @@ -154,6 +157,95 @@ func ensureNameNamespace(t *testing.T, actualName, actualNamespace, name, namesp } } +func ensureDeployments(t *testing.T, actions []clienttesting.Action, verb, serverURL, registrationClusterName, workClusterName string) { + deployments := []*appsv1.Deployment{} + for _, action := range actions { + if action.GetVerb() != verb || action.GetResource().Resource != "deployments" { + continue + } + + if verb == "create" { + object := action.(clienttesting.CreateActionImpl).Object + deployments = append(deployments, object.(*appsv1.Deployment)) + } + + if verb == "update" { + object := action.(clienttesting.UpdateActionImpl).Object + deployments = append(deployments, object.(*appsv1.Deployment)) + } + } + + if len(deployments) != 2 { + t.Errorf("Expect %s 2 deployment, actual %d", verb, len(deployments)) + } + + for _, deployment := range deployments { + if strings.HasSuffix(deployment.Name, "registration-agent") { + ensureRegistrationDeployment(t, deployment, serverURL, registrationClusterName) + } else if strings.HasSuffix(deployment.Name, "work-agent") { + ensureWorkDeployment(t, deployment, workClusterName) + } else { + t.Errorf("Unexpected deployment name %s", deployment.Name) + } + } +} + +func ensureRegistrationDeployment(t *testing.T, deployment *appsv1.Deployment, serverURL, clusterName string) { + if len(deployment.Spec.Template.Spec.Containers) != 1 { + t.Errorf("Expect 1 containers in deployment spec, actual %d", len(deployment.Spec.Template.Spec.Containers)) + } + args := deployment.Spec.Template.Spec.Containers[0].Args + if serverURL == "" && len(args) != 4 { + t.Errorf("Expect 4 args in container spec, actual %d", len(args)) + } + if serverURL != "" && len(deployment.Spec.Template.Spec.Containers[0].Args) != 5 { + t.Errorf("Expect 5 args in container spec, actual %d", len(args)) + } + clusterNameArg := "" + serverURLArg := "" + for _, arg := range args { + if strings.HasPrefix(arg, "--cluster-name=") { + clusterNameArg = arg + } + if strings.HasPrefix(arg, "--spoke-external-server-urls=") { + serverURLArg = arg + } + } + + desiredServerURLArg := "" + if serverURL != "" { + desiredServerURLArg = fmt.Sprintf("--spoke-external-server-urls=%s", serverURL) + } + if serverURLArg != desiredServerURLArg { + t.Errorf("Server url args not correct, expect %q, actual %q", desiredServerURLArg, serverURLArg) + } + + desiredClusterNameArg := fmt.Sprintf("--cluster-name=%s", clusterName) + if clusterNameArg != desiredClusterNameArg { + t.Errorf("Cluster name arg not correct, expect %q, actual %q", desiredClusterNameArg, clusterNameArg) + } +} + +func ensureWorkDeployment(t *testing.T, deployment *appsv1.Deployment, clusterName string) { + if len(deployment.Spec.Template.Spec.Containers) != 1 { + t.Errorf("Expect 1 containers in deployment spec, actual %d", len(deployment.Spec.Template.Spec.Containers)) + } + args := deployment.Spec.Template.Spec.Containers[0].Args + if len(args) != 4 { + t.Errorf("Expect 4 args in container spec, actual %d", len(args)) + } + clusterNameArg := "" + for _, arg := range args { + if strings.HasPrefix(arg, "--spoke-cluster-name") { + clusterNameArg = arg + } + } + desiredClusterNameArg := fmt.Sprintf("--spoke-cluster-name=%s", clusterName) + if desiredClusterNameArg != clusterNameArg { + t.Errorf("Expect cluster namee arg is %q, actual %q", desiredClusterNameArg, clusterNameArg) + } +} + func ensureObject(t *testing.T, object runtime.Object, klusterlet *opratorapiv1.Klusterlet) { access, err := meta.Accessor(object) if err != nil { @@ -390,3 +482,75 @@ func TestGetServersFromKlusterlet(t *testing.T) { }) } } + +func TestClusterNameChange(t *testing.T) { + klusterlet := newKlusterlet("klusterlet", "testns", "cluster1") + namespace := newNamespace("testns") + bootStrapSecret := newSecret(bootstrapHubKubeConfigSecret, "testns") + hubSecret := newSecret(hubKubeConfigSecret, "testns") + hubSecret.Data["kubeconfig"] = []byte("dummuykubeconnfig") + hubSecret.Data["cluster-name"] = []byte("cluster1") + controller := newTestController(klusterlet, bootStrapSecret, hubSecret, namespace) + syncContext := newFakeSyncContext(t, "klusterlet") + err := controller.controller.sync(nil, syncContext) + if err != nil { + t.Errorf("Expected non error when sync, %v", err) + } + + // Check if deployment has the right cluster name set + ensureDeployments(t, controller.kubeClient.Actions(), "create", "", "cluster1", "cluster1") + + // Update klusterlet with unset cluster name and rerun sync + controller.kubeClient.ClearActions() + klusterlet = newKlusterlet("klusterlet", "testns", "") + controller.operatorStore.Update(klusterlet) + // Set generation to another number so we can force the update, we will read generatioin from status in the future + controller.controller.registrationGeneration = 100 + controller.controller.workGeneration = 100 + + err = controller.controller.sync(nil, syncContext) + if err != nil { + t.Errorf("Expected non error when sync, %v", err) + } + ensureDeployments(t, controller.kubeClient.Actions(), "update", "", "", "cluster1") + + // Update hubconfigsecret and sync again + hubSecret.Data["cluster-name"] = []byte("cluster2") + controller.kubeClient.PrependReactor("get", "secrets", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { + if action.GetVerb() != "get" { + return false, nil, nil + } + + getAction := action.(clienttesting.GetActionImpl) + if getAction.Name != hubKubeConfigSecret { + return false, nil, errors.NewNotFound( + corev1.Resource("secrets"), hubKubeConfigSecret) + } + return true, hubSecret, nil + }) + controller.kubeClient.ClearActions() + // Set generation to another number so we can force the update, we will read generatioin from status in the future + controller.controller.registrationGeneration = 100 + controller.controller.workGeneration = 100 + + err = controller.controller.sync(nil, syncContext) + if err != nil { + t.Errorf("Expected non error when sync, %v", err) + } + ensureDeployments(t, controller.kubeClient.Actions(), "update", "", "", "cluster2") + + // Update klusterlet with different cluster name and rerun sync + klusterlet = newKlusterlet("klusterlet", "testns", "cluster3") + klusterlet.Spec.ExternalServerURLs = []opratorapiv1.ServerURL{{URL: "https://localhost"}} + controller.kubeClient.ClearActions() + controller.operatorStore.Update(klusterlet) + // Set generation to another number so we can force the update, we will read generatioin from status in the future + controller.controller.registrationGeneration = 100 + controller.controller.workGeneration = 100 + + err = controller.controller.sync(nil, syncContext) + if err != nil { + t.Errorf("Expected non error when sync, %v", err) + } + ensureDeployments(t, controller.kubeClient.Actions(), "update", "https://localhost", "cluster3", "cluster3") +} diff --git a/test/integration/hub_test.go b/test/integration/clustermanager_test.go similarity index 97% rename from test/integration/hub_test.go rename to test/integration/clustermanager_test.go index 3de64e2e1..cfb6950bd 100644 --- a/test/integration/hub_test.go +++ b/test/integration/clustermanager_test.go @@ -26,7 +26,7 @@ func startHubOperator(ctx context.Context) { gomega.Expect(err).NotTo(gomega.HaveOccurred()) } -var _ = ginkgo.Describe("HubCore", func() { +var _ = ginkgo.Describe("ClusterManager", func() { var cancel context.CancelFunc var err error var clusterManagerName string @@ -168,6 +168,8 @@ var _ = ginkgo.Describe("HubCore", func() { return true }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + util.AssertClusterManagerCondition(clusterManagerName, operatorClient, "Applied", metav1.ConditionTrue, eventuallyTimeout, eventuallyInterval) + err := operatorClient.OperatorV1().ClusterManagers().Delete(context.Background(), clusterManagerName, metav1.DeleteOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) diff --git a/test/integration/klusterlet_test.go b/test/integration/klusterlet_test.go new file mode 100644 index 000000000..305769f22 --- /dev/null +++ b/test/integration/klusterlet_test.go @@ -0,0 +1,225 @@ +package integration + +import ( + "context" + "fmt" + "strings" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + + "github.com/openshift/library-go/pkg/controller/controllercmd" + + operatorapiv1 "github.com/open-cluster-management/api/operator/v1" + "github.com/open-cluster-management/registration-operator/pkg/operators" + "github.com/open-cluster-management/registration-operator/test/integration/util" +) + +func startKlusterletOperator(ctx context.Context) { + err := operators.RunKlusterletOperator(ctx, &controllercmd.ControllerContext{ + KubeConfig: restConfig, + EventRecorder: util.NewIntegrationTestEventRecorder("integration"), + }) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) +} + +var _ = ginkgo.Describe("Klusterlet", func() { + var cancel context.CancelFunc + var klusterlet *operatorapiv1.Klusterlet + var klusterletNamespace string + var registrationRoleName string + var registrationDeploymentName string + var registrationSAName string + var workRoleName string + var workDeploymentName string + var workSAName string + + ginkgo.BeforeEach(func() { + var ctx context.Context + + klusterletNamespace = fmt.Sprintf("open-cluster-manager-%s", rand.String(6)) + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: klusterletNamespace, + }, + } + _, err := kubeClient.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + // Create a dummy bootstrap secret + bootStrapSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bootstrap-hub-kubeconfig", + Namespace: klusterletNamespace, + }, + Data: map[string][]byte{ + "kubeconfig": []byte("dummy"), + }, + } + _, err = kubeClient.CoreV1().Secrets(klusterletNamespace).Create(context.Background(), bootStrapSecret, metav1.CreateOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + klusterlet = &operatorapiv1.Klusterlet{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("klusterlet-%s", rand.String(6)), + }, + Spec: operatorapiv1.KlusterletSpec{ + RegistrationImagePullSpec: "quay.io/open-cluster-management/registration", + WorkImagePullSpec: "quay.io/open-cluster-management/work", + ExternalServerURLs: []operatorapiv1.ServerURL{ + { + URL: "https://localhost", + }, + }, + ClusterName: "testcluster", + Namespace: klusterletNamespace, + }, + } + + ctx, cancel = context.WithCancel(context.Background()) + go startKlusterletOperator(ctx) + }) + + ginkgo.AfterEach(func() { + err := kubeClient.CoreV1().Namespaces().Delete(context.Background(), klusterletNamespace, metav1.DeleteOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + if cancel != nil { + cancel() + } + }) + + ginkgo.Context("Deploy and clean klusterlet component", func() { + ginkgo.BeforeEach(func() { + registrationDeploymentName = fmt.Sprintf("%s-registration-agent", klusterlet.Name) + workDeploymentName = fmt.Sprintf("%s-work-agent", klusterlet.Name) + registrationRoleName = fmt.Sprintf("system:open-cluster-management:%s", registrationDeploymentName) + workRoleName = fmt.Sprintf("system:open-cluster-management:%s", workDeploymentName) + registrationSAName = fmt.Sprintf("%s-registration-sa", klusterlet.Name) + workSAName = fmt.Sprintf("%s-work-sa", klusterlet.Name) + }) + + ginkgo.It("should have expected resource created successfully", func() { + _, err := operatorClient.OperatorV1().Klusterlets().Create(context.Background(), klusterlet, metav1.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Check clusterrole/clusterrolebinding + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoles().Get(context.Background(), registrationRoleName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoles().Get(context.Background(), workRoleName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoleBindings().Get(context.Background(), registrationRoleName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoleBindings().Get(context.Background(), workRoleName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check role/rolebinding + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().Roles(klusterletNamespace).Get(context.Background(), registrationRoleName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().RoleBindings(klusterletNamespace).Get(context.Background(), registrationRoleName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check service account + gomega.Eventually(func() bool { + if _, err := kubeClient.CoreV1().ServiceAccounts(klusterletNamespace).Get(context.Background(), registrationSAName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.CoreV1().ServiceAccounts(klusterletNamespace).Get(context.Background(), workSAName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check deployment + gomega.Eventually(func() bool { + if _, err := kubeClient.AppsV1().Deployments(klusterletNamespace).Get(context.Background(), registrationDeploymentName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.AppsV1().Deployments(klusterletNamespace).Get(context.Background(), workDeploymentName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + util.AssertKlusterletCondition(klusterlet.Name, operatorClient, "Applied", metav1.ConditionTrue, eventuallyTimeout, eventuallyInterval) + }) + + ginkgo.It("should have correct registration deployment when server url is empty", func() { + klusterlet.Spec.ExternalServerURLs = []operatorapiv1.ServerURL{} + _, err := operatorClient.OperatorV1().Klusterlets().Create(context.Background(), klusterlet, metav1.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + gomega.Eventually(func() bool { + if _, err := kubeClient.AppsV1().Deployments(klusterletNamespace).Get(context.Background(), registrationDeploymentName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + deployment, err := kubeClient.AppsV1().Deployments(klusterletNamespace).Get(context.Background(), registrationDeploymentName, metav1.GetOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(len(deployment.Spec.Template.Spec.Containers)).Should(gomega.Equal(1)) + // external-server-url should not be set + for _, arg := range deployment.Spec.Template.Spec.Containers[0].Args { + gomega.Expect(strings.Contains(arg, "--spoke-external-server-urls")).NotTo(gomega.BeTrue()) + } + }) + + ginkgo.It("should have correct work deployment when clusterName is empty", func() { + klusterlet.Spec.ClusterName = "" + _, err := operatorClient.OperatorV1().Klusterlets().Create(context.Background(), klusterlet, metav1.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + gomega.Eventually(func() bool { + if _, err := kubeClient.AppsV1().Deployments(klusterletNamespace).Get(context.Background(), workDeploymentName, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + deployment, err := kubeClient.AppsV1().Deployments(klusterletNamespace).Get(context.Background(), workDeploymentName, metav1.GetOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(len(deployment.Spec.Template.Spec.Containers)).Should(gomega.Equal(1)) + + for _, arg := range deployment.Spec.Template.Spec.Containers[0].Args { + if strings.HasPrefix(arg, "--spoke-cluster-name") { + gomega.Expect(arg).Should(gomega.Equal("--spoke-cluster-name=")) + } + } + }) + }) +}) diff --git a/test/integration/util/assertion.go b/test/integration/util/assertion.go new file mode 100644 index 000000000..8f8753e45 --- /dev/null +++ b/test/integration/util/assertion.go @@ -0,0 +1,37 @@ +package util + +import ( + "context" + + "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + operatorclientset "github.com/open-cluster-management/api/client/operator/clientset/versioned" +) + +func AssertKlusterletCondition( + name string, operatorClient operatorclientset.Interface, expectedType string, expectedWorkStatus metav1.ConditionStatus, eventuallyTimeout, eventuallyInterval int) { + gomega.Eventually(func() bool { + klusterlet, err := operatorClient.OperatorV1().Klusterlets().Get(context.Background(), name, metav1.GetOptions{}) + if err != nil { + return false + } + + // check work status condition + return HasCondition(klusterlet.Status.Conditions, expectedType, expectedWorkStatus) + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) +} + +func AssertClusterManagerCondition( + name string, operatorClient operatorclientset.Interface, expectedType string, expectedWorkStatus metav1.ConditionStatus, eventuallyTimeout, eventuallyInterval int) { + gomega.Eventually(func() bool { + klusterlet, err := operatorClient.OperatorV1().ClusterManagers().Get(context.Background(), name, metav1.GetOptions{}) + if err != nil { + return false + } + + // check work status condition + return HasCondition(klusterlet.Status.Conditions, expectedType, expectedWorkStatus) + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) +} diff --git a/test/integration/util/util.go b/test/integration/util/util.go index 0406a7cf9..a38b6d171 100644 --- a/test/integration/util/util.go +++ b/test/integration/util/util.go @@ -6,6 +6,9 @@ import ( "github.com/onsi/ginkgo" "github.com/openshift/library-go/pkg/operator/events" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + operatorapiv1 "github.com/open-cluster-management/api/operator/v1" ) func NewIntegrationTestEventRecorder(componet string) events.Recorder { @@ -43,3 +46,20 @@ func (r *IntegrationTestEventRecorder) Warning(reason, message string) { func (r *IntegrationTestEventRecorder) Warningf(reason, messageFmt string, args ...interface{}) { r.Warning(reason, fmt.Sprintf(messageFmt, args...)) } + +func HasCondition(conditions []operatorapiv1.StatusCondition, expectedType string, expectedStatus metav1.ConditionStatus) bool { + found := false + for _, condition := range conditions { + if condition.Type != expectedType { + continue + } + found = true + + if condition.Status != expectedStatus { + return false + } + return true + } + + return found +}