diff --git a/Makefile b/Makefile index 621d7dcc7..fced32a0d 100644 --- a/Makefile +++ b/Makefile @@ -325,6 +325,12 @@ test-unit: $(SETUP_ENVTEST) $(GOTESTSUM) $(HELM) ## Run unit KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" $(GOTESTSUM) --junitfile=.coverage/junit.xml --format testname -- -mod=vendor \ -covermode=atomic -coverprofile=.coverage/cover.out -p=4 ./internal/controller/... +.PHONY: test-integration-github +test-integration-github: $(SETUP_ENVTEST) $(GOTESTSUM) + @mkdir -p $(shell pwd)/.coverage + KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" $(GOTESTSUM) --junitfile=../.coverage/junit.xml --format testname -- -mod=vendor \ + -covermode=atomic -coverprofile=../.coverage/cover.out -p=1 ./internal/test/integration/github/... + ##@ Verify ########## # Verify # diff --git a/internal/test/integration/github/integration_suite_test.go b/internal/test/integration/github/integration_suite_test.go new file mode 100644 index 000000000..3c594fb6c --- /dev/null +++ b/internal/test/integration/github/integration_suite_test.go @@ -0,0 +1,70 @@ +/* +Copyright 2023 The Kubernetes Authors. +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 github + +import ( + "testing" + "time" + + "github.com/SovereignCloudStack/cluster-stack-operator/internal/controller" + "github.com/SovereignCloudStack/cluster-stack-operator/internal/test/helpers" + githubclient "github.com/SovereignCloudStack/cluster-stack-operator/pkg/github/client" + "github.com/SovereignCloudStack/cluster-stack-operator/pkg/kube" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + ctrl "sigs.k8s.io/controller-runtime" + controllerruntimecontroller "sigs.k8s.io/controller-runtime/pkg/controller" +) + +const ( + timeout = time.Second * 20 + interval = 1000 * time.Millisecond +) + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Controller Suite") +} + +var ( + ctx = ctrl.SetupSignalHandler() + testEnv *helpers.TestEnvironment +) + +var _ = BeforeSuite(func() { + testEnv = helpers.NewTestEnvironment() + Expect((&controller.ClusterStackReconciler{ + Client: testEnv.Manager.GetClient(), + GitHubClientFactory: githubclient.NewFactory(), + ReleaseDirectory: "/tmp/downloads", + }).SetupWithManager(ctx, testEnv.Manager, controllerruntimecontroller.Options{})).To(Succeed()) + Expect((&controller.ClusterStackReleaseReconciler{ + Client: testEnv.Manager.GetClient(), + RESTConfig: testEnv.Manager.GetConfig(), + KubeClientFactory: kube.NewFactory(), + GitHubClientFactory: githubclient.NewFactory(), + ReleaseDirectory: "/tmp/downloads", + }).SetupWithManager(ctx, testEnv.Manager, controllerruntimecontroller.Options{})).To(Succeed()) + + go func() { + defer GinkgoRecover() + Expect(testEnv.StartManager(ctx)).To(Succeed()) + }() + <-testEnv.Manager.Elected() +}) + +var _ = AfterSuite(func() { + Expect(testEnv.Stop()).To(Succeed()) +}) diff --git a/internal/test/integration/github/integration_test.go b/internal/test/integration/github/integration_test.go new file mode 100644 index 000000000..2efe783b3 --- /dev/null +++ b/internal/test/integration/github/integration_test.go @@ -0,0 +1,186 @@ +/* +Copyright 2023 The Kubernetes Authors. +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 github + +import ( + csov1alpha1 "github.com/SovereignCloudStack/cluster-stack-operator/api/v1alpha1" + "github.com/SovereignCloudStack/cluster-stack-operator/pkg/clusterstack" + "github.com/SovereignCloudStack/cluster-stack-operator/pkg/test/utils" + csv "github.com/SovereignCloudStack/cluster-stack-operator/pkg/version" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + 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/client" +) + +const ( + provider = "docker" + name = "ferrol" + kubernetesVersion = "1.27" + version = "v1" +) + +var _ = Describe("ClusterStackReconciler", func() { + Context("auto subscribe false", func() { + var ( + clusterStack *csov1alpha1.ClusterStack + testNs *corev1.Namespace + clusterStackReleaseKey types.NamespacedName + ) + + BeforeEach(func() { + var err error + testNs, err = testEnv.CreateNamespace(ctx, "clusterstack-integration") + Expect(err).NotTo(HaveOccurred()) + + clusterStack = &csov1alpha1.ClusterStack{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + Namespace: testNs.Name, + }, + Spec: csov1alpha1.ClusterStackSpec{ + Provider: provider, + Name: name, + KubernetesVersion: kubernetesVersion, + Versions: []string{version}, + AutoSubscribe: false, + NoProvider: true, + }, + } + Expect(testEnv.Create(ctx, clusterStack)).To(Succeed()) + + cs, err := clusterstack.New(clusterStack.Spec.Provider, clusterStack.Spec.Name, clusterStack.Spec.KubernetesVersion, version) + Expect(err).To(BeNil()) + + clusterStackReleaseKey = types.NamespacedName{Name: cs.String(), Namespace: testNs.Name} + }) + + AfterEach(func() { + Expect(testEnv.Cleanup(ctx, testNs, clusterStack)).To(Succeed()) + }) + + It("creates the cluster stack release object", func() { + Eventually(func() error { + var clusterStackRelease csov1alpha1.ClusterStackRelease + return testEnv.Get(ctx, clusterStackReleaseKey, &clusterStackRelease) + }, timeout, interval).Should(BeNil()) + }) + + It("sets ClusterStackReleaseDownloaded condition once ClusterStackRelease object is created", func() { + Eventually(func() bool { + var clusterStackRelease csov1alpha1.ClusterStackRelease + return utils.IsPresentAndTrue(ctx, testEnv.Client, clusterStackReleaseKey, &clusterStackRelease, csov1alpha1.ClusterStackReleaseAssetsReadyCondition) + }, timeout, interval).Should(BeTrue()) + }) + + It("sets ClusterStackRelease Status ready after ClusterStackRelease object is created", func() { + Eventually(func() bool { + var foundClusterStackRelease csov1alpha1.ClusterStackRelease + if err := testEnv.Get(ctx, clusterStackReleaseKey, &foundClusterStackRelease); err == nil { + return foundClusterStackRelease.Status.Ready + } + return false + }, timeout, interval).Should(BeTrue()) + }) + }) + + Context("auto subscribe true", func() { + var ( + clusterStack *csov1alpha1.ClusterStack + testNs *corev1.Namespace + clusterStackKey types.NamespacedName + clusterStackReleaseKey types.NamespacedName + ) + + BeforeEach(func() { + var err error + testNs, err = testEnv.CreateNamespace(ctx, "clusterstack-integration") + Expect(err).NotTo(HaveOccurred()) + + clusterStack = &csov1alpha1.ClusterStack{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test2", + Namespace: testNs.Name, + }, + Spec: csov1alpha1.ClusterStackSpec{ + Provider: provider, + Name: name, + KubernetesVersion: kubernetesVersion, + Versions: []string{}, + AutoSubscribe: true, + NoProvider: true, + ProviderRef: nil, + }, + } + Expect(testEnv.Create(ctx, clusterStack)).To(Succeed()) + + clusterStackKey = types.NamespacedName{Name: clusterStack.Name, Namespace: testNs.Name} + + cs, err := clusterstack.New(clusterStack.Spec.Provider, clusterStack.Spec.Name, clusterStack.Spec.KubernetesVersion, version) + Expect(err).To(BeNil()) + + clusterStackReleaseKey = types.NamespacedName{Name: cs.String(), Namespace: testNs.Name} + }) + + AfterEach(func() { + Expect(testEnv.Cleanup(ctx, testNs, clusterStack)).To(Succeed()) + }) + + It("finds the new cluster stack release", func() { + Eventually(func() bool { + var clusterStackReleaseList csov1alpha1.ClusterStackReleaseList + if err := testEnv.List(ctx, &clusterStackReleaseList, &client.ListOptions{Namespace: testNs.Name}); err != nil { + testEnv.GetLogger().Error(err, "failed to get clusterStackList") + return false + } + + var clusterStack csov1alpha1.ClusterStack + if err := testEnv.Get(ctx, clusterStackKey, &clusterStack); err != nil { + testEnv.GetLogger().Error(err, "failed to get clusterStack", "key", clusterStackKey) + return false + } + + for _, csrSummary := range clusterStack.Status.Summary { + v, err := csv.New(csrSummary.Name) + Expect(err).To(BeNil()) + + oldVersion, err := csv.New(version) + Expect(err).To(BeNil()) + + cmp, err := v.Compare(oldVersion) + Expect(err).To(BeNil()) + + if cmp >= 0 { + return true + } + } + return false + }, timeout, interval).Should(BeTrue()) + }) + + It("creates the new cluster stack release", func() { + Eventually(func() bool { + var clusterStackRelease csov1alpha1.ClusterStackRelease + if err := testEnv.Get(ctx, clusterStackReleaseKey, &clusterStackRelease); err != nil { + testEnv.GetLogger().Error(err, "failed to get clusterStackRelease", "key", clusterStackReleaseKey) + return false + } + + return true + }, timeout, interval).Should(BeTrue()) + }) + }) +}) diff --git a/pkg/test/utils/conditions.go b/pkg/test/utils/conditions.go index d193525e0..0c68a5339 100644 --- a/pkg/test/utils/conditions.go +++ b/pkg/test/utils/conditions.go @@ -19,7 +19,6 @@ package utils import ( "context" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -29,7 +28,9 @@ import ( // IsPresentAndFalseWithReason returns if condition is present in the object status with false and a reason or not. func IsPresentAndFalseWithReason(ctx context.Context, c client.Client, key types.NamespacedName, getter conditions.Getter, condition clusterv1.ConditionType, reason string) bool { - ExpectWithOffset(1, c.Get(ctx, key, getter)).To(Succeed()) + if err := c.Get(ctx, key, getter); err != nil { + return false + } if !conditions.Has(getter, condition) { return false } @@ -40,7 +41,9 @@ func IsPresentAndFalseWithReason(ctx context.Context, c client.Client, key types // IsPresentAndTrue returns if condition is present in the object status with true or not. func IsPresentAndTrue(ctx context.Context, c client.Client, key types.NamespacedName, getter conditions.Getter, condition clusterv1.ConditionType) bool { - ExpectWithOffset(1, c.Get(ctx, key, getter)).To(Succeed()) + if err := c.Get(ctx, key, getter); err != nil { + return false + } if !conditions.Has(getter, condition) { return false }