diff --git a/Makefile b/Makefile index e0c313142..00ff7685b 100644 --- a/Makefile +++ b/Makefile @@ -127,8 +127,10 @@ golangci-lint: $(LOCALBIN)/golangci-lint run --fix .PHONY: test -test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out +test: manifests generate gowork fmt vet envtest ginkgo ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) -v debug --bin-dir $(LOCALBIN) use $(ENVTEST_K8S_VERSION) -p path)" \ + OPERATOR_TEMPLATES="$(PWD)/templates" \ + $(GINKGO) --trace --cover --coverpkg=../../pkg/openstack,../../pkg/openstackclient,../../pkg/util,../../controllers,../../apis/client/v1beta1,../../apis/core/v1beta1 --coverprofile cover.out --covermode=atomic ${PROC_CMD} $(GINKGO_ARGS) ./tests/... ./apis/client/... ##@ Build @@ -202,6 +204,7 @@ $(LOCALBIN): KUSTOMIZE ?= $(LOCALBIN)/kustomize CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen ENVTEST ?= $(LOCALBIN)/setup-envtest +GINKGO ?= $(LOCALBIN)/ginkgo ## Tool Versions KUSTOMIZE_VERSION ?= v3.8.7 @@ -223,6 +226,11 @@ envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. $(ENVTEST): $(LOCALBIN) test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest +.PHONY: ginkgo +ginkgo: $(GINKGO) ## Download ginkgo locally if necessary. +$(GINKGO): $(LOCALBIN) + test -s $(LOCALBIN)/ginkgo || GOBIN=$(LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo + .PHONY: bundle bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. operator-sdk generate kustomize manifests -q @@ -314,9 +322,7 @@ govet: get-ci-tools GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/govet.sh ./apis # Run go test against code -gotest: get-ci-tools - GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/gotest.sh - GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/gotest.sh ./apis +gotest: test # Run golangci-lint test against code golangci: get-ci-tools diff --git a/go.mod b/go.mod index f8c305908..bfae64679 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231005132119-e75019792469 github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231006072650-7fe7fe16bcd1 github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231006072650-7fe7fe16bcd1 + github.com/openstack-k8s-operators/lib-common/modules/test v0.1.2-0.20231001084618-12369665b166 github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231006072008-252ecd8282fd github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231005123831-d6230489c98e @@ -45,8 +46,10 @@ require ( github.com/go-logr/zapr v1.2.4 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 // indirect + github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0 // indirect github.com/metal3-io/baremetal-operator/apis v0.3.1 // indirect github.com/metal3-io/baremetal-operator/pkg/hardwareutils v0.2.0 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/tools v0.13.0 // indirect sigs.k8s.io/gateway-api v0.6.0 // indirect ) @@ -68,7 +71,7 @@ require ( github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.1 // indirect + github.com/google/uuid v1.3.1 github.com/gophercloud/gophercloud v1.7.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 361093f2b..26f491d28 100644 --- a/go.sum +++ b/go.sum @@ -106,6 +106,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0 h1:VzM3TYHDgqPkettiP6I6q2jOeQFL4nrJM+UcAc4f6Fs= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0/go.mod h1:nqCI7aelBJU61wiBeeZWJ6oi4bJy5nrjkM6lWIMA4j0= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -159,6 +161,8 @@ github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.2023100 github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231006072650-7fe7fe16bcd1/go.mod h1:LOXXvTQCwhOBNd+0FTlgllpa3wqlkI6Vf3Q5QVRVPlw= github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231006072650-7fe7fe16bcd1 h1:+vRt690N+He4uJM0Cvk7Fguw0zs395A8qfV5Uq8B7kw= github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231006072650-7fe7fe16bcd1/go.mod h1:qkK/2JzIGOzoctfe0sbL+mSelrEmErAND4vuj1qTU/A= +github.com/openstack-k8s-operators/lib-common/modules/test v0.1.2-0.20231001084618-12369665b166 h1:lh3WHM+3DcPlXK4I3QWHmvV+cPCy+dmiMdfImHF/Nqc= +github.com/openstack-k8s-operators/lib-common/modules/test v0.1.2-0.20231001084618-12369665b166/go.mod h1:z/Plc5ef+C/lFZMTHGdOdoo04mimjXyqS9DZKxCzlXk= github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231006072008-252ecd8282fd h1:Boq6nfKtxocwOnW2HqOMBVfz4NrhSbNE+HPdSv4nEq8= github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231006072008-252ecd8282fd/go.mod h1:rJkTThV08Be6hnxw7IbDSrIPQ/FqQ2Uzhe9NdppoP4E= github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 h1:FB0xB6whYM6W4XIncYo2mPiOJWkFsIOWtCT+UOtvOaQ= @@ -251,6 +255,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/tests/functional/base_test.go b/tests/functional/base_test.go new file mode 100644 index 000000000..4e9946d42 --- /dev/null +++ b/tests/functional/base_test.go @@ -0,0 +1,52 @@ +/* +Copyright 2023. + +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 functional_test + +import ( + . "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + openstackclientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" +) + +func GetDefaultOpenStackClientSpec() map[string]interface{} { + return map[string]interface{}{} +} + +func CreateOpenStackClient(name types.NamespacedName, spec map[string]interface{}) client.Object { + + raw := map[string]interface{}{ + "apiVersion": "client.openstack.org/v1beta1", + "kind": "OpenStackClient", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } + return th.CreateUnstructured(raw) +} + +func GetOpenStackClient(name types.NamespacedName) *openstackclientv1.OpenStackClient { + instance := &openstackclientv1.OpenStackClient{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} diff --git a/tests/functional/openstackclient_webhook_test.go b/tests/functional/openstackclient_webhook_test.go new file mode 100644 index 000000000..1fa3eb870 --- /dev/null +++ b/tests/functional/openstackclient_webhook_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2023. + +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 functional_test + +import ( + "os" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/types" + + openstackclientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" +) + +var _ = Describe("OpenStackClient Webhook", func() { + + var openstackclientName types.NamespacedName + + BeforeEach(func() { + + openstackclientName = types.NamespacedName{ + Name: "foo", + Namespace: namespace, + } + + err := os.Setenv("OPERATOR_TEMPLATES", "../../templates") + Expect(err).NotTo(HaveOccurred()) + }) + + When("A OpenStackClient instance is created without container images", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateOpenStackClient(openstackclientName, GetDefaultOpenStackClientSpec())) + }) + + It("should have the defaults initialized by webhook", func() { + OpenStackClient := GetOpenStackClient(openstackclientName) + Expect(OpenStackClient.Spec.ContainerImage).Should(Equal( + openstackclientv1.OpenStackClientContainerImage, + )) + }) + }) + + When("A OpenStackClient instance is created with container images", func() { + BeforeEach(func() { + openstackclientSpec := GetDefaultOpenStackClientSpec() + openstackclientSpec["containerImage"] = "api-container-image" + DeferCleanup(th.DeleteInstance, CreateOpenStackClient(openstackclientName, openstackclientSpec)) + }) + + It("should use the given values", func() { + OpenStackClient := GetOpenStackClient(openstackclientName) + Expect(OpenStackClient.Spec.ContainerImage).Should(Equal( + "api-container-image", + )) + }) + }) +}) diff --git a/tests/functional/suite_test.go b/tests/functional/suite_test.go new file mode 100644 index 000000000..af510db37 --- /dev/null +++ b/tests/functional/suite_test.go @@ -0,0 +1,177 @@ +package functional_test + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + "github.com/go-logr/logr" + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.uber.org/zap/zapcore" + + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "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" + + routev1 "github.com/openshift/api/route/v1" + test "github.com/openstack-k8s-operators/lib-common/modules/test" + openstackclientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" + + client_ctrl "github.com/openstack-k8s-operators/openstack-operator/controllers/client" + + common_test "github.com/openstack-k8s-operators/lib-common/modules/test/helpers" + //+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 + k8sClient client.Client + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc + logger logr.Logger + th *common_test.TestHelper + namespace string +) + +const ( + timeout = time.Second * 2 + + SecretName = "test-osp-secret" + + interval = time.Millisecond * 200 +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true), func(o *zap.Options) { + o.Development = true + o.TimeEncoder = zapcore.ISO8601TimeEncoder + })) + + ctx, cancel = context.WithCancel(context.TODO()) + + routev1CRDs, err := test.GetOpenShiftCRDDir("route/v1", "../../go.mod") + Expect(err).ShouldNot(HaveOccurred()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "config", "crd", "bases"), + routev1CRDs, + }, + ErrorIfCRDPathMissing: true, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + // NOTE(gibi): if localhost is resolved to ::1 (ipv6) then starting + // the webhook fails as it try to parse the address as ipv4 and + // failing on the colons in ::1 + LocalServingHost: "127.0.0.1", + }, + } + + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = openstackclientv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = routev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + logger = ctrl.Log.WithName("---Test---") + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + th = common_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(th).NotTo(BeNil()) + + // Start the controller-manager if goroutine + webhookInstallOptions := &testEnv.WebhookInstallOptions + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + // NOTE(gibi): disable metrics reporting in test to allow + // parallel test execution. Otherwise each instance would like to + // bind to the same port + MetricsBindAddress: "0", + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + LeaderElection: false, + }) + Expect(err).ToNot(HaveOccurred()) + + kclient, err := kubernetes.NewForConfig(cfg) + Expect(err).ToNot(HaveOccurred(), "failed to create kclient") + + err = (&openstackclientv1.OpenStackClient{}).SetupWebhookWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + + openstackclientv1.SetupDefaults() + + err = (&client_ctrl.OpenStackClientReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + go func() { + defer GinkgoRecover() + err = k8sManager.Start(ctx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: 10 * time.Second} + addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + cancel() + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) + +var _ = BeforeEach(func() { + // NOTE(gibi): We need to create a unique namespace for each test run + // as namespaces cannot be deleted in a locally running envtest. See + // https://book.kubebuilder.io/reference/envtest.html#namespace-usage-limitation + namespace = uuid.New().String() + th.CreateNamespace(namespace) + // We still request the delete of the Namespace to properly cleanup if + // we run the test in an existing cluster. + DeferCleanup(th.DeleteNamespace, namespace) +})