diff --git a/Makefile b/Makefile index 152e38a9..604e8af5 100644 --- a/Makefile +++ b/Makefile @@ -119,8 +119,8 @@ golangci-lint: $(LOCALBIN)/golangci-lint run --fix --timeout=5m .PHONY: test -test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... ./api/... -coverprofile cover.out +test: manifests generate fmt vet envtest ginkgo ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) -v debug --bin-dir $(LOCALBIN) use $(ENVTEST_K8S_VERSION) -p path)" OPERATOR_TEMPLATES="$(shell pwd)/templates" $(GINKGO) --trace --cover --coverpkg=../../pkg/,../../controllers,../../api/v1beta1 --coverprofile cover.out --covermode=atomic --randomize-all ${PROC_CMD} $(GINKGO_ARGS) ./tests/... ##@ Build @@ -180,6 +180,7 @@ KUSTOMIZE ?= $(LOCALBIN)/kustomize CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen ENVTEST ?= $(LOCALBIN)/setup-envtest OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk +GINKGO ?= $(LOCALBIN)/ginkgo ## Tool Versions KUSTOMIZE_VERSION ?= v3.8.7 @@ -208,6 +209,11 @@ operator-sdk: $(OPERATOR_SDK) ## Download operator-sdk locally if necessary. $(OPERATOR_SDK): $(LOCALBIN) test -s $(OPERATOR_SDK) || curl -o $(LOCALBIN)/operator-sdk -L https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_linux_amd64 && chmod +x $(LOCALBIN)/operator-sdk +.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: operator-sdk manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. $(OPERATOR_SDK) generate kustomize manifests -q diff --git a/api/v1beta1/conditions.go b/api/v1beta1/conditions.go index abd10982..a75cfc37 100644 --- a/api/v1beta1/conditions.go +++ b/api/v1beta1/conditions.go @@ -21,9 +21,6 @@ import ( // Designate Condition Types used by API objects. const ( - // DesignateRabbitMqTransportURLReadyCondition Status=True condition which indicates if the RabbitMQ TransportURLUrl is ready - DesignateRabbitMqTransportURLReadyCondition condition.Type = "DesignateRabbitMqTransportURLReady" - // DesignateAPIReadyCondition Status=True condition which indicates if the DesignateAPI is configured and operational DesignateAPIReadyCondition condition.Type = "DesignateAPIReady" @@ -51,21 +48,6 @@ const () // Common Messages used by API objects. const ( - // - // DesignateRabbitMqTransportURLReady condition messages - // - // DesignateRabbitMqTransportURLReadyInitMessage - DesignateRabbitMqTransportURLReadyInitMessage = "DesignateRabbitMqTransportURL not started" - - // DesignateRabbitMqTransportURLReadyRunningMessage - DesignateRabbitMqTransportURLReadyRunningMessage = "DesignateRabbitMqTransportURL creation in progress" - - // DesignateRabbitMqTransportURLReadyMessage - DesignateRabbitMqTransportURLReadyMessage = "DesignateRabbitMqTransportURL successfully created" - - // DesignateRabbitMqTransportURLReadyErrorMessage - DesignateRabbitMqTransportURLReadyErrorMessage = "DesignateRabbitMqTransportURL error occured %s" - // // DesignateAPIReady condition messages // diff --git a/controllers/designate_controller.go b/controllers/designate_controller.go index 1df770e6..0d289f9c 100644 --- a/controllers/designate_controller.go +++ b/controllers/designate_controller.go @@ -193,7 +193,7 @@ func (r *DesignateReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( condition.UnknownCondition(condition.ReadyCondition, condition.InitReason, condition.ReadyInitMessage), condition.UnknownCondition(condition.DBReadyCondition, condition.InitReason, condition.DBReadyInitMessage), condition.UnknownCondition(condition.DBSyncReadyCondition, condition.InitReason, condition.DBSyncReadyInitMessage), - condition.UnknownCondition(designatev1beta1.DesignateRabbitMqTransportURLReadyCondition, condition.InitReason, designatev1beta1.DesignateRabbitMqTransportURLReadyInitMessage), + condition.UnknownCondition(condition.RabbitMqTransportURLReadyCondition, condition.InitReason, condition.RabbitMqTransportURLReadyInitMessage), condition.UnknownCondition(condition.InputReadyCondition, condition.InitReason, condition.InputReadyInitMessage), condition.UnknownCondition(condition.ServiceConfigReadyCondition, condition.InitReason, condition.ServiceConfigReadyInitMessage), condition.UnknownCondition(designatev1beta1.DesignateAPIReadyCondition, condition.InitReason, designatev1beta1.DesignateAPIReadyInitMessage), @@ -567,10 +567,10 @@ func (r *DesignateReconciler) reconcileNormal(ctx context.Context, instance *des transportURL, op, err := r.transportURLCreateOrUpdate(ctx, instance) if err != nil { instance.Status.Conditions.Set(condition.FalseCondition( - designatev1beta1.DesignateRabbitMqTransportURLReadyCondition, + condition.RabbitMqTransportURLReadyCondition, condition.ErrorReason, condition.SeverityWarning, - designatev1beta1.DesignateRabbitMqTransportURLReadyErrorMessage, + condition.RabbitMqTransportURLReadyErrorMessage, err.Error())) return ctrl.Result{}, err } @@ -592,8 +592,8 @@ func (r *DesignateReconciler) reconcileNormal(ctx context.Context, instance *des } instance.Status.Conditions.MarkTrue( - designatev1beta1.DesignateRabbitMqTransportURLReadyCondition, - designatev1beta1.DesignateRabbitMqTransportURLReadyMessage) + condition.RabbitMqTransportURLReadyCondition, + condition.RabbitMqTransportURLReadyMessage) // end transportURL hostIPs, err := getRedisServiceIPs(ctx, instance, helper) diff --git a/controllers/designateapi_controller.go b/controllers/designateapi_controller.go index 61bc17fb..c18ab7af 100644 --- a/controllers/designateapi_controller.go +++ b/controllers/designateapi_controller.go @@ -656,8 +656,6 @@ func (r *DesignateAPIReconciler) reconcileNormal(ctx context.Context, instance * return ctrlResult, err } - instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) - // run check OpenStack secret - end // @@ -758,7 +756,6 @@ func (r *DesignateAPIReconciler) reconcileNormal(ctx context.Context, instance * return ctrlResult, err } - instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) // run check parent Designate CR config maps - end // @@ -808,6 +805,8 @@ func (r *DesignateAPIReconciler) reconcileNormal(ctx context.Context, instance * // Create ConfigMaps and Secrets - end + instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + // // TODO check when/if Init, Update, or Upgrade should/could be skipped // diff --git a/controllers/designatecentral_controller.go b/controllers/designatecentral_controller.go index 23fe1456..b3aecf60 100644 --- a/controllers/designatecentral_controller.go +++ b/controllers/designatecentral_controller.go @@ -429,7 +429,6 @@ func (r *DesignateCentralReconciler) reconcileNormal(ctx context.Context, instan return ctrlResult, err } - instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) // run check parent Designate CR config maps - end // @@ -517,6 +516,7 @@ func (r *DesignateCentralReconciler) reconcileNormal(ctx context.Context, instan // Create ConfigMaps and Secrets - end + instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) // // TODO check when/if Init, Update, or Upgrade should/could be skipped // diff --git a/controllers/designatemdns_controller.go b/controllers/designatemdns_controller.go index 2a5cc759..f7d8c6c8 100644 --- a/controllers/designatemdns_controller.go +++ b/controllers/designatemdns_controller.go @@ -419,7 +419,6 @@ func (r *DesignateMdnsReconciler) reconcileNormal(ctx context.Context, instance return ctrlResult, err } - instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) // run check parent Designate CR config maps - end // @@ -507,6 +506,7 @@ func (r *DesignateMdnsReconciler) reconcileNormal(ctx context.Context, instance // Create ConfigMaps and Secrets - end + instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) // // TODO check when/if Init, Update, or Upgrade should/could be skipped // diff --git a/controllers/designateproducer_controller.go b/controllers/designateproducer_controller.go index fbc4fd50..0933f15a 100644 --- a/controllers/designateproducer_controller.go +++ b/controllers/designateproducer_controller.go @@ -428,7 +428,6 @@ func (r *DesignateProducerReconciler) reconcileNormal(ctx context.Context, insta return ctrlResult, err } - instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) // run check parent Designate CR config maps - end // @@ -516,6 +515,7 @@ func (r *DesignateProducerReconciler) reconcileNormal(ctx context.Context, insta // Create ConfigMaps and Secrets - end + instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) // // TODO check when/if Init, Update, or Upgrade should/could be skipped // diff --git a/demo/examples/ns_records_CR_example.yaml b/demo/examples/ns_records_CR_example.yaml index b5568099..30251415 100644 --- a/demo/examples/ns_records_CR_example.yaml +++ b/demo/examples/ns_records_CR_example.yaml @@ -5,7 +5,7 @@ metadata: name: designate-ns-records-params data: ns_records: | - - host: host1 + - host: ns1.example.com. priority: 1 - - host: host2 + - host: ns2.example.com. priority: 2 diff --git a/go.mod b/go.mod index 57ea23dd..63c678a3 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,9 @@ go 1.21 require ( github.com/go-logr/logr v1.4.2 + github.com/go-playground/validator/v10 v10.23.0 + github.com/google/uuid v1.6.0 + github.com/gophercloud/gophercloud v1.14.1 github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.5 github.com/onsi/ginkgo/v2 v2.20.1 github.com/onsi/gomega v1.34.1 @@ -11,6 +14,7 @@ require ( github.com/openstack-k8s-operators/infra-operator/apis v0.5.1-0.20241213080025-18e54a028c8b github.com/openstack-k8s-operators/keystone-operator/api v0.5.1-0.20241212135809-dc78e7221d12 github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20241216113837-d172b3ac0f4e + github.com/openstack-k8s-operators/lib-common/modules/test v0.5.1-0.20241029151503-4878b3fa3333 github.com/openstack-k8s-operators/mariadb-operator/api v0.5.1-0.20241212160155-4e7d8f749820 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.29.10 @@ -27,10 +31,13 @@ require ( github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -39,11 +46,10 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gophercloud/gophercloud v1.14.1 // indirect 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/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -59,7 +65,9 @@ require ( github.com/spf13/pflag v1.0.5 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.23.0 // indirect diff --git a/go.sum b/go.sum index c3022861..a15715d4 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -24,6 +26,14 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= +github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -63,6 +73,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -88,6 +100,8 @@ github.com/openstack-k8s-operators/lib-common/modules/openstack v0.5.1-0.2024121 github.com/openstack-k8s-operators/lib-common/modules/openstack v0.5.1-0.20241216113837-d172b3ac0f4e/go.mod h1:IASoGvp5QM/tBJUd/8i8uIjj4DBnI+64Ydh4r7pmnvA= github.com/openstack-k8s-operators/lib-common/modules/storage v0.5.1-0.20241216113837-d172b3ac0f4e h1:Qz0JFEoRDUyjEWorNY3LggwxTsmpMtQkcpmZDQulGHQ= github.com/openstack-k8s-operators/lib-common/modules/storage v0.5.1-0.20241216113837-d172b3ac0f4e/go.mod h1:tfgBeLRqmlH/NQkLPe7396rj+t0whv2wPuMb8Ttvh8w= +github.com/openstack-k8s-operators/lib-common/modules/test v0.5.1-0.20241029151503-4878b3fa3333 h1:zUlxLqucrLMNDp6dc3I7eYWZyGVE7tLrPyWR/n+VD9w= +github.com/openstack-k8s-operators/lib-common/modules/test v0.5.1-0.20241029151503-4878b3fa3333/go.mod h1:LV0jo5etIsGyINpmB37i4oWR8zU6ApIuh7fsqGGA41o= github.com/openstack-k8s-operators/mariadb-operator/api v0.5.1-0.20241212160155-4e7d8f749820 h1:l5GgpBYprWIXTgGRAFkeJpuq7KjoTg9zioIc826zjZI= github.com/openstack-k8s-operators/mariadb-operator/api v0.5.1-0.20241212160155-4e7d8f749820/go.mod h1:348EPtAdpE2LxHAH4bHdCMNP7HyX6DevwEsF9DQ0S2k= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -124,11 +138,15 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/pkg/designate/dbsync.go b/pkg/designate/dbsync.go index 239a3b47..0bc07bd7 100644 --- a/pkg/designate/dbsync.go +++ b/pkg/designate/dbsync.go @@ -52,7 +52,7 @@ func DbSyncJob( job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ - Name: ServiceName + "-db-sync", + Name: instance.Name + "-db-sync", Namespace: instance.Namespace, Labels: labels, }, @@ -66,7 +66,7 @@ func DbSyncJob( ServiceAccountName: instance.RbacResourceName(), Containers: []corev1.Container{ { - Name: ServiceName + "-db-sync", + Name: instance.Name + "-db-sync", Command: []string{ "/bin/bash", }, diff --git a/pkg/designate/generate_bind9_pools_yaml.go b/pkg/designate/generate_bind9_pools_yaml.go index f1237684..176df79a 100644 --- a/pkg/designate/generate_bind9_pools_yaml.go +++ b/pkg/designate/generate_bind9_pools_yaml.go @@ -18,52 +18,55 @@ import ( "fmt" "gopkg.in/yaml.v2" "os" + "os/exec" + "path/filepath" + "strings" "text/template" ) type Pool struct { - Name string - Description string - Attributes map[string]string - NSRecords []NSRecord - Nameservers []Nameserver - Targets []Target - CatalogZone *CatalogZone // it is a pointer because it is optional + Name string `yaml:"name"` + Description string `yaml:"description"` + Attributes map[string]string `yaml:"attributes"` + NSRecords []NSRecord `yaml:"ns_records"` + Nameservers []Nameserver `yaml:"nameservers"` + Targets []Target `yaml:"targets"` + CatalogZone *CatalogZone `yaml:"catalog_zone,omitempty"` // it is a pointer because it is optional } type NSRecord struct { - Hostname string - Priority int + Hostname string `yaml:"hostname"` + Priority int `yaml:"priority"` } type Nameserver struct { - Host string - Port int + Host string `yaml:"host"` + Port int `yaml:"port"` } type Target struct { - Type string - Description string - Masters []Master - Options Options + Type string `yaml:"type"` + Description string `yaml:"description"` + Masters []Master `yaml:"masters"` + Options Options `yaml:"options"` } type Master struct { - Host string - Port int + Host string `yaml:"host"` + Port int `yaml:"port"` } type Options struct { - Host string - Port int - RNDCHost string - RNDCPort int - RNDCConfigFile string + Host string `yaml:"host"` + Port int `yaml:"port"` + RNDCHost string `yaml:"rndc_host"` + RNDCPort int `yaml:"rndc_port"` + RNDCConfigFile string `yaml:"rndc_config_file"` } type CatalogZone struct { - FQDN string - Refresh string + FQDN string `yaml:"fqdn"` + Refresh int `yaml:"refresh"` } func GeneratePoolsYamlData(BindMap, MdnsMap, NsRecordsMap map[string]string) (string, error) { @@ -114,7 +117,7 @@ func GeneratePoolsYamlData(BindMap, MdnsMap, NsRecordsMap map[string]string) (st // Catalog zone is an optional section // catalogZone := &CatalogZone{ // FQDN: "example.org.", - // Refresh: "60", + // Refresh: 60, // } defaultAttributes := make(map[string]string) // Create the Pool struct with the dynamic values @@ -128,7 +131,12 @@ func GeneratePoolsYamlData(BindMap, MdnsMap, NsRecordsMap map[string]string) (st CatalogZone: nil, // set to catalogZone if this section should be presented } - PoolsYaml, err := os.ReadFile(PoolsYamlPath) + poolsYamlPath, err := getPoolsYamlPath() + if err != nil { + return "", err + } + + PoolsYaml, err := os.ReadFile(poolsYamlPath) if err != nil { return "", err } @@ -145,3 +153,13 @@ func GeneratePoolsYamlData(BindMap, MdnsMap, NsRecordsMap map[string]string) (st return buf.String(), nil } + +// We have this function so different tests could find PoolsYamlPath +func getPoolsYamlPath() (string, error) { + cmdOut, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() + if err != nil { + return "", fmt.Errorf("failed to get repository root directory from: %s because of %w", string(cmdOut), err) + } + repoRoot := strings.TrimSpace(string(cmdOut)) + return filepath.Join(repoRoot, PoolsYamlPath), nil +} diff --git a/tests/functional/api_fixture.go b/tests/functional/api_fixture.go new file mode 100644 index 00000000..632e1d7a --- /dev/null +++ b/tests/functional/api_fixture.go @@ -0,0 +1,126 @@ +/* +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 functional_test + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/go-logr/logr" + "github.com/google/uuid" + + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + + "github.com/gophercloud/gophercloud/openstack/identity/v3/projects" + "github.com/gophercloud/gophercloud/openstack/identity/v3/users" + keystone_helpers "github.com/openstack-k8s-operators/keystone-operator/api/test/helpers" + + api "github.com/openstack-k8s-operators/lib-common/modules/test/apis" +) + +var ( + keystoneProjects = []projects.Project{ + { + Name: "admin", + ID: uuid.New().String(), + }, + { + Name: "service", + ID: uuid.New().String(), + }, + } +) + +func keystoneHandleProjects( + f *keystone_helpers.KeystoneAPIFixture, + w http.ResponseWriter, + r *http.Request, +) { + f.LogRequest(r) + switch r.Method { + case "GET": + keystoneGetProject(f, w, r) + default: + f.UnexpectedRequest(w, r) + return + } +} + +func GetProject(name string) *projects.Project { + for _, p := range keystoneProjects { + if p.Name == name { + return &p + } + } + return nil +} + +func keystoneGetProject( + f *keystone_helpers.KeystoneAPIFixture, + w http.ResponseWriter, + r *http.Request, +) { + nameFilter := r.URL.Query().Get("name") + var s struct { + Projects []projects.Project `json:"projects"` + } + project := GetProject(nameFilter) + s.Projects = []projects.Project{*project} + + bytes, err := json.Marshal(&s) + if err != nil { + f.InternalError(err, "Error during marshalling response", w, r) + return + } + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(200) + fmt.Fprint(w, string(bytes)) + f.APIFixture.Log.Info(fmt.Sprintf("GetProject returns %s", string(bytes))) +} + +type APIFixtures struct { + Keystone *keystone_helpers.KeystoneAPIFixture +} + +func SetupAPIFixtures(logger logr.Logger) APIFixtures { + keystone := keystone_helpers.NewKeystoneAPIFixtureWithServer(logger) + keystone.Users = map[string]users.User{ + "designate": { + Name: "designate", + ID: uuid.New().String(), + }, + } + keystone.Setup( + api.Handler{Pattern: "/", Func: keystone.HandleVersion}, + api.Handler{Pattern: "/v3/users", Func: keystone.HandleUsers}, + api.Handler{Pattern: "/v3/domains", Func: keystone.HandleDomains}, + api.Handler{Pattern: "/v3/projects", Func: func(w http.ResponseWriter, r *http.Request) { + keystoneHandleProjects(keystone, w, r) + }}, + api.Handler{Pattern: "/v3/auth/tokens", Func: func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(202) + } + }}) + DeferCleanup(keystone.Cleanup) + return APIFixtures{ + Keystone: keystone, + } +} diff --git a/tests/functional/base_test.go b/tests/functional/base_test.go new file mode 100644 index 00000000..aac51962 --- /dev/null +++ b/tests/functional/base_test.go @@ -0,0 +1,465 @@ +/* +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 functional_test + +import ( + "encoding/json" + "fmt" + "time" + + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + + networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + designatev1 "github.com/openstack-k8s-operators/designate-operator/api/v1beta1" + "github.com/openstack-k8s-operators/designate-operator/pkg/designate" + rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" + redisv1 "github.com/openstack-k8s-operators/infra-operator/apis/redis/v1beta1" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/endpoint" +) + +const ( + SecretName = "test-secret" + KeystoneSecretName = "%s-keystone-secret" + RabbitmqSecretName = "rabbitmq-secret" + + PublicCertSecretName = "public-tls-certs" + InternalCertSecretName = "internal-tls-certs" + CABundleSecretName = "combined-ca-bundle" + + timeout = time.Second * 5 + interval = timeout / 100 +) + +func CreateTransportURL(name types.NamespacedName) *rabbitmqv1.TransportURL { + transportURL := &rabbitmqv1.TransportURL{ + ObjectMeta: metav1.ObjectMeta{ + Name: name.Name, + Namespace: name.Namespace, + }, + Spec: rabbitmqv1.TransportURLSpec{ + RabbitmqClusterName: "rabbitmq", + }, + } + Expect(k8sClient.Create(ctx, transportURL)).Should(Succeed()) + return infra.GetTransportURL(name) +} + +func CreateTransportURLSecret(name types.NamespacedName) *corev1.Secret { + secret := th.CreateSecret( + name, + map[string][]byte{ + "transport_url": []byte(fmt.Sprintf("rabbit://%s/", name)), + }, + ) + logger.Info("Created TransportURLSecret", "secret", secret) + return secret +} + +func createAndSimulateRedis(name types.NamespacedName) { + replicas := int32(1) + redis := &redisv1.Redis{ + ObjectMeta: metav1.ObjectMeta{ + Name: "designate-redis", + Namespace: name.Namespace, + }, + Spec: redisv1.RedisSpec{ + RedisSpecCore: redisv1.RedisSpecCore{ + Replicas: &replicas, + }, + ContainerImage: "repo/redis-image", + }, + } + Expect(k8sClient.Create(ctx, redis)).Should(Succeed()) + DeferCleanup(k8sClient.Delete, ctx, redis) + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "designate-redis", + Namespace: name.Namespace, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "redis", + Port: 6379, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, svc)).Should(Succeed()) + DeferCleanup(k8sClient.Delete, ctx, svc) +} + +func SimulateKeystoneReady( + name types.NamespacedName, + publicEndpointURL string, + internalEndpointURL string, +) { + secretName := fmt.Sprintf(KeystoneSecretName, name.Name) + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + Data: map[string][]byte{ + "admin-password": []byte("12345678"), + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + DeferCleanup(k8sClient.Delete, ctx, secret) + Eventually(func(g Gomega) { + ks := keystone.GetKeystoneAPI(name) + ks.Spec.Secret = secretName + ks.Spec.Region = "RegionOne" + ks.Spec.AdminProject = "admin" + g.Expect(k8sClient.Update(ctx, ks)).To(Succeed()) + ks.Status.APIEndpoints[string(endpoint.EndpointInternal)] = internalEndpointURL + ks.Status.APIEndpoints[string(endpoint.EndpointPublic)] = publicEndpointURL + g.Expect(k8sClient.Status().Update(ctx, ks)).To(Succeed()) + }, timeout, interval).Should(Succeed()) +} + +func GetDefaultDesignateSpec(bind9ReplicaCount, mdnsReplicaCount int) map[string]interface{} { + spec := map[string]interface{}{ + "databaseInstance": "test-designate-db-instance", + "secret": SecretName, + "designateNetworkAttachment": "designate-attachement", + "databaseAccount": "designate-db-account", + } + spec["designateBackendbind9"] = designatev1.DesignateBackendbind9Spec{ + DesignateBackendbind9SpecBase: designatev1.DesignateBackendbind9SpecBase{ + Replicas: ptr.To(int32(bind9ReplicaCount)), + }, + } + spec["designateMdns"] = designatev1.DesignateMdnsSpec{ + DesignateMdnsSpecBase: designatev1.DesignateMdnsSpecBase{ + Replicas: ptr.To(int32(mdnsReplicaCount)), + }, + } + return spec +} + +func CreateDesignate(name types.NamespacedName, spec map[string]interface{}) client.Object { + + raw := map[string]interface{}{ + "apiVersion": "designate.openstack.org/v1beta1", + "kind": "Designate", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } + return th.CreateUnstructured(raw) +} + +func GetDesignate(name types.NamespacedName) *designatev1.Designate { + instance := &designatev1.Designate{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} + +func DesignateConditionGetter(name types.NamespacedName) condition.Conditions { + instance := GetDesignate(name) + return instance.Status.Conditions +} + +func CreateDesignateSecret(namespace string) *corev1.Secret { + secret := th.CreateSecret( + types.NamespacedName{Namespace: namespace, Name: SecretName}, + map[string][]byte{ + "DesignatePassword": []byte("DesignatePassword12345678"), + }, + ) + logger.Info("Secret created", "name", SecretName, "namespace", namespace) + return secret +} + +// DesignateAPI +func GetDefaultDesignateAPISpec() map[string]interface{} { + return map[string]interface{}{ + "databaseHostname": "hostname-for-designate-api", + "databaseInstance": "test-designate-db-instance", + "secret": SecretName, + "designateNetworkAttachment": "designate-attachement", + "containerImage": "repo/designate-api-image", + "serviceAccount": "designate", + } +} + +func CreateDesignateAPI(name types.NamespacedName, spec map[string]interface{}) client.Object { + raw := map[string]interface{}{ + "apiVersion": "designate.openstack.org/v1beta1", + "kind": "DesignateAPI", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } + return th.CreateUnstructured(raw) +} + +func GetDesignateAPI(name types.NamespacedName) *designatev1.DesignateAPI { + instance := &designatev1.DesignateAPI{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} + +func DesignateAPIConditionGetter(name types.NamespacedName) condition.Conditions { + instance := GetDesignateAPI(name) + return instance.Status.Conditions +} + +func SimulateDesignateAPIReady(name types.NamespacedName) { + Eventually(func(g Gomega) { + designateAPI := GetDesignateAPI(name) + designateAPI.Status.ObservedGeneration = designateAPI.Generation + designateAPI.Status.ReadyCount = 1 + g.Expect(k8sClient.Status().Update(ctx, designateAPI)).To(Succeed()) + }, timeout, interval).Should(Succeed()) +} + +// DesignateBackendbind9 +func GetDefaultDesignateBackendbind9Spec() map[string]interface{} { + return map[string]interface{}{ + "databaseHostname": "hostname-for-designate-backendbind9", + "secret": SecretName, + "designateNetworkAttachment": "designate-attachement", + "containerImage": "repo/designate-backendbind9-image", + "serviceAccount": "designate", + } +} + +func CreateDesignateBackendbind9(name types.NamespacedName, spec map[string]interface{}) client.Object { + raw := map[string]interface{}{ + "apiVersion": "designate.openstack.org/v1beta1", + "kind": "DesignateBackendbind9", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } + return th.CreateUnstructured(raw) +} + +func GetDesignateBackendbind9(name types.NamespacedName) *designatev1.DesignateBackendbind9 { + instance := &designatev1.DesignateBackendbind9{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} + +func DesignateBackendbind9ConditionGetter(name types.NamespacedName) condition.Conditions { + instance := GetDesignateBackendbind9(name) + return instance.Status.Conditions +} + +// DesignateMdns +func GetDefaultDesignateMdnsSpec() map[string]interface{} { + return map[string]interface{}{ + "databaseHostname": "hostname-for-designate-mdns", + "databaseInstance": "test-designate-db-instance", + "secret": SecretName, + "designateNetworkAttachment": "designate-attachement", + "containerImage": "repo/designate-mdns-image", + "serviceAccount": "designate", + } +} + +func CreateDesignateMdns(name types.NamespacedName, spec map[string]interface{}) client.Object { + raw := map[string]interface{}{ + "apiVersion": "designate.openstack.org/v1beta1", + "kind": "DesignateMdns", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } + return th.CreateUnstructured(raw) +} + +func GetDesignateMdns(name types.NamespacedName) *designatev1.DesignateMdns { + instance := &designatev1.DesignateMdns{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} + +func DesignateMdnsConditionGetter(name types.NamespacedName) condition.Conditions { + instance := GetDesignateMdns(name) + return instance.Status.Conditions +} + +// DesignateCentral +func GetDefaultDesignateCentralSpec() map[string]interface{} { + return map[string]interface{}{ + "databaseHostname": "hostname-for-designate-central", + "secret": SecretName, + "designateNetworkAttachment": "designate-attachement", + "containerImage": "repo/designate-central-image", + "serviceAccount": "designate", + } +} + +func CreateDesignateCentral(name types.NamespacedName, spec map[string]interface{}) client.Object { + raw := map[string]interface{}{ + "apiVersion": "designate.openstack.org/v1beta1", + "kind": "DesignateCentral", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } + return th.CreateUnstructured(raw) +} + +func GetDesignateCentral(name types.NamespacedName) *designatev1.DesignateCentral { + instance := &designatev1.DesignateCentral{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} + +func DesignateCentralConditionGetter(name types.NamespacedName) condition.Conditions { + instance := GetDesignateCentral(name) + return instance.Status.Conditions +} + +// DesignateProducer +func GetDefaultDesignateProducerSpec() map[string]interface{} { + return map[string]interface{}{ + "databaseHostname": "hostname-for-designate-producer", + "secret": SecretName, + "designateNetworkAttachment": "designate-attachement", + "containerImage": "repo/designate-producer-image", + "serviceAccount": "designate", + } +} + +func CreateDesignateProducer(name types.NamespacedName, spec map[string]interface{}) client.Object { + raw := map[string]interface{}{ + "apiVersion": "designate.openstack.org/v1beta1", + "kind": "DesignateProducer", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } + return th.CreateUnstructured(raw) +} + +func GetDesignateProducer(name types.NamespacedName) *designatev1.DesignateProducer { + instance := &designatev1.DesignateProducer{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} + +func DesignateProducerConditionGetter(name types.NamespacedName) condition.Conditions { + instance := GetDesignateProducer(name) + return instance.Status.Conditions +} + +// Network attachment +func CreateNAD(name types.NamespacedName) client.Object { + raw := map[string]interface{}{ + "apiVersion": "k8s.cni.cncf.io/v1", + "kind": "NetworkAttachmentDefinition", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": map[string]interface{}{ + "config": `{ + "cniVersion": "0.3.1", + "name": "designate", + "type": "bridge", + "ipam": { + "type": "whereabouts", + "range": "172.28.0.0/24", + "range_start": "172.28.0.30", + "range_end": "172.28.0.70" + } + }`, + }, + } + return th.CreateUnstructured(raw) +} + +func GetNADConfig(name types.NamespacedName) *designate.NADConfig { + nad := &networkv1.NetworkAttachmentDefinition{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, nad)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + nadConfig := &designate.NADConfig{} + jsonDoc := []byte(nad.Spec.Config) + err := json.Unmarshal(jsonDoc, nadConfig) + if err != nil { + return nil + } + + return nadConfig +} + +func CreateNode(name types.NamespacedName) client.Object { + raw := map[string]interface{}{ + "apiVersion": "v1", + "kind": "Node", + "metadata": map[string]interface{}{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": map[string]interface{}{}, + } + return th.CreateUnstructured(raw) +} + +func CreateDesignateNSRecordsConfigMap(name types.NamespacedName) client.Object { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: designate.NsRecordsConfigMap, + Namespace: name.Namespace, + }, + Data: map[string]string{ + "ns_records": `- hostname: ns1.example.com. + priority: 1 +- hostname: ns2.example.com. + priority: 2`, + }, + } +} diff --git a/tests/functional/designate_controller_test.go b/tests/functional/designate_controller_test.go new file mode 100644 index 00000000..1ac532d4 --- /dev/null +++ b/tests/functional/designate_controller_test.go @@ -0,0 +1,722 @@ +/* +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 functional_test + +import ( + "fmt" + "gopkg.in/yaml.v2" + "math/rand" + "net" + + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "k8s.io/apimachinery/pkg/types" + + corev1 "k8s.io/api/core/v1" + + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + + //revive:disable-next-line:dot-imports + validator "github.com/go-playground/validator/v10" + designatev1 "github.com/openstack-k8s-operators/designate-operator/api/v1beta1" + "github.com/openstack-k8s-operators/designate-operator/pkg/designate" + . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" +) + +func createAndSimulateKeystone( + designateName types.NamespacedName, +) APIFixtures { + apiFixtures := SetupAPIFixtures(logger) + keystoneName := keystone.CreateKeystoneAPIWithFixture(namespace, apiFixtures.Keystone) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneName) + keystonePublicEndpoint := fmt.Sprintf("http://keystone-for-%s-public", designateName.Name) + SimulateKeystoneReady(keystoneName, keystonePublicEndpoint, apiFixtures.Keystone.Endpoint()) + return apiFixtures +} + +func createAndSimulateDesignateSecrets( + designateName types.NamespacedName, +) { + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateSecret(designateName.Namespace)) +} + +func createAndSimulateTransportURL( + transportURLName types.NamespacedName, + transportURLSecretName types.NamespacedName, +) { + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURL(transportURLName)) + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) + infra.SimulateTransportURLReady(transportURLName) +} + +func createAndSimulateNSRecordsConfigMap( + NsRecordsConfigMapName types.NamespacedName, +) { + configMap := CreateDesignateNSRecordsConfigMap(NsRecordsConfigMapName) + err := k8sClient.Create(ctx, configMap) + Expect(err).ToNot(HaveOccurred()) + + Eventually(func() error { + return k8sClient.Get(ctx, NsRecordsConfigMapName, &corev1.ConfigMap{}) + }, timeout, interval).Should(Succeed()) + + DeferCleanup(k8sClient.Delete, ctx, configMap) +} + +func createAndSimulateDB(spec map[string]interface{}) { + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + spec["databaseInstance"].(string), + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + mariadb.CreateMariaDBAccount(namespace, spec["databaseAccount"].(string), mariadbv1.MariaDBAccountSpec{ + Secret: "osp-secret", + UserName: "designate", + }) + mariadb.CreateMariaDBDatabase(namespace, designate.DatabaseCRName, mariadbv1.MariaDBDatabaseSpec{}) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: spec["databaseAccount"].(string)}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: designate.DatabaseCRName}) +} + +func createAndSimulateBind9(bind9Name types.NamespacedName) { + bind9Spec := GetDefaultDesignateBackendbind9Spec() + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateBackendbind9(bind9Name, bind9Spec)) +} + +func createAndSimulateMdns(mdnsName types.NamespacedName) { + mdnsSpec := GetDefaultDesignateMdnsSpec() + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateMdns(mdnsName, mdnsSpec)) +} + +var _ = Describe("Designate controller", func() { + var name string + var spec map[string]interface{} + var designateName types.NamespacedName + var designateBind9Name types.NamespacedName + var designateMdnsName types.NamespacedName + var designateNSRecordConfigMapName types.NamespacedName + var transportURLName types.NamespacedName + var transportURLSecretName types.NamespacedName + var designateDBSyncName types.NamespacedName + var designateRedisName types.NamespacedName + var bind9ReplicaCount int + var mdnsReplicaCount int + + BeforeEach(func() { + name = fmt.Sprintf("designate-%s", uuid.New().String()) + bind9ReplicaCount = rand.Intn(5) + 1 + mdnsReplicaCount = rand.Intn(5) + 1 + spec = GetDefaultDesignateSpec(bind9ReplicaCount, mdnsReplicaCount) + + designateName = types.NamespacedName{ + Namespace: namespace, + Name: name, + } + + transportURLName = types.NamespacedName{ + Namespace: namespace, + Name: name + "-designate-transport", + } + + transportURLSecretName = types.NamespacedName{ + Namespace: namespace, + Name: RabbitmqSecretName, + } + + designateDBSyncName = types.NamespacedName{ + Namespace: namespace, + Name: designateName.Name + "-db-sync", + } + + designateRedisName = types.NamespacedName{ + Namespace: namespace, + Name: "designate-redis", + } + + designateBind9Name = types.NamespacedName{ + Namespace: namespace, + Name: fmt.Sprintf("designate-bind9-%s", uuid.New().String()), + } + + designateMdnsName = types.NamespacedName{ + Namespace: namespace, + Name: fmt.Sprintf("designate-mdns-%s", uuid.New().String()), + } + + designateNSRecordConfigMapName = types.NamespacedName{ + Namespace: namespace, + Name: designate.NsRecordsConfigMap, + } + }) + + When("a Designate instance is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDesignate(designateName, spec)) + }) + + It("should have the Spec fields initialized", func() { + designate := GetDesignate(designateName) + Expect(designate.Spec.DatabaseInstance).Should(Equal("test-designate-db-instance")) + Expect(designate.Spec.Secret).Should(Equal(SecretName)) + }) + + It("should have the Status fields initialized", func() { + designate := GetDesignate(designateName) + Expect(designate.Status.DatabaseHostname).Should(Equal("")) + Expect(designate.Status.TransportURLSecret).Should(Equal("")) + Expect(designate.Status.DesignateAPIReadyCount).Should(Equal(int32(0))) + Expect(designate.Status.DesignateCentralReadyCount).Should(Equal(int32(0))) + Expect(designate.Status.DesignateWorkerReadyCount).Should(Equal(int32(0))) + Expect(designate.Status.DesignateMdnsReadyCount).Should(Equal(int32(0))) + Expect(designate.Status.DesignateProducerReadyCount).Should(Equal(int32(0))) + Expect(designate.Status.DesignateBackendbind9ReadyCount).Should(Equal(int32(0))) + Expect(designate.Status.DesignateUnboundReadyCount).Should(Equal(int32(0))) + }) + + It("should have Unknown Conditions initialized as TransportUrl not created", func() { + for _, cond := range []condition.Type{ + condition.RabbitMqTransportURLReadyCondition, + condition.DBReadyCondition, + condition.ServiceConfigReadyCondition, + } { + th.ExpectCondition( + designateName, + ConditionGetterFunc(DesignateConditionGetter), + cond, + corev1.ConditionUnknown, + ) + } + // TODO(oschwart) InputReadyCondition is set to False while the controller is waiting for the transportURL to be created, this is probably not the correct behavior + for _, cond := range []condition.Type{ + condition.InputReadyCondition, + condition.ReadyCondition, + } { + th.ExpectCondition( + designateName, + ConditionGetterFunc(DesignateConditionGetter), + cond, + corev1.ConditionFalse, + ) + } + }) + + It("should have a finalizer", func() { + // the reconciler loop adds the finalizer so we have to wait for + // it to run + Eventually(func() []string { + return GetDesignate(designateName).Finalizers + }, timeout, interval).Should(ContainElement("openstack.org/designate")) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: designateName.Namespace, + Name: fmt.Sprintf("%s-%s", designateName.Name, "config-data"), + } + th.AssertSecretDoesNotExist(secret) + }) + }) + + // TransportURL + When("a proper secret is provider, TransportURL is created", func() { + BeforeEach(func() { + createAndSimulateKeystone(designateName) + createAndSimulateRedis(designateRedisName) + createAndSimulateDesignateSecrets(designateName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + DeferCleanup(th.DeleteInstance, CreateDesignate(designateName, spec)) + }) + + It("should be in state of having the input ready", func() { + th.ExpectCondition( + designateName, + ConditionGetterFunc(DesignateConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should be in state of having the TransportURL ready", func() { + th.ExpectCondition( + designateName, + ConditionGetterFunc(DesignateConditionGetter), + condition.RabbitMqTransportURLReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: designateName.Namespace, + Name: fmt.Sprintf("%s-%s", designateName.Name, "config-data"), + } + th.AssertSecretDoesNotExist(secret) + }) + }) + + // NAD + // TODO + + // DB + When("DB is created", func() { + BeforeEach(func() { + createAndSimulateKeystone(designateName) + createAndSimulateRedis(designateRedisName) + createAndSimulateDesignateSecrets(designateName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + DeferCleanup(th.DeleteInstance, CreateDesignate(designateName, spec)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetDesignate(designateName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + }) + + It("should set DBReady Condition and set DatabaseHostname Status", func() { + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetDesignate(designateName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: designate.DatabaseCRName}) + th.SimulateJobSuccess(designateDBSyncName) + designate := GetDesignate(designateName) + hostname := "hostname-for-" + designate.Spec.DatabaseInstance + "." + namespace + ".svc" + Expect(designate.Status.DatabaseHostname).To(Equal(hostname)) + + th.ExpectCondition( + designateName, + ConditionGetterFunc(DesignateConditionGetter), + condition.DBReadyCondition, + corev1.ConditionTrue, + ) + + th.ExpectCondition( + designateName, + ConditionGetterFunc(DesignateConditionGetter), + condition.DBSyncReadyCondition, + corev1.ConditionFalse, + ) + }) + }) + + // Config + When("The Config Secrets are created", func() { + + BeforeEach(func() { + createAndSimulateKeystone(designateName) + createAndSimulateRedis(designateRedisName) + createAndSimulateDesignateSecrets(designateName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + + createAndSimulateDB(spec) + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["designateNetworkAttachment"].(string), + Namespace: namespace, + })) + + DeferCleanup(th.DeleteInstance, CreateDesignate(designateName, spec)) + + th.SimulateJobSuccess(designateDBSyncName) + }) + + It("should set Service Config Ready Condition", func() { + th.ExpectCondition( + designateName, + ConditionGetterFunc(DesignateConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should create the designate.conf file in a Secret", func() { + instance := GetDesignate(designateName) + + configData := th.GetSecret( + types.NamespacedName{ + Namespace: designateName.Namespace, + Name: fmt.Sprintf("%s-config-data", designateName.Name)}) + Expect(configData).ShouldNot(BeNil()) + conf := string(configData.Data["designate.conf"]) + Expect(conf).Should( + ContainSubstring( + fmt.Sprintf( + "username=%s\n", + instance.Spec.ServiceUser))) + + dbs := []struct { + Name string + DatabaseAccount string + Keyword string + }{ + { + Name: designate.DatabaseName, + DatabaseAccount: instance.Spec.DatabaseAccount, + Keyword: "connection", + }, + } + + for _, db := range dbs { + databaseAccount := mariadb.GetMariaDBAccount( + types.NamespacedName{ + Namespace: namespace, + Name: db.DatabaseAccount}) + databaseSecret := th.GetSecret( + types.NamespacedName{ + Namespace: namespace, + Name: databaseAccount.Spec.Secret}) + + Expect(conf).Should( + ContainSubstring( + fmt.Sprintf( + "%s=mysql+pymysql://%s:%s@%s/%s?read_default_file=/etc/my.cnf", + db.Keyword, + databaseAccount.Spec.UserName, + databaseSecret.Data[mariadbv1.DatabasePasswordSelector], + instance.Status.DatabaseHostname, + db.Name))) + } + }) + + It("should create a Secret for the scripts", func() { + scriptData := th.GetSecret( + types.NamespacedName{ + Namespace: designateName.Namespace, + Name: fmt.Sprintf("%s-scripts", designateName.Name)}) + Expect(scriptData).ShouldNot(BeNil()) + }) + }) + + // Networks Annotation + When("Network Annotation is created", func() { + BeforeEach(func() { + createAndSimulateKeystone(designateName) + createAndSimulateRedis(designateRedisName) + createAndSimulateDesignateSecrets(designateName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + + createAndSimulateDB(spec) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["designateNetworkAttachment"].(string), + Namespace: namespace, + })) + + DeferCleanup(th.DeleteInstance, CreateDesignate(designateName, spec)) + }) + + It("should set the NetworkAttachementReady condition", func() { + th.ExpectCondition( + designateName, + ConditionGetterFunc(DesignateConditionGetter), + condition.NetworkAttachmentsReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + + // API Deployment + When("Designate is created with nodeSelector", func() { + BeforeEach(func() { + spec["nodeSelector"] = map[string]interface{}{ + "foo": "bar", + } + createAndSimulateKeystone(designateName) + createAndSimulateRedis(designateRedisName) + createAndSimulateDesignateSecrets(designateName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + createAndSimulateDB(spec) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["designateNetworkAttachment"].(string), + Namespace: namespace, + })) + + DeferCleanup(th.DeleteInstance, CreateDesignate(designateName, spec)) + + th.SimulateJobSuccess(designateDBSyncName) + + // TODO: assert nodeSelector on more resources when supported + }) + + It("sets nodeSelector in resource specs", func() { + Eventually(func(g Gomega) { + g.Expect(th.GetJob(designateDBSyncName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + }, timeout, interval).Should(Succeed()) + }) + + It("updates nodeSelector in resource specs when changed", func() { + Eventually(func(g Gomega) { + g.Expect(th.GetJob(designateDBSyncName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + designate := GetDesignate(designateName) + newNodeSelector := map[string]string{ + "foo2": "bar2", + } + designate.Spec.NodeSelector = &newNodeSelector + g.Expect(k8sClient.Update(ctx, designate)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + th.SimulateJobSuccess(designateDBSyncName) + g.Expect(th.GetJob(designateDBSyncName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo2": "bar2"})) + }, timeout, interval).Should(Succeed()) + }) + + It("removes nodeSelector from resource specs when cleared", func() { + Eventually(func(g Gomega) { + g.Expect(th.GetJob(designateDBSyncName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + designate := GetDesignate(designateName) + emptyNodeSelector := map[string]string{} + designate.Spec.NodeSelector = &emptyNodeSelector + g.Expect(k8sClient.Update(ctx, designate)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + th.SimulateJobSuccess(designateDBSyncName) + g.Expect(th.GetJob(designateDBSyncName).Spec.Template.Spec.NodeSelector).To(BeNil()) + }, timeout, interval).Should(Succeed()) + }) + + It("removes nodeSelector from resource specs when nilled", func() { + Eventually(func(g Gomega) { + g.Expect(th.GetJob(designateDBSyncName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + designate := GetDesignate(designateName) + designate.Spec.NodeSelector = nil + g.Expect(k8sClient.Update(ctx, designate)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + th.SimulateJobSuccess(designateDBSyncName) + g.Expect(th.GetJob(designateDBSyncName).Spec.Template.Spec.NodeSelector).To(BeNil()) + }, timeout, interval).Should(Succeed()) + }) + }) + + When("Designate Bind9 and Mdns services are created", func() { + BeforeEach(func() { + createAndSimulateKeystone(designateName) + createAndSimulateRedis(designateRedisName) + createAndSimulateDesignateSecrets(designateName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + createAndSimulateDB(spec) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["designateNetworkAttachment"].(string), + Namespace: namespace, + })) + DeferCleanup(th.DeleteInstance, CreateDesignate(designateName, spec)) + th.SimulateJobSuccess(designateDBSyncName) + + createAndSimulateBind9(designateBind9Name) + createAndSimulateMdns(designateMdnsName) + }) + + It("should have Unknown Conditions initialized for Designate services conditions initially", func() { + for _, cond := range []condition.Type{ + designatev1.DesignateAPIReadyCondition, + designatev1.DesignateBackendbind9ReadyCondition, + designatev1.DesignateCentralReadyCondition, + designatev1.DesignateMdnsReadyCondition, + designatev1.DesignateProducerReadyCondition, + designatev1.DesignateUnboundReadyCondition, + designatev1.DesignateWorkerReadyCondition, + } { + th.ExpectCondition( + designateName, + ConditionGetterFunc(DesignateConditionGetter), + cond, + corev1.ConditionUnknown, + ) + } + }) + + It("should create ConfigMaps for Bind9 and Mdns predictable IPs", func() { + bindConfigMap := th.GetConfigMap(types.NamespacedName{ + Name: designate.BindPredIPConfigMap, + Namespace: namespace}) + Expect(len(bindConfigMap.Data)).To(Equal(bind9ReplicaCount)) + + usedIPs := make(map[string]bool) + for key, ipAddress := range bindConfigMap.Data { + // verify key with bind_address_N format + Expect(key).To(MatchRegexp(`^bind_address_\d+$`)) + + // verify valid IP format + ip := net.ParseIP(ipAddress) + Expect(ip).NotTo(BeNil(), "Invalid IP format: %s", ipAddress) + + // check there are no duplicate IPs + Expect(usedIPs[ipAddress]).To(BeFalse(), "Duplicate IP found: %s", ipAddress) + usedIPs[ipAddress] = true + } + + mdnsConfigMap := th.GetConfigMap(types.NamespacedName{ + Name: designate.MdnsPredIPConfigMap, + Namespace: namespace}) + Expect(len(mdnsConfigMap.Data)).To(Equal(mdnsReplicaCount)) + for key, ipAddress := range mdnsConfigMap.Data { + // verify key with mdns_address_N format + Expect(key).To(MatchRegexp(`^mdns_address_\d+$`)) + + // verify valid IP format + ip := net.ParseIP(ipAddress) + Expect(ip).NotTo(BeNil(), "Invalid IP format: %s", ipAddress) + + // check there are no duplicate IPs + Expect(usedIPs[ipAddress]).To(BeFalse(), "Duplicate IP found: %s", ipAddress) + usedIPs[ipAddress] = true + } + }) + }) + + When("Designate ns_records ConfigMap is created", func() { + BeforeEach(func() { + createAndSimulateKeystone(designateName) + createAndSimulateRedis(designateRedisName) + createAndSimulateDesignateSecrets(designateName) + createAndSimulateTransportURL(transportURLName, transportURLSecretName) + createAndSimulateDB(spec) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["designateNetworkAttachment"].(string), + Namespace: namespace, + })) + DeferCleanup(th.DeleteInstance, CreateDesignate(designateName, spec)) + th.SimulateJobSuccess(designateDBSyncName) + + createAndSimulateBind9(designateBind9Name) + createAndSimulateMdns(designateMdnsName) + createAndSimulateNSRecordsConfigMap(designateNSRecordConfigMapName) + }) + + It("should have created a valid pools.yaml configmap", func() { + nsRecordsConfigMap := th.GetConfigMap(types.NamespacedName{ + Name: designate.NsRecordsConfigMap, + Namespace: namespace}) + Expect(nsRecordsConfigMap).ToNot(BeNil()) + + poolsYamlConfigMap := th.GetConfigMap(types.NamespacedName{ + Name: designate.PoolsYamlsConfigMap, + Namespace: namespace}) + Expect(poolsYamlConfigMap).ToNot(BeNil()) + + var pools []designate.Pool + err := yaml.Unmarshal([]byte(poolsYamlConfigMap.Data[designate.PoolsYamlsConfigMap]), &pools) + Expect(err).ToNot(HaveOccurred()) + + validate := validator.New() + + for _, pool := range pools { + Expect(pool.Name).ToNot(BeEmpty(), "Pool name should not be an empty string") + Expect(pool.Description).ToNot(BeEmpty(), "Pool description should not be an empty string") + + // Check attributes if exists + if len(pool.Attributes) > 0 { + for _, v := range pool.Attributes { + Expect(v).ToNot(BeEmpty(), "Attribute value should not be an empty string") + } + } + + // Check NS records + Expect(pool.NSRecords).ToNot(BeEmpty(), "NS records should not be empty") + for _, ns := range pool.NSRecords { + err := validate.Var(ns.Hostname, "fqdn") + Expect(err).ToNot(HaveOccurred(), "NS record hostname should be valid FQDN") + Expect(ns.Priority).To(BeNumerically(">", 0), "NS record priority should be a positive number") + } + + // Check nameservers + Expect(pool.Nameservers).ToNot(BeEmpty(), "Nameservers should not be empty") + for _, ns := range pool.Nameservers { + ip := net.ParseIP(ns.Host) + Expect(ip).NotTo(BeNil(), "Nameserver host should be valid IP") + Expect(ns.Port).To(Equal(53), "Nameserver port should be 53") + } + + // Check targets + Expect(pool.Targets).ToNot(BeEmpty(), "Targets should not be empty") + var numOfBindHosts int + for i, target := range pool.Targets { + // Check target type (Designate Backend) + Expect(target.Type).To(Equal("bind9"), "Only Bind9 is a supported Designate backend") + + // Check description format + serverNum := i + 1 + expectedDesc := fmt.Sprintf("BIND9 Server %d (%s)", serverNum, target.Options.Host) + Expect(target.Description).To(Equal(expectedDesc), "Target description format mismatch") + + // Check masters + Expect(target.Masters).To(HaveLen(mdnsReplicaCount), "Masters count should match mdnsReplicaCount") + for _, master := range target.Masters { + ip := net.ParseIP(master.Host) + Expect(ip).NotTo(BeNil(), "Master host should be valid IP") + Expect(master.Port).To(BeNumerically(">", 0), "Master port should be a positive number") + } + + // Check options + Expect(target.Options.Host).To(Equal(target.Options.RNDCHost), "Options Host and RNDCHost should match") + + // We can't know the order which the nameservers are stored, so we make sure they match the options + // host and rndc_host. After this loop we assert the len(nameservers) == len(hosts) + foundMatch := false + for _, ns := range pool.Nameservers { + if ns.Host == target.Options.Host { + foundMatch = true + numOfBindHosts++ + break + } + } + Expect(foundMatch).To(BeTrue(), "Options Host should match one of the nameserver hosts") + + // Check options values + Expect(target.Options.Port).To(Equal(53), "Options port should be 53") + Expect(target.Options.RNDCPort).To(BeNumerically(">", 0), "RNDC port should be a positive number") + + // Validate RNDC config file path + expectedRndcPath := fmt.Sprintf("/etc/designate/rndc-keys/rndc-key-%d.conf", serverNum) + Expect(target.Options.RNDCConfigFile).To(Equal(expectedRndcPath), "RNDC config file path mismatch") + } + + // Validate len(nameservers) == len(hosts) - which are all Bind9 hosts + Expect(numOfBindHosts).To(Equal(len(pool.Nameservers))) + + if pool.CatalogZone != nil { + err := validate.Var(pool.CatalogZone.FQDN, "fqdn") + Expect(err).ToNot(HaveOccurred()) + Expect(pool.CatalogZone.Refresh).To(BeNumerically(">", 0), "catalog_zone_refresh should be a positive number") + } + } + }) + }) +}) diff --git a/tests/functional/designateapi_controller_test.go b/tests/functional/designateapi_controller_test.go new file mode 100644 index 00000000..64f55d12 --- /dev/null +++ b/tests/functional/designateapi_controller_test.go @@ -0,0 +1,258 @@ +/* +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 functional_test + +import ( + "fmt" + + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "k8s.io/apimachinery/pkg/types" + + corev1 "k8s.io/api/core/v1" + + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + //revive:disable-next-line:dot-imports + "github.com/openstack-k8s-operators/designate-operator/pkg/designate" + . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" +) + +var _ = Describe("DesignateAPI controller", func() { + var name string + var spec map[string]interface{} + var designateAPIName types.NamespacedName + var transportURLSecretName types.NamespacedName + + BeforeEach(func() { + name = fmt.Sprintf("designate-api-%s", uuid.New().String()) + spec = GetDefaultDesignateAPISpec() + + designateAPIName = types.NamespacedName{ + Namespace: namespace, + Name: name, + } + + transportURLSecretName = types.NamespacedName{ + Namespace: namespace, + Name: RabbitmqSecretName, + } + spec["transportURLSecret"] = transportURLSecretName.Name + }) + + When("a DesignateAPI instance is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDesignateAPI(designateAPIName, spec)) + }) + + It("should have the Status fields initialized", func() { + designateAPI := GetDesignateAPI((designateAPIName)) + Expect(designateAPI.Status.ReadyCount).Should(Equal(int32(0))) + }) + + It("should have Waiting Conditions initialized as TransportUrl not created", func() { + for _, cond := range []condition.Type{ + condition.InputReadyCondition, + } { + th.ExpectCondition( + designateAPIName, + ConditionGetterFunc(DesignateAPIConditionGetter), + cond, + corev1.ConditionFalse, + ) + } + }) + + It("should have a finalizer", func() { + // the reconciler loop adds the finalizer so we have to wait for + // it to run + Eventually(func() []string { + return GetDesignateAPI(designateAPIName).Finalizers + }, timeout, interval).Should(ContainElement("openstack.org/designateapi")) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: designateAPIName.Namespace, + Name: fmt.Sprintf("%s-%s", designateAPIName.Name, "config-data"), + } + th.AssertSecretDoesNotExist(secret) + }) + }) + + // Secret and Transport available + When("a proper secret is provided and TransportURL is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDesignateAPI(designateAPIName, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateSecret(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: designateAPIName.Namespace, + Name: fmt.Sprintf("%s-%s", designateAPIName.Name, "config-data"), + } + th.AssertSecretDoesNotExist(secret) + }) + }) + + // TLS Validation + + // Config + When("config files are created", func() { + var keystoneInternalEndpoint string + var keystonePublicEndpoint string + + BeforeEach(func() { + keystoneName := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneName) + keystoneInternalEndpoint = fmt.Sprintf("http://keystone-for-%s-internal", designateAPIName.Name) + keystonePublicEndpoint = fmt.Sprintf("http://keystone-for-%s-public", designateAPIName.Name) + SimulateKeystoneReady(keystoneName, keystonePublicEndpoint, keystoneInternalEndpoint) + + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateSecret(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) + + spec["customServiceConfig"] = "[DEFAULT]\ndebug=True\n" + DeferCleanup(th.DeleteInstance, CreateDesignateAPI(designateAPIName, spec)) + + mariaDBDatabaseName := mariadb.CreateMariaDBDatabase(namespace, designate.DatabaseCRName, mariadbv1.MariaDBDatabaseSpec{}) + mariaDBDatabase := mariadb.GetMariaDBDatabase(mariaDBDatabaseName) + DeferCleanup(k8sClient.Delete, ctx, mariaDBDatabase) + + designateAPI := GetDesignateAPI(designateAPIName) + apiMariaDBAccount, apiMariaDBSecret := mariadb.CreateMariaDBAccountAndSecret( + types.NamespacedName{ + Namespace: namespace, + Name: designateAPI.Spec.DatabaseAccount, + }, mariadbv1.MariaDBAccountSpec{}) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBAccount) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBSecret) + }) + + It("should be in state of having the input ready", func() { + th.ExpectCondition( + designateAPIName, + ConditionGetterFunc(DesignateAPIConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should set Service Config Ready Condition", func() { + th.ExpectCondition( + designateAPIName, + ConditionGetterFunc(DesignateAPIConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should create the designate.conf file in a Secret", func() { + configData := th.GetSecret( + types.NamespacedName{ + Namespace: designateAPIName.Namespace, + Name: fmt.Sprintf("%s-config-data", designateAPIName.Name)}) + Expect(configData).ShouldNot(BeNil()) + conf := string(configData.Data["designate.conf"]) + instance := GetDesignateAPI(designateAPIName) + + dbs := []struct { + Name string + DatabaseAccount string + Keyword string + }{ + { + Name: designate.DatabaseName, + DatabaseAccount: instance.Spec.DatabaseAccount, + Keyword: "connection", + }, + } + + for _, db := range dbs { + databaseAccount := mariadb.GetMariaDBAccount( + types.NamespacedName{ + Namespace: namespace, + Name: db.DatabaseAccount}) + databaseSecret := th.GetSecret( + types.NamespacedName{ + Namespace: namespace, + Name: databaseAccount.Spec.Secret}) + + Expect(conf).Should( + ContainSubstring( + fmt.Sprintf( + "%s=mysql+pymysql://%s:%s@%s/%s?read_default_file=/etc/my.cnf", + db.Keyword, + databaseAccount.Spec.UserName, + databaseSecret.Data[mariadbv1.DatabasePasswordSelector], + instance.Spec.DatabaseHostname, + db.Name))) + } + + Expect(conf).Should( + ContainSubstring(fmt.Sprintf( + "www_authenticate_uri=%s\n", keystonePublicEndpoint))) + // TBC: [keystone_authtoken].auth_url and [service_auth].auth_url differ? + Expect(conf).Should( + ContainSubstring(fmt.Sprintf( + "auth_url=%s\n", keystoneInternalEndpoint))) + Expect(conf).Should( + ContainSubstring(fmt.Sprintf( + "auth_url=%s\n", keystoneInternalEndpoint))) + Expect(conf).Should( + ContainSubstring(fmt.Sprintf( + "username=%s\n", instance.Spec.ServiceUser))) + + ospSecret := th.GetSecret(types.NamespacedName{ + Name: SecretName, + Namespace: namespace}) + Expect(conf).Should( + ContainSubstring(fmt.Sprintf( + "\npassword=%s\n", string(ospSecret.Data["DesignatePassword"])))) + + transportURLSecret := th.GetSecret(transportURLSecretName) + Expect(conf).Should( + ContainSubstring(fmt.Sprintf( + "transport_url=%s\n", string(transportURLSecret.Data["transport_url"])))) + }) + + It("should create a Secret with customServiceConfig input", func() { + configData := th.GetSecret( + types.NamespacedName{ + Namespace: designateAPIName.Namespace, + Name: fmt.Sprintf("%s-config-data", designateAPIName.Name)}) + Expect(configData).ShouldNot(BeNil()) + conf := string(configData.Data["custom.conf"]) + Expect(conf).Should( + ContainSubstring("[DEFAULT]\ndebug=True\n")) + }) + }) + + // NAD + + // Networks Annotation + + // Service + + // Keystone Service + + // Deployment +}) diff --git a/tests/functional/designatebackendbind9_controller_test.go b/tests/functional/designatebackendbind9_controller_test.go new file mode 100644 index 00000000..ab99b20e --- /dev/null +++ b/tests/functional/designatebackendbind9_controller_test.go @@ -0,0 +1,188 @@ +/* +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 functional_test + +import ( + "fmt" + + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + // revive:disable-next-line:dot-imports + "github.com/openstack-k8s-operators/designate-operator/pkg/designate" + . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" +) + +var _ = Describe("DesignateBackendbind9 controller", func() { + var name string + var spec map[string]interface{} + var designateBackendbind9Name types.NamespacedName + var transportURLSecretName types.NamespacedName + + BeforeEach(func() { + name = fmt.Sprintf("designate-backendbind9-%s", uuid.New().String()) + spec = GetDefaultDesignateBackendbind9Spec() + + transportURLSecretName = types.NamespacedName{ + Namespace: namespace, + Name: RabbitmqSecretName, + } + + designateBackendbind9Name = types.NamespacedName{ + Name: name, + Namespace: namespace, + } + spec["transportURLSecret"] = transportURLSecretName.Name + }) + When("a DesignateBackendbind9 instance is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDesignateBackendbind9(designateBackendbind9Name, spec)) + }) + + It("should have the Status fields initialized", func() { + designateBackendbind9 := GetDesignateBackendbind9(designateBackendbind9Name) + Expect(designateBackendbind9.Status.ReadyCount).Should(Equal(int32(0))) + }) + + It("should have a finalizer", func() { + // the reconciler loop adds the finalizer so we have to wait for + // it to run + Eventually(func() []string { + return GetDesignateBackendbind9(designateBackendbind9Name).Finalizers + }, timeout, interval).Should(ContainElement("openstack.org/designatebackendbind9")) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: designateBackendbind9Name.Namespace, + Name: fmt.Sprintf("%s-%s", designateBackendbind9Name.Name, "config-data"), + } + th.AssertSecretDoesNotExist(secret) + }) + }) + + // Maybe we could delete the one above, it is copied from the api tests + When("a proper secret is provided and TransportURL is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDesignateBackendbind9(designateBackendbind9Name, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateSecret(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: designateBackendbind9Name.Namespace, + Name: fmt.Sprintf("%s-%s", designateBackendbind9Name.Name, "config-data"), + } + th.AssertSecretDoesNotExist(secret) + }) + }) + + When("config files are created", func() { + var keystoneInternalEndpoint string + var keystonePublicEndpoint string + + BeforeEach(func() { + keystoneName := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneName) + keystoneInternalEndpoint = fmt.Sprintf("http://keystone-for-%s-internal", designateBackendbind9Name.Name) + keystonePublicEndpoint = fmt.Sprintf("http://keystone-for-%s-public", designateBackendbind9Name.Name) + SimulateKeystoneReady(keystoneName, keystonePublicEndpoint, keystoneInternalEndpoint) + + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateSecret(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) + + spec["customServiceConfig"] = "[DEFAULT]\ndebug=True\n" + DeferCleanup(th.DeleteInstance, CreateDesignateBackendbind9(designateBackendbind9Name, spec)) + + mariaDBDatabaseName := mariadb.CreateMariaDBDatabase(namespace, designate.DatabaseCRName, mariadbv1.MariaDBDatabaseSpec{}) + mariaDBDatabase := mariadb.GetMariaDBDatabase(mariaDBDatabaseName) + DeferCleanup(k8sClient.Delete, ctx, mariaDBDatabase) + + designateBackendbind9 := GetDesignateBackendbind9(designateBackendbind9Name) + apiMariaDBAccount, apiMariaDBSecret := mariadb.CreateMariaDBAccountAndSecret( + types.NamespacedName{ + Namespace: namespace, + Name: designateBackendbind9.Spec.DatabaseAccount, + }, mariadbv1.MariaDBAccountSpec{}) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBAccount) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBSecret) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["designateNetworkAttachment"].(string), + Namespace: namespace, + })) + }) + + It("should be in state of having the input ready", func() { + th.ExpectCondition( + designateBackendbind9Name, + ConditionGetterFunc(DesignateBackendbind9ConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + + // It("should set Service Config Ready Condition", func() { + // th.ExpectCondition( + // designateBackendbind9Name, + // ConditionGetterFunc(DesignateBackendbind9ConditionGetter), + // condition.ServiceConfigReadyCondition, + // corev1.ConditionTrue, + // ) + // }) + + // For some reason the 2 above tests fail because they can't find + // the secret with -config-data suffix + + // It("should create the designate.conf file in a Secret", func() { + // // TODO(oschwart): remove below debug printing + // secretList := &corev1.SecretList{} + // listOpts := []client.ListOption{ + // client.InNamespace(designateBackendbind9Name.Namespace), + // } + // if err := k8sClient.List(ctx, secretList, listOpts...); err != nil { + // return + // } + + // fmt.Printf("\nSecrets in namespace %s:\n", designateBackendbind9Name.Namespace) + // for _, secret := range secretList.Items { + // fmt.Printf("- Name: %s\n", secret.Name) + // } + // configData := th.GetSecret( + // types.NamespacedName{ + // Namespace: designateBackendbind9Name.Namespace, + // Name: fmt.Sprintf("%s-config-named", designateBackendbind9Name.Name)}) + // Expect(configData).ShouldNot(BeNil()) + // }) + + // It("should create a Secret with customServiceConfig input", func() { + // configData := th.GetSecret( + // types.NamespacedName{ + // Namespace: designateBackendbind9Name.Namespace, + // Name: fmt.Sprintf("%s-config-data", designateBackendbind9Name.Name)}) + // Expect(configData).ShouldNot(BeNil()) + // conf := string(configData.Data["custom.conf"]) + // Expect(conf).Should( + // ContainSubstring("[DEFAULT]\ndebug=True\n")) + // }) + }) +}) diff --git a/tests/functional/designatecentral_controller_test.go b/tests/functional/designatecentral_controller_test.go new file mode 100644 index 00000000..77ce6c9e --- /dev/null +++ b/tests/functional/designatecentral_controller_test.go @@ -0,0 +1,253 @@ +/* +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 functional_test + +import ( + "fmt" + + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + // "sigs.k8s.io/controller-runtime/pkg/client" + // revive:disable-next-line:dot-imports + "github.com/openstack-k8s-operators/designate-operator/pkg/designate" + . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" +) + +var _ = Describe("DesignateCentral controller", func() { + var name string + var spec map[string]interface{} + var designateCentralName types.NamespacedName + var designateRedisName types.NamespacedName + var transportURLSecretName types.NamespacedName + + BeforeEach(func() { + name = fmt.Sprintf("designate-central-%s", uuid.New().String()) + spec = GetDefaultDesignateCentralSpec() + + transportURLSecretName = types.NamespacedName{ + Namespace: namespace, + Name: RabbitmqSecretName, + } + + designateCentralName = types.NamespacedName{ + Name: name, + Namespace: namespace, + } + + designateRedisName = types.NamespacedName{ + Namespace: namespace, + Name: "designate-redis", + } + }) + When("a DesignateCentral instance is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDesignateCentral(designateCentralName, spec)) + }) + + It("should have the Status fields initialized", func() { + designateCentral := GetDesignateCentral(designateCentralName) + Expect(designateCentral.Status.ReadyCount).Should(Equal(int32(0))) + }) + + It("should have a finalizer", func() { + // the reconciler loop adds the finalizer so we have to wait for + // it to run + Eventually(func() []string { + return GetDesignateCentral(designateCentralName).Finalizers + }, timeout, interval).Should(ContainElement("openstack.org/designatecentral")) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: designateCentralName.Namespace, + Name: fmt.Sprintf("%s-%s", designateCentralName.Name, "config-data"), + } + th.AssertSecretDoesNotExist(secret) + }) + }) + + When("a proper secret is provided and TransportURL is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDesignateCentral(designateCentralName, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateSecret(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: designateCentralName.Namespace, + Name: fmt.Sprintf("%s-%s", designateCentralName.Name, "config-data"), + } + th.AssertSecretDoesNotExist(secret) + }) + }) + + When("config files are created", func() { + var keystoneInternalEndpoint string + var keystonePublicEndpoint string + + BeforeEach(func() { + keystoneName := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneName) + keystoneInternalEndpoint = fmt.Sprintf("http://keystone-for-%s-internal", designateCentralName.Name) + keystonePublicEndpoint = fmt.Sprintf("http://keystone-for-%s-public", designateCentralName.Name) + SimulateKeystoneReady(keystoneName, keystonePublicEndpoint, keystoneInternalEndpoint) + + createAndSimulateRedis(designateRedisName) + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateSecret(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) + + spec["customServiceConfig"] = "[DEFAULT]\ndebug=True\n" + DeferCleanup(th.DeleteInstance, CreateDesignateCentral(designateCentralName, spec)) + + mariaDBDatabaseName := mariadb.CreateMariaDBDatabase(namespace, designate.DatabaseCRName, mariadbv1.MariaDBDatabaseSpec{}) + mariaDBDatabase := mariadb.GetMariaDBDatabase(mariaDBDatabaseName) + DeferCleanup(k8sClient.Delete, ctx, mariaDBDatabase) + + designateCentral := GetDesignateCentral(designateCentralName) + apiMariaDBAccount, apiMariaDBSecret := mariadb.CreateMariaDBAccountAndSecret( + types.NamespacedName{ + Namespace: namespace, + Name: designateCentral.Spec.DatabaseAccount, + }, mariadbv1.MariaDBAccountSpec{}) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBAccount) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBSecret) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["designateNetworkAttachment"].(string), + Namespace: namespace, + })) + }) + + It("should be in state of having the input ready", func() { + th.ExpectCondition( + designateCentralName, + ConditionGetterFunc(DesignateCentralConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should set Service Config Ready Condition", func() { + th.ExpectCondition( + designateCentralName, + ConditionGetterFunc(DesignateCentralConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + }) + + // It("should create the designate.conf file in a Secret", func() { + // // TODO(oschwart): remove below debug printing + // secretList := &corev1.SecretList{} + // listOpts := []client.ListOption{ + // client.InNamespace(namespace), + // } + // if err := k8sClient.List(ctx, secretList, listOpts...); err != nil { + // return + // } + + // fmt.Printf("\n=== Secrets in namespace %s ===\n", namespace) + // for _, secret := range secretList.Items { + // fmt.Printf("- Name: %s\n", secret.Name) + // } + // configData := th.GetSecret( + // // configData := th.GetSecret( + // types.NamespacedName{ + // Namespace: namespace, + // Name: fmt.Sprintf("%s-config-data", designateCentralName.Name)}) + // Expect(configData).ShouldNot(BeNil()) + // conf := string(configData.Data["designate.conf"]) + // // instance := GetDesignateCentral(designateCentralName) + + // // dbs := []struct { + // // Name string + // // DatabaseAccount string + // // Keyword string + // // }{ + // // { + // // Name: designate.DatabaseName, + // // DatabaseAccount: instance.Spec.DatabaseAccount, + // // Keyword: "connection", + // // }, + // // } + + // // for _, db := range dbs { + // // databaseAccount := mariadb.GetMariaDBAccount( + // // types.NamespacedName{ + // // Namespace: namespace, + // // Name: db.DatabaseAccount}) + // // databaseSecret := th.GetSecret( + // // types.NamespacedName{ + // // Namespace: namespace, + // // Name: databaseAccount.Spec.Secret}) + + // // Expect(conf).Should( + // // ContainSubstring( + // // fmt.Sprintf( + // // "%s=mysql+pymysql://%s:%s@%s/%s?read_default_file=/etc/my.cnf", + // // db.Keyword, + // // databaseAccount.Spec.UserName, + // // databaseSecret.Data[mariadbv1.DatabasePasswordSelector], + // // instance.Spec.DatabaseHostname, + // // db.Name))) + // // } + + // Expect(conf).Should( + // ContainSubstring(fmt.Sprintf( + // "www_authenticate_uri=%s\n", keystonePublicEndpoint))) + // // TBC: [keystone_authtoken].auth_url and [service_auth].auth_url differ? + // Expect(conf).Should( + // ContainSubstring(fmt.Sprintf( + // "auth_url=%s\n", keystoneInternalEndpoint))) + // Expect(conf).Should( + // ContainSubstring(fmt.Sprintf( + // "auth_url=%s\n", keystoneInternalEndpoint))) + // // Expect(conf).Should( + // // ContainSubstring(fmt.Sprintf( + // // "username=%s\n", instance.Spec.ServiceUser))) + + // ospSecret := th.GetSecret(types.NamespacedName{ + // Name: SecretName, + // Namespace: namespace}) + // Expect(conf).Should( + // ContainSubstring(fmt.Sprintf( + // "\npassword=%s\n", string(ospSecret.Data["DesignatePassword"])))) + + // transportURLSecret := th.GetSecret(transportURLSecretName) + // Expect(conf).Should( + // ContainSubstring(fmt.Sprintf( + // "transport_url=%s\n", string(transportURLSecret.Data["transport_url"])))) + // }) + + // It("should create a Secret with customServiceConfig input", func() { + // configData := th.GetSecret( + // types.NamespacedName{ + // Namespace: designateCentralName.Namespace, + // Name: fmt.Sprintf("%s-config-data", designateCentralName.Name)}) + // Expect(configData).ShouldNot(BeNil()) + // conf := string(configData.Data["custom.conf"]) + // Expect(conf).Should( + // ContainSubstring("[DEFAULT]\ndebug=True\n")) + // }) + }) +}) diff --git a/tests/functional/designateproducer_controller_test.go b/tests/functional/designateproducer_controller_test.go new file mode 100644 index 00000000..904112e6 --- /dev/null +++ b/tests/functional/designateproducer_controller_test.go @@ -0,0 +1,253 @@ +/* +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 functional_test + +import ( + "fmt" + + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + // "sigs.k8s.io/controller-runtime/pkg/client" + // revive:disable-next-line:dot-imports + "github.com/openstack-k8s-operators/designate-operator/pkg/designate" + . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" +) + +var _ = Describe("DesignateProducer controller", func() { + var name string + var spec map[string]interface{} + var designateProducerName types.NamespacedName + var designateRedisName types.NamespacedName + var transportURLSecretName types.NamespacedName + + BeforeEach(func() { + name = fmt.Sprintf("designate-producer-%s", uuid.New().String()) + spec = GetDefaultDesignateProducerSpec() + + transportURLSecretName = types.NamespacedName{ + Namespace: namespace, + Name: RabbitmqSecretName, + } + + designateProducerName = types.NamespacedName{ + Name: name, + Namespace: namespace, + } + + designateRedisName = types.NamespacedName{ + Namespace: namespace, + Name: "designate-redis", + } + }) + When("a DesignateProducer instance is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDesignateProducer(designateProducerName, spec)) + }) + + It("should have the Status fields initialized", func() { + designateProducer := GetDesignateProducer(designateProducerName) + Expect(designateProducer.Status.ReadyCount).Should(Equal(int32(0))) + }) + + It("should have a finalizer", func() { + // the reconciler loop adds the finalizer so we have to wait for + // it to run + Eventually(func() []string { + return GetDesignateProducer(designateProducerName).Finalizers + }, timeout, interval).Should(ContainElement("openstack.org/designateproducer")) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: designateProducerName.Namespace, + Name: fmt.Sprintf("%s-%s", designateProducerName.Name, "config-data"), + } + th.AssertSecretDoesNotExist(secret) + }) + }) + + When("a proper secret is provided and TransportURL is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateDesignateProducer(designateProducerName, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateSecret(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: designateProducerName.Namespace, + Name: fmt.Sprintf("%s-%s", designateProducerName.Name, "config-data"), + } + th.AssertSecretDoesNotExist(secret) + }) + }) + + When("config files are created", func() { + var keystoneInternalEndpoint string + var keystonePublicEndpoint string + + BeforeEach(func() { + keystoneName := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneName) + keystoneInternalEndpoint = fmt.Sprintf("http://keystone-for-%s-internal", designateProducerName.Name) + keystonePublicEndpoint = fmt.Sprintf("http://keystone-for-%s-public", designateProducerName.Name) + SimulateKeystoneReady(keystoneName, keystonePublicEndpoint, keystoneInternalEndpoint) + + createAndSimulateRedis(designateRedisName) + DeferCleanup(k8sClient.Delete, ctx, CreateDesignateSecret(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreateTransportURLSecret(transportURLSecretName)) + + spec["customServiceConfig"] = "[DEFAULT]\ndebug=True\n" + DeferCleanup(th.DeleteInstance, CreateDesignateProducer(designateProducerName, spec)) + + mariaDBDatabaseName := mariadb.CreateMariaDBDatabase(namespace, designate.DatabaseCRName, mariadbv1.MariaDBDatabaseSpec{}) + mariaDBDatabase := mariadb.GetMariaDBDatabase(mariaDBDatabaseName) + DeferCleanup(k8sClient.Delete, ctx, mariaDBDatabase) + + designateProducer := GetDesignateProducer(designateProducerName) + apiMariaDBAccount, apiMariaDBSecret := mariadb.CreateMariaDBAccountAndSecret( + types.NamespacedName{ + Namespace: namespace, + Name: designateProducer.Spec.DatabaseAccount, + }, mariadbv1.MariaDBAccountSpec{}) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBAccount) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBSecret) + + DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{ + Name: spec["designateNetworkAttachment"].(string), + Namespace: namespace, + })) + }) + + It("should be in state of having the input ready", func() { + th.ExpectCondition( + designateProducerName, + ConditionGetterFunc(DesignateProducerConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should set Service Config Ready Condition", func() { + th.ExpectCondition( + designateProducerName, + ConditionGetterFunc(DesignateProducerConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + }) + + // It("should create the designate.conf file in a Secret", func() { + // // TODO(oschwart): remove below debug printing + // secretList := &corev1.SecretList{} + // listOpts := []client.ListOption{ + // client.InNamespace(namespace), + // } + // if err := k8sClient.List(ctx, secretList, listOpts...); err != nil { + // return + // } + + // fmt.Printf("\n=== Secrets in namespace %s ===\n", namespace) + // for _, secret := range secretList.Items { + // fmt.Printf("- Name: %s\n", secret.Name) + // } + // configData := th.GetSecret( + // // configData := th.GetSecret( + // types.NamespacedName{ + // Namespace: namespace, + // Name: fmt.Sprintf("%s-config-data", designateProducerName.Name)}) + // Expect(configData).ShouldNot(BeNil()) + // conf := string(configData.Data["designate.conf"]) + // // instance := GetDesignateProducer(designateProducerName) + + // // dbs := []struct { + // // Name string + // // DatabaseAccount string + // // Keyword string + // // }{ + // // { + // // Name: designate.DatabaseName, + // // DatabaseAccount: instance.Spec.DatabaseAccount, + // // Keyword: "connection", + // // }, + // // } + + // // for _, db := range dbs { + // // databaseAccount := mariadb.GetMariaDBAccount( + // // types.NamespacedName{ + // // Namespace: namespace, + // // Name: db.DatabaseAccount}) + // // databaseSecret := th.GetSecret( + // // types.NamespacedName{ + // // Namespace: namespace, + // // Name: databaseAccount.Spec.Secret}) + + // // Expect(conf).Should( + // // ContainSubstring( + // // fmt.Sprintf( + // // "%s=mysql+pymysql://%s:%s@%s/%s?read_default_file=/etc/my.cnf", + // // db.Keyword, + // // databaseAccount.Spec.UserName, + // // databaseSecret.Data[mariadbv1.DatabasePasswordSelector], + // // instance.Spec.DatabaseHostname, + // // db.Name))) + // // } + + // Expect(conf).Should( + // ContainSubstring(fmt.Sprintf( + // "www_authenticate_uri=%s\n", keystonePublicEndpoint))) + // // TBC: [keystone_authtoken].auth_url and [service_auth].auth_url differ? + // Expect(conf).Should( + // ContainSubstring(fmt.Sprintf( + // "auth_url=%s\n", keystoneInternalEndpoint))) + // Expect(conf).Should( + // ContainSubstring(fmt.Sprintf( + // "auth_url=%s\n", keystoneInternalEndpoint))) + // // Expect(conf).Should( + // // ContainSubstring(fmt.Sprintf( + // // "username=%s\n", instance.Spec.ServiceUser))) + + // ospSecret := th.GetSecret(types.NamespacedName{ + // Name: SecretName, + // Namespace: namespace}) + // Expect(conf).Should( + // ContainSubstring(fmt.Sprintf( + // "\npassword=%s\n", string(ospSecret.Data["DesignatePassword"])))) + + // transportURLSecret := th.GetSecret(transportURLSecretName) + // Expect(conf).Should( + // ContainSubstring(fmt.Sprintf( + // "transport_url=%s\n", string(transportURLSecret.Data["transport_url"])))) + // }) + + // It("should create a Secret with customServiceConfig input", func() { + // configData := th.GetSecret( + // types.NamespacedName{ + // Namespace: designateProducerName.Namespace, + // Name: fmt.Sprintf("%s-config-data", designateProducerName.Name)}) + // Expect(configData).ShouldNot(BeNil()) + // conf := string(configData.Data["custom.conf"]) + // Expect(conf).Should( + // ContainSubstring("[DEFAULT]\ndebug=True\n")) + // }) + }) +}) diff --git a/tests/functional/suite_test.go b/tests/functional/suite_test.go new file mode 100644 index 00000000..4e14da82 --- /dev/null +++ b/tests/functional/suite_test.go @@ -0,0 +1,263 @@ +/* +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 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" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "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" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + + networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + designatev1 "github.com/openstack-k8s-operators/designate-operator/api/v1beta1" + "github.com/openstack-k8s-operators/designate-operator/controllers" + memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" + rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" + redisv1 "github.com/openstack-k8s-operators/infra-operator/apis/redis/v1beta1" + keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" + test "github.com/openstack-k8s-operators/lib-common/modules/test" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" + + infra_test "github.com/openstack-k8s-operators/infra-operator/apis/test/helpers" + keystone_test "github.com/openstack-k8s-operators/keystone-operator/api/test/helpers" + common_test "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/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 // You'll be using this client in your tests. + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc + logger logr.Logger + th *common_test.TestHelper + keystone *keystone_test.TestHelper + mariadb *mariadb_test.TestHelper + infra *infra_test.TestHelper + namespace string +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + networkv1CRD, err := test.GetCRDDirFromModule( + "github.com/k8snetworkplumbingwg/network-attachment-definition-client", "../../go.mod", "artifacts/networks-crd.yaml") + Expect(err).ShouldNot(HaveOccurred()) + keystoneCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/keystone-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + mariaDBCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/mariadb-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + infraCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/infra-operator/apis", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "config", "crd", "bases"), + // NOTE(gibi): we need to list all the external CRDs our operator depends on + keystoneCRDs, + mariaDBCRDs, + infraCRDs, + }, + CRDInstallOptions: envtest.CRDInstallOptions{ + Paths: []string{ + networkv1CRD, + }, + }, + ErrorIfCRDPathMissing: true, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + }, + } + + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + // NOTE(gibi): Need to add all API schemas our operator can own. + // Keep this in synch with DesignateReconciler.SetupWithManager, + // otherwise the reconciler loop will silently not start + // in the test env. + err = designatev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = mariadbv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = keystonev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = memcachedv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = rabbitmqv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = batchv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = corev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = appsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = networkv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = redisv1.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()) + keystone = keystone_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(keystone).NotTo(BeNil()) + mariadb = mariadb_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(mariadb).NotTo(BeNil()) + infra = infra_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(infra).NotTo(BeNil()) + + // Start the controller-manager if goroutine + webhookInstallOptions := &testEnv.WebhookInstallOptions + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + Metrics: metricsserver.Options{ + BindAddress: "0", + }, + WebhookServer: webhook.NewServer( + webhook.Options{ + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + }), + LeaderElection: false, + }) + Expect(err).ToNot(HaveOccurred()) + + err = (&designatev1.Designate{}).SetupWebhookWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + + kclient, err := kubernetes.NewForConfig(cfg) + Expect(err).ToNot(HaveOccurred(), "failed to create kclient") + + err = (&controllers.DesignateReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + }).SetupWithManager(ctx, k8sManager) + Expect(err).ToNot(HaveOccurred()) + err = (&controllers.DesignateAPIReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + }).SetupWithManager(ctx, k8sManager) + Expect(err).ToNot(HaveOccurred()) + err = (&controllers.DesignateBackendbind9Reconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + err = (&controllers.DesignateCentralReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + }).SetupWithManager(ctx, k8sManager) + Expect(err).ToNot(HaveOccurred()) + err = (&controllers.DesignateProducerReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + }).SetupWithManager(ctx, k8sManager) + Expect(err).ToNot(HaveOccurred()) + + // Acquire environmental defaults and initialize operator defaults with them + designatev1.SetupDefaults() + + 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: 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) +}) diff --git a/tests/kuttl/common/assert-sample-deployment.yaml b/tests/kuttl/common/assert-sample-deployment.yaml index ff983098..ac5ab947 100644 --- a/tests/kuttl/common/assert-sample-deployment.yaml +++ b/tests/kuttl/common/assert-sample-deployment.yaml @@ -63,10 +63,6 @@ status: reason: Ready status: "True" type: DesignateProducerReady - - message: DesignateRabbitMqTransportURL successfully created - reason: Ready - status: "True" - type: DesignateRabbitMqTransportURLReady - message: Setup complete reason: Ready status: "True" @@ -87,6 +83,10 @@ status: reason: Ready status: "True" type: NetworkAttachmentsReady + - message: RabbitMqTransportURL successfully created + reason: Ready + status: "True" + type: RabbitMqTransportURLReady - message: RoleBinding created reason: Ready status: "True" diff --git a/tests/kuttl/tests/basic/01-assert.yaml b/tests/kuttl/tests/basic/01-assert.yaml index 2879aa60..19279533 100644 --- a/tests/kuttl/tests/basic/01-assert.yaml +++ b/tests/kuttl/tests/basic/01-assert.yaml @@ -63,10 +63,6 @@ status: reason: Ready status: "True" type: DesignateProducerReady - - message: DesignateRabbitMqTransportURL successfully created - reason: Ready - status: "True" - type: DesignateRabbitMqTransportURLReady - message: Setup complete reason: Ready status: "True" @@ -87,6 +83,10 @@ status: reason: Ready status: "True" type: NetworkAttachmentsReady + - message: RabbitMqTransportURL successfully created + reason: Ready + status: "True" + type: RabbitMqTransportURLReady - message: RoleBinding created reason: Ready status: "True" diff --git a/tests/kuttl/tests/designate_scale/01-assert.yaml b/tests/kuttl/tests/designate_scale/01-assert.yaml index b58beb10..75789301 100644 --- a/tests/kuttl/tests/designate_scale/01-assert.yaml +++ b/tests/kuttl/tests/designate_scale/01-assert.yaml @@ -31,78 +31,6 @@ status: designateWorkerReadyCount: 1 databaseHostname: openstack.designate-kuttl-tests.svc conditions: - # - message: Setup complete - # reason: Ready - # status: "True" - # type: Ready - # - message: DB create completed - # reason: Ready - # status: "True" - # type: DBReady - # - message: DBsync completed - # reason: Ready - # status: "True" - # type: DBSyncReady - # - message: Setup complete - # reason: Ready - # status: "True" - # type: InputReady - # - message: MariaDBAccount creation complete - # reason: Ready - # status: "True" - # type: MariaDBAccountReady - # - message: NetworkAttachments completed - # reason: Ready - # status: "True" - # type: NetworkAttachmentsReady - # - message: Setup complete - # reason: Ready - # status: "True" - # type: DesignateAPIReady - # - message: Setup complete - # reason: Ready - # status: "True" - # type: DesignateCentralReady - # - message: Setup complete - # reason: Ready - # status: "True" - # type: DesignateWorkerReady - # - message: Setup complete - # reason: Ready - # status: "True" - # type: DesignateMdnsReady - # - message: Setup complete - # reason: Ready - # status: "True" - # type: DesignateProducerReady - # - message: Setup complete - # reason: Ready - # status: "True" - # type: DesignateBackendbind9Ready - # - message: Setup complete - # reason: Ready - # status: "True" - # type: DesignateUnboundReady - # - message: DesignateRabbitMqTransportURL successfully created - # reason: Ready - # status: "True" - # type: DesignateRabbitMqTransportURLReady - # - message: RoleBinding created - # reason: Ready - # status: "True" - # type: RoleBindingReady - # - message: Role created - # reason: Ready - # status: "True" - # type: RoleReady - # - message: ServiceAccount created - # reason: Ready - # status: "True" - # type: ServiceAccountReady - # - message: Service config create completed - # reason: Ready - # status: "True" - # type: ServiceConfigReady - message: Setup complete reason: Ready status: "True" @@ -135,10 +63,6 @@ status: reason: Ready status: "True" type: DesignateProducerReady - - message: DesignateRabbitMqTransportURL successfully created - reason: Ready - status: "True" - type: DesignateRabbitMqTransportURLReady - message: Setup complete reason: Ready status: "True" @@ -159,6 +83,10 @@ status: reason: Ready status: "True" type: NetworkAttachmentsReady + - message: RabbitMqTransportURL successfully created + reason: Ready + status: "True" + type: RabbitMqTransportURLReady - message: RoleBinding created reason: Ready status: "True"