diff --git a/api/go.mod b/api/go.mod index b6d27f35..67df8b83 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,6 +3,7 @@ module github.com/openstack-k8s-operators/mariadb-operator/api go 1.19 require ( + github.com/go-logr/logr v1.2.4 github.com/onsi/ginkgo/v2 v2.12.0 github.com/onsi/gomega v1.27.10 github.com/openstack-k8s-operators/lib-common/modules/common v0.1.0 @@ -19,7 +20,6 @@ require ( github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/zapr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect @@ -36,11 +36,13 @@ require ( github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openshift/api v3.9.0+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect diff --git a/api/go.sum b/api/go.sum index 5f86c0d0..97d29d04 100644 --- a/api/go.sum +++ b/api/go.sum @@ -195,6 +195,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -228,6 +230,8 @@ github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs= +github.com/openshift/api v3.9.0+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= github.com/openstack-k8s-operators/lib-common/modules/common v0.1.0 h1:F1iYRBwa0cZ2VHw8Zs4frqSWQ1B/tiCuSwH/DuHb8VM= github.com/openstack-k8s-operators/lib-common/modules/common v0.1.0/go.mod h1:3hAC5Ce0AOSt85BqD6DgTKNkJHmpXwqbwL8mVWRJQqo= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/api/test/helpers/crd.go b/api/test/helpers/crd.go new file mode 100644 index 00000000..dfab10aa --- /dev/null +++ b/api/test/helpers/crd.go @@ -0,0 +1,144 @@ +/* +Copyright 2023 Red Hat +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 helpers + +import ( + "context" + "time" + + "github.com/go-logr/logr" + "github.com/onsi/gomega" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + corev1 "k8s.io/api/core/v1" + + base "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" +) + +// TestHelper is a collection of helpers for testing operators. It extends the +// generic TestHelper from modules/test. +type TestHelper struct { + *base.TestHelper +} + +// NewTestHelper returns a TestHelper +func NewTestHelper( + ctx context.Context, + k8sClient client.Client, + timeout time.Duration, + interval time.Duration, + logger logr.Logger, +) *TestHelper { + helper := &TestHelper{} + helper.TestHelper = base.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + return helper +} + +// CreateDBService creates a k8s Service object that matches with the +// Expectations of lib-common database module as a Service for the MariaDB +func (tc *TestHelper) CreateDBService(namespace string, mariadbCRName string, spec corev1.ServiceSpec) types.NamespacedName { + // The Name is used as the hostname to access the service. So + // we generate something unique for the MariaDB CR it represents + // so we can assert that the correct Service is selected. + serviceName := "hostname-for-" + mariadbCRName + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + // NOTE(gibi): The lib-common databvase module looks up the + // Service exposed by MariaDB via these labels. + Labels: map[string]string{ + "app": "mariadb", + "cr": "mariadb-" + mariadbCRName, + }, + }, + Spec: spec, + } + gomega.Expect(tc.K8sClient.Create(tc.Ctx, service)).Should(gomega.Succeed()) + + return types.NamespacedName{Name: serviceName, Namespace: namespace} +} + +// DeleteDBService The function deletes the Service if exists and wait for it to disappear from the API. +// If the Service does not exists then it is assumed to be successfully deleted. +// Example: +// +// th.DeleteDBService(types.NamespacedName{Name: "my-service", Namespace: "my-namespace"}) +// +// or: +// +// DeferCleanup(th.DeleteDBService, th.CreateDBService(cell0.MariaDBDatabaseName.Namespace, cell0.MariaDBDatabaseName.Name, serviceSpec)) +func (tc *TestHelper) DeleteDBService(name types.NamespacedName) { + gomega.Eventually(func(g gomega.Gomega) { + service := &corev1.Service{} + err := tc.K8sClient.Get(tc.Ctx, name, service) + // if it is already gone that is OK + if k8s_errors.IsNotFound(err) { + return + } + g.Expect(err).NotTo(gomega.HaveOccurred()) + + g.Expect(tc.K8sClient.Delete(tc.Ctx, service)).Should(gomega.Succeed()) + + err = tc.K8sClient.Get(tc.Ctx, name, service) + g.Expect(k8s_errors.IsNotFound(err)).To(gomega.BeTrue()) + }, tc.Timeout, tc.Interval).Should(gomega.Succeed()) +} + +// GetMariaDBDatabase waits for and retrieves a MariaDBDatabase resource from the Kubernetes cluster +// +// Example: +// +// mariadbDatabase := th.GetMariaDBDatabase(types.NamespacedName{Name: "my-mariadb-database", Namespace: "my-namespace"}) +func (tc *TestHelper) GetMariaDBDatabase(name types.NamespacedName) *mariadbv1.MariaDBDatabase { + instance := &mariadbv1.MariaDBDatabase{} + gomega.Eventually(func(g gomega.Gomega) { + g.Expect(tc.K8sClient.Get(tc.Ctx, name, instance)).Should(gomega.Succeed()) + }, tc.Timeout, tc.Interval).Should(gomega.Succeed()) + return instance +} + +// SimulateMariaDBDatabaseCompleted simulates a completed state for a MariaDBDatabase resource in a Kubernetes cluster. +// +// Example: +// +// th.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Name: "my-mariadb-database", Namespace: "my-namespace"}) +// +// or +// +// DeferCleanup(th.SimulateMariaDBDatabaseCompleted, types.NamespacedName{Name: "my-mariadb-database", Namespace: "my-namespace"}) +func (tc *TestHelper) SimulateMariaDBDatabaseCompleted(name types.NamespacedName) { + gomega.Eventually(func(g gomega.Gomega) { + db := tc.GetMariaDBDatabase(name) + db.Status.Completed = true + // This can return conflict so we have the gomega.Eventually block to retry + g.Expect(tc.K8sClient.Status().Update(tc.Ctx, db)).To(gomega.Succeed()) + + }, tc.Timeout, tc.Interval).Should(gomega.Succeed()) + + tc.Logger.Info("Simulated DB completed", "on", name) +} + +// AssertMariaDBDatabaseDoesNotExist ensures the MariaDBDatabase resource does not exist in a k8s cluster. +func (tc *TestHelper) AssertMariaDBDatabaseDoesNotExist(name types.NamespacedName) { + instance := &mariadbv1.MariaDBDatabase{} + gomega.Eventually(func(g gomega.Gomega) { + err := tc.K8sClient.Get(tc.Ctx, name, instance) + g.Expect(k8s_errors.IsNotFound(err)).To(gomega.BeTrue()) + }, tc.Timeout, tc.Interval).Should(gomega.Succeed()) +}