From c957afb73a05ebd36799ca48f030c2277442be5b Mon Sep 17 00:00:00 2001 From: jgilaber Date: Tue, 10 Dec 2024 17:10:36 +0100 Subject: [PATCH] Add initial watcher api conf generation Implement an initial version of watcher config generation. This change adds a watcher config template and generates a secret with the templated config. Some fields that require changes in the watcher controller like the transporturl and memcached servers. This change also modifies the WatcherAPI functional tests so the WatcherAPI instances use a different name that the Watcher one, so it's easier to debug. --- .../watcher.openstack.org_watcherapis.yaml | 5 + api/bases/watcher.openstack.org_watchers.yaml | 5 + api/v1beta1/common_types.go | 5 + api/v1beta1/watcherapi_types.go | 1 + .../watcher.openstack.org_watcherapis.yaml | 5 + .../bases/watcher.openstack.org_watchers.yaml | 5 + config/rbac/role.yaml | 25 +++ controllers/watcher_common.go | 79 ++++++++ controllers/watcherapi_controller.go | 93 ++++++++-- go.mod | 2 +- main.go | 2 + templates/watcher/config/00-default.conf | 48 +++++ .../watcher/config/10-watcher-wsgi-main.conf | 39 ++++ templates/watcher/config/httpd.conf | 45 +++++ templates/watcher/config/main | 52 ++++++ templates/watcher/config/mime.conf | 38 ++++ .../watcher/config/watcher-api-config.json | 46 +++++ templates/watcherapi | 1 + tests/functional/base_test.go | 5 +- tests/functional/sample_test.go | 2 +- tests/functional/suite_test.go | 7 + tests/functional/watcher_test_data.go | 10 + .../functional/watcherapi_controller_test.go | 173 +++++++++++++++--- .../default/watcher-api/03-assert.yaml | 10 + .../watcher-api/03-deploy-watcher-api.yaml | 2 + 25 files changed, 664 insertions(+), 41 deletions(-) create mode 100644 templates/watcher/config/00-default.conf create mode 100644 templates/watcher/config/10-watcher-wsgi-main.conf create mode 100644 templates/watcher/config/httpd.conf create mode 100755 templates/watcher/config/main create mode 100644 templates/watcher/config/mime.conf create mode 100644 templates/watcher/config/watcher-api-config.json create mode 120000 templates/watcherapi diff --git a/api/bases/watcher.openstack.org_watcherapis.yaml b/api/bases/watcher.openstack.org_watcherapis.yaml index a7e0f63..6a1ae8e 100644 --- a/api/bases/watcher.openstack.org_watcherapis.yaml +++ b/api/bases/watcher.openstack.org_watcherapis.yaml @@ -49,6 +49,11 @@ spec: MariaDB instance name Required to use the mariadb-operator instance to create the DB and user type: string + memcachedInstance: + default: memcached + description: MemcachedInstance is the name of the Memcached CR that + all watcher service will use. + type: string passwordSelectors: default: service: WatcherPassword diff --git a/api/bases/watcher.openstack.org_watchers.yaml b/api/bases/watcher.openstack.org_watchers.yaml index 28bae46..f11f730 100644 --- a/api/bases/watcher.openstack.org_watchers.yaml +++ b/api/bases/watcher.openstack.org_watchers.yaml @@ -49,6 +49,11 @@ spec: MariaDB instance name Required to use the mariadb-operator instance to create the DB and user type: string + memcachedInstance: + default: memcached + description: MemcachedInstance is the name of the Memcached CR that + all watcher service will use. + type: string passwordSelectors: default: service: WatcherPassword diff --git a/api/v1beta1/common_types.go b/api/v1beta1/common_types.go index 611cbc6..d95da78 100644 --- a/api/v1beta1/common_types.go +++ b/api/v1beta1/common_types.go @@ -38,6 +38,11 @@ type WatcherCommon struct { // +kubebuilder:default=watcher // DatabaseAccount - MariaDBAccount CR name used for watcher DB, defaults to watcher DatabaseAccount string `json:"databaseAccount"` + + // +kubebuilder:validation:Optional + // +kubebuilder:default=memcached + // MemcachedInstance is the name of the Memcached CR that all watcher service will use. + MemcachedInstance string `json:"memcachedInstance"` } // WatcherTemplate defines the fields used in the top level CR diff --git a/api/v1beta1/watcherapi_types.go b/api/v1beta1/watcherapi_types.go index 90cacf2..de1b64f 100644 --- a/api/v1beta1/watcherapi_types.go +++ b/api/v1beta1/watcherapi_types.go @@ -27,6 +27,7 @@ type WatcherAPISpec struct { // Important: Run "make" to regenerate code after modifying this file WatcherCommon `json:",inline"` + // +kubebuilder:validation:Required // Secret containing all passwords / keys needed Secret string `json:"secret"` diff --git a/config/crd/bases/watcher.openstack.org_watcherapis.yaml b/config/crd/bases/watcher.openstack.org_watcherapis.yaml index a7e0f63..6a1ae8e 100644 --- a/config/crd/bases/watcher.openstack.org_watcherapis.yaml +++ b/config/crd/bases/watcher.openstack.org_watcherapis.yaml @@ -49,6 +49,11 @@ spec: MariaDB instance name Required to use the mariadb-operator instance to create the DB and user type: string + memcachedInstance: + default: memcached + description: MemcachedInstance is the name of the Memcached CR that + all watcher service will use. + type: string passwordSelectors: default: service: WatcherPassword diff --git a/config/crd/bases/watcher.openstack.org_watchers.yaml b/config/crd/bases/watcher.openstack.org_watchers.yaml index 28bae46..f11f730 100644 --- a/config/crd/bases/watcher.openstack.org_watchers.yaml +++ b/config/crd/bases/watcher.openstack.org_watchers.yaml @@ -49,6 +49,11 @@ spec: MariaDB instance name Required to use the mariadb-operator instance to create the DB and user type: string + memcachedInstance: + default: memcached + description: MemcachedInstance is the name of the Memcached CR that + all watcher service will use. + type: string passwordSelectors: default: service: WatcherPassword diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3a073f5..5272bb1 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -28,6 +28,14 @@ rules: - patch - update - watch +- apiGroups: + - keystone.openstack.org + resources: + - keystoneapis + verbs: + - get + - list + - watch - apiGroups: - keystone.openstack.org resources: @@ -82,6 +90,23 @@ rules: - patch - update - watch +- apiGroups: + - memcached.openstack.org + resources: + - memcacheds + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - memcached.openstack.org + resources: + - memcacheds/finalizers + verbs: + - patch + - update - apiGroups: - rabbitmq.openstack.org resources: diff --git a/controllers/watcher_common.go b/controllers/watcher_common.go index 6331edc..51681eb 100644 --- a/controllers/watcher_common.go +++ b/controllers/watcher_common.go @@ -15,7 +15,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/env" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" "github.com/openstack-k8s-operators/lib-common/modules/common/util" ) @@ -188,3 +192,78 @@ func ensureSecret( return hash, ctrl.Result{}, *secret, nil } + +func GenerateConfigsGeneric( + ctx context.Context, helper *helper.Helper, + instance client.Object, + envVars *map[string]env.Setter, + templateParameters map[string]interface{}, + customData map[string]string, + cmLabels map[string]string, + scripts bool, +) error { + + cms := []util.Template{ + // Templates where the watcher config is stored + { + Name: fmt.Sprintf("%s-config-data", instance.GetName()), + Namespace: instance.GetNamespace(), + Type: util.TemplateTypeConfig, + InstanceType: instance.GetObjectKind().GroupVersionKind().Kind, + ConfigOptions: templateParameters, + CustomData: customData, + Labels: cmLabels, + }, + } + if scripts { + cms = append(cms, util.Template{ + Name: fmt.Sprintf("%s-scripts", instance.GetName()), + Namespace: instance.GetNamespace(), + Type: util.TemplateTypeScripts, + InstanceType: instance.GetObjectKind().GroupVersionKind().Kind, + ConfigOptions: templateParameters, + Labels: cmLabels, + }) + } + return secret.EnsureSecrets(ctx, helper, instance, cms, envVars) +} + +// ensureMemcached - gets the Memcached instance used for watcher services cache backend +func ensureMemcached( + ctx context.Context, + helper *helper.Helper, + namespaceName string, + memcachedName string, + conditionUpdater conditionUpdater, +) (*memcachedv1.Memcached, error) { + memcached, err := memcachedv1.GetMemcachedByName(ctx, helper, memcachedName, namespaceName) + if err != nil { + if k8s_errors.IsNotFound(err) { + conditionUpdater.Set(condition.FalseCondition( + condition.MemcachedReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.MemcachedReadyWaitingMessage)) + return nil, fmt.Errorf("memcached %s not found", memcachedName) + } + conditionUpdater.Set(condition.FalseCondition( + condition.MemcachedReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.MemcachedReadyErrorMessage, + err.Error())) + return nil, err + } + + if !memcached.IsReady() { + conditionUpdater.Set(condition.FalseCondition( + condition.MemcachedReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.MemcachedReadyWaitingMessage)) + return nil, fmt.Errorf("memcached %s is not ready", memcachedName) + } + conditionUpdater.MarkTrue(condition.MemcachedReadyCondition, condition.MemcachedReadyMessage) + + return memcached, err +} diff --git a/controllers/watcherapi_controller.go b/controllers/watcherapi_controller.go index 19fee10..e3fd48d 100644 --- a/controllers/watcherapi_controller.go +++ b/controllers/watcherapi_controller.go @@ -30,9 +30,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/go-logr/logr" + memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" + keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/endpoint" "github.com/openstack-k8s-operators/lib-common/modules/common/env" "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/labels" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" watcherv1beta1 "github.com/openstack-k8s-operators/watcher-operator/api/v1beta1" @@ -59,6 +64,11 @@ func (r *WatcherAPIReconciler) GetLogger(ctx context.Context) logr.Logger { //+kubebuilder:rbac:groups=watcher.openstack.org,resources=watcherapis/status,verbs=get;update;patch //+kubebuilder:rbac:groups=watcher.openstack.org,resources=watcherapis/finalizers,verbs=update //+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneapis,verbs=get;list;watch; +//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneservices,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneendpoints,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=memcached.openstack.org,resources=memcacheds,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=memcached.openstack.org,resources=memcacheds/finalizers,verbs=update;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -138,6 +148,7 @@ func (r *WatcherAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request) types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Secret}, []string{ instance.Spec.PasswordSelectors.Service, + TransportURLSelector, }, helper.GetClient(), &instance.Status.Conditions, @@ -163,7 +174,26 @@ func (r *WatcherAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request) // all our input checks out so report InputReady instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) - err = r.generateServiceConfigs(ctx, instance, secret, db, helper, &configVars) + memcached, err := ensureMemcached(ctx, helper, instance.Namespace, instance.Spec.MemcachedInstance, &instance.Status.Conditions) + + if err != nil { + return ctrl.Result{}, err + } + // Add finalizer to Memcached to prevent it from being deleted now that we're using it + if controllerutil.AddFinalizer(memcached, helper.GetFinalizer()) { + err := helper.GetClient().Update(ctx, memcached) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.MemcachedReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.MemcachedReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + } + + err = r.generateServiceConfigs(ctx, instance, secret, db, memcached, helper, &configVars) if err != nil { return ctrl.Result{}, err } @@ -181,25 +211,65 @@ func (r *WatcherAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request) } // generateServiceConfigs - create Secret which holds the service configuration -// NOTE - jgilaber this function is WIP, currently implements a fraction of its -// functionality and will be expanded of further iteration to actually generate -// the service configs func (r *WatcherAPIReconciler) generateServiceConfigs( ctx context.Context, instance *watcherv1beta1.WatcherAPI, secret corev1.Secret, db *mariadbv1.Database, + memcachedInstance *memcachedv1.Memcached, helper *helper.Helper, envVars *map[string]env.Setter, ) error { Log := r.GetLogger(ctx) Log.Info("generateServiceConfigs - reconciling") - // replace by actual usage in future iterations - _ = db - _ = helper - _ = instance - _ = secret - _ = envVars + labels := labels.GetLabels(instance, labels.GetGroupLabel(watcher.ServiceName), map[string]string{}) + // jgilaber this might be wrong? we should probably get keystonapi in the + // watcher controller and set the url in the spec eventually? + keystoneAPI, err := keystonev1.GetKeystoneAPI(ctx, helper, instance.Namespace, map[string]string{}) + // KeystoneAPI not available we should not aggregate the error and continue + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.ServiceConfigReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.ServiceConfigReadyErrorMessage, + "keystoneAPI not found")) + return err + } + keystoneInternalURL, err := keystoneAPI.GetEndpoint(endpoint.EndpointInternal) + if err != nil { + return err + } + // customData hold any customization for the service. + // NOTE jgilaber making an empty map for now, we'll probably want to + // implement CustomServiceConfig later + customData := map[string]string{} + + databaseAccount := db.GetAccount() + databaseSecret := db.GetSecret() + templateParameters := map[string]interface{}{ + "DatabaseConnection": fmt.Sprintf("mysql+pymysql://%s:%s@%s/%s?charset=utf8", + databaseAccount.Spec.UserName, + string(databaseSecret.Data[mariadbv1.DatabasePasswordSelector]), + db.GetDatabaseHostname(), + watcher.DatabaseName, + ), + "KeystoneAuthURL": keystoneInternalURL, + "ServicePassword": string(secret.Data[instance.Spec.PasswordSelectors.Service]), + "ServiceUser": instance.Spec.ServiceUser, + "TransportURL": string(secret.Data[TransportURLSelector]), + "MemcachedServers": memcachedInstance.GetMemcachedServerListString(), + } - return nil + // create httpd vhost template parameters + httpdVhostConfig := map[string]interface{}{} + for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { + endptConfig := map[string]interface{}{} + endptConfig["ServerName"] = fmt.Sprintf("%s-%s.%s.svc", watcher.ServiceName, endpt.String(), instance.Namespace) + endptConfig["TLS"] = false // default TLS to false, and set it below when implemented + httpdVhostConfig[endpt.String()] = endptConfig + } + templateParameters["VHosts"] = httpdVhostConfig + + return GenerateConfigsGeneric(ctx, helper, instance, envVars, templateParameters, customData, labels, false) } func (r *WatcherAPIReconciler) reconcileDelete(ctx context.Context, instance *watcherv1beta1.WatcherAPI, helper *helper.Helper) (ctrl.Result, error) { @@ -221,6 +291,7 @@ func (r *WatcherAPIReconciler) initStatus(instance *watcherv1beta1.WatcherAPI) e condition.UnknownCondition(condition.ReadyCondition, condition.InitReason, condition.ReadyInitMessage), condition.UnknownCondition(condition.InputReadyCondition, condition.InitReason, condition.InputReadyInitMessage), condition.UnknownCondition(condition.ServiceConfigReadyCondition, condition.InitReason, condition.ServiceConfigReadyMessage), + condition.UnknownCondition(condition.MemcachedReadyCondition, condition.InitReason, condition.MemcachedReadyInitMessage), ) instance.Status.Conditions.Init(&cl) diff --git a/go.mod b/go.mod index 4d93654..a350da6 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( k8s.io/api v0.29.10 k8s.io/apimachinery v0.29.10 k8s.io/client-go v0.29.10 + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 sigs.k8s.io/controller-runtime v0.17.6 ) @@ -75,7 +76,6 @@ require ( k8s.io/component-base v0.29.10 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/main.go b/main.go index 084ea8c..ad482cf 100644 --- a/main.go +++ b/main.go @@ -37,6 +37,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" @@ -58,6 +59,7 @@ func init() { utilruntime.Must(mariadbv1.AddToScheme(scheme)) utilruntime.Must(rabbitmqv1.AddToScheme(scheme)) utilruntime.Must(keystonev1.AddToScheme(scheme)) + utilruntime.Must(memcachedv1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } diff --git a/templates/watcher/config/00-default.conf b/templates/watcher/config/00-default.conf new file mode 100644 index 0000000..8bf3025 --- /dev/null +++ b/templates/watcher/config/00-default.conf @@ -0,0 +1,48 @@ +[DEFAULT] +state_path = /var/lib/watcher +transport_url = {{ .TransportURL }} +control_exchange = watcher +debug = True + +[database] +connection = {{ .DatabaseConnection }} + +[oslo_policy] +policy_file = /etc/watcher/policy.yaml.sample + +[oslo_messaging_notifications] +driver = messagingv2 + +[keystone_authtoken] +memcached_servers = {{ .MemcachedServers }} +# TODO jgilaber implement handling this option when we add tls support +# cafile = /var/lib/ca-bundle.pem +project_domain_name = Default +project_name = service +user_domain_name = Default +password = {{ .ServicePassword }} +username = {{ .ServiceUser }} +auth_url = {{ .KeystoneAuthURL }} +interface = internal +auth_type = password + +[watcher_clients_auth] +# TODO jgilaber implement handling this option when we add tls support +# cafile = /var/lib/ca-bundle.pem +project_domain_name = Default +project_name = service +user_domain_name = Default +password = {{ .ServicePassword }} +username = {{ .ServiceUser }} +auth_url = {{ .KeystoneAuthURL }} +interface = internal +auth_type = password + +[oslo_concurrency] +lock_path = /var/lib/watcher/tmp + +[watcher_datasources] +datasources = ceilometer + +[cache] +memcached_servers = {{ .MemcachedServers }} diff --git a/templates/watcher/config/10-watcher-wsgi-main.conf b/templates/watcher/config/10-watcher-wsgi-main.conf new file mode 100644 index 0000000..044929f --- /dev/null +++ b/templates/watcher/config/10-watcher-wsgi-main.conf @@ -0,0 +1,39 @@ +{{ if (index . "VHosts") }} +{{ range $endpt, $vhost := .VHosts }} +# {{ $endpt }} vhost {{ $vhost.ServerName }} configuration + + ServerName {{ $vhost.ServerName }} + + ## Vhost docroot + DocumentRoot "/var/www/cgi-bin/watcher" + + ## Directories, there should at least be a declaration for /var/www/cgi-bin/watcher + + + Options -Indexes +FollowSymLinks +MultiViews + AllowOverride None + Require all granted + + + ## Logging + ErrorLog "/var/log/watcher/error.log" + ServerSignature Off + CustomLog "/var/log/watcher/access.log" combined env=!forwarded + +{{- if $vhost.TLS }} + SetEnvIf X-Forwarded-Proto https HTTPS=1 + + ## SSL directives + SSLEngine on + SSLCertificateFile "{{ $vhost.SSLCertificateFile }}" + SSLCertificateKeyFile "{{ $vhost.SSLCertificateKeyFile }}" +{{- end }} + + ## WSGI configuration + WSGIApplicationGroup %{GLOBAL} + WSGIDaemonProcess {{ $endpt }} display-name={{ $endpt }} group=watcher processes=8 threads=1 user=watcher + WSGIProcessGroup {{ $endpt }} + WSGIScriptAlias / "/var/www/cgi-bin/watcher/main" + +{{ end }} +{{ end }} diff --git a/templates/watcher/config/httpd.conf b/templates/watcher/config/httpd.conf new file mode 100644 index 0000000..31f97aa --- /dev/null +++ b/templates/watcher/config/httpd.conf @@ -0,0 +1,45 @@ +ServerTokens Prod +ServerSignature Off +TraceEnable Off + +ServerName "watcher.openstack.svc" +ServerRoot "/etc/httpd" + +PidFile run/httpd.pid +Timeout 90 +KeepAlive On +MaxKeepAliveRequests 100 +KeepAliveTimeout 15 +LimitRequestFieldSize 8190 +LimitRequestFields 100 + +User apache +Group apache +Listen 9311 + +AccessFileName .htaccess + + Require all denied + + + + Options FollowSymLinks + AllowOverride None + + + + HostnameLookups Off + LogLevel debug + EnableSendfile On + + Include "/etc/httpd/conf.modules.d/*.conf" + + LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined + LogFormat "%a %l %u %t \"%r\" %>s %b" common + LogFormat "%{Referer}i -> %U" referer + LogFormat "%{User-agent}i" agent + LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\"" forwarded + + CustomLog "/var/log/watcher/access.log" combined env=!forwarded + ErrorLog "/var/log/watcher/error.log" + IncludeOptional "/etc/httpd/conf.d/*.conf" diff --git a/templates/watcher/config/main b/templates/watcher/config/main new file mode 100755 index 0000000..3b2e937 --- /dev/null +++ b/templates/watcher/config/main @@ -0,0 +1,52 @@ +#!/usr/bin/python3 +#PBR Generated from 'wsgi_scripts' + +import threading + +from watcher.api.wsgi import initialize_wsgi_app + +if __name__ == "__main__": + import argparse + import socket + import sys + import wsgiref.simple_server as wss + + parser = argparse.ArgumentParser( + description=initialize_wsgi_app.__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + usage='%(prog)s [-h] [--port PORT] [--host IP] -- [passed options]') + parser.add_argument('--port', '-p', type=int, default=8000, + help='TCP port to listen on') + parser.add_argument('--host', '-b', default='', + help='IP to bind the server to') + parser.add_argument('args', + nargs=argparse.REMAINDER, + metavar='-- [passed options]', + help="'--' is the separator of the arguments used " + "to start the WSGI server and the arguments passed " + "to the WSGI application.") + args = parser.parse_args() + if args.args: + if args.args[0] == '--': + args.args.pop(0) + else: + parser.error("unrecognized arguments: %s" % ' '.join(args.args)) + sys.argv[1:] = args.args + server = wss.make_server(args.host, args.port, initialize_wsgi_app()) + + print("*" * 80) + print("STARTING test server watcher.api.wsgi.initialize_wsgi_app") + url = "http://%s:%d/" % (server.server_name, server.server_port) + print("Available at %s" % url) + print("DANGER! For testing only, do not use in production") + print("*" * 80) + sys.stdout.flush() + + server.serve_forever() +else: + application = None + app_lock = threading.Lock() + + with app_lock: + if application is None: + application = initialize_wsgi_app() diff --git a/templates/watcher/config/mime.conf b/templates/watcher/config/mime.conf new file mode 100644 index 0000000..ac91a1c --- /dev/null +++ b/templates/watcher/config/mime.conf @@ -0,0 +1,38 @@ +TypesConfig /etc/mime.types + +AddType application/x-compress .Z +AddType application/x-gzip .gz .tgz +AddType application/x-bzip2 .bz2 + +AddLanguage ca .ca +AddLanguage cs .cz .cs +AddLanguage da .dk +AddLanguage de .de +AddLanguage el .el +AddLanguage en .en +AddLanguage eo .eo +AddLanguage es .es +AddLanguage et .et +AddLanguage fr .fr +AddLanguage he .he +AddLanguage hr .hr +AddLanguage it .it +AddLanguage ja .ja +AddLanguage ko .ko +AddLanguage ltz .ltz +AddLanguage nl .nl +AddLanguage nn .nn +AddLanguage no .no +AddLanguage pl .po +AddLanguage pt .pt +AddLanguage pt-BR .pt-br +AddLanguage ru .ru +AddLanguage sv .sv +AddLanguage zh-CN .zh-cn +AddLanguage zh-TW .zh-tw + +AddHandler type-map var + +AddOutputFilter INCLUDES .shtml + +AddType text/html .shtml diff --git a/templates/watcher/config/watcher-api-config.json b/templates/watcher/config/watcher-api-config.json new file mode 100644 index 0000000..e0a3524 --- /dev/null +++ b/templates/watcher/config/watcher-api-config.json @@ -0,0 +1,46 @@ +{ + "command": "/usr/sbin/httpd -DFOREGROUND", + "config_files": [ + { + "source": "/var/lib/config-data/default/00-default.conf", + "dest": "/etc/watcher/watcher.conf.d/00-default.conf", + "owner": "watcher", + "perm": "0600" + }, + { + "source": "/var/lib/config-data/default/10-watcher_wsgi_main.conf", + "dest": "/etc/httpd/conf.d/10-watcher_wsgi_main.conf", + "owner": "root", + "perm": "0640", + "optional": true + }, + { + "source": "/var/lib/config-data/default/httpd.conf", + "dest": "/etc/httpd/conf/httpd.conf", + "owner": "root", + "perm": "0640", + "optional": true + }, + { + "source": "/var/lib/config-data/default/main", + "dest": "/var/www/cgi-bin/watcher/main", + "owner": "watcher", + "perm": "0640", + "optional": true + }, + { + "source": "/var/lib/config-data/default/mime.conf", + "dest": "/etc/httpd/conf.modules.d/mime.conf", + "owner": "root", + "perm": "0640", + "optional": true + } + ], + "permissions": [ + { + "path": "/var/log/watcher", + "owner": "watcher:watcher", + "recurse": true + } + ] +} diff --git a/templates/watcherapi b/templates/watcherapi new file mode 120000 index 0000000..cba20eb --- /dev/null +++ b/templates/watcherapi @@ -0,0 +1 @@ +watcher/ \ No newline at end of file diff --git a/tests/functional/base_test.go b/tests/functional/base_test.go index 69f4a92..5f09642 100644 --- a/tests/functional/base_test.go +++ b/tests/functional/base_test.go @@ -38,8 +38,9 @@ func GetDefaultWatcherSpec() map[string]interface{} { func GetDefaultWatcherAPISpec() map[string]interface{} { return map[string]interface{}{ - "databaseInstance": "openstack", - "secret": SecretName, + "databaseInstance": "openstack", + "secret": SecretName, + "memcachedInstance": "memcached", } } diff --git a/tests/functional/sample_test.go b/tests/functional/sample_test.go index 4efea79..91d6c57 100644 --- a/tests/functional/sample_test.go +++ b/tests/functional/sample_test.go @@ -72,7 +72,7 @@ var _ = Describe("Samples", func() { When("watcher_v1beta1_watcherapi.yaml sample is applied", func() { It("WatcherAPI is created", func() { - name := CreateWatcherAPIFromSample("watcher_v1beta1_watcherapi.yaml", watcherTest.Instance) + name := CreateWatcherAPIFromSample("watcher_v1beta1_watcherapi.yaml", watcherTest.WatcherAPI) GetWatcherAPI(name) }) }) diff --git a/tests/functional/suite_test.go b/tests/functional/suite_test.go index 84a41d2..63d0e88 100644 --- a/tests/functional/suite_test.go +++ b/tests/functional/suite_test.go @@ -28,6 +28,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" test "github.com/openstack-k8s-operators/lib-common/modules/test" @@ -67,6 +68,8 @@ const ( SecretName = "test-osp-secret" interval = time.Millisecond * 200 + + MemcachedInstance = "memcached" ) func TestAPIs(t *testing.T) { @@ -123,12 +126,16 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) err = mariadbv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = keystonev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) err = corev1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) err = rabbitmqv1.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()) logger = ctrl.Log.WithName("---Test---") //+kubebuilder:scaffold:scheme diff --git a/tests/functional/watcher_test_data.go b/tests/functional/watcher_test_data.go index 18a5666..d04950b 100644 --- a/tests/functional/watcher_test_data.go +++ b/tests/functional/watcher_test_data.go @@ -40,6 +40,8 @@ type WatcherTestData struct { InternalTopLevelSecretName types.NamespacedName WatcherTransportURL types.NamespacedName KeystoneServiceName types.NamespacedName + WatcherAPI types.NamespacedName + MemcachedNamespace types.NamespacedName } // GetWatcherTestData is a function that initialize the WatcherTestData @@ -80,5 +82,13 @@ func GetWatcherTestData(watcherName types.NamespacedName) WatcherTestData { Namespace: watcherName.Namespace, Name: "watcher", }, + WatcherAPI: types.NamespacedName{ + Namespace: watcherName.Namespace, + Name: "watcher-api", + }, + MemcachedNamespace: types.NamespacedName{ + Namespace: watcherName.Namespace, + Name: "memcached", + }, } } diff --git a/tests/functional/watcherapi_controller_test.go b/tests/functional/watcherapi_controller_test.go index e1ef701..332fa95 100644 --- a/tests/functional/watcherapi_controller_test.go +++ b/tests/functional/watcherapi_controller_test.go @@ -7,37 +7,41 @@ import ( . "github.com/onsi/gomega" //revive:disable:dot-imports //revive:disable-next-line:dot-imports + memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" watcherv1beta1 "github.com/openstack-k8s-operators/watcher-operator/api/v1beta1" "github.com/openstack-k8s-operators/watcher-operator/pkg/watcher" corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" ) var ( MinimalWatcherAPISpec = map[string]interface{}{ - "secret": "osp-secret", - "databaseInstance": "openstack", + "secret": "osp-secret", + "databaseInstance": "openstack", + "memcachedInstance": "memcached", } ) var _ = Describe("WatcherAPI controller with minimal spec values", func() { When("A Watcher instance is created from minimal spec", func() { BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.Instance, MinimalWatcherAPISpec)) + DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.WatcherAPI, MinimalWatcherAPISpec)) }) It("should have the Spec fields defaulted", func() { - WatcherAPI := GetWatcherAPI(watcherTest.Instance) + WatcherAPI := GetWatcherAPI(watcherTest.WatcherAPI) Expect(WatcherAPI.Spec.DatabaseInstance).Should(Equal("openstack")) Expect(WatcherAPI.Spec.DatabaseAccount).Should(Equal("watcher")) Expect(WatcherAPI.Spec.Secret).Should(Equal("osp-secret")) + Expect(WatcherAPI.Spec.MemcachedInstance).Should(Equal("memcached")) Expect(WatcherAPI.Spec.PasswordSelectors).Should(Equal(watcherv1beta1.PasswordSelector{Service: "WatcherPassword"})) }) It("should have the Status fields initialized", func() { - WatcherAPI := GetWatcherAPI(watcherTest.Instance) + WatcherAPI := GetWatcherAPI(watcherTest.WatcherAPI) Expect(WatcherAPI.Status.ObservedGeneration).To(Equal(int64(0))) }) @@ -45,7 +49,7 @@ var _ = Describe("WatcherAPI controller with minimal spec values", func() { // the reconciler loop adds the finalizer so we have to wait for // it to run Eventually(func() []string { - return GetWatcherAPI(watcherTest.Instance).Finalizers + return GetWatcherAPI(watcherTest.WatcherAPI).Finalizers }, timeout, interval).Should(ContainElement("openstack.org/watcherapi")) }) @@ -55,24 +59,25 @@ var _ = Describe("WatcherAPI controller with minimal spec values", func() { var _ = Describe("WatcherAPI controller", func() { When("A WatcherAPI instance is created", func() { BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.Instance, GetDefaultWatcherAPISpec())) + DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.WatcherAPI, GetDefaultWatcherAPISpec())) }) It("should have the Spec fields defaulted", func() { - WatcherAPI := GetWatcherAPI(watcherTest.Instance) + WatcherAPI := GetWatcherAPI(watcherTest.WatcherAPI) Expect(WatcherAPI.Spec.DatabaseInstance).Should(Equal("openstack")) Expect(WatcherAPI.Spec.DatabaseAccount).Should(Equal("watcher")) Expect(WatcherAPI.Spec.Secret).Should(Equal("test-osp-secret")) + Expect(WatcherAPI.Spec.MemcachedInstance).Should(Equal("memcached")) }) It("should have the Status fields initialized", func() { - WatcherAPI := GetWatcherAPI(watcherTest.Instance) + WatcherAPI := GetWatcherAPI(watcherTest.WatcherAPI) Expect(WatcherAPI.Status.ObservedGeneration).To(Equal(int64(0))) }) It("should have ReadyCondition false", func() { th.ExpectCondition( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.ReadyCondition, corev1.ConditionFalse, @@ -81,7 +86,7 @@ var _ = Describe("WatcherAPI controller", func() { It("should have input not ready", func() { th.ExpectCondition( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.InputReadyCondition, corev1.ConditionFalse, @@ -90,7 +95,7 @@ var _ = Describe("WatcherAPI controller", func() { It("should have service config input unknown", func() { th.ExpectCondition( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.ServiceConfigReadyCondition, corev1.ConditionUnknown, @@ -101,16 +106,17 @@ var _ = Describe("WatcherAPI controller", func() { // the reconciler loop adds the finalizer so we have to wait for // it to run Eventually(func() []string { - return GetWatcherAPI(watcherTest.Instance).Finalizers + return GetWatcherAPI(watcherTest.WatcherAPI).Finalizers }, timeout, interval).Should(ContainElement("openstack.org/watcherapi")) }) }) - When("the secret is created with all the expected fields", func() { + When("the secret is created with all the expected fields and has all the required infra", func() { BeforeEach(func() { secret := th.CreateSecret( watcherTest.InternalTopLevelSecretName, map[string][]byte{ "WatcherPassword": []byte("service-password"), + "transport_url": []byte("url"), }, ) DeferCleanup(k8sClient.Delete, ctx, secret) @@ -122,19 +128,35 @@ var _ = Describe("WatcherAPI controller", func() { watcherTest.WatcherDatabaseAccount, mariadbv1.MariaDBAccountSpec{}) DeferCleanup(k8sClient.Delete, ctx, apiMariaDBAccount) DeferCleanup(k8sClient.Delete, ctx, apiMariaDBSecret) - DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.Instance, GetDefaultWatcherAPISpec())) + DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.WatcherAPI, GetDefaultWatcherAPISpec())) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(watcherTest.WatcherAPI.Namespace)) + memcachedSpec := memcachedv1.MemcachedSpec{ + MemcachedSpecCore: memcachedv1.MemcachedSpecCore{ + Replicas: ptr.To(int32(1)), + }, + } + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(watcherTest.WatcherAPI.Namespace, MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(watcherTest.MemcachedNamespace) }) It("should have input ready", func() { th.ExpectCondition( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.InputReadyCondition, corev1.ConditionTrue, ) }) + It("should have memcached ready true", func() { + th.ExpectCondition( + watcherTest.WatcherAPI, + ConditionGetterFunc(WatcherAPIConditionGetter), + condition.MemcachedReadyCondition, + corev1.ConditionTrue, + ) + }) It("should have config service input ready", func() { th.ExpectCondition( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.ServiceConfigReadyCondition, corev1.ConditionTrue, @@ -156,7 +178,7 @@ var _ = Describe("WatcherAPI controller", func() { watcherTest.WatcherDatabaseAccount, mariadbv1.MariaDBAccountSpec{}) DeferCleanup(k8sClient.Delete, ctx, apiMariaDBAccount) DeferCleanup(k8sClient.Delete, ctx, apiMariaDBSecret) - DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.Instance, GetDefaultWatcherAPISpec())) + DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.WatcherAPI, GetDefaultWatcherAPISpec())) }) It("should have input false", func() { errorString := fmt.Sprintf( @@ -164,7 +186,7 @@ var _ = Describe("WatcherAPI controller", func() { "field 'WatcherPassword' not found in secret/test-osp-secret", ) th.ExpectConditionWithDetails( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.InputReadyCondition, corev1.ConditionFalse, @@ -174,7 +196,7 @@ var _ = Describe("WatcherAPI controller", func() { }) It("should have config service input unknown", func() { th.ExpectCondition( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.ServiceConfigReadyCondition, corev1.ConditionUnknown, @@ -183,11 +205,11 @@ var _ = Describe("WatcherAPI controller", func() { }) When("A WatcherAPI instance without secret is created", func() { BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.Instance, GetDefaultWatcherAPISpec())) + DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.WatcherAPI, GetDefaultWatcherAPISpec())) }) It("is missing the secret", func() { th.ExpectConditionWithDetails( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.InputReadyCondition, corev1.ConditionFalse, @@ -202,13 +224,14 @@ var _ = Describe("WatcherAPI controller", func() { watcherTest.InternalTopLevelSecretName, map[string][]byte{ "WatcherPassword": []byte("service-password"), + "transport_url": []byte("url"), }, ) DeferCleanup(k8sClient.Delete, ctx, secret) - DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.Instance, GetDefaultWatcherAPISpec())) + DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.WatcherAPI, GetDefaultWatcherAPISpec())) }) It("should have input not ready", func() { - WatcherAPI := GetWatcherAPI(watcherTest.Instance) + WatcherAPI := GetWatcherAPI(watcherTest.WatcherAPI) customErrorString := fmt.Sprintf( "couldn't get database %s and account %s", watcher.DatabaseCRName, @@ -219,7 +242,7 @@ var _ = Describe("WatcherAPI controller", func() { customErrorString, ) th.ExpectConditionWithDetails( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.InputReadyCondition, corev1.ConditionFalse, @@ -229,11 +252,109 @@ var _ = Describe("WatcherAPI controller", func() { }) It("should have config service unknown", func() { th.ExpectCondition( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.ServiceConfigReadyCondition, corev1.ConditionUnknown, ) }) }) + When("secret and db are created, but there is no memcached", func() { + BeforeEach(func() { + secret := th.CreateSecret( + watcherTest.InternalTopLevelSecretName, + map[string][]byte{ + "WatcherPassword": []byte("service-password"), + "transport_url": []byte("url"), + }, + ) + DeferCleanup(k8sClient.Delete, ctx, secret) + mariadb.CreateMariaDBDatabase(watcherTest.WatcherDatabaseName.Namespace, watcherTest.WatcherDatabaseName.Name, mariadbv1.MariaDBDatabaseSpec{}) + DeferCleanup(k8sClient.Delete, ctx, mariadb.GetMariaDBDatabase(watcherTest.WatcherDatabaseName)) + + mariadb.SimulateMariaDBTLSDatabaseCompleted(watcherTest.WatcherDatabaseName) + apiMariaDBAccount, apiMariaDBSecret := mariadb.CreateMariaDBAccountAndSecret( + watcherTest.WatcherDatabaseAccount, mariadbv1.MariaDBAccountSpec{}) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBAccount) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBSecret) + DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.WatcherAPI, GetDefaultWatcherAPISpec())) + }) + It("should have input ready true", func() { + th.ExpectCondition( + watcherTest.WatcherAPI, + ConditionGetterFunc(WatcherAPIConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + It("should have memcached ready false", func() { + th.ExpectConditionWithDetails( + watcherTest.WatcherAPI, + ConditionGetterFunc(WatcherAPIConditionGetter), + condition.MemcachedReadyCondition, + corev1.ConditionFalse, + condition.RequestedReason, + condition.MemcachedReadyWaitingMessage, + ) + }) + }) + When("secret, db and memcached are created, but there is no keystoneapi", func() { + BeforeEach(func() { + secret := th.CreateSecret( + watcherTest.InternalTopLevelSecretName, + map[string][]byte{ + "WatcherPassword": []byte("service-password"), + "transport_url": []byte("url"), + }, + ) + DeferCleanup(k8sClient.Delete, ctx, secret) + mariadb.CreateMariaDBDatabase(watcherTest.WatcherDatabaseName.Namespace, watcherTest.WatcherDatabaseName.Name, mariadbv1.MariaDBDatabaseSpec{}) + DeferCleanup(k8sClient.Delete, ctx, mariadb.GetMariaDBDatabase(watcherTest.WatcherDatabaseName)) + + mariadb.SimulateMariaDBTLSDatabaseCompleted(watcherTest.WatcherDatabaseName) + apiMariaDBAccount, apiMariaDBSecret := mariadb.CreateMariaDBAccountAndSecret( + watcherTest.WatcherDatabaseAccount, mariadbv1.MariaDBAccountSpec{}) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBAccount) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBSecret) + memcachedSpec := memcachedv1.MemcachedSpec{ + MemcachedSpecCore: memcachedv1.MemcachedSpecCore{ + Replicas: ptr.To(int32(1)), + }, + } + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(watcherTest.WatcherAPI.Namespace, MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(watcherTest.MemcachedNamespace) + DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.WatcherAPI, GetDefaultWatcherAPISpec())) + + }) + It("should have input ready true", func() { + th.ExpectCondition( + watcherTest.WatcherAPI, + ConditionGetterFunc(WatcherAPIConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) + It("should have memcached ready true", func() { + th.ExpectCondition( + watcherTest.WatcherAPI, + ConditionGetterFunc(WatcherAPIConditionGetter), + condition.MemcachedReadyCondition, + corev1.ConditionTrue, + ) + }) + It("should have config service input unknown", func() { + errorString := fmt.Sprintf( + condition.ServiceConfigReadyErrorMessage, + "keystoneAPI not found", + ) + th.ExpectConditionWithDetails( + watcherTest.WatcherAPI, + ConditionGetterFunc(WatcherAPIConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + errorString, + ) + }) + }) }) diff --git a/tests/kuttl/test-suites/default/watcher-api/03-assert.yaml b/tests/kuttl/test-suites/default/watcher-api/03-assert.yaml index 84ab971..a524eee 100644 --- a/tests/kuttl/test-suites/default/watcher-api/03-assert.yaml +++ b/tests/kuttl/test-suites/default/watcher-api/03-assert.yaml @@ -20,7 +20,17 @@ status: reason: Ready status: "True" type: InputReady + - message: " Memcached instance has been provisioned" + reason: Ready + status: "True" + type: MemcachedReady - message: Service config create completed reason: Ready status: "True" type: ServiceConfigReady +--- +apiVersion: v1 +kind: Secret +metadata: + name: watcherapi-kuttl-config-data +type: Opaque diff --git a/tests/kuttl/test-suites/default/watcher-api/03-deploy-watcher-api.yaml b/tests/kuttl/test-suites/default/watcher-api/03-deploy-watcher-api.yaml index 38e6684..1e0af9c 100644 --- a/tests/kuttl/test-suites/default/watcher-api/03-deploy-watcher-api.yaml +++ b/tests/kuttl/test-suites/default/watcher-api/03-deploy-watcher-api.yaml @@ -5,6 +5,7 @@ metadata: type: Opaque stringData: WatcherPassword: password + transport_url: rabbitmq-transport-url-watcher-kuttl-watcher-transport --- apiVersion: watcher.openstack.org/v1beta1 kind: WatcherAPI @@ -13,3 +14,4 @@ metadata: spec: databaseInstance: openstack secret: watcherapi-secret + memcachedInstance: "memcached"