diff --git a/api/bases/watcher.openstack.org_watcherapis.yaml b/api/bases/watcher.openstack.org_watcherapis.yaml index 33f3eee..2c4b8df 100644 --- a/api/bases/watcher.openstack.org_watcherapis.yaml +++ b/api/bases/watcher.openstack.org_watcherapis.yaml @@ -64,6 +64,12 @@ spec: secret: description: Secret containing all passwords / keys needed type: string + serviceUser: + default: watcher + description: |- + ServiceUser - optional username used for this service to register in + keystone + type: string required: - databaseInstance - secret diff --git a/api/bases/watcher.openstack.org_watchers.yaml b/api/bases/watcher.openstack.org_watchers.yaml index 44da3f2..749cdd1 100644 --- a/api/bases/watcher.openstack.org_watchers.yaml +++ b/api/bases/watcher.openstack.org_watchers.yaml @@ -71,6 +71,12 @@ spec: default: osp-secret description: Secret containing all passwords / keys needed type: string + serviceUser: + default: watcher + description: |- + ServiceUser - optional username used for this service to register in + keystone + type: string required: - databaseInstance - rabbitMqClusterName diff --git a/api/v1beta1/common_types.go b/api/v1beta1/common_types.go index 45cac2b..a8fc3ca 100644 --- a/api/v1beta1/common_types.go +++ b/api/v1beta1/common_types.go @@ -32,6 +32,12 @@ 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=watcher + // ServiceUser - optional username used for this service to register in + // keystone + ServiceUser string `json:"serviceUser"` } // WatcherTemplate defines the fields used in the top level CR diff --git a/config/crd/bases/watcher.openstack.org_watcherapis.yaml b/config/crd/bases/watcher.openstack.org_watcherapis.yaml index 33f3eee..2c4b8df 100644 --- a/config/crd/bases/watcher.openstack.org_watcherapis.yaml +++ b/config/crd/bases/watcher.openstack.org_watcherapis.yaml @@ -64,6 +64,12 @@ spec: secret: description: Secret containing all passwords / keys needed type: string + serviceUser: + default: watcher + description: |- + ServiceUser - optional username used for this service to register in + keystone + type: string required: - databaseInstance - secret diff --git a/config/crd/bases/watcher.openstack.org_watchers.yaml b/config/crd/bases/watcher.openstack.org_watchers.yaml index 44da3f2..749cdd1 100644 --- a/config/crd/bases/watcher.openstack.org_watchers.yaml +++ b/config/crd/bases/watcher.openstack.org_watchers.yaml @@ -71,6 +71,12 @@ spec: default: osp-secret description: Secret containing all passwords / keys needed type: string + serviceUser: + default: watcher + description: |- + ServiceUser - optional username used for this service to register in + keystone + type: string required: - databaseInstance - rabbitMqClusterName diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index df73387..c563ee4 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -28,6 +28,38 @@ rules: - patch - update - watch +- apiGroups: + - keystone.openstack.org + resources: + - keystoneapis + verbs: + - get + - list + - watch +- apiGroups: + - keystone.openstack.org + resources: + - keystoneendpoints + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - keystone.openstack.org + resources: + - keystoneservices + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - mariadb.openstack.org resources: diff --git a/controllers/watcher_common.go b/controllers/watcher_common.go index 6331edc..dcc316c 100644 --- a/controllers/watcher_common.go +++ b/controllers/watcher_common.go @@ -16,6 +16,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "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 +191,38 @@ 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) +} diff --git a/controllers/watcherapi_controller.go b/controllers/watcherapi_controller.go index 19fee10..8a642fd 100644 --- a/controllers/watcherapi_controller.go +++ b/controllers/watcherapi_controller.go @@ -30,9 +30,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/go-logr/logr" + 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 +63,9 @@ 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; // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -181,9 +188,6 @@ 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, @@ -192,14 +196,58 @@ func (r *WatcherAPIReconciler) generateServiceConfigs( 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&plugin=dbcounter", + 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": "", // TODO jgilaber implement getting this URL once we + // have rabbitmq support added to the Watcher controller + "MemcachedServers": "", // TODO jgilaber implement getting this URL once we + // have memchache support + } - 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) { diff --git a/go.mod b/go.mod index e65f82d..27ba5bb 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/onsi/ginkgo/v2 v2.20.1 github.com/onsi/gomega v1.34.1 github.com/openstack-k8s-operators/infra-operator/apis v0.5.0 + github.com/openstack-k8s-operators/keystone-operator/api v0.5.0 github.com/openstack-k8s-operators/lib-common/modules/common v0.5.0 github.com/openstack-k8s-operators/lib-common/modules/test v0.5.0 github.com/openstack-k8s-operators/mariadb-operator/api v0.5.0 @@ -38,6 +39,7 @@ 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/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 @@ -47,6 +49,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/openshift/api v3.9.0+incompatible // indirect + github.com/openstack-k8s-operators/lib-common/modules/openstack v0.4.1-0.20241014140317-e5c35d28f3af // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect diff --git a/go.sum b/go.sum index 81429a6..da36f4e 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQu github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= +github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -77,8 +79,12 @@ github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094 h1:J1wuGhVxpsHykZBa6 github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4= github.com/openstack-k8s-operators/infra-operator/apis v0.5.0 h1:+1Q1Ux7DeEg3dPsVEWsm+MCJASlAy9FH/CGRD5jZeXo= github.com/openstack-k8s-operators/infra-operator/apis v0.5.0/go.mod h1:J9oUh3eGBvAFfyUMiPxPRBSxAcO8rnwITN4RTh/It+8= +github.com/openstack-k8s-operators/keystone-operator/api v0.5.0 h1:h/Ce2OjdNrkDh/rJuZPdOsxrsm2uC+E57Mmf34oyWR0= +github.com/openstack-k8s-operators/keystone-operator/api v0.5.0/go.mod h1:saoorrsPo3DzDPGM6PJ8sQJBNuNRGCHjRHChRQmkoQ0= github.com/openstack-k8s-operators/lib-common/modules/common v0.5.0 h1:wto7Vprhr84z2LJzjbbw589MGkfjKtpHnhIhzgOa+BI= github.com/openstack-k8s-operators/lib-common/modules/common v0.5.0/go.mod h1:tNeup9Xl7j2eaeMslJ/rt59NNEAw7ATf6RuebS/YkSk= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.4.1-0.20241014140317-e5c35d28f3af h1:fevDUHmqcnI4wDTKupKe/CcgVdgNpZXWkJx8u0/xEXs= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.4.1-0.20241014140317-e5c35d28f3af/go.mod h1:djfljx3jfHqywhY3oDvPg/GLKwiFVkds6v7P7/Yg+8g= github.com/openstack-k8s-operators/lib-common/modules/test v0.5.0 h1:rUVJUKFWQuXYQ3LPNs7wIJdka5EqUmLMT3RRpWkuqRo= github.com/openstack-k8s-operators/lib-common/modules/test v0.5.0/go.mod h1:LV0jo5etIsGyINpmB37i4oWR8zU6ApIuh7fsqGGA41o= github.com/openstack-k8s-operators/mariadb-operator/api v0.5.0 h1:XBx1TuyKhgtWAigYVcdqTUzIwWRYHN63pfa0zxHB12M= @@ -116,6 +122,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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/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= @@ -128,6 +135,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= @@ -141,6 +149,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -152,6 +161,7 @@ golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= diff --git a/main.go b/main.go index 60cf23f..084ea8c 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" 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" watcherv1beta1 "github.com/openstack-k8s-operators/watcher-operator/api/v1beta1" @@ -56,6 +57,7 @@ func init() { utilruntime.Must(watcherv1beta1.AddToScheme(scheme)) utilruntime.Must(mariadbv1.AddToScheme(scheme)) utilruntime.Must(rabbitmqv1.AddToScheme(scheme)) + utilruntime.Must(keystonev1.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..3d2d7c9 --- /dev/null +++ b/templates/watcher/config/00-default.conf @@ -0,0 +1,43 @@ +[DEFAULT] +state_path = /opt/stack/data/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 = /opt/stack/data/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] +memcached_servers = {{ .MemcachedServers }} +# TODO jgilaber implement handling this option when we add tls support +# cafile = /opt/stack/data/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 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/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 6171813..6c72fd5 100644 --- a/tests/functional/suite_test.go +++ b/tests/functional/suite_test.go @@ -29,6 +29,8 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" + keystone_test "github.com/openstack-k8s-operators/keystone-operator/api/test/helpers" + 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" watcherv1 "github.com/openstack-k8s-operators/watcher-operator/api/v1beta1" @@ -53,6 +55,7 @@ var ( th *common_test.TestHelper mariadb *mariadb_test.TestHelper infra *infra_test.TestHelper + keystone *keystone_test.TestHelper namespace string watcherName types.NamespacedName watcherTest WatcherTestData @@ -88,12 +91,17 @@ var _ = BeforeSuite(func() { "github.com/openstack-k8s-operators/infra-operator/apis", "../../go.mod", "bases") Expect(err).ShouldNot(HaveOccurred()) + keystoneCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/keystone-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + By("bootstrapping test environment") testEnv = &envtest.Environment{ CRDDirectoryPaths: []string{ filepath.Join("..", "..", "config", "crd", "bases"), mariaDBCRDs, rabbitmqCRDs, + keystoneCRDs, }, ErrorIfCRDPathMissing: true, @@ -115,6 +123,8 @@ 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) @@ -132,6 +142,8 @@ var _ = BeforeSuite(func() { Expect(mariadb).NotTo(BeNil()) infra = infra_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) Expect(infra).NotTo(BeNil()) + keystone = keystone_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(keystone).NotTo(BeNil()) // Start the controller-manager if goroutine webhookInstallOptions := &testEnv.WebhookInstallOptions diff --git a/tests/functional/watcher_test_data.go b/tests/functional/watcher_test_data.go index ce7c42d..75fe550 100644 --- a/tests/functional/watcher_test_data.go +++ b/tests/functional/watcher_test_data.go @@ -39,6 +39,7 @@ type WatcherTestData struct { WatcherDatabaseAccountSecret types.NamespacedName InternalTopLevelSecretName types.NamespacedName WatcherTransportURL types.NamespacedName + WatcherAPI types.NamespacedName } // GetWatcherTestData is a function that initialize the WatcherTestData @@ -75,5 +76,9 @@ func GetWatcherTestData(watcherName types.NamespacedName) WatcherTestData { Namespace: watcherName.Namespace, Name: fmt.Sprintf("%s-watcher-transport", watcherName.Name), }, + WatcherAPI: types.NamespacedName{ + Namespace: watcherName.Namespace, + Name: "watcher-api", + }, } } diff --git a/tests/functional/watcherapi_controller_test.go b/tests/functional/watcherapi_controller_test.go index e1ef701..b390668 100644 --- a/tests/functional/watcherapi_controller_test.go +++ b/tests/functional/watcherapi_controller_test.go @@ -25,11 +25,11 @@ var ( 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")) @@ -37,7 +37,7 @@ var _ = Describe("WatcherAPI controller with minimal spec values", func() { }) 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 +45,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 +55,24 @@ 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")) }) 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 +81,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 +90,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,7 +101,7 @@ 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")) }) }) @@ -122,11 +122,12 @@ 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)) }) It("should have input ready", func() { th.ExpectCondition( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.InputReadyCondition, corev1.ConditionTrue, @@ -134,7 +135,7 @@ var _ = Describe("WatcherAPI controller", func() { }) It("should have config service input ready", func() { th.ExpectCondition( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.ServiceConfigReadyCondition, corev1.ConditionTrue, @@ -156,7 +157,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 +165,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 +175,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 +184,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, @@ -205,10 +206,10 @@ var _ = Describe("WatcherAPI controller", func() { }, ) 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 +220,7 @@ var _ = Describe("WatcherAPI controller", func() { customErrorString, ) th.ExpectConditionWithDetails( - watcherTest.Instance, + watcherTest.WatcherAPI, ConditionGetterFunc(WatcherAPIConditionGetter), condition.InputReadyCondition, corev1.ConditionFalse, @@ -229,11 +230,53 @@ 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 keystoneapi", func() { + BeforeEach(func() { + secret := th.CreateSecret( + watcherTest.InternalTopLevelSecretName, + map[string][]byte{ + "WatcherPassword": []byte("service-password"), + }, + ) + 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 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..1881c8d 100644 --- a/tests/kuttl/test-suites/default/watcher-api/03-assert.yaml +++ b/tests/kuttl/test-suites/default/watcher-api/03-assert.yaml @@ -24,3 +24,9 @@ status: reason: Ready status: "True" type: ServiceConfigReady +--- +apiVersion: v1 +kind: Secret +metadata: + name: watcherapi-kuttl-config-data +type: Opaque