diff --git a/.ci/jenkins/Jenkinsfile.e2e.cluster b/.ci/jenkins/Jenkinsfile.e2e.cluster index c00f07ffa..b5154523a 100644 --- a/.ci/jenkins/Jenkinsfile.e2e.cluster +++ b/.ci/jenkins/Jenkinsfile.e2e.cluster @@ -2,9 +2,12 @@ helper = null -minikubeClusterPlatform = 'minikube' +KIND_VERSION = "v0.20.0" +kindClusterPlatform = 'kind' openshiftClusterPlatform = 'openshift' +kindLogsFolder = "/tmp/kind-logs" + pipeline { agent { label "${env.AGENT_LABEL ?: "rhel8"} && podman && !built-in" @@ -54,6 +57,31 @@ pipeline { } } } + stage('Load image into Kind') { + when { + expression { + return getClusterName() == kindClusterPlatform + } + } + steps { + script { + kind.loadImage(getTestImage()) + } + } + } + stage('Deploy the operator') { + when { + expression { + return getClusterName() == kindClusterPlatform + } + } + steps { + script { + sh "make deploy IMG=${getTestImage()}" + sh "kubectl wait pod -A -l control-plane=sonataflow-operator --for condition=Ready --timeout=120s" + } + } + } stage('Prepare for e2e tests') { when { expression { @@ -90,16 +118,25 @@ pipeline { make test-e2e """ } catch (err) { + kind.exportLogs(kindLogsFolder) sh 'make undeploy' + deleteKindCluster() throw err } sh 'kubectl get pods -A' + deleteKindCluster() } } } } } post { + always { + script { + archiveArtifacts(artifacts: "**${kindLogsFolder}/**/*.*,**/e2e-test-report.xml") + junit '**/e2e-test-report.xml' + } + } cleanup { script { clean() @@ -111,7 +148,6 @@ pipeline { void clean() { helper.cleanGoPath() util.cleanNode(containerEngine) - cleanupCluster() } String getTestImage() { @@ -128,10 +164,12 @@ String getOperatorVersion() { void setupCluster() { switch (getClusterName()) { - case minikubeClusterPlatform: - setupMinikube() + case kindClusterPlatform: + echo 'Creating kind cluster' + createKindCluster() break case openshiftClusterPlatform: + echo 'Setting up Openshift' setupOpenshift() break default: @@ -139,13 +177,12 @@ void setupCluster() { } } -void setupMinikube() { - // Start minikube - minikube.minikubeMemory = '12g' - minikube.start() +void createKindCluster() { + sh(script: "make KIND_VERSION=${KIND_VERSION} create-cluster", returnStdout: true) +} - minikube.waitForMinikubeStarted() - minikube.waitForMinikubeRegistry() +void deleteKindCluster() { + sh(script: "make delete-cluster", returnStdout: true) } void setupOpenshift() { @@ -155,8 +192,9 @@ void setupOpenshift() { void cleanupCluster() { switch (getClusterName()) { - case minikubeClusterPlatform: - minikube.stop() + case kindClusterPlatform: + echo 'Deleting kind cluster' + deleteKindCluster() break case openshiftClusterPlatform: echo 'Nothing to cleanup on openshift. All good !' @@ -168,8 +206,8 @@ void cleanupCluster() { void executeInCluster(Closure executeClosure) { switch (getClusterName()) { - case minikubeClusterPlatform: - echo "Execute in minikube" + case kindClusterPlatform: + echo "Execute in kind" executeClosure() break case openshiftClusterPlatform: @@ -185,7 +223,7 @@ void executeInCluster(Closure executeClosure) { void getPlatformCRFilePath() { switch (getClusterName()) { - case minikubeClusterPlatform: + case kindClusterPlatform: return 'test/testdata/sonataflow.org_v1alpha08_sonataflowplatform_withCache_minikube.yaml' case openshiftClusterPlatform: return 'test/testdata/sonataflow.org_v1alpha08_sonataflowplatform_openshift.yaml' diff --git a/.ci/jenkins/scripts/helper.groovy b/.ci/jenkins/scripts/helper.groovy index c9c848a8a..870a3381b 100644 --- a/.ci/jenkins/scripts/helper.groovy +++ b/.ci/jenkins/scripts/helper.groovy @@ -1,7 +1,7 @@ openshift = null container = null properties = null -minikube = null +kind = null defaultImageParamsPrefix = 'IMAGE' baseImageParamsPrefix = 'BASE_IMAGE' @@ -19,7 +19,7 @@ void initPipeline() { container.containerEngineTlsOptions = env.CONTAINER_ENGINE_TLS_OPTIONS ?: '' container.containerOpenshift = openshift - minikube = load '.ci/jenkins/scripts/minikube.groovy' + kind = load '.ci/jenkins/scripts/kind.groovy' } void updateDisplayName() { diff --git a/.ci/jenkins/scripts/kind.groovy b/.ci/jenkins/scripts/kind.groovy new file mode 100644 index 000000000..bd28dfbd3 --- /dev/null +++ b/.ci/jenkins/scripts/kind.groovy @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +void exportLogs(String destination) { + println "Exporting kind logs to ${destination}" + def exportKindLogs = sh(returnStatus: true, script: """ + mkdir -p .${destination} + kind export logs --loglevel=debug .${destination} + """) +} + +void loadImage(String imageName) { + sh "kind load docker-image ${imageName}" +} + +return this diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 99862c39d..30951d090 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -26,7 +26,8 @@ jobs: # Packages for testing with Podman - name: Install package run: | - sudo apt-get -y install \ + sudo apt-get update &&\ + sudo apt-get -y install --no-install-recommends \ btrfs-progs \ libgpgme-dev \ libbtrfs-dev \ diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index da3202d3f..8a299bd8f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -26,7 +26,8 @@ jobs: steps: - name: Install package run: | - sudo apt-get -y install \ + sudo apt-get update &&\ + sudo apt-get -y install --no-install-recommends \ btrfs-progs \ libgpgme-dev \ libbtrfs-dev \ @@ -76,6 +77,12 @@ jobs: run: | make test-e2e + - name: Retrieve cluster events and list pods + if: failure() + run: | + kubectl get events + kubectl get pod -A + - name: Export kind logs if: always() run: | diff --git a/Makefile b/Makefile index f8f4e43d7..f7ac15545 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # To re-generate a bundle for another specific version without changing the standard setup, you can: # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) -VERSION ?= 2.0.0-snapshot +VERSION ?= 999.0.0-snapshot REDUCED_VERSION ?= latest # CHANNELS define the bundle channels used in the bundle. @@ -347,7 +347,7 @@ generate-all: generate generate-deploy bundle addheaders vet fmt .PHONY: test-e2e # You will need to have a Minikube/Kind cluster up in running to run this target, and run container-builder before the test test-e2e: install-operator-sdk - go test ./test/e2e/* -v -ginkgo.v -timeout 30m + go test ./test/e2e/* -v -ginkgo.v -ginkgo.no-color -ginkgo.junit-report=./e2e-test-report.xml -timeout 60m .PHONY: before-pr before-pr: test generate-all diff --git a/api/v1alpha08/sonataflow_persistence_types.go b/api/v1alpha08/sonataflow_persistence_types.go new file mode 100644 index 000000000..d0b08e467 --- /dev/null +++ b/api/v1alpha08/sonataflow_persistence_types.go @@ -0,0 +1,102 @@ +// Copyright 2024 Apache Software Foundation (ASF) +// +// 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 v1alpha08 + +// PlatformPersistenceOptionsSpec configures the DataBase in the platform spec. This specification can +// be used by workflows and platform services when they don't provide one of their own. +// +optional +// +kubebuilder:validation:MaxProperties=1 +type PlatformPersistenceOptionsSpec struct { + // Connect configured services to a postgresql database. + // +optional + PostgreSQL *PlatformPersistencePostgreSQL `json:"postgresql,omitempty"` +} + +// PlatformPersistencePostgreSQL configure postgresql connection in a platform to be shared +// by platform services and workflows when required. +// +kubebuilder:validation:MinProperties=2 +// +kubebuilder:validation:MaxProperties=2 +type PlatformPersistencePostgreSQL struct { + // Secret reference to the database user credentials + SecretRef PostgreSQLSecretOptions `json:"secretRef"` + // Service reference to postgresql datasource. Mutually exclusive to jdbcUrl. + // +optional + ServiceRef *SQLServiceOptions `json:"serviceRef,omitempty"` + // PostgreSql JDBC URL. Mutually exclusive to serviceRef. + // e.g. "jdbc:postgresql://host:port/database?currentSchema=data-index-service" + // +optional + JdbcUrl string `json:"jdbcUrl,omitempty"` +} + +// PersistenceOptionsSpec configures the DataBase support for both platform services and workflows. For services, it allows +// configuring a generic database connectivity if the service does not come with its own configured. In case of workflows, +// the operator will add the necessary JDBC properties to in the workflow's application.properties so that it can communicate +// with the persistence service based on the spec provided here. +// +optional +// +kubebuilder:validation:MaxProperties=1 +type PersistenceOptionsSpec struct { + // Connect configured services to a postgresql database. + // +optional + PostgreSQL *PersistencePostgreSQL `json:"postgresql,omitempty"` +} + +// PersistencePostgreSQL configure postgresql connection for service(s). +// +kubebuilder:validation:MinProperties=2 +// +kubebuilder:validation:MaxProperties=2 +type PersistencePostgreSQL struct { + // Secret reference to the database user credentials + SecretRef PostgreSQLSecretOptions `json:"secretRef"` + // Service reference to postgresql datasource. Mutually exclusive to jdbcUrl. + // +optional + ServiceRef *PostgreSQLServiceOptions `json:"serviceRef,omitempty"` + // PostgreSql JDBC URL. Mutually exclusive to serviceRef. + // e.g. "jdbc:postgresql://host:port/database?currentSchema=data-index-service" + // +optional + JdbcUrl string `json:"jdbcUrl,omitempty"` +} + +// PostgreSQLSecretOptions use credential secret for postgresql connection. +type PostgreSQLSecretOptions struct { + // Name of the postgresql credentials secret. + Name string `json:"name"` + // Defaults to POSTGRESQL_USER + // +optional + UserKey string `json:"userKey,omitempty"` + // Defaults to POSTGRESQL_PASSWORD + // +optional + PasswordKey string `json:"passwordKey,omitempty"` +} + +type SQLServiceOptions struct { + // Name of the postgresql k8s service. + Name string `json:"name"` + // Namespace of the postgresql k8s service. Defaults to the SonataFlowPlatform's local namespace. + // +optional + Namespace string `json:"namespace,omitempty"` + // Port to use when connecting to the postgresql k8s service. Defaults to 5432. + // +optional + Port *int `json:"port,omitempty"` + // Name of postgresql database to be used. Defaults to "sonataflow" + // +optional + DatabaseName string `json:"databaseName,omitempty"` +} + +// PostgreSQLServiceOptions use k8s service to configure postgresql jdbc url. +type PostgreSQLServiceOptions struct { + *SQLServiceOptions `json:",inline"` + // Schema of postgresql database to be used. Defaults to "data-index-service" + // +optional + DatabaseSchema string `json:"databaseSchema,omitempty"` +} diff --git a/api/v1alpha08/sonataflow_types.go b/api/v1alpha08/sonataflow_types.go index 37bd50f34..ac32fe357 100644 --- a/api/v1alpha08/sonataflow_types.go +++ b/api/v1alpha08/sonataflow_types.go @@ -657,9 +657,11 @@ type SonataFlowSpec struct { // PodTemplate describes the deployment details of this SonataFlow instance. //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="podTemplate" PodTemplate PodTemplateSpec `json:"podTemplate,omitempty"` - // Persists service to a datasource of choice. Ephemeral by default. - // +optional - Persistence *PersistenceOptions `json:"persistence,omitempty"` + // Persistence defines the database persistence configuration for the workflow + Persistence *PersistenceOptionsSpec `json:"persistence,omitempty"` + // Sink describes the sinkBinding details of this SonataFlow instance. + //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="sink" + Sink *duckv1.Destination `json:"sink,omitempty"` } // SonataFlowStatus defines the observed state of SonataFlow diff --git a/api/v1alpha08/sonataflowclusterplatform_types.go b/api/v1alpha08/sonataflowclusterplatform_types.go index 4016e5072..d5205d9ca 100644 --- a/api/v1alpha08/sonataflowclusterplatform_types.go +++ b/api/v1alpha08/sonataflowclusterplatform_types.go @@ -28,9 +28,23 @@ const ( // SonataFlowClusterPlatformSpec defines the desired state of SonataFlowClusterPlatform type SonataFlowClusterPlatformSpec struct { + // PlatformRef defines which existing SonataFlowPlatform's supporting services should be used cluster-wide. + //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="PlatformRef" PlatformRef SonataFlowPlatformRef `json:"platformRef"` + // Capabilities defines which platform capabilities should be applied cluster-wide. If nil, defaults to `capabilities.workflows["services"]` + //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Capabilities" + Capabilities *SonataFlowClusterPlatformCapSpec `json:"capabilities,omitempty"` } +// SonataFlowClusterPlatformCapSpec defines which platform capabilities should be applied cluster-wide +type SonataFlowClusterPlatformCapSpec struct { + // Workflows defines which platform capabilities should be applied to workflows cluster-wide. + Workflows []WorkFlowCapability `json:"workflows,omitempty"` +} + +// +kubebuilder:validation:Enum=services +type WorkFlowCapability string + // SonataFlowPlatformRef defines which existing SonataFlowPlatform's supporting services should be used cluster-wide. type SonataFlowPlatformRef struct { // Name of the SonataFlowPlatform @@ -78,6 +92,7 @@ func (in *SonataFlowClusterPlatformStatus) IsDuplicated() bool { // +kubebuilder:printcolumn:name="Platform_NS",type=string,JSONPath=`.spec.platformRef.namespace` // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=='Succeed')].status` // +kubebuilder:printcolumn:name="Reason",type=string,JSONPath=`.status.conditions[?(@.type=='Succeed')].reason` +// +operator-sdk:csv:customresourcedefinitions:resources={{SonataFlowPlatform,sonataflow.org/v1alpha08,"A SonataFlow Platform"}} type SonataFlowClusterPlatform struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/api/v1alpha08/sonataflowplatform_services_types.go b/api/v1alpha08/sonataflowplatform_services_types.go index 9ecad3de9..a16bc2782 100644 --- a/api/v1alpha08/sonataflowplatform_services_types.go +++ b/api/v1alpha08/sonataflowplatform_services_types.go @@ -32,61 +32,8 @@ type ServiceSpec struct { Enabled *bool `json:"enabled,omitempty"` // Persists service to a datasource of choice. Ephemeral by default. // +optional - Persistence *PersistenceOptions `json:"persistence,omitempty"` + Persistence *PersistenceOptionsSpec `json:"persistence,omitempty"` // PodTemplate describes the deployment details of this platform service instance. //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="podTemplate" PodTemplate PodTemplateSpec `json:"podTemplate,omitempty"` } - -// PersistenceOptions configure the services to persist to a datasource of choice -// +kubebuilder:validation:MaxProperties=1 -type PersistenceOptions struct { - // Connect configured services to a postgresql database. - // +optional - PostgreSql *PersistencePostgreSql `json:"postgresql,omitempty"` -} - -// PersistencePostgreSql configure postgresql connection for service(s). -// +kubebuilder:validation:MinProperties=2 -// +kubebuilder:validation:MaxProperties=2 -type PersistencePostgreSql struct { - // Secret reference to the database user credentials - SecretRef PostgreSqlSecretOptions `json:"secretRef"` - // Service reference to postgresql datasource. Mutually exclusive to jdbcUrl. - // +optional - ServiceRef *PostgreSqlServiceOptions `json:"serviceRef,omitempty"` - // PostgreSql JDBC URL. Mutually exclusive to serviceRef. - // e.g. "jdbc:postgresql://host:port/database?currentSchema=data-index-service" - // +optional - JdbcUrl string `json:"jdbcUrl,omitempty"` -} - -// PostgreSqlSecretOptions use credential secret for postgresql connection. -type PostgreSqlSecretOptions struct { - // Name of the postgresql credentials secret. - Name string `json:"name"` - // Defaults to POSTGRESQL_USER - // +optional - UserKey string `json:"userKey,omitempty"` - // Defaults to POSTGRESQL_PASSWORD - // +optional - PasswordKey string `json:"passwordKey,omitempty"` -} - -// PostgreSqlServiceOptions use k8s service to configure postgresql jdbc url. -type PostgreSqlServiceOptions struct { - // Name of the postgresql k8s service. - Name string `json:"name"` - // Namespace of the postgresql k8s service. Defaults to the SonataFlowPlatform's local namespace. - // +optional - Namespace string `json:"namespace,omitempty"` - // Port to use when connecting to the postgresql k8s service. Defaults to 5432. - // +optional - Port *int `json:"port,omitempty"` - // Name of postgresql database to be used. Defaults to "sonataflow" - // +optional - DatabaseName string `json:"databaseName,omitempty"` - // Schema of postgresql database to be used. Defaults to "data-index-service" - // +optional - DatabaseSchema string `json:"databaseSchema,omitempty"` -} diff --git a/api/v1alpha08/sonataflowplatform_types.go b/api/v1alpha08/sonataflowplatform_types.go index 0ee7e9ab7..c702c149b 100644 --- a/api/v1alpha08/sonataflowplatform_types.go +++ b/api/v1alpha08/sonataflowplatform_types.go @@ -45,7 +45,13 @@ type SonataFlowPlatformSpec struct { // Only workflows without the `sonataflow.org/profile: dev` annotation will be configured to use these service(s). // Setting this will override the use of any cluster-scoped services that might be defined via `SonataFlowClusterPlatform`. // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Services" Services *ServicesPlatformSpec `json:"services,omitempty"` + // Persistence defines the platform persistence configuration. When this field is set, + // the configuration is used as the persistence for platform services and sonataflow instances + // that don't provide one of their own. + // +optional + Persistence *PlatformPersistenceOptionsSpec `json:"persistence,omitempty"` } // PlatformCluster is the kind of orchestration cluster the platform is installed into @@ -80,6 +86,7 @@ type SonataFlowPlatformStatus struct { //+operator-sdk:csv:customresourcedefinitions:type=status,displayName="info" Info map[string]string `json:"info,omitempty"` // ClusterPlatformRef information related to the (optional) active SonataFlowClusterPlatform + //+operator-sdk:csv:customresourcedefinitions:type=status,displayName="clusterPlatformRef" ClusterPlatformRef *SonataFlowClusterPlatformRefStatus `json:"clusterPlatformRef,omitempty"` } diff --git a/api/v1alpha08/zz_generated.deepcopy.go b/api/v1alpha08/zz_generated.deepcopy.go index d4449494f..fb2db1872 100644 --- a/api/v1alpha08/zz_generated.deepcopy.go +++ b/api/v1alpha08/zz_generated.deepcopy.go @@ -26,6 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -320,42 +321,83 @@ func (in *Flow) DeepCopy() *Flow { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistenceOptions) DeepCopyInto(out *PersistenceOptions) { +func (in *PersistenceOptionsSpec) DeepCopyInto(out *PersistenceOptionsSpec) { *out = *in - if in.PostgreSql != nil { - in, out := &in.PostgreSql, &out.PostgreSql - *out = new(PersistencePostgreSql) + if in.PostgreSQL != nil { + in, out := &in.PostgreSQL, &out.PostgreSQL + *out = new(PersistencePostgreSQL) (*in).DeepCopyInto(*out) } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistenceOptions. -func (in *PersistenceOptions) DeepCopy() *PersistenceOptions { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistenceOptionsSpec. +func (in *PersistenceOptionsSpec) DeepCopy() *PersistenceOptionsSpec { if in == nil { return nil } - out := new(PersistenceOptions) + out := new(PersistenceOptionsSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PersistencePostgreSql) DeepCopyInto(out *PersistencePostgreSql) { +func (in *PersistencePostgreSQL) DeepCopyInto(out *PersistencePostgreSQL) { *out = *in out.SecretRef = in.SecretRef if in.ServiceRef != nil { in, out := &in.ServiceRef, &out.ServiceRef - *out = new(PostgreSqlServiceOptions) + *out = new(PostgreSQLServiceOptions) (*in).DeepCopyInto(*out) } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistencePostgreSql. -func (in *PersistencePostgreSql) DeepCopy() *PersistencePostgreSql { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistencePostgreSQL. +func (in *PersistencePostgreSQL) DeepCopy() *PersistencePostgreSQL { if in == nil { return nil } - out := new(PersistencePostgreSql) + out := new(PersistencePostgreSQL) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlatformPersistenceOptionsSpec) DeepCopyInto(out *PlatformPersistenceOptionsSpec) { + *out = *in + if in.PostgreSQL != nil { + in, out := &in.PostgreSQL, &out.PostgreSQL + *out = new(PlatformPersistencePostgreSQL) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlatformPersistenceOptionsSpec. +func (in *PlatformPersistenceOptionsSpec) DeepCopy() *PlatformPersistenceOptionsSpec { + if in == nil { + return nil + } + out := new(PlatformPersistenceOptionsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlatformPersistencePostgreSQL) DeepCopyInto(out *PlatformPersistencePostgreSQL) { + *out = *in + out.SecretRef = in.SecretRef + if in.ServiceRef != nil { + in, out := &in.ServiceRef, &out.ServiceRef + *out = new(SQLServiceOptions) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlatformPersistencePostgreSQL. +func (in *PlatformPersistencePostgreSQL) DeepCopy() *PlatformPersistencePostgreSQL { + if in == nil { + return nil + } + out := new(PlatformPersistencePostgreSQL) in.DeepCopyInto(out) return out } @@ -586,36 +628,36 @@ func (in *PodTemplateSpec) DeepCopy() *PodTemplateSpec { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PostgreSqlSecretOptions) DeepCopyInto(out *PostgreSqlSecretOptions) { +func (in *PostgreSQLSecretOptions) DeepCopyInto(out *PostgreSQLSecretOptions) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSqlSecretOptions. -func (in *PostgreSqlSecretOptions) DeepCopy() *PostgreSqlSecretOptions { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLSecretOptions. +func (in *PostgreSQLSecretOptions) DeepCopy() *PostgreSQLSecretOptions { if in == nil { return nil } - out := new(PostgreSqlSecretOptions) + out := new(PostgreSQLSecretOptions) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PostgreSqlServiceOptions) DeepCopyInto(out *PostgreSqlServiceOptions) { +func (in *PostgreSQLServiceOptions) DeepCopyInto(out *PostgreSQLServiceOptions) { *out = *in - if in.Port != nil { - in, out := &in.Port, &out.Port - *out = new(int) - **out = **in + if in.SQLServiceOptions != nil { + in, out := &in.SQLServiceOptions, &out.SQLServiceOptions + *out = new(SQLServiceOptions) + (*in).DeepCopyInto(*out) } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSqlServiceOptions. -func (in *PostgreSqlServiceOptions) DeepCopy() *PostgreSqlServiceOptions { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLServiceOptions. +func (in *PostgreSQLServiceOptions) DeepCopy() *PostgreSQLServiceOptions { if in == nil { return nil } - out := new(PostgreSqlServiceOptions) + out := new(PostgreSQLServiceOptions) in.DeepCopyInto(out) return out } @@ -635,6 +677,26 @@ func (in *RegistrySpec) DeepCopy() *RegistrySpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SQLServiceOptions) DeepCopyInto(out *SQLServiceOptions) { + *out = *in + if in.Port != nil { + in, out := &in.Port, &out.Port + *out = new(int) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLServiceOptions. +func (in *SQLServiceOptions) DeepCopy() *SQLServiceOptions { + if in == nil { + return nil + } + out := new(SQLServiceOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { *out = *in @@ -645,7 +707,7 @@ func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { } if in.Persistence != nil { in, out := &in.Persistence, &out.Persistence - *out = new(PersistenceOptions) + *out = new(PersistenceOptionsSpec) (*in).DeepCopyInto(*out) } in.PodTemplate.DeepCopyInto(&out.PodTemplate) @@ -809,7 +871,7 @@ func (in *SonataFlowClusterPlatform) DeepCopyInto(out *SonataFlowClusterPlatform *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -831,6 +893,26 @@ func (in *SonataFlowClusterPlatform) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SonataFlowClusterPlatformCapSpec) DeepCopyInto(out *SonataFlowClusterPlatformCapSpec) { + *out = *in + if in.Workflows != nil { + in, out := &in.Workflows, &out.Workflows + *out = make([]WorkFlowCapability, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SonataFlowClusterPlatformCapSpec. +func (in *SonataFlowClusterPlatformCapSpec) DeepCopy() *SonataFlowClusterPlatformCapSpec { + if in == nil { + return nil + } + out := new(SonataFlowClusterPlatformCapSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SonataFlowClusterPlatformList) DeepCopyInto(out *SonataFlowClusterPlatformList) { *out = *in @@ -888,6 +970,11 @@ func (in *SonataFlowClusterPlatformRefStatus) DeepCopy() *SonataFlowClusterPlatf func (in *SonataFlowClusterPlatformSpec) DeepCopyInto(out *SonataFlowClusterPlatformSpec) { *out = *in out.PlatformRef = in.PlatformRef + if in.Capabilities != nil { + in, out := &in.Capabilities, &out.Capabilities + *out = new(SonataFlowClusterPlatformCapSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SonataFlowClusterPlatformSpec. @@ -1032,6 +1119,11 @@ func (in *SonataFlowPlatformSpec) DeepCopyInto(out *SonataFlowPlatformSpec) { *out = new(ServicesPlatformSpec) (*in).DeepCopyInto(*out) } + if in.Persistence != nil { + in, out := &in.Persistence, &out.Persistence + *out = new(PlatformPersistenceOptionsSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SonataFlowPlatformSpec. @@ -1080,7 +1172,12 @@ func (in *SonataFlowSpec) DeepCopyInto(out *SonataFlowSpec) { in.PodTemplate.DeepCopyInto(&out.PodTemplate) if in.Persistence != nil { in, out := &in.Persistence, &out.Persistence - *out = new(PersistenceOptions) + *out = new(PersistenceOptionsSpec) + (*in).DeepCopyInto(*out) + } + if in.Sink != nil { + in, out := &in.Sink, &out.Sink + *out = new(duckv1.Destination) (*in).DeepCopyInto(*out) } } diff --git a/bddframework/go.mod b/bddframework/go.mod index eb43df226..fa6bd8461 100644 --- a/bddframework/go.mod +++ b/bddframework/go.mod @@ -47,7 +47,7 @@ require ( github.com/blendle/zapdriver v1.3.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cloudevents/sdk-go/v2 v2.4.1 // indirect + github.com/cloudevents/sdk-go/v2 v2.15.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/emirpasic/gods v1.12.0 // indirect diff --git a/bddframework/go.sum b/bddframework/go.sum index edfaddf67..38b0bee35 100644 --- a/bddframework/go.sum +++ b/bddframework/go.sum @@ -149,8 +149,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudevents/conformance v0.2.0/go.mod h1:rHKDwylBH89Rns6U3wL9ww8bg9/4GbwRCDNuyoC6bcc= github.com/cloudevents/sdk-go/observability/opencensus/v2 v2.4.1/go.mod h1:lhEpxMrIUkeu9rVRgoAbyqZ8GR8Hd3DUy+thHUxAHoI= -github.com/cloudevents/sdk-go/v2 v2.4.1 h1:rZJoz9QVLbWQmnvLPDFEmv17Czu+CfSPwMO6lhJ72xQ= github.com/cloudevents/sdk-go/v2 v2.4.1/go.mod h1:MZiMwmAh5tGj+fPFvtHv9hKurKqXtdB9haJYMJ/7GJY= +github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= diff --git a/bundle.osl/manifests/logic-operator-rhel8-builder-config_v1_configmap.yaml b/bundle.osl/manifests/logic-operator-rhel8-builder-config_v1_configmap.yaml index fad50726a..76405972e 100644 --- a/bundle.osl/manifests/logic-operator-rhel8-builder-config_v1_configmap.yaml +++ b/bundle.osl/manifests/logic-operator-rhel8-builder-config_v1_configmap.yaml @@ -5,7 +5,8 @@ data: Dockerfile: "FROM registry.redhat.io/openshift-serverless-1-tech-preview/logic-swf-builder-rhel8:latest AS builder\n\n# variables that can be overridden by the builder\n# To add a Quarkus extension to your application\nARG QUARKUS_EXTENSIONS\n# Args to pass to the Quarkus - CLI add extension command\nARG QUARKUS_ADD_EXTENSION_ARGS\n \n# Copy from build + CLI add extension command\nARG QUARKUS_ADD_EXTENSION_ARGS\n# Additional java/mvn arguments to pass to the builder\n + ARG MAVEN_ARGS_APPEND\n\n# Copy from build context to skeleton resources project\nCOPY --chmod=644 * ./resources/\n\nRUN /home/kogito/launch/build-app.sh ./resources\n \n#=============================\n# Runtime Run\n#=============================\nFROM registry.access.redhat.com/ubi9/openjdk-17:latest\n\nENV diff --git a/bundle/manifests/sonataflow-operator-builder-config_v1_configmap.yaml b/bundle/manifests/sonataflow-operator-builder-config_v1_configmap.yaml index ab8cabb58..2d787ee01 100644 --- a/bundle/manifests/sonataflow-operator-builder-config_v1_configmap.yaml +++ b/bundle/manifests/sonataflow-operator-builder-config_v1_configmap.yaml @@ -5,8 +5,9 @@ data: Dockerfile: "FROM quay.io/kiegroup/kogito-swf-builder-nightly:latest AS builder\n\n# variables that can be overridden by the builder\n# To add a Quarkus extension to your application\nARG QUARKUS_EXTENSIONS\n# Args to pass to the Quarkus CLI - add extension command\nARG QUARKUS_ADD_EXTENSION_ARGS\n\n# Copy from build context - to skeleton resources project\nCOPY --chown=1001 . ./resources\n\nRUN /home/kogito/launch/build-app.sh + add extension command\nARG QUARKUS_ADD_EXTENSION_ARGS\n# Additional java/mvn arguments + to pass to the builder\nARG MAVEN_ARGS_APPEND\n\n# Copy from build context to + skeleton resources project\nCOPY --chown=1001 . ./resources\n\nRUN /home/kogito/launch/build-app.sh ./resources\n \n#=============================\n# Runtime Run\n#=============================\nFROM registry.access.redhat.com/ubi9/openjdk-17:latest\n\nENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'\n \ \n# We make four distinct layers so if there are application changes the library diff --git a/bundle/manifests/sonataflow-operator.clusterserviceversion.yaml b/bundle/manifests/sonataflow-operator.clusterserviceversion.yaml index 65359c86e..7597683f5 100644 --- a/bundle/manifests/sonataflow-operator.clusterserviceversion.yaml +++ b/bundle/manifests/sonataflow-operator.clusterserviceversion.yaml @@ -127,7 +127,7 @@ metadata: operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 repository: https://github.com/apache/incubator-kie-kogito-serverless-operator support: Red Hat - name: sonataflow-operator.v2.0.0-snapshot + name: sonataflow-operator.v999.0.0-snapshot namespace: placeholder spec: apiservicedefinitions: {} @@ -185,7 +185,19 @@ spec: displayName: Sonata Flow Cluster Platform kind: SonataFlowClusterPlatform name: sonataflowclusterplatforms.sonataflow.org + resources: + - kind: SonataFlowPlatform + name: A SonataFlow Platform + version: sonataflow.org/v1alpha08 specDescriptors: + - description: Capabilities defines which platform capabilities should be applied + cluster-wide. If nil, defaults to `capabilities.workflows["services"]` + displayName: Capabilities + path: capabilities + - description: PlatformRef defines which existing SonataFlowPlatform's supporting + services should be used cluster-wide. + displayName: PlatformRef + path: platformRef - description: Name of the SonataFlowPlatform displayName: Platform_Name path: platformRef.name @@ -236,6 +248,13 @@ spec: no build required) displayName: DevMode path: devMode + - description: 'Services attributes for deploying supporting applications like + Data Index & Job Service. Only workflows without the `sonataflow.org/profile: + dev` annotation will be configured to use these service(s). Setting this + will override the use of any cluster-scoped services that might be defined + via `SonataFlowClusterPlatform`.' + displayName: Services + path: services - description: PodTemplate describes the deployment details of this platform service instance. displayName: podTemplate @@ -249,6 +268,10 @@ spec: or OpenShift) displayName: cluster path: cluster + - description: ClusterPlatformRef information related to the (optional) active + SonataFlowClusterPlatform + displayName: clusterPlatformRef + path: clusterPlatformRef - description: Info generic information related to the build displayName: info path: info @@ -289,6 +312,9 @@ spec: definition. For example, a collection of OpenAPI specification files. displayName: resources path: resources + - description: Sink describes the sinkBinding details of this SonataFlow instance. + displayName: sink + path: sink statusDescriptors: - description: Address is used as a part of Addressable interface (status.address.url) for knative @@ -377,6 +403,36 @@ spec: - patch - update - watch + - apiGroups: + - eventing.knative.dev + resources: + - triggers + - triggers/status + - triggers/finalizers + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + - apiGroups: + - sources.knative.dev + resources: + - sinkbindings + - sinkbindings/status + - sinkbindings/finalizers + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch - apiGroups: - coordination.k8s.io resources: @@ -793,4 +849,4 @@ spec: minKubeVersion: 1.23.0 provider: name: Red Hat - version: 2.0.0-snapshot + version: 999.0.0-snapshot diff --git a/bundle/manifests/sonataflow.org_sonataflowclusterplatforms.yaml b/bundle/manifests/sonataflow.org_sonataflowclusterplatforms.yaml index faa83264e..a5071d1e3 100644 --- a/bundle/manifests/sonataflow.org_sonataflowclusterplatforms.yaml +++ b/bundle/manifests/sonataflow.org_sonataflowclusterplatforms.yaml @@ -49,8 +49,21 @@ spec: description: SonataFlowClusterPlatformSpec defines the desired state of SonataFlowClusterPlatform properties: + capabilities: + description: Capabilities defines which platform capabilities should + be applied cluster-wide. If nil, defaults to `capabilities.workflows["services"]` + properties: + workflows: + description: Workflows defines which platform capabilities should + be applied to workflows cluster-wide. + items: + enum: + - services + type: string + type: array + type: object platformRef: - description: SonataFlowPlatformRef defines which existing SonataFlowPlatform's + description: PlatformRef defines which existing SonataFlowPlatform's supporting services should be used cluster-wide. properties: name: diff --git a/bundle/manifests/sonataflow.org_sonataflowplatforms.yaml b/bundle/manifests/sonataflow.org_sonataflowplatforms.yaml index 5910e8b78..1752ac7c2 100644 --- a/bundle/manifests/sonataflow.org_sonataflowplatforms.yaml +++ b/bundle/manifests/sonataflow.org_sonataflowplatforms.yaml @@ -420,6 +420,63 @@ spec: of the operator's default. type: string type: object + persistence: + description: Persistence defines the platform persistence configuration. + When this field is set, the configuration is used as the persistence + for platform services and sonataflow instances that don't provide + one of their own. + maxProperties: 1 + properties: + postgresql: + description: Connect configured services to a postgresql database. + maxProperties: 2 + minProperties: 2 + properties: + jdbcUrl: + description: PostgreSql JDBC URL. Mutually exclusive to serviceRef. + e.g. "jdbc:postgresql://host:port/database?currentSchema=data-index-service" + type: string + secretRef: + description: Secret reference to the database user credentials + properties: + name: + description: Name of the postgresql credentials secret. + type: string + passwordKey: + description: Defaults to POSTGRESQL_PASSWORD + type: string + userKey: + description: Defaults to POSTGRESQL_USER + type: string + required: + - name + type: object + serviceRef: + description: Service reference to postgresql datasource. Mutually + exclusive to jdbcUrl. + properties: + databaseName: + description: Name of postgresql database to be used. Defaults + to "sonataflow" + type: string + name: + description: Name of the postgresql k8s service. + type: string + namespace: + description: Namespace of the postgresql k8s service. + Defaults to the SonataFlowPlatform's local namespace. + type: string + port: + description: Port to use when connecting to the postgresql + k8s service. Defaults to 5432. + type: integer + required: + - name + type: object + required: + - secretRef + type: object + type: object services: description: 'Services attributes for deploying supporting applications like Data Index & Job Service. Only workflows without the `sonataflow.org/profile: diff --git a/bundle/manifests/sonataflow.org_sonataflows.yaml b/bundle/manifests/sonataflow.org_sonataflows.yaml index 13d4cf538..6836cd493 100644 --- a/bundle/manifests/sonataflow.org_sonataflows.yaml +++ b/bundle/manifests/sonataflow.org_sonataflows.yaml @@ -2103,8 +2103,8 @@ spec: - states type: object persistence: - description: Persists service to a datasource of choice. Ephemeral - by default. + description: Persistence defines the database persistence configuration + for the workflow maxProperties: 1 properties: postgresql: @@ -9346,6 +9346,53 @@ spec: type: object type: array type: object + sink: + description: Sink describes the sinkBinding details of this SonataFlow + instance. + properties: + CACerts: + description: CACerts are Certification Authority (CA) certificates + in PEM format according to https://www.rfc-editor.org/rfc/rfc7468. + If set, these CAs are appended to the set of CAs provided by + the Addressable target, if any. + type: string + ref: + description: Ref points to an Addressable. + properties: + address: + description: Address points to a specific Address Name. + type: string + apiVersion: + description: API version of the referent. + type: string + group: + description: 'Group of the API, without the version of the + group. This can be used as an alternative to the APIVersion, + and then resolved using ResolveGroup. Note: This API is + EXPERIMENTAL and might break anytime. For more details: + https://github.com/knative/eventing/issues/5086' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + This is optional field, it gets defaulted to the object + holding it if left out.' + type: string + required: + - kind + - name + type: object + uri: + description: URI can be an absolute URL(non-empty scheme and non-empty + host) pointing to the target or a relative URI. Relative URIs + will be resolved using the base URI retrieved from Ref. + type: string + type: object required: - flow type: object diff --git a/config/crd/bases/sonataflow.org_sonataflowclusterplatforms.yaml b/config/crd/bases/sonataflow.org_sonataflowclusterplatforms.yaml index a740ddea9..152750050 100644 --- a/config/crd/bases/sonataflow.org_sonataflowclusterplatforms.yaml +++ b/config/crd/bases/sonataflow.org_sonataflowclusterplatforms.yaml @@ -50,8 +50,21 @@ spec: description: SonataFlowClusterPlatformSpec defines the desired state of SonataFlowClusterPlatform properties: + capabilities: + description: Capabilities defines which platform capabilities should + be applied cluster-wide. If nil, defaults to `capabilities.workflows["services"]` + properties: + workflows: + description: Workflows defines which platform capabilities should + be applied to workflows cluster-wide. + items: + enum: + - services + type: string + type: array + type: object platformRef: - description: SonataFlowPlatformRef defines which existing SonataFlowPlatform's + description: PlatformRef defines which existing SonataFlowPlatform's supporting services should be used cluster-wide. properties: name: diff --git a/config/crd/bases/sonataflow.org_sonataflowplatforms.yaml b/config/crd/bases/sonataflow.org_sonataflowplatforms.yaml index 9db303538..a1cb0213b 100644 --- a/config/crd/bases/sonataflow.org_sonataflowplatforms.yaml +++ b/config/crd/bases/sonataflow.org_sonataflowplatforms.yaml @@ -421,6 +421,63 @@ spec: of the operator's default. type: string type: object + persistence: + description: Persistence defines the platform persistence configuration. + When this field is set, the configuration is used as the persistence + for platform services and sonataflow instances that don't provide + one of their own. + maxProperties: 1 + properties: + postgresql: + description: Connect configured services to a postgresql database. + maxProperties: 2 + minProperties: 2 + properties: + jdbcUrl: + description: PostgreSql JDBC URL. Mutually exclusive to serviceRef. + e.g. "jdbc:postgresql://host:port/database?currentSchema=data-index-service" + type: string + secretRef: + description: Secret reference to the database user credentials + properties: + name: + description: Name of the postgresql credentials secret. + type: string + passwordKey: + description: Defaults to POSTGRESQL_PASSWORD + type: string + userKey: + description: Defaults to POSTGRESQL_USER + type: string + required: + - name + type: object + serviceRef: + description: Service reference to postgresql datasource. Mutually + exclusive to jdbcUrl. + properties: + databaseName: + description: Name of postgresql database to be used. Defaults + to "sonataflow" + type: string + name: + description: Name of the postgresql k8s service. + type: string + namespace: + description: Namespace of the postgresql k8s service. + Defaults to the SonataFlowPlatform's local namespace. + type: string + port: + description: Port to use when connecting to the postgresql + k8s service. Defaults to 5432. + type: integer + required: + - name + type: object + required: + - secretRef + type: object + type: object services: description: 'Services attributes for deploying supporting applications like Data Index & Job Service. Only workflows without the `sonataflow.org/profile: diff --git a/config/crd/bases/sonataflow.org_sonataflows.yaml b/config/crd/bases/sonataflow.org_sonataflows.yaml index 4ec54e7f8..a9df118fd 100644 --- a/config/crd/bases/sonataflow.org_sonataflows.yaml +++ b/config/crd/bases/sonataflow.org_sonataflows.yaml @@ -2104,8 +2104,8 @@ spec: - states type: object persistence: - description: Persists service to a datasource of choice. Ephemeral - by default. + description: Persistence defines the database persistence configuration + for the workflow maxProperties: 1 properties: postgresql: @@ -9347,6 +9347,53 @@ spec: type: object type: array type: object + sink: + description: Sink describes the sinkBinding details of this SonataFlow + instance. + properties: + CACerts: + description: CACerts are Certification Authority (CA) certificates + in PEM format according to https://www.rfc-editor.org/rfc/rfc7468. + If set, these CAs are appended to the set of CAs provided by + the Addressable target, if any. + type: string + ref: + description: Ref points to an Addressable. + properties: + address: + description: Address points to a specific Address Name. + type: string + apiVersion: + description: API version of the referent. + type: string + group: + description: 'Group of the API, without the version of the + group. This can be used as an alternative to the APIVersion, + and then resolved using ResolveGroup. Note: This API is + EXPERIMENTAL and might break anytime. For more details: + https://github.com/knative/eventing/issues/5086' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + This is optional field, it gets defaulted to the object + holding it if left out.' + type: string + required: + - kind + - name + type: object + uri: + description: URI can be an absolute URL(non-empty scheme and non-empty + host) pointing to the target or a relative URI. Relative URIs + will be resolved using the base URI retrieved from Ref. + type: string + type: object required: - flow type: object diff --git a/config/manager/osl/sonataflow_builder_dockerfile.yaml b/config/manager/osl/sonataflow_builder_dockerfile.yaml index c0aade94d..d09c4c40c 100644 --- a/config/manager/osl/sonataflow_builder_dockerfile.yaml +++ b/config/manager/osl/sonataflow_builder_dockerfile.yaml @@ -5,7 +5,9 @@ FROM registry.redhat.io/openshift-serverless-1-tech-preview/logic-swf-builder-rh ARG QUARKUS_EXTENSIONS # Args to pass to the Quarkus CLI add extension command ARG QUARKUS_ADD_EXTENSION_ARGS - +# Additional java/mvn arguments to pass to the builder +ARG MAVEN_ARGS_APPEND + # Copy from build context to skeleton resources project COPY --chown=1001 . ./resources diff --git a/config/manager/sonataflow_builder_dockerfile.yaml b/config/manager/sonataflow_builder_dockerfile.yaml index 20c34c1d2..d95cbf0fd 100644 --- a/config/manager/sonataflow_builder_dockerfile.yaml +++ b/config/manager/sonataflow_builder_dockerfile.yaml @@ -5,6 +5,8 @@ FROM quay.io/kiegroup/kogito-swf-builder-nightly:latest AS builder ARG QUARKUS_EXTENSIONS # Args to pass to the Quarkus CLI add extension command ARG QUARKUS_ADD_EXTENSION_ARGS +# Additional java/mvn arguments to pass to the builder +ARG MAVEN_ARGS_APPEND # Copy from build context to skeleton resources project COPY --chown=1001 . ./resources diff --git a/config/manifests/bases/sonataflow-operator.clusterserviceversion.yaml b/config/manifests/bases/sonataflow-operator.clusterserviceversion.yaml index fcd5460ea..089ae24b4 100644 --- a/config/manifests/bases/sonataflow-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/sonataflow-operator.clusterserviceversion.yaml @@ -69,7 +69,19 @@ spec: displayName: Sonata Flow Cluster Platform kind: SonataFlowClusterPlatform name: sonataflowclusterplatforms.sonataflow.org + resources: + - kind: SonataFlowPlatform + name: A SonataFlow Platform + version: sonataflow.org/v1alpha08 specDescriptors: + - description: Capabilities defines which platform capabilities should be applied + cluster-wide. If nil, defaults to `capabilities.workflows["services"]` + displayName: Capabilities + path: capabilities + - description: PlatformRef defines which existing SonataFlowPlatform's supporting + services should be used cluster-wide. + displayName: PlatformRef + path: platformRef - description: Name of the SonataFlowPlatform displayName: Platform_Name path: platformRef.name @@ -120,6 +132,13 @@ spec: no build required) displayName: DevMode path: devMode + - description: 'Services attributes for deploying supporting applications like + Data Index & Job Service. Only workflows without the `sonataflow.org/profile: + dev` annotation will be configured to use these service(s). Setting this + will override the use of any cluster-scoped services that might be defined + via `SonataFlowClusterPlatform`.' + displayName: Services + path: services - description: PodTemplate describes the deployment details of this platform service instance. displayName: podTemplate @@ -133,6 +152,10 @@ spec: or OpenShift) displayName: cluster path: cluster + - description: ClusterPlatformRef information related to the (optional) active + SonataFlowClusterPlatform + displayName: clusterPlatformRef + path: clusterPlatformRef - description: Info generic information related to the build displayName: info path: info @@ -173,6 +196,9 @@ spec: definition. For example, a collection of OpenAPI specification files. displayName: resources path: resources + - description: Sink describes the sinkBinding details of this SonataFlow instance. + displayName: sink + path: sink statusDescriptors: - description: Address is used as a part of Addressable interface (status.address.url) for knative diff --git a/config/rbac/builder_role.yaml b/config/rbac/builder_role.yaml index 9d00da3f5..70b2ab54e 100644 --- a/config/rbac/builder_role.yaml +++ b/config/rbac/builder_role.yaml @@ -57,6 +57,36 @@ rules: resources: - roles - rolebindings + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - eventing.knative.dev + resources: + - triggers + - triggers/status + - triggers/finalizers + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - sources.knative.dev + resources: + - sinkbindings + - sinkbindings/status + - sinkbindings/finalizers verbs: - create - delete diff --git a/controllers/builder/containerbuilder.go b/controllers/builder/containerbuilder.go index 15918a86f..a3fc344a2 100644 --- a/controllers/builder/containerbuilder.go +++ b/controllers/builder/containerbuilder.go @@ -138,7 +138,7 @@ func (c *containerBuilderManager) scheduleNewKanikoBuildWithContainerFile(build workflowDefinition: workflowDef, workflow: workflow, dockerfile: platform.GetCustomizedDockerfile(c.commonConfig.Data[c.commonConfig.Data[configKeyDefaultBuilderResourceName]], *c.platform), - imageTag: workflowdef.GetWorkflowAppImageNameTag(workflow), + imageTag: buildNamespacedImageTag(workflow), } if c.platform.Spec.Build.Config.Timeout == nil { @@ -199,3 +199,10 @@ func newBuild(buildInput kanikoBuildInput, platform api.PlatformContainerBuild, WithBuildArgs(buildInput.task.BuildArgs). WithEnvs(buildInput.task.Envs).Schedule() } + +// buildNamespacedImageTag For the kaniko build we prepend the namespace to the calculated image name/tag to avoid potential +// collisions if the same workflows is deployed in a different namespace. In OpenShift this last is not needed since the +// ImageStreams are already namespaced. +func buildNamespacedImageTag(workflow *operatorapi.SonataFlow) string { + return workflow.Namespace + "/" + workflowdef.GetWorkflowAppImageNameTag(workflow) +} diff --git a/controllers/clusterplatform/clusterplatform.go b/controllers/clusterplatform/clusterplatform.go index 6c1ffe45e..e9a8c86cd 100644 --- a/controllers/clusterplatform/clusterplatform.go +++ b/controllers/clusterplatform/clusterplatform.go @@ -30,6 +30,10 @@ import ( ctrl "sigs.k8s.io/controller-runtime/pkg/client" ) +const ( + PlatformServices operatorapi.WorkFlowCapability = "services" +) + // GetActiveClusterPlatform returns the currently installed active cluster platform. func GetActiveClusterPlatform(ctx context.Context, c ctrl.Client) (*operatorapi.SonataFlowClusterPlatform, error) { return getClusterPlatform(ctx, c, true) diff --git a/controllers/clusterplatform/defaults.go b/controllers/clusterplatform/defaults.go new file mode 100644 index 000000000..17657fc62 --- /dev/null +++ b/controllers/clusterplatform/defaults.go @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 clusterplatform + +import ( + "context" + + operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" + "github.com/apache/incubator-kie-kogito-serverless-operator/container-builder/client" + "github.com/apache/incubator-kie-kogito-serverless-operator/log" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime/pkg/client" +) + +func configureDefaults(ctx context.Context, c client.Client, cp *operatorapi.SonataFlowClusterPlatform, verbose bool) error { + if cp.Spec.Capabilities == nil { + cp.Spec.Capabilities = &operatorapi.SonataFlowClusterPlatformCapSpec{ + Workflows: []operatorapi.WorkFlowCapability{PlatformServices}, + } + } + + return updateClusterPlatform(ctx, c, cp) +} + +func updateClusterPlatform(ctx context.Context, c client.Client, cp *operatorapi.SonataFlowClusterPlatform) error { + sfcPlatform := operatorapi.SonataFlowClusterPlatform{} + if err := c.Get(ctx, ctrl.ObjectKey{Namespace: cp.Namespace, Name: cp.Name}, &sfcPlatform); err != nil { + klog.V(log.E).ErrorS(err, "Error reading the Cluster Platform") + return err + } + + sfcPlatform.Spec = cp.Spec + if err := c.Update(ctx, &sfcPlatform); err != nil { + klog.V(log.E).ErrorS(err, "Error updating the Cluster Platform") + } + + return nil +} diff --git a/controllers/clusterplatform/initialize.go b/controllers/clusterplatform/initialize.go index 15a9b9f82..ac595e366 100644 --- a/controllers/clusterplatform/initialize.go +++ b/controllers/clusterplatform/initialize.go @@ -61,6 +61,10 @@ func (action *initializeAction) Handle(ctx context.Context, cPlatform *operatora } return nil } + + if err = configureDefaults(ctx, action.client, cPlatform, true); err != nil { + return err + } cPlatform.Status.Version = metadata.SpecVersion platformRef := cPlatform.Spec.PlatformRef diff --git a/controllers/platform/k8s.go b/controllers/platform/k8s.go index 5a853f6a7..f2e5313e7 100644 --- a/controllers/platform/k8s.go +++ b/controllers/platform/k8s.go @@ -25,8 +25,8 @@ import ( operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" "github.com/apache/incubator-kie-kogito-serverless-operator/container-builder/client" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/platform/services" - "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/constants" + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/variables" "github.com/apache/incubator-kie-kogito-serverless-operator/log" "github.com/apache/incubator-kie-kogito-serverless-operator/utils" kubeutil "github.com/apache/incubator-kie-kogito-serverless-operator/utils/kubernetes" @@ -61,19 +61,17 @@ func (action *serviceAction) Handle(ctx context.Context, platform *operatorapi.S return nil, err } - if platform.Spec.Services != nil { - psDI := services.NewDataIndexHandler(platform) - if psDI.IsServiceSetInSpec() { - if err := createOrUpdateServiceComponents(ctx, action.client, platform, psDI); err != nil { - return nil, err - } + psDI := services.NewDataIndexHandler(platform) + if psDI.IsServiceSetInSpec() { + if err := createOrUpdateServiceComponents(ctx, action.client, platform, psDI); err != nil { + return nil, err } + } - psJS := services.NewJobServiceHandler(platform) - if psJS.IsServiceSetInSpec() { - if err := createOrUpdateServiceComponents(ctx, action.client, platform, psJS); err != nil { - return nil, err - } + psJS := services.NewJobServiceHandler(platform) + if psJS.IsServiceSetInSpec() { + if err := createOrUpdateServiceComponents(ctx, action.client, platform, psJS); err != nil { + return nil, err } } @@ -94,8 +92,8 @@ func createOrUpdateDeployment(ctx context.Context, client client.Client, platfor readyProbe := &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ - Path: common.QuarkusHealthPathReady, - Port: common.DefaultHTTPWorkflowPortIntStr, + Path: constants.QuarkusHealthPathReady, + Port: variables.DefaultHTTPWorkflowPortIntStr, Scheme: corev1.URISchemeHTTP, }, }, @@ -106,7 +104,7 @@ func createOrUpdateDeployment(ctx context.Context, client client.Client, platfor FailureThreshold: int32(4), } liveProbe := readyProbe.DeepCopy() - liveProbe.ProbeHandler.HTTPGet.Path = common.QuarkusHealthPathLive + liveProbe.ProbeHandler.HTTPGet.Path = constants.QuarkusHealthPathLive imageTag := psh.GetServiceImageName(constants.PersistenceTypeEphemeral) dataDeployContainer := &corev1.Container{ Image: imageTag, @@ -204,7 +202,7 @@ func createOrUpdateService(ctx context.Context, client client.Client, platform * Name: utils.HttpScheme, Protocol: corev1.ProtocolTCP, Port: 80, - TargetPort: common.DefaultHTTPWorkflowPortIntStr, + TargetPort: variables.DefaultHTTPWorkflowPortIntStr, }, }, Selector: selectorLbl, diff --git a/controllers/platform/services/properties.go b/controllers/platform/services/properties.go index c541f5b04..02fc65002 100644 --- a/controllers/platform/services/properties.go +++ b/controllers/platform/services/properties.go @@ -24,14 +24,13 @@ import ( "net/url" "strings" + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/workflowdef" - "github.com/apache/incubator-kie-kogito-serverless-operator/log" "github.com/apache/incubator-kie-kogito-serverless-operator/utils" "k8s.io/klog/v2" operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" - "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/constants" "github.com/magiconair/properties" @@ -97,7 +96,7 @@ func (a *serviceAppPropertyHandler) Build() string { return props.String() } -func generateReactiveURL(postgresSpec *operatorapi.PersistencePostgreSql, schema string, namespace string, dbName string, port int) (string, error) { +func generateReactiveURL(postgresSpec *operatorapi.PersistencePostgreSQL, schema string, namespace string, dbName string, port int) (string, error) { if len(postgresSpec.JdbcUrl) > 0 { s := strings.TrimLeft(postgresSpec.JdbcUrl, "jdbc:") u, err := url.Parse(s) @@ -165,6 +164,7 @@ func GenerateDataIndexWorkflowProperties(workflow *operatorapi.SonataFlow, platf di := NewDataIndexHandler(platform) if workflow != nil && !profiles.IsDevProfile(workflow) && di.IsServiceEnabled() { props.Set(constants.KogitoProcessDefinitionsEventsEnabled, "true") + props.Set(constants.KogitoProcessDefinitionsEventsErrorsEnabled, "true") props.Set(constants.KogitoProcessInstancesEventsEnabled, "true") props.Set(constants.KogitoDataIndexHealthCheckEnabled, "true") di := NewDataIndexHandler(platform) diff --git a/controllers/platform/services/properties_services_test.go b/controllers/platform/services/properties_services_test.go index 9cdae2e9f..46ea31a44 100644 --- a/controllers/platform/services/properties_services_test.go +++ b/controllers/platform/services/properties_services_test.go @@ -238,12 +238,12 @@ func setJobServiceJDBC(jdbc string) plfmOptionFn { p.Spec.Services.JobService = &operatorapi.ServiceSpec{} } if p.Spec.Services.JobService.Persistence == nil { - p.Spec.Services.JobService.Persistence = &operatorapi.PersistenceOptions{} + p.Spec.Services.JobService.Persistence = &operatorapi.PersistenceOptionsSpec{} } - if p.Spec.Services.JobService.Persistence.PostgreSql == nil { - p.Spec.Services.JobService.Persistence.PostgreSql = &operatorapi.PersistencePostgreSql{} + if p.Spec.Services.JobService.Persistence.PostgreSQL == nil { + p.Spec.Services.JobService.Persistence.PostgreSQL = &operatorapi.PersistencePostgreSQL{} } - p.Spec.Services.JobService.Persistence.PostgreSql.JdbcUrl = jdbc + p.Spec.Services.JobService.Persistence.PostgreSQL.JdbcUrl = jdbc } } @@ -256,11 +256,11 @@ func setDataIndexJDBC(jdbc string) plfmOptionFn { p.Spec.Services.DataIndex = &operatorapi.ServiceSpec{} } if p.Spec.Services.DataIndex.Persistence == nil { - p.Spec.Services.DataIndex.Persistence = &operatorapi.PersistenceOptions{} + p.Spec.Services.DataIndex.Persistence = &operatorapi.PersistenceOptionsSpec{} } - if p.Spec.Services.DataIndex.Persistence.PostgreSql == nil { - p.Spec.Services.DataIndex.Persistence.PostgreSql = &operatorapi.PersistencePostgreSql{} + if p.Spec.Services.DataIndex.Persistence.PostgreSQL == nil { + p.Spec.Services.DataIndex.Persistence.PostgreSQL = &operatorapi.PersistencePostgreSQL{} } - p.Spec.Services.DataIndex.Persistence.PostgreSql.JdbcUrl = jdbc + p.Spec.Services.DataIndex.Persistence.PostgreSQL.JdbcUrl = jdbc } } diff --git a/controllers/platform/services/properties_test.go b/controllers/platform/services/properties_test.go index 7f37b1923..828f11e76 100644 --- a/controllers/platform/services/properties_test.go +++ b/controllers/platform/services/properties_test.go @@ -34,7 +34,7 @@ const ( var _ = Describe("Platform properties", func() { var _ = Context("PostgreSQL properties", func() { - var _ = DescribeTable("Generate a reactive URL", func(spec *operatorapi.PersistencePostgreSql, expectedReactiveURL string, expectedError bool) { + var _ = DescribeTable("Generate a reactive URL", func(spec *operatorapi.PersistencePostgreSQL, expectedReactiveURL string, expectedError bool) { res, err := generateReactiveURL(spec, defaultSchema, "default", constants.DefaultDatabaseName, constants.DefaultPostgreSQLPort) if expectedError { Expect(err).NotTo(BeNil()) @@ -72,10 +72,10 @@ var _ = Describe("Platform properties", func() { }) }) -type optionFn func(*operatorapi.PersistencePostgreSql) +type optionFn func(*operatorapi.PersistencePostgreSQL) -func generatePostgreSQLOptions(options ...optionFn) *operatorapi.PersistencePostgreSql { - p := &operatorapi.PersistencePostgreSql{} +func generatePostgreSQLOptions(options ...optionFn) *operatorapi.PersistencePostgreSQL { + p := &operatorapi.PersistencePostgreSQL{} for _, f := range options { f(p) } @@ -83,51 +83,63 @@ func generatePostgreSQLOptions(options ...optionFn) *operatorapi.PersistencePost } func setJDBC(url string) optionFn { - return func(o *operatorapi.PersistencePostgreSql) { + return func(o *operatorapi.PersistencePostgreSQL) { o.JdbcUrl = url } } func setServiceName(svcName string) optionFn { - return func(o *operatorapi.PersistencePostgreSql) { + return func(o *operatorapi.PersistencePostgreSQL) { if o.ServiceRef == nil { - o.ServiceRef = &operatorapi.PostgreSqlServiceOptions{} + o.ServiceRef = &operatorapi.PostgreSQLServiceOptions{} + } + if o.ServiceRef.SQLServiceOptions == nil { + o.ServiceRef.SQLServiceOptions = &operatorapi.SQLServiceOptions{} } o.ServiceRef.Name = svcName } } func setDatabaseSchemaName(dbSchemaName string) optionFn { - return func(o *operatorapi.PersistencePostgreSql) { + return func(o *operatorapi.PersistencePostgreSQL) { if o.ServiceRef == nil { - o.ServiceRef = &operatorapi.PostgreSqlServiceOptions{} + o.ServiceRef = &operatorapi.PostgreSQLServiceOptions{} } o.ServiceRef.DatabaseSchema = dbSchemaName } } func setDatabaseName(dbName string) optionFn { - return func(o *operatorapi.PersistencePostgreSql) { + return func(o *operatorapi.PersistencePostgreSQL) { if o.ServiceRef == nil { - o.ServiceRef = &operatorapi.PostgreSqlServiceOptions{} + o.ServiceRef = &operatorapi.PostgreSQLServiceOptions{} + } + if o.ServiceRef.SQLServiceOptions == nil { + o.ServiceRef.SQLServiceOptions = &operatorapi.SQLServiceOptions{} } o.ServiceRef.DatabaseName = dbName } } func setServiceNamespace(svcNamespace string) optionFn { - return func(o *operatorapi.PersistencePostgreSql) { + return func(o *operatorapi.PersistencePostgreSQL) { if o.ServiceRef == nil { - o.ServiceRef = &operatorapi.PostgreSqlServiceOptions{} + o.ServiceRef = &operatorapi.PostgreSQLServiceOptions{} + } + if o.ServiceRef.SQLServiceOptions == nil { + o.ServiceRef.SQLServiceOptions = &operatorapi.SQLServiceOptions{} } o.ServiceRef.Namespace = svcNamespace } } func setDBPort(portNumber int) optionFn { - return func(o *operatorapi.PersistencePostgreSql) { + return func(o *operatorapi.PersistencePostgreSQL) { if o.ServiceRef == nil { - o.ServiceRef = &operatorapi.PostgreSqlServiceOptions{} + o.ServiceRef = &operatorapi.PostgreSQLServiceOptions{} + } + if o.ServiceRef.SQLServiceOptions == nil { + o.ServiceRef.SQLServiceOptions = &operatorapi.SQLServiceOptions{} } o.ServiceRef.Port = &portNumber } diff --git a/controllers/platform/services/services.go b/controllers/platform/services/services.go index 05aedbb60..4b442c6a1 100644 --- a/controllers/platform/services/services.go +++ b/controllers/platform/services/services.go @@ -159,7 +159,7 @@ func (d DataIndexHandler) GetServiceBaseUrl() string { } func (d DataIndexHandler) GetLocalServiceBaseUrl() string { - return generateServiceURL(constants.KogitoServiceURLProtocol, d.platform.Namespace, d.GetServiceName()) + return GenerateServiceURL(constants.KogitoServiceURLProtocol, d.platform.Namespace, d.GetServiceName()) } func (d DataIndexHandler) GetEnvironmentVariables() []corev1.EnvVar { @@ -198,11 +198,19 @@ func (d DataIndexHandler) MergePodSpec(podSpec corev1.PodSpec) (corev1.PodSpec, return *c, err } +// hasPostgreSQLConfigured returns true when either the SonataFlow Platform PostgreSQL CR's structure or the one in the Data Index service specification is not nil +func (d DataIndexHandler) hasPostgreSQLConfigured() bool { + return d.IsServiceSetInSpec() && + ((d.platform.Spec.Services.DataIndex.Persistence != nil && d.platform.Spec.Services.DataIndex.Persistence.PostgreSQL != nil) || + (d.platform.Spec.Persistence != nil && d.platform.Spec.Persistence.PostgreSQL != nil)) +} + func (d DataIndexHandler) ConfigurePersistence(containerSpec *corev1.Container) *corev1.Container { - if d.platform.Spec.Services.DataIndex.Persistence != nil && d.platform.Spec.Services.DataIndex.Persistence.PostgreSql != nil { + if d.hasPostgreSQLConfigured() { + p := persistence.RetrieveConfiguration(d.platform.Spec.Services.DataIndex.Persistence, d.platform.Spec.Persistence, d.GetServiceName()) c := containerSpec.DeepCopy() c.Image = d.GetServiceImageName(constants.PersistenceTypePostgreSQL) - c.Env = append(c.Env, persistence.ConfigurePostgreSqlEnv(d.platform.Spec.Services.DataIndex.Persistence.PostgreSql, d.GetServiceName(), d.platform.Namespace)...) + c.Env = append(c.Env, persistence.ConfigurePostgreSQLEnv(p.PostgreSQL, d.GetServiceName(), d.platform.Namespace)...) // specific to DataIndex c.Env = append(c.Env, corev1.EnvVar{Name: quarkusHibernateORMDatabaseGeneration, Value: "update"}, corev1.EnvVar{Name: quarkusFlywayMigrateAtStart, Value: "true"}) return c @@ -319,7 +327,7 @@ func (j JobServiceHandler) GetServiceBaseUrl() string { } func (j JobServiceHandler) GetLocalServiceBaseUrl() string { - return generateServiceURL(constants.JobServiceURLProtocol, j.platform.Namespace, j.GetServiceName()) + return GenerateServiceURL(constants.JobServiceURLProtocol, j.platform.Namespace, j.GetServiceName()) } func (j JobServiceHandler) GetEnvironmentVariables() []corev1.EnvVar { @@ -358,14 +366,22 @@ func (j JobServiceHandler) MergeContainerSpec(containerSpec *corev1.Container) ( return c, err } +// hasPostgreSQLConfigured returns true when either the SonataFlow Platform PostgreSQL CR's structure or the one in the Job service specification is not nil +func (j JobServiceHandler) hasPostgreSQLConfigured() bool { + return j.IsServiceSetInSpec() && + ((j.platform.Spec.Services.JobService.Persistence != nil && j.platform.Spec.Services.JobService.Persistence.PostgreSQL != nil) || + (j.platform.Spec.Persistence != nil && j.platform.Spec.Persistence.PostgreSQL != nil)) +} + func (j JobServiceHandler) ConfigurePersistence(containerSpec *corev1.Container) *corev1.Container { - if j.platform.Spec.Services.JobService.Persistence != nil && j.platform.Spec.Services.JobService.Persistence.PostgreSql != nil { + if j.hasPostgreSQLConfigured() { c := containerSpec.DeepCopy() c.Image = j.GetServiceImageName(constants.PersistenceTypePostgreSQL) - c.Env = append(c.Env, persistence.ConfigurePostgreSqlEnv(j.platform.Spec.Services.JobService.Persistence.PostgreSql, j.GetServiceName(), j.platform.Namespace)...) + p := persistence.RetrieveConfiguration(j.platform.Spec.Services.JobService.Persistence, j.platform.Spec.Persistence, j.GetServiceName()) + c.Env = append(c.Env, persistence.ConfigurePostgreSQLEnv(p.PostgreSQL, j.GetServiceName(), j.platform.Namespace)...) // Specific to Job Service - c.Env = append(c.Env, corev1.EnvVar{Name: quarkusFlywayMigrateAtStart, Value: "true"}) + c.Env = append(c.Env, corev1.EnvVar{Name: "QUARKUS_FLYWAY_MIGRATE_AT_START", Value: "true"}) return c } return containerSpec @@ -379,17 +395,18 @@ func (j JobServiceHandler) MergePodSpec(podSpec corev1.PodSpec) (corev1.PodSpec, func (j JobServiceHandler) GenerateServiceProperties() (*properties.Properties, error) { props := properties.NewProperties() - props.Set(constants.KogitoServiceURLProperty, generateServiceURL(constants.KogitoServiceURLProtocol, j.platform.Namespace, j.GetServiceName())) + props.Set(constants.KogitoServiceURLProperty, GenerateServiceURL(constants.KogitoServiceURLProtocol, j.platform.Namespace, j.GetServiceName())) props.Set(constants.JobServiceKafkaSmallRyeHealthProperty, "false") // add data source reactive URL - jspec := j.platform.Spec.Services.JobService - if j.IsServiceSetInSpec() && jspec.Persistence != nil && jspec.Persistence.PostgreSql != nil { - dataSourceReactiveURL, err := generateReactiveURL(jspec.Persistence.PostgreSql, j.GetServiceName(), j.platform.Namespace, constants.DefaultDatabaseName, constants.DefaultPostgreSQLPort) + if j.hasPostgreSQLConfigured() { + p := persistence.RetrieveConfiguration(j.platform.Spec.Services.JobService.Persistence, j.platform.Spec.Persistence, j.GetServiceName()) + dataSourceReactiveURL, err := generateReactiveURL(p.PostgreSQL, j.GetServiceName(), j.platform.Namespace, constants.DefaultDatabaseName, constants.DefaultPostgreSQLPort) if err != nil { return nil, err } props.Set(constants.JobServiceDataSourceReactiveURL, dataSourceReactiveURL) } + if isDataIndexEnabled(j.platform) { di := NewDataIndexHandler(j.platform) props.Set(constants.JobServiceStatusChangeEvents, "true") @@ -430,7 +447,7 @@ func isServicesSet(platform *operatorapi.SonataFlowPlatform) bool { return platform != nil && platform.Spec.Services != nil } -func generateServiceURL(protocol string, namespace string, name string) string { +func GenerateServiceURL(protocol string, namespace string, name string) string { var serviceUrl string if len(namespace) > 0 { serviceUrl = fmt.Sprintf("%s://%s.%s", protocol, name, namespace) diff --git a/controllers/profiles/common/constants/objects.go b/controllers/profiles/common/constants/objects.go new file mode 100644 index 000000000..caddd1a10 --- /dev/null +++ b/controllers/profiles/common/constants/objects.go @@ -0,0 +1,24 @@ +// Copyright 2024 Apache Software Foundation (ASF) +// +// 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 constants + +const ( + QuarkusHealthPathReady = "/q/health/ready" + QuarkusHealthPathLive = "/q/health/live" + + // Quarkus Health Check Probe configuration. + // See: https://quarkus.io/guides/smallrye-health#running-the-health-check + QuarkusHealthPathStarted = "/q/health/started" +) diff --git a/controllers/profiles/common/constants/platform_services.go b/controllers/profiles/common/constants/platform_services.go index e0f249273..c0bc5086a 100644 --- a/controllers/profiles/common/constants/platform_services.go +++ b/controllers/profiles/common/constants/platform_services.go @@ -34,14 +34,15 @@ const ( JobServiceDataSourceReactiveURL = "quarkus.datasource.reactive.url" JobServiceJobEventsPath = "/v2/jobs/events" - KogitoProcessEventsProtocol = "http" - KogitoProcessInstancesEventsURL = "mp.messaging.outgoing.kogito-processinstances-events.url" - KogitoProcessInstancesEventsEnabled = "kogito.events.processinstances.enabled" - KogitoProcessInstancesEventsPath = "/processes" - KogitoProcessDefinitionsEventsURL = "mp.messaging.outgoing.kogito-processdefinitions-events.url" - KogitoProcessDefinitionsEventsEnabled = "kogito.events.processdefinitions.enabled" - KogitoProcessDefinitionsEventsPath = "/definitions" - KogitoUserTasksEventsEnabled = "kogito.events.usertasks.enabled" + KogitoProcessEventsProtocol = "http" + KogitoProcessInstancesEventsURL = "mp.messaging.outgoing.kogito-processinstances-events.url" + KogitoProcessInstancesEventsEnabled = "kogito.events.processinstances.enabled" + KogitoProcessInstancesEventsPath = "/processes" + KogitoProcessDefinitionsEventsURL = "mp.messaging.outgoing.kogito-processdefinitions-events.url" + KogitoProcessDefinitionsEventsEnabled = "kogito.events.processdefinitions.enabled" + KogitoProcessDefinitionsEventsErrorsEnabled = "kogito.events.processdefinitions.errors.propagate" + KogitoProcessDefinitionsEventsPath = "/definitions" + KogitoUserTasksEventsEnabled = "kogito.events.usertasks.enabled" // KogitoDataIndexHealthCheckEnabled configures if a workflow must check for the data index availability as part // of its start health check. KogitoDataIndexHealthCheckEnabled = "kogito.data-index.health-enabled" diff --git a/controllers/profiles/common/constants/workflows.go b/controllers/profiles/common/constants/workflows.go index a64002ad2..8087f963b 100644 --- a/controllers/profiles/common/constants/workflows.go +++ b/controllers/profiles/common/constants/workflows.go @@ -16,4 +16,11 @@ package constants const ( MicroprofileServiceCatalogPropertyPrefix = "org.kie.kogito.addons.discovery." + KogitoOutgoingEventsURL = "mp.messaging.outgoing.kogito_outgoing_stream.url" + KogitoOutgoingEventsConnector = "mp.messaging.outgoing.kogito_outgoing_stream.connector" + KogitoIncomingEventsConnector = "mp.messaging.incoming.kogito_incoming_stream.connector" + KogitoIncomingEventsPath = "mp.messaging.incoming.kogito_incoming_stream.path" + KnativeHealthEnabled = "org.kie.kogito.addons.knative.eventing.health-enabled" + KnativeInjectedEnvVar = "${K_SINK}" + KnativeEventingBrokerDefault = "default" ) diff --git a/controllers/profiles/common/ensurer.go b/controllers/profiles/common/ensurer.go index 7d258dbb3..a1b8c92c3 100644 --- a/controllers/profiles/common/ensurer.go +++ b/controllers/profiles/common/ensurer.go @@ -32,12 +32,13 @@ import ( var _ ObjectEnsurer = &defaultObjectEnsurer{} var _ ObjectEnsurer = &noopObjectEnsurer{} +var _ ObjectsEnsurer = &defaultObjectsEnsurer{} type ObjectEnsurer interface { Ensure(ctx context.Context, workflow *operatorapi.SonataFlow, visitors ...MutateVisitor) (client.Object, controllerutil.OperationResult, error) } type ObjectEnsurerWithPlatform interface { - Ensure(ctx context.Context, workflow *operatorapi.SonataFlow, platform *operatorapi.SonataFlowPlatform, visitors ...MutateVisitor) (client.Object, controllerutil.OperationResult, error) + Ensure(ctx context.Context, workflow *operatorapi.SonataFlow, pl *operatorapi.SonataFlowPlatform, visitors ...MutateVisitor) (client.Object, controllerutil.OperationResult, error) } // MutateVisitor is a visitor function that mutates the given object before performing any updates in the cluster. @@ -77,22 +78,10 @@ func (d *defaultObjectEnsurer) Ensure(ctx context.Context, workflow *operatorapi result := controllerutil.OperationResultNone object, err := d.creator(workflow) - if err != nil { - return nil, result, err - } - if result, err = controllerutil.CreateOrPatch(ctx, d.c, object, - func() error { - for _, v := range visitors { - if visitorErr := v(object)(); visitorErr != nil { - return visitorErr - } - } - return controllerutil.SetControllerReference(workflow, object, d.c.Scheme()) - }); err != nil { + if err != nil || object == nil { return nil, result, err } - klog.V(log.I).InfoS("Object operation finalized", "result", result, "kind", object.GetObjectKind().GroupVersionKind().String(), "name", object.GetName(), "namespace", object.GetNamespace()) - return object, result, nil + return ensureObject(ctx, workflow, visitors, result, d.c, object) } // defaultObjectEnsurerWithPlatform is the equivalent of defaultObjectEnsurer for resources that require a reference to the SonataFlowPlatform @@ -136,3 +125,61 @@ func (d *noopObjectEnsurer) Ensure(ctx context.Context, workflow *operatorapi.So result := controllerutil.OperationResultNone return nil, result, nil } + +// ObjectsEnsurer is an ensurer to apply multiple objects +type ObjectsEnsurer interface { + Ensure(ctx context.Context, workflow *operatorapi.SonataFlow, visitors ...MutateVisitor) []ObjectEnsurerResult +} + +type ObjectEnsurerResult struct { + client.Object + Result controllerutil.OperationResult + Error error +} + +func NewObjectsEnsurer(client client.Client, creator ObjectsCreator) ObjectsEnsurer { + return &defaultObjectsEnsurer{ + c: client, + creator: creator, + } +} + +type defaultObjectsEnsurer struct { + ObjectsEnsurer + c client.Client + creator ObjectsCreator +} + +func (d *defaultObjectsEnsurer) Ensure(ctx context.Context, workflow *operatorapi.SonataFlow, visitors ...MutateVisitor) []ObjectEnsurerResult { + result := controllerutil.OperationResultNone + + objects, err := d.creator(workflow) + if err != nil { + return []ObjectEnsurerResult{{nil, result, err}} + } + var ensureResult []ObjectEnsurerResult + for _, object := range objects { + ensureObject, c, err := ensureObject(ctx, workflow, visitors, result, d.c, object) + ensureResult = append(ensureResult, ObjectEnsurerResult{ensureObject, c, err}) + if err != nil { + return ensureResult + } + } + return ensureResult +} + +func ensureObject(ctx context.Context, workflow *operatorapi.SonataFlow, visitors []MutateVisitor, result controllerutil.OperationResult, c client.Client, object client.Object) (client.Object, controllerutil.OperationResult, error) { + if result, err := controllerutil.CreateOrPatch(ctx, c, object, + func() error { + for _, v := range visitors { + if visitorErr := v(object)(); visitorErr != nil { + return visitorErr + } + } + return controllerutil.SetControllerReference(workflow, object, c.Scheme()) + }); err != nil { + return nil, result, err + } + klog.V(log.I).InfoS("Object operation finalized", "result", result, "kind", object.GetObjectKind().GroupVersionKind().String(), "name", object.GetName(), "namespace", object.GetNamespace()) + return object, result, nil +} diff --git a/controllers/profiles/common/knative.go b/controllers/profiles/common/knative.go new file mode 100644 index 000000000..13f5bc49c --- /dev/null +++ b/controllers/profiles/common/knative.go @@ -0,0 +1,76 @@ +// Copyright 2024 Apache Software Foundation (ASF) +// +// 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 common + +import ( + "context" + + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/knative" + + operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" + "github.com/apache/incubator-kie-kogito-serverless-operator/log" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ KnativeEventingHandler = &knativeObjectManager{} + +type knativeObjectManager struct { + sinkBinding ObjectEnsurer + trigger ObjectsEnsurer + *StateSupport +} + +func NewKnativeEventingHandler(support *StateSupport) KnativeEventingHandler { + return &knativeObjectManager{ + sinkBinding: NewObjectEnsurer(support.C, SinkBindingCreator), + trigger: NewObjectsEnsurer(support.C, TriggersCreator), + StateSupport: support, + } +} + +type KnativeEventingHandler interface { + Ensure(ctx context.Context, workflow *operatorapi.SonataFlow) ([]client.Object, error) +} + +func (k knativeObjectManager) Ensure(ctx context.Context, workflow *operatorapi.SonataFlow) ([]client.Object, error) { + var objs []client.Object + + if workflow.Spec.Flow.Events == nil { + // skip if no event is found + klog.V(log.I).InfoS("skip knative resource creation as no event is found") + } else if workflow.Spec.Sink == nil { + klog.V(log.I).InfoS("Spec.Sink is not provided") + } else if knativeAvail, err := knative.GetKnativeAvailability(k.Cfg); err != nil || knativeAvail == nil || !knativeAvail.Eventing { + klog.V(log.I).InfoS("Knative Eventing is not installed") + } else { + // create sinkBinding and trigger + sinkBinding, _, err := k.sinkBinding.Ensure(ctx, workflow) + if err != nil { + return objs, err + } else if sinkBinding != nil { + objs = append(objs, sinkBinding) + } + + triggers := k.trigger.Ensure(ctx, workflow) + for _, trigger := range triggers { + if trigger.Error != nil { + return objs, trigger.Error + } + objs = append(objs, trigger.Object) + } + } + return objs, nil +} diff --git a/controllers/profiles/common/mutate_visitors.go b/controllers/profiles/common/mutate_visitors.go index 8ccbcab35..6b70bc0f1 100644 --- a/controllers/profiles/common/mutate_visitors.go +++ b/controllers/profiles/common/mutate_visitors.go @@ -27,7 +27,6 @@ import ( "github.com/imdario/mergo" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -57,13 +56,13 @@ func ImageDeploymentMutateVisitor(workflow *operatorapi.SonataFlow, image string } // DeploymentMutateVisitor guarantees the state of the default Deployment object -func DeploymentMutateVisitor(workflow *operatorapi.SonataFlow) MutateVisitor { +func DeploymentMutateVisitor(workflow *operatorapi.SonataFlow, plf *operatorapi.SonataFlowPlatform) MutateVisitor { return func(object client.Object) controllerutil.MutateFn { return func() error { if kubeutil.IsObjectNew(object) { return nil } - original, err := DeploymentCreator(workflow) + original, err := DeploymentCreator(workflow, plf) if err != nil { return err } @@ -106,7 +105,7 @@ func ServiceMutateVisitor(workflow *operatorapi.SonataFlow) MutateVisitor { } func ManagedPropertiesMutateVisitor(ctx context.Context, catalog discovery.ServiceCatalog, - workflow *operatorapi.SonataFlow, platform *operatorapi.SonataFlowPlatform, userProps *corev1.ConfigMap) MutateVisitor { + workflow *operatorapi.SonataFlow, plf *operatorapi.SonataFlowPlatform, userProps *corev1.ConfigMap) MutateVisitor { return func(object client.Object) controllerutil.MutateFn { return func() error { managedProps := object.(*corev1.ConfigMap) @@ -122,7 +121,7 @@ func ManagedPropertiesMutateVisitor(ctx context.Context, catalog discovery.Servi userProperties = "" } // In the future, if this needs change, instead we can receive an AppPropertyHandler in this mutator - props, err := properties.NewAppPropertyHandler(workflow, platform) + props, err := properties.NewAppPropertyHandler(workflow, plf) if err != nil { return err } @@ -138,7 +137,7 @@ func ManagedPropertiesMutateVisitor(ctx context.Context, catalog discovery.Servi // This method can be used as an alternative to the Kubernetes ConfigMap refresher. // // See: https://kubernetes.io/docs/concepts/configuration/configmap/#mounted-configmaps-are-updated-automatically -func RolloutDeploymentIfCMChangedMutateVisitor(workflow *operatorapi.SonataFlow, userPropsCM *v1.ConfigMap, managedPropsCM *v1.ConfigMap) MutateVisitor { +func RolloutDeploymentIfCMChangedMutateVisitor(workflow *operatorapi.SonataFlow, userPropsCM *corev1.ConfigMap, managedPropsCM *corev1.ConfigMap) MutateVisitor { return func(object client.Object) controllerutil.MutateFn { return func() error { deployment := object.(*appsv1.Deployment) diff --git a/controllers/profiles/common/object_creators.go b/controllers/profiles/common/object_creators.go index 0e2d6f209..4b28e9806 100644 --- a/controllers/profiles/common/object_creators.go +++ b/controllers/profiles/common/object_creators.go @@ -20,17 +20,28 @@ package common import ( + "fmt" + "strings" + + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/workflowdef" + + cncfmodel "github.com/serverlessworkflow/sdk-go/v2/model" + "github.com/imdario/mergo" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/tracker" "sigs.k8s.io/controller-runtime/pkg/client" operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/constants" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/persistence" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/properties" + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/variables" "github.com/apache/incubator-kie-kogito-serverless-operator/utils" kubeutil "github.com/apache/incubator-kie-kogito-serverless-operator/utils/kubernetes" "github.com/apache/incubator-kie-kogito-serverless-operator/utils/openshift" @@ -45,16 +56,12 @@ type ObjectCreator func(workflow *operatorapi.SonataFlow) (client.Object, error) // SonataFlowPlatform type ObjectCreatorWithPlatform func(workflow *operatorapi.SonataFlow, platform *operatorapi.SonataFlowPlatform) (client.Object, error) +// ObjectsCreator creates multiple resources +type ObjectsCreator func(workflow *operatorapi.SonataFlow) ([]client.Object, error) + const ( defaultHTTPServicePort = 80 - // Quarkus Health Check Probe configuration. - // See: https://quarkus.io/guides/smallrye-health#running-the-health-check - - quarkusHealthPathStarted = "/q/health/started" - QuarkusHealthPathReady = "/q/health/ready" - QuarkusHealthPathLive = "/q/health/live" - // Default deployment health check configuration // See: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ @@ -65,13 +72,9 @@ const ( defaultSchemaName = "default" ) -var ( - DefaultHTTPWorkflowPortIntStr = intstr.FromInt(constants.DefaultHTTPWorkflowPortInt) -) - // DeploymentCreator is an objectCreator for a base Kubernetes Deployments for profiles that need to deploy the workflow on a vanilla deployment. // It serves as a basis for a basic Quarkus Java application, expected to listen on http 8080. -func DeploymentCreator(workflow *operatorapi.SonataFlow) (client.Object, error) { +func DeploymentCreator(workflow *operatorapi.SonataFlow, plf *operatorapi.SonataFlowPlatform) (client.Object, error) { lbl := workflowproj.GetDefaultLabels(workflow) deployment := &appsv1.Deployment{ @@ -97,8 +100,7 @@ func DeploymentCreator(workflow *operatorapi.SonataFlow) (client.Object, error) if err := mergo.Merge(&deployment.Spec.Template.Spec, workflow.Spec.PodTemplate.PodSpec.ToPodSpec(), mergo.WithOverride); err != nil { return nil, err } - - flowContainer, err := defaultContainer(workflow) + flowContainer, err := defaultContainer(workflow, plf) if err != nil { return nil, err } @@ -115,9 +117,9 @@ func getReplicasOrDefault(workflow *operatorapi.SonataFlow) *int32 { return workflow.Spec.PodTemplate.Replicas } -func defaultContainer(workflow *operatorapi.SonataFlow) (*corev1.Container, error) { +func defaultContainer(workflow *operatorapi.SonataFlow, plf *operatorapi.SonataFlowPlatform) (*corev1.Container, error) { defaultContainerPort := corev1.ContainerPort{ - ContainerPort: DefaultHTTPWorkflowPortIntStr.IntVal, + ContainerPort: variables.DefaultHTTPWorkflowPortIntStr.IntVal, Name: utils.HttpScheme, Protocol: corev1.ProtocolTCP, } @@ -128,8 +130,8 @@ func defaultContainer(workflow *operatorapi.SonataFlow) (*corev1.Container, erro LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ - Path: QuarkusHealthPathLive, - Port: DefaultHTTPWorkflowPortIntStr, + Path: constants.QuarkusHealthPathLive, + Port: variables.DefaultHTTPWorkflowPortIntStr, }, }, TimeoutSeconds: healthTimeoutSeconds, @@ -137,8 +139,8 @@ func defaultContainer(workflow *operatorapi.SonataFlow) (*corev1.Container, erro ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ - Path: QuarkusHealthPathReady, - Port: DefaultHTTPWorkflowPortIntStr, + Path: constants.QuarkusHealthPathReady, + Port: variables.DefaultHTTPWorkflowPortIntStr, }, }, TimeoutSeconds: healthTimeoutSeconds, @@ -146,8 +148,8 @@ func defaultContainer(workflow *operatorapi.SonataFlow) (*corev1.Container, erro StartupProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ - Path: quarkusHealthPathStarted, - Port: DefaultHTTPWorkflowPortIntStr, + Path: constants.QuarkusHealthPathStarted, + Port: variables.DefaultHTTPWorkflowPortIntStr, }, }, InitialDelaySeconds: healthStartedInitialDelaySeconds, @@ -161,13 +163,19 @@ func defaultContainer(workflow *operatorapi.SonataFlow) (*corev1.Container, erro if err := mergo.Merge(defaultFlowContainer, workflow.Spec.PodTemplate.Container.ToContainer(), mergo.WithOverride); err != nil { return nil, err } - defaultFlowContainer = ConfigurePersistence(defaultFlowContainer, workflow.Spec.Persistence, defaultSchemaName, workflow.Namespace) + var pper *operatorapi.PlatformPersistenceOptionsSpec + if plf != nil && plf.Spec.Persistence != nil { + pper = plf.Spec.Persistence + } + if p := persistence.RetrieveConfiguration(workflow.Spec.Persistence, pper, workflow.Name); p != nil { + defaultFlowContainer = persistence.ConfigurePersistence(defaultFlowContainer, p, workflow.Name, workflow.Namespace) + } // immutable defaultFlowContainer.Name = operatorapi.DefaultContainerName portIdx := -1 for i := range defaultFlowContainer.Ports { if defaultFlowContainer.Ports[i].Name == utils.HttpScheme || - defaultFlowContainer.Ports[i].ContainerPort == DefaultHTTPWorkflowPortIntStr.IntVal { + defaultFlowContainer.Ports[i].ContainerPort == variables.DefaultHTTPWorkflowPortIntStr.IntVal { portIdx = i break } @@ -197,7 +205,7 @@ func ServiceCreator(workflow *operatorapi.SonataFlow) (client.Object, error) { Ports: []corev1.ServicePort{{ Protocol: corev1.ProtocolTCP, Port: defaultHTTPServicePort, - TargetPort: DefaultHTTPWorkflowPortIntStr, + TargetPort: variables.DefaultHTTPWorkflowPortIntStr, }}, }, } @@ -205,6 +213,85 @@ func ServiceCreator(workflow *operatorapi.SonataFlow) (client.Object, error) { return service, nil } +// SinkBindingCreator is an ObjectsCreator for SinkBinding. +// It will create v1.SinkBinding based on events defined in workflow. +func SinkBindingCreator(workflow *operatorapi.SonataFlow) (client.Object, error) { + lbl := workflowproj.GetDefaultLabels(workflow) + + // skip if no produced event is found + if workflow.Spec.Sink == nil || !workflowdef.ContainsEventKind(workflow, cncfmodel.EventKindProduced) { + return nil, nil + } + + sink := workflow.Spec.Sink + + // subject must be deployment to inject K_SINK, service won't work + sinkBinding := &sourcesv1.SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: strings.ToLower(fmt.Sprintf("%s-sb", workflow.Name)), + Namespace: workflow.Namespace, + Labels: lbl, + }, + Spec: sourcesv1.SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: *sink, + }, + BindingSpec: duckv1.BindingSpec{ + Subject: tracker.Reference{ + Name: workflow.Name, + Namespace: workflow.Namespace, + APIVersion: "apps/v1", + Kind: "Deployment", + }, + }, + }, + } + return sinkBinding, nil +} + +// TriggersCreator is an ObjectsCreator for Triggers. +// It will create a list of eventingv1.Trigger based on events defined in workflow. +func TriggersCreator(workflow *operatorapi.SonataFlow) ([]client.Object, error) { + var resultObjects []client.Object + lbl := workflowproj.GetDefaultLabels(workflow) + + //consumed + events := workflow.Spec.Flow.Events + for _, event := range events { + // filter out produce events + if event.Kind == cncfmodel.EventKindProduced { + continue + } + + // construct eventingv1.Trigger + trigger := &eventingv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: strings.ToLower(fmt.Sprintf("%s-%s-trigger", workflow.Name, event.Name)), + Namespace: workflow.Namespace, + Labels: lbl, + }, + Spec: eventingv1.TriggerSpec{ + Broker: constants.KnativeEventingBrokerDefault, + Filter: &eventingv1.TriggerFilter{ + Attributes: eventingv1.TriggerFilterAttributes{ + "type": event.Type, + }, + }, + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{ + Name: workflow.Name, + Namespace: workflow.Namespace, + APIVersion: "v1", + Kind: "Service", + }, + }, + }, + } + resultObjects = append(resultObjects, trigger) + } + return resultObjects, nil +} + // OpenShiftRouteCreator is an ObjectCreator for a basic Route for a workflow running on OpenShift. // It enables the exposition of the service using an OpenShift Route. // See: https://github.com/openshift/api/blob/d170fcdc0fa638b664e4f35f2daf753cb4afe36b/route/v1/route.crd.yaml @@ -226,11 +313,3 @@ func ManagedPropsConfigMapCreator(workflow *operatorapi.SonataFlow, platform *op } return workflowproj.CreateNewManagedPropsConfigMap(workflow, props), nil } - -func ConfigurePersistence(serviceContainer *corev1.Container, options *operatorapi.PersistenceOptions, defaultSchema, namespace string) *corev1.Container { - c := serviceContainer.DeepCopy() - if options != nil && options.PostgreSql != nil { - c.Env = append(c.Env, persistence.ConfigurePostgreSqlEnv(options.PostgreSql, defaultSchema, namespace)...) - } - return c -} diff --git a/controllers/profiles/common/object_creators_test.go b/controllers/profiles/common/object_creators_test.go index ae252248e..236aa025c 100644 --- a/controllers/profiles/common/object_creators_test.go +++ b/controllers/profiles/common/object_creators_test.go @@ -23,10 +23,13 @@ import ( "context" "testing" + sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + "github.com/magiconair/properties" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/apache/incubator-kie-kogito-serverless-operator/utils" kubeutil "github.com/apache/incubator-kie-kogito-serverless-operator/utils/kubernetes" @@ -128,7 +131,7 @@ func TestMergePodSpec(t *testing.T) { }, } - object, err := DeploymentCreator(workflow) + object, err := DeploymentCreator(workflow, nil) assert.NoError(t, err) deployment := object.(*appsv1.Deployment) @@ -163,7 +166,7 @@ func TestMergePodSpec_OverrideContainers(t *testing.T) { }, } - object, err := DeploymentCreator(workflow) + object, err := DeploymentCreator(workflow, nil) assert.NoError(t, err) deployment := object.(*appsv1.Deployment) @@ -175,6 +178,35 @@ func TestMergePodSpec_OverrideContainers(t *testing.T) { assert.Empty(t, flowContainer.Env) } +func Test_ensureWorkflowSinkBindingIsCreated(t *testing.T) { + workflow := test.GetVetEventSonataFlow(t.Name()) + + //On Kubernetes we want the service exposed in Dev with NodePort + sinkBinding, _ := SinkBindingCreator(workflow) + sinkBinding.SetUID("1") + sinkBinding.SetResourceVersion("1") + + reflectSinkBinding := sinkBinding.(*sourcesv1.SinkBinding) + + assert.NotNil(t, reflectSinkBinding) + assert.NotNil(t, reflectSinkBinding.Spec) + assert.NotEmpty(t, reflectSinkBinding.Spec.Sink) + assert.Equal(t, reflectSinkBinding.Spec.Sink.Ref.Kind, "Broker") +} + +func Test_ensureWorkflowTriggersAreCreated(t *testing.T) { + workflow := test.GetVetEventSonataFlow(t.Name()) + + //On Kubernetes we want the service exposed in Dev with NodePort + triggers, _ := TriggersCreator(workflow) + + assert.NotEmpty(t, triggers) + assert.Len(t, triggers, 2) + for _, trigger := range triggers { + assert.Contains(t, []string{"vet-vetappointmentrequestreceived-trigger", "vet-vetappointmentinfo-trigger"}, trigger.GetName()) + } +} + func TestMergePodSpec_WithPostgreSQL_and_JDBC_URL_field(t *testing.T) { workflow := test.GetBaseSonataFlow(t.Name()) workflow.Spec = v1alpha08.SonataFlowSpec{ @@ -213,15 +245,15 @@ func TestMergePodSpec_WithPostgreSQL_and_JDBC_URL_field(t *testing.T) { }, }, }, - Persistence: &v1alpha08.PersistenceOptions{ - PostgreSql: &v1alpha08.PersistencePostgreSql{ - SecretRef: v1alpha08.PostgreSqlSecretOptions{Name: "test"}, + Persistence: &v1alpha08.PersistenceOptionsSpec{ + PostgreSQL: &v1alpha08.PersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "test"}, JdbcUrl: "jdbc:postgresql://host:port/database?currentSchema=workflow", }, }, } - object, err := DeploymentCreator(workflow) + object, err := DeploymentCreator(workflow, nil) assert.NoError(t, err) deployment := object.(*appsv1.Deployment) @@ -256,6 +288,18 @@ func TestMergePodSpec_WithPostgreSQL_and_JDBC_URL_field(t *testing.T) { Name: "QUARKUS_DATASOURCE_JDBC_URL", Value: "jdbc:postgresql://host:port/database?currentSchema=workflow", }, + { + Name: "KOGITO_PERSISTENCE_TYPE", + Value: "jdbc", + }, + { + Name: "KOGITO_PERSISTENCE_PROTO_MARSHALLER", + Value: "false", + }, + { + Name: "KOGITO_PERSISTENCE_QUERY_TIMEOUT_MILLIS", + Value: "10000", + }, } assert.Len(t, deployment.Spec.Template.Spec.Containers, 2) assert.Equal(t, "superuser", deployment.Spec.Template.Spec.ServiceAccountName) @@ -271,7 +315,7 @@ var ( postgreSQLPort = 5432 ) -func TestMergePodSpec_OverrideContainers_WithPostgreSQL_and_ServiceRef(t *testing.T) { +func TestMergePodSpec_OverrideContainers_WithPostgreSQL_In_Workflow_CR(t *testing.T) { workflow := test.GetBaseSonataFlow(t.Name()) workflow.Spec = v1alpha08.SonataFlowSpec{ PodTemplate: v1alpha08.PodTemplateSpec{ @@ -291,20 +335,21 @@ func TestMergePodSpec_OverrideContainers_WithPostgreSQL_and_ServiceRef(t *testin }, }, }, - Persistence: &v1alpha08.PersistenceOptions{ - PostgreSql: &v1alpha08.PersistencePostgreSql{ - SecretRef: v1alpha08.PostgreSqlSecretOptions{Name: "test"}, - ServiceRef: &v1alpha08.PostgreSqlServiceOptions{ - Name: "test", - Namespace: "foo", - Port: &postgreSQLPort, - DatabaseName: "petstore", + Persistence: &v1alpha08.PersistenceOptionsSpec{ + PostgreSQL: &v1alpha08.PersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "test"}, + ServiceRef: &v1alpha08.PostgreSQLServiceOptions{ + SQLServiceOptions: &v1alpha08.SQLServiceOptions{ + Name: "test", + Namespace: "foo", + Port: &postgreSQLPort, + DatabaseName: "petstore"}, DatabaseSchema: "bar"}, }, }, } - object, err := DeploymentCreator(workflow) + object, err := DeploymentCreator(workflow, nil) assert.NoError(t, err) deployment := object.(*appsv1.Deployment) @@ -335,6 +380,206 @@ func TestMergePodSpec_OverrideContainers_WithPostgreSQL_and_ServiceRef(t *testin Name: "QUARKUS_DATASOURCE_JDBC_URL", Value: "jdbc:postgresql://test.foo:5432/petstore?currentSchema=bar", }, + { + Name: "KOGITO_PERSISTENCE_TYPE", + Value: "jdbc", + }, + { + Name: "KOGITO_PERSISTENCE_PROTO_MARSHALLER", + Value: "false", + }, + { + Name: "KOGITO_PERSISTENCE_QUERY_TIMEOUT_MILLIS", + Value: "10000", + }, + } + assert.Len(t, deployment.Spec.Template.Spec.Containers, 1) + flowContainer, _ := kubeutil.GetContainerByName(v1alpha08.DefaultContainerName, &deployment.Spec.Template.Spec) + assert.Empty(t, flowContainer.Image) + assert.Equal(t, int32(8080), flowContainer.Ports[0].ContainerPort) + assert.Equal(t, expectedEnvVars, flowContainer.Env) +} + +func TestMergePodSpec_WithServicedPostgreSQL_In_Platform_CR_And_Worflow_Requesting_It(t *testing.T) { + p := &v1alpha08.SonataFlowPlatform{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "default", + }, + Spec: v1alpha08.SonataFlowPlatformSpec{ + Persistence: &v1alpha08.PlatformPersistenceOptionsSpec{ + PostgreSQL: &v1alpha08.PlatformPersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{ + Name: "foo_secret", + UserKey: "username", + PasswordKey: "password", + }, + ServiceRef: &v1alpha08.SQLServiceOptions{ + Name: "service_name", + Namespace: "service_namespace", + Port: &postgreSQLPort, + DatabaseName: "foo", + }, + }, + }, + }, + } + + workflow := test.GetBaseSonataFlow(t.Name()) + workflow.Spec = v1alpha08.SonataFlowSpec{ + Persistence: nil, + } + object, err := DeploymentCreator(workflow, p) + assert.NoError(t, err) + + deployment := object.(*appsv1.Deployment) + expectedEnvVars := []corev1.EnvVar{ + { + Name: "QUARKUS_DATASOURCE_USERNAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "foo_secret"}, Key: "username", + }, + }, + }, + { + Name: "QUARKUS_DATASOURCE_PASSWORD", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "foo_secret"}, Key: "password", + }, + }, + }, + { + Name: "QUARKUS_DATASOURCE_DB_KIND", + Value: "postgresql", + }, + { + Name: "QUARKUS_DATASOURCE_JDBC_URL", + Value: "jdbc:postgresql://service_name.service_namespace:5432/foo?currentSchema=greeting", + }, + { + Name: "KOGITO_PERSISTENCE_TYPE", + Value: "jdbc", + }, + { + Name: "KOGITO_PERSISTENCE_PROTO_MARSHALLER", + Value: "false", + }, + { + Name: "KOGITO_PERSISTENCE_QUERY_TIMEOUT_MILLIS", + Value: "10000", + }, + } + assert.Len(t, deployment.Spec.Template.Spec.Containers, 1) + flowContainer, _ := kubeutil.GetContainerByName(v1alpha08.DefaultContainerName, &deployment.Spec.Template.Spec) + assert.Empty(t, flowContainer.Image) + assert.Equal(t, int32(8080), flowContainer.Ports[0].ContainerPort) + assert.Equal(t, expectedEnvVars, flowContainer.Env) +} + +func TestMergePodSpec_WithServicedPostgreSQL_In_Platform_And_In_Workflow_CR(t *testing.T) { + + p := &v1alpha08.SonataFlowPlatform{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "default", + }, + Spec: v1alpha08.SonataFlowPlatformSpec{ + Persistence: &v1alpha08.PlatformPersistenceOptionsSpec{ + PostgreSQL: &v1alpha08.PlatformPersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{ + Name: "foo_secret", + UserKey: "username", + PasswordKey: "password", + }, + ServiceRef: &v1alpha08.SQLServiceOptions{ + Name: "service_name", + Namespace: "service_namespace", + Port: &postgreSQLPort, + DatabaseName: "foo", + }, + }, + }, + }, + } + workflow := test.GetBaseSonataFlow(t.Name()) + workflow.Spec = v1alpha08.SonataFlowSpec{ + PodTemplate: v1alpha08.PodTemplateSpec{ + PodSpec: v1alpha08.PodSpec{ + // Try to override the workflow container via the podspec + Containers: []corev1.Container{ + { + Name: v1alpha08.DefaultContainerName, + Image: "quay.io/example/my-workflow:1.0.0", + Ports: []corev1.ContainerPort{ + {Name: utils.HttpScheme, ContainerPort: 9090}, + }, + Env: []corev1.EnvVar{ + {Name: "ENV1", Value: "VALUE_CUSTOM"}, + }, + }, + }, + }, + }, + Persistence: &v1alpha08.PersistenceOptionsSpec{ + PostgreSQL: &v1alpha08.PersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "test"}, + ServiceRef: &v1alpha08.PostgreSQLServiceOptions{ + SQLServiceOptions: &v1alpha08.SQLServiceOptions{ + Name: "test", + Namespace: "default", + Port: &postgreSQLPort, + DatabaseName: "my_database"}, + DatabaseSchema: "bar"}, + }, + }, + } + object, err := DeploymentCreator(workflow, p) + assert.NoError(t, err) + + deployment := object.(*appsv1.Deployment) + expectedEnvVars := []corev1.EnvVar{ + { + Name: "QUARKUS_DATASOURCE_USERNAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "test"}, Key: "POSTGRESQL_USER", + }, + }, + }, + { + Name: "QUARKUS_DATASOURCE_PASSWORD", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "test"}, Key: "POSTGRESQL_PASSWORD", + }, + }, + }, + { + Name: "QUARKUS_DATASOURCE_DB_KIND", + Value: "postgresql", + }, + { + Name: "QUARKUS_DATASOURCE_JDBC_URL", + Value: "jdbc:postgresql://test.default:5432/my_database?currentSchema=bar", + }, + { + Name: "KOGITO_PERSISTENCE_TYPE", + Value: "jdbc", + }, + { + Name: "KOGITO_PERSISTENCE_PROTO_MARSHALLER", + Value: "false", + }, + { + Name: "KOGITO_PERSISTENCE_QUERY_TIMEOUT_MILLIS", + Value: "10000", + }, } assert.Len(t, deployment.Spec.Template.Spec.Containers, 1) flowContainer, _ := kubeutil.GetContainerByName(v1alpha08.DefaultContainerName, &deployment.Spec.Template.Spec) @@ -342,3 +587,42 @@ func TestMergePodSpec_OverrideContainers_WithPostgreSQL_and_ServiceRef(t *testin assert.Equal(t, int32(8080), flowContainer.Ports[0].ContainerPort) assert.Equal(t, expectedEnvVars, flowContainer.Env) } + +func TestMergePodSpec_WithServicedPostgreSQL_In_Platform_But_Workflow_CR_Not_Requesting_it(t *testing.T) { + p := &v1alpha08.SonataFlowPlatform{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "default", + }, + Spec: v1alpha08.SonataFlowPlatformSpec{ + Persistence: &v1alpha08.PlatformPersistenceOptionsSpec{ + PostgreSQL: &v1alpha08.PlatformPersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{ + Name: "foo_secret", + UserKey: "username", + PasswordKey: "password", + }, + ServiceRef: &v1alpha08.SQLServiceOptions{ + Name: "service_name", + Namespace: "service_namespace", + Port: &postgreSQLPort, + DatabaseName: "foo", + }, + }, + }, + }, + } + workflow := test.GetBaseSonataFlow(t.Name()) + workflow.Spec = v1alpha08.SonataFlowSpec{ + Persistence: &v1alpha08.PersistenceOptionsSpec{}, + } + object, err := DeploymentCreator(workflow, p) + assert.NoError(t, err) + + deployment := object.(*appsv1.Deployment) + assert.Len(t, deployment.Spec.Template.Spec.Containers, 1) + flowContainer, _ := kubeutil.GetContainerByName(v1alpha08.DefaultContainerName, &deployment.Spec.Template.Spec) + assert.Empty(t, flowContainer.Image) + assert.Equal(t, int32(8080), flowContainer.Ports[0].ContainerPort) + assert.Nil(t, flowContainer.Env) +} diff --git a/controllers/profiles/common/persistence/postgreSQL.go b/controllers/profiles/common/persistence/postgreSQL.go deleted file mode 100644 index ebb695af6..000000000 --- a/controllers/profiles/common/persistence/postgreSQL.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2023 Apache Software Foundation (ASF) -// -// 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 persistence - -import ( - "strconv" - - corev1 "k8s.io/api/core/v1" - - operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" - "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/constants" -) - -const ( - defaultSchemaName = "default" - defaultDatabaseName = "sonataflow" - - quarkusDatasourceJDBCURL string = "QUARKUS_DATASOURCE_JDBC_URL" - quarkusDatasourceDBKind string = "QUARKUS_DATASOURCE_DB_KIND" - quarkusDatasourceUsername string = "QUARKUS_DATASOURCE_USERNAME" - quarkusDatasourcePassword string = "QUARKUS_DATASOURCE_PASSWORD" -) - -func ConfigurePostgreSqlEnv(postgresql *operatorapi.PersistencePostgreSql, databaseSchema, databaseNamespace string) []corev1.EnvVar { - dataSourcePort := constants.DefaultPostgreSQLPort - databaseName := defaultDatabaseName - dataSourceURL := postgresql.JdbcUrl - if postgresql.ServiceRef != nil { - if len(postgresql.ServiceRef.DatabaseSchema) > 0 { - databaseSchema = postgresql.ServiceRef.DatabaseSchema - } - if len(postgresql.ServiceRef.Namespace) > 0 { - databaseNamespace = postgresql.ServiceRef.Namespace - } - if postgresql.ServiceRef.Port != nil { - dataSourcePort = *postgresql.ServiceRef.Port - } - if len(postgresql.ServiceRef.DatabaseName) > 0 { - databaseName = postgresql.ServiceRef.DatabaseName - } - dataSourceURL = "jdbc:" + constants.PersistenceTypePostgreSQL + "://" + postgresql.ServiceRef.Name + "." + databaseNamespace + ":" + strconv.Itoa(dataSourcePort) + "/" + databaseName + "?currentSchema=" + databaseSchema - } - secretRef := corev1.LocalObjectReference{ - Name: postgresql.SecretRef.Name, - } - postgresUsername := "POSTGRESQL_USER" - if len(postgresql.SecretRef.UserKey) > 0 { - postgresUsername = postgresql.SecretRef.UserKey - } - postgresPassword := "POSTGRESQL_PASSWORD" - if len(postgresql.SecretRef.PasswordKey) > 0 { - postgresPassword = postgresql.SecretRef.PasswordKey - } - return []corev1.EnvVar{ - { - Name: quarkusDatasourceUsername, - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - Key: postgresUsername, - LocalObjectReference: secretRef, - }, - }, - }, - { - Name: quarkusDatasourcePassword, - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - Key: postgresPassword, - LocalObjectReference: secretRef, - }, - }, - }, - { - Name: quarkusDatasourceDBKind, - Value: constants.PersistenceTypePostgreSQL, - }, - { - Name: quarkusDatasourceJDBCURL, - Value: dataSourceURL, - }, - } -} diff --git a/controllers/profiles/common/persistence/postgresql.go b/controllers/profiles/common/persistence/postgresql.go new file mode 100644 index 000000000..6da517bf0 --- /dev/null +++ b/controllers/profiles/common/persistence/postgresql.go @@ -0,0 +1,147 @@ +// Copyright 2023 Apache Software Foundation (ASF) +// +// 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 persistence + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + + "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" + operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/constants" +) + +const ( + defaultDatabaseName = "sonataflow" + timeoutSeconds = 3 + failureThreshold = 5 + initialPeriodSeconds = 15 + initialDelaySeconds = 10 + successThreshold = 1 + + postgreSQLCPULimit = "500m" + postgreSQLMemoryLimit = "256Mi" + postgreSQLMemoryRequest = "256Mi" + postgreSQLCPURequest = "100m" + + defaultPostgreSQLUsername = "sonataflow" + defaultPostgresSQLPassword = "sonataflow" +) + +func ConfigurePostgreSQLEnv(postgresql *operatorapi.PersistencePostgreSQL, databaseSchema, databaseNamespace string) []corev1.EnvVar { + dataSourcePort := constants.DefaultPostgreSQLPort + databaseName := defaultDatabaseName + dataSourceURL := postgresql.JdbcUrl + if postgresql.ServiceRef != nil { + if len(postgresql.ServiceRef.DatabaseSchema) > 0 { + databaseSchema = postgresql.ServiceRef.DatabaseSchema + } + if len(postgresql.ServiceRef.Namespace) > 0 { + databaseNamespace = postgresql.ServiceRef.Namespace + } + if postgresql.ServiceRef.Port != nil { + dataSourcePort = *postgresql.ServiceRef.Port + } + if len(postgresql.ServiceRef.DatabaseName) > 0 { + databaseName = postgresql.ServiceRef.DatabaseName + } + dataSourceURL = fmt.Sprintf("jdbc:postgresql://%s.%s:%d/%s?currentSchema=%s", postgresql.ServiceRef.Name, databaseNamespace, dataSourcePort, databaseName, databaseSchema) + } + secretRef := corev1.LocalObjectReference{ + Name: postgresql.SecretRef.Name, + } + quarkusDatasourceUsername := "POSTGRESQL_USER" + if len(postgresql.SecretRef.UserKey) > 0 { + quarkusDatasourceUsername = postgresql.SecretRef.UserKey + } + quarkusDatasourcePassword := "POSTGRESQL_PASSWORD" + if len(postgresql.SecretRef.PasswordKey) > 0 { + quarkusDatasourcePassword = postgresql.SecretRef.PasswordKey + } + return []corev1.EnvVar{ + { + Name: "QUARKUS_DATASOURCE_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: quarkusDatasourceUsername, + LocalObjectReference: secretRef, + }, + }, + }, + { + Name: "QUARKUS_DATASOURCE_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: quarkusDatasourcePassword, + LocalObjectReference: secretRef, + }, + }, + }, + { + Name: "QUARKUS_DATASOURCE_DB_KIND", + Value: constants.PersistenceTypePostgreSQL, + }, + { + Name: "QUARKUS_DATASOURCE_JDBC_URL", + Value: dataSourceURL, + }, + { + Name: "KOGITO_PERSISTENCE_TYPE", + Value: "jdbc", + }, + { + Name: "KOGITO_PERSISTENCE_PROTO_MARSHALLER", + Value: "false", + }, + { + Name: "KOGITO_PERSISTENCE_QUERY_TIMEOUT_MILLIS", + Value: "10000", + }, + } +} + +func ConfigurePersistence(serviceContainer *corev1.Container, config *operatorapi.PersistenceOptionsSpec, defaultSchema, namespace string) *corev1.Container { + if config.PostgreSQL == nil { + return serviceContainer + } + c := serviceContainer.DeepCopy() + c.Env = append(c.Env, ConfigurePostgreSQLEnv(config.PostgreSQL, defaultSchema, namespace)...) + return c +} + +func RetrieveConfiguration(primary *v1alpha08.PersistenceOptionsSpec, platformPersistence *v1alpha08.PlatformPersistenceOptionsSpec, schema string) *v1alpha08.PersistenceOptionsSpec { + if primary != nil { + return primary + } + if platformPersistence == nil { + return nil + } + c := &v1alpha08.PersistenceOptionsSpec{} + if platformPersistence.PostgreSQL != nil { + c.PostgreSQL = &v1alpha08.PersistencePostgreSQL{ + SecretRef: platformPersistence.PostgreSQL.SecretRef, + } + if platformPersistence.PostgreSQL.ServiceRef != nil { + c.PostgreSQL.ServiceRef = &v1alpha08.PostgreSQLServiceOptions{ + SQLServiceOptions: platformPersistence.PostgreSQL.ServiceRef, + DatabaseSchema: schema, + } + } else { + c.PostgreSQL.JdbcUrl = platformPersistence.PostgreSQL.JdbcUrl + } + } + return c +} diff --git a/controllers/profiles/common/properties/application.go b/controllers/profiles/common/properties/application.go index 414891405..9b5926085 100644 --- a/controllers/profiles/common/properties/application.go +++ b/controllers/profiles/common/properties/application.go @@ -40,9 +40,6 @@ import ( var ( immutableApplicationProperties = fmt.Sprintf("quarkus.http.port=%d\n"+ "quarkus.http.host=0.0.0.0\n"+ - // We disable the Knative health checks to not block the pod to run if Knative objects are not available - // See: https://kiegroup.github.io/kogito-docs/serverlessworkflow/latest/eventing/consume-produce-events-with-knative-eventing.html#ref-knative-eventing-add-on-source-configuration - "org.kie.kogito.addons.knative.eventing.health-enabled=false\n"+ "quarkus.devservices.enabled=false\n"+ "quarkus.kogito.devservices.enabled=false\n", constants.DefaultHTTPWorkflowPortInt) _ AppPropertyHandler = &appPropertyHandler{} @@ -160,6 +157,11 @@ func NewAppPropertyHandler(workflow *operatorapi.SonataFlow, platform *operatora return nil, err } props.Merge(p) + p, err = generateKnativeEventingWorkflowProperties(workflow) + if err != nil { + return nil, err + } + props.Merge(p) props.Sort() } handler.defaultMutableProperties = props diff --git a/controllers/profiles/common/properties/application_test.go b/controllers/profiles/common/properties/application_test.go index d0f4d5dff..7133db17e 100644 --- a/controllers/profiles/common/properties/application_test.go +++ b/controllers/profiles/common/properties/application_test.go @@ -124,13 +124,12 @@ func Test_appPropertyHandler_WithUserPropertiesWithNoUserOverrides(t *testing.T) assert.NoError(t, err) generatedProps, propsErr := properties.LoadString(props.WithUserProperties(userProperties).Build()) assert.NoError(t, propsErr) - assert.Equal(t, 7, len(generatedProps.Keys())) + assert.Equal(t, 6, len(generatedProps.Keys())) assert.NotContains(t, "property1", generatedProps.Keys()) assert.NotContains(t, "property2", generatedProps.Keys()) assert.Equal(t, "http://greeting.default", generatedProps.GetString("kogito.service.url", "")) assert.Equal(t, "8080", generatedProps.GetString("quarkus.http.port", "")) assert.Equal(t, "0.0.0.0", generatedProps.GetString("quarkus.http.host", "")) - assert.Equal(t, "false", generatedProps.GetString("org.kie.kogito.addons.knative.eventing.health-enabled", "")) assert.Equal(t, "false", generatedProps.GetString("quarkus.devservices.enabled", "")) assert.Equal(t, "false", generatedProps.GetString("quarkus.kogito.devservices.enabled", "")) assert.Equal(t, "false", generatedProps.GetString(constants.KogitoUserTasksEventsEnabled, "")) @@ -157,7 +156,7 @@ func Test_appPropertyHandler_WithUserPropertiesWithServiceDiscovery(t *testing.T Build()) generatedProps.DisableExpansion = true assert.NoError(t, propsErr) - assert.Equal(t, 21, len(generatedProps.Keys())) + assert.Equal(t, 20, len(generatedProps.Keys())) assert.NotContains(t, "property1", generatedProps.Keys()) assert.NotContains(t, "property2", generatedProps.Keys()) assertHasProperty(t, generatedProps, "service1", myService1Address) @@ -181,7 +180,6 @@ func Test_appPropertyHandler_WithUserPropertiesWithServiceDiscovery(t *testing.T assertHasProperty(t, generatedProps, "kogito.service.url", fmt.Sprintf("http://greeting.%s", defaultNamespace)) assertHasProperty(t, generatedProps, "quarkus.http.port", "8080") assertHasProperty(t, generatedProps, "quarkus.http.host", "0.0.0.0") - assertHasProperty(t, generatedProps, "org.kie.kogito.addons.knative.eventing.health-enabled", "false") assertHasProperty(t, generatedProps, "quarkus.devservices.enabled", "false") assertHasProperty(t, generatedProps, "quarkus.kogito.devservices.enabled", "false") assertHasProperty(t, generatedProps, constants.KogitoUserTasksEventsEnabled, "false") @@ -242,11 +240,12 @@ func Test_appPropertyHandler_WithServicesWithUserOverrides(t *testing.T) { assert.NoError(t, err) generatedProps, propsErr = properties.LoadString(props.WithUserProperties(userProperties).Build()) assert.NoError(t, propsErr) - assert.Equal(t, 16, len(generatedProps.Keys())) + assert.Equal(t, 17, len(generatedProps.Keys())) assert.NotContains(t, "property1", generatedProps.Keys()) assert.NotContains(t, "property2", generatedProps.Keys()) assert.Equal(t, "http://"+platform.Name+"-"+constants.DataIndexServiceName+"."+platform.Namespace+"/definitions", generatedProps.GetString(constants.KogitoProcessDefinitionsEventsURL, "")) assert.Equal(t, "true", generatedProps.GetString(constants.KogitoProcessDefinitionsEventsEnabled, "")) + assert.Equal(t, "true", generatedProps.GetString(constants.KogitoProcessDefinitionsEventsErrorsEnabled, "")) assert.Equal(t, "http://"+platform.Name+"-"+constants.DataIndexServiceName+"."+platform.Namespace+"/processes", generatedProps.GetString(constants.KogitoProcessInstancesEventsURL, "")) assert.Equal(t, "true", generatedProps.GetString(constants.KogitoProcessInstancesEventsEnabled, "")) assert.Equal(t, "http://"+platform.Name+"-"+constants.JobServiceName+"."+platform.Namespace+"/v2/jobs/events", generatedProps.GetString(constants.JobServiceRequestEventsURL, "")) @@ -275,7 +274,6 @@ func Test_appPropertyHandler_WithServicesWithUserOverrides(t *testing.T) { assert.Equal(t, "http://"+platform.Name+"-"+constants.JobServiceName+"."+platform.Namespace+"/v2/jobs/events", generatedProps.GetString(constants.JobServiceRequestEventsURL, "")) assert.Equal(t, "", generatedProps.GetString(constants.JobServiceStatusChangeEvents, "")) assert.Equal(t, "", generatedProps.GetString(constants.JobServiceStatusChangeEventsURL, "")) - assert.Equal(t, "http://"+platform.Name+"-"+constants.JobServiceName+"."+platform.Namespace, generatedProps.GetString(constants.KogitoJobServiceURL, "")) // disabling job service bypasses config of outgoing events url platform.Spec.Services.JobService.Enabled = nil @@ -529,6 +527,7 @@ func generateDataIndexWorkflowProductionProperties() *properties.Properties { dataIndexProdProperties.Set("mp.messaging.outgoing.kogito-processdefinitions-events.url", "http://foo-data-index-service.default/definitions") dataIndexProdProperties.Set("mp.messaging.outgoing.kogito-processinstances-events.url", "http://foo-data-index-service.default/processes") dataIndexProdProperties.Set("kogito.events.processdefinitions.enabled", "true") + dataIndexProdProperties.Set("kogito.events.processdefinitions.errors.propagate", "true") dataIndexProdProperties.Set("kogito.events.processinstances.enabled", "true") dataIndexProdProperties.Set("kogito.events.usertasks.enabled", "false") dataIndexProdProperties.Sort() @@ -570,6 +569,7 @@ func generateDataIndexAndJobServiceWorkflowProductionProperties() *properties.Pr dataIndexJobServiceProdProperties.Set("mp.messaging.outgoing.kogito-job-service-job-request-events.connector", "quarkus-http") dataIndexJobServiceProdProperties.Set("mp.messaging.outgoing.kogito-job-service-job-request-events.url", "http://foo-jobs-service.default/v2/jobs/events") dataIndexJobServiceProdProperties.Set("kogito.events.processdefinitions.enabled", "true") + dataIndexJobServiceProdProperties.Set("kogito.events.processdefinitions.errors.propagate", "true") dataIndexJobServiceProdProperties.Set("kogito.events.processinstances.enabled", "true") dataIndexJobServiceProdProperties.Set("kogito.events.usertasks.enabled", "false") dataIndexJobServiceProdProperties.Set("mp.messaging.outgoing.kogito-processdefinitions-events.url", "http://foo-data-index-service.default/definitions") @@ -665,11 +665,11 @@ func setJobServiceJDBC(jdbc string) plfmOptionFn { p.Spec.Services.JobService = &operatorapi.ServiceSpec{} } if p.Spec.Services.JobService.Persistence == nil { - p.Spec.Services.JobService.Persistence = &operatorapi.PersistenceOptions{} + p.Spec.Services.JobService.Persistence = &operatorapi.PersistenceOptionsSpec{} } - if p.Spec.Services.JobService.Persistence.PostgreSql == nil { - p.Spec.Services.JobService.Persistence.PostgreSql = &operatorapi.PersistencePostgreSql{} + if p.Spec.Services.JobService.Persistence.PostgreSQL == nil { + p.Spec.Services.JobService.Persistence.PostgreSQL = &operatorapi.PersistencePostgreSQL{} } - p.Spec.Services.JobService.Persistence.PostgreSql.JdbcUrl = jdbc + p.Spec.Services.JobService.Persistence.PostgreSQL.JdbcUrl = jdbc } } diff --git a/controllers/profiles/common/properties/properties.go b/controllers/profiles/common/properties/properties.go new file mode 100644 index 000000000..ec4c3b098 --- /dev/null +++ b/controllers/profiles/common/properties/properties.go @@ -0,0 +1,49 @@ +// Copyright 2024 Apache Software Foundation (ASF) +// +// 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 properties + +import ( + operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/constants" + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/workflowdef" + "github.com/magiconair/properties" + cncfmodel "github.com/serverlessworkflow/sdk-go/v2/model" +) + +// GenerateKnativeEventingWorkflowProperties returns the set of application properties required for the workflow to produce or consume +// Knative Events. +// Never nil. +func generateKnativeEventingWorkflowProperties(workflow *operatorapi.SonataFlow) (*properties.Properties, error) { + props := properties.NewProperties() + if workflow == nil || workflow.Spec.Sink == nil { + props.Set(constants.KnativeHealthEnabled, "false") + return props, nil + } + // verify ${K_SINK} + props.Set(constants.KnativeHealthEnabled, "true") + if workflowdef.ContainsEventKind(workflow, cncfmodel.EventKindProduced) { + props.Set(constants.KogitoOutgoingEventsConnector, constants.QuarkusHTTP) + props.Set(constants.KogitoOutgoingEventsURL, constants.KnativeInjectedEnvVar) + } + if workflowdef.ContainsEventKind(workflow, cncfmodel.EventKindConsumed) { + props.Set(constants.KogitoIncomingEventsConnector, constants.QuarkusHTTP) + var path = "/" + if workflow.Spec.Sink.URI != nil { + path = workflow.Spec.Sink.URI.Path + } + props.Set(constants.KogitoIncomingEventsPath, path) + } + return props, nil +} diff --git a/controllers/profiles/common/reconciler.go b/controllers/profiles/common/reconciler.go index 64ecd07ac..b2800005b 100644 --- a/controllers/profiles/common/reconciler.go +++ b/controllers/profiles/common/reconciler.go @@ -23,7 +23,10 @@ import ( "context" "fmt" + "k8s.io/client-go/rest" + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/discovery" + "k8s.io/client-go/tools/record" "k8s.io/klog/v2" @@ -38,12 +41,13 @@ import ( // StateSupport is the shared structure with common accessors used throughout the whole reconciliation profiles type StateSupport struct { C client.Client + Cfg *rest.Config Catalog discovery.ServiceCatalog Recorder record.EventRecorder } // PerformStatusUpdate updates the SonataFlow Status conditions -func (s StateSupport) PerformStatusUpdate(ctx context.Context, workflow *operatorapi.SonataFlow) (bool, error) { +func (s *StateSupport) PerformStatusUpdate(ctx context.Context, workflow *operatorapi.SonataFlow) (bool, error) { var err error workflow.Status.ObservedGeneration = workflow.Generation if err = s.C.Status().Update(ctx, workflow); err != nil { diff --git a/controllers/profiles/common/variables/k8s.go b/controllers/profiles/common/variables/k8s.go new file mode 100644 index 000000000..e15a27986 --- /dev/null +++ b/controllers/profiles/common/variables/k8s.go @@ -0,0 +1,24 @@ +// Copyright 2024 Apache Software Foundation (ASF) +// +// 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 variables + +import ( + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/constants" + "k8s.io/apimachinery/pkg/util/intstr" +) + +var ( + DefaultHTTPWorkflowPortIntStr = intstr.FromInt(constants.DefaultHTTPWorkflowPortInt) +) diff --git a/controllers/profiles/dev/object_creators_dev.go b/controllers/profiles/dev/object_creators_dev.go index 03b857502..63b7b8fe3 100644 --- a/controllers/profiles/dev/object_creators_dev.go +++ b/controllers/profiles/dev/object_creators_dev.go @@ -22,13 +22,13 @@ package dev import ( "path" + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" - "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/workflowdef" kubeutil "github.com/apache/incubator-kie-kogito-serverless-operator/utils/kubernetes" @@ -54,8 +54,9 @@ func serviceCreator(workflow *operatorapi.SonataFlow) (client.Object, error) { return service, nil } -func deploymentCreator(workflow *operatorapi.SonataFlow) (client.Object, error) { - obj, err := common.DeploymentCreator(workflow) +func deploymentCreator(workflow *operatorapi.SonataFlow, plf *operatorapi.SonataFlowPlatform) (client.Object, error) { + + obj, err := common.DeploymentCreator(workflow, plf) if err != nil { return nil, err } @@ -84,13 +85,13 @@ func workflowDefConfigMapCreator(workflow *operatorapi.SonataFlow) (client.Objec } // deploymentMutateVisitor guarantees the state of the default Deployment object -func deploymentMutateVisitor(workflow *operatorapi.SonataFlow) common.MutateVisitor { +func deploymentMutateVisitor(workflow *operatorapi.SonataFlow, plf *operatorapi.SonataFlowPlatform) common.MutateVisitor { return func(object client.Object) controllerutil.MutateFn { return func() error { if kubeutil.IsObjectNew(object) { return nil } - original, err := deploymentCreator(workflow) + original, err := deploymentCreator(workflow, plf) if err != nil { return err } @@ -150,7 +151,7 @@ func mountDevConfigMapsMutateVisitor(workflow *operatorapi.SonataFlow, flowDefCM } if len(deployment.Spec.Template.Spec.Volumes) == 0 { - deployment.Spec.Template.Spec.Volumes = make([]corev1.Volume, 0, len(resourceVolumes)+2) + deployment.Spec.Template.Spec.Volumes = make([]corev1.Volume, 0, len(resourceVolumes)+1) } kubeutil.AddOrReplaceVolume(&deployment.Spec.Template.Spec, defaultResourcesVolume) kubeutil.AddOrReplaceVolume(&deployment.Spec.Template.Spec, resourceVolumes...) diff --git a/controllers/profiles/dev/profile_dev.go b/controllers/profiles/dev/profile_dev.go index 42b9151ab..0bd5a3970 100644 --- a/controllers/profiles/dev/profile_dev.go +++ b/controllers/profiles/dev/profile_dev.go @@ -20,13 +20,13 @@ package dev import ( + "github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/discovery" "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common" "github.com/apache/incubator-kie-kogito-serverless-operator/log" @@ -46,6 +46,7 @@ func (d developmentProfile) GetProfile() metadata.ProfileType { func NewProfileReconciler(client client.Client, cfg *rest.Config, recorder record.EventRecorder) profiles.ProfileReconciler { support := &common.StateSupport{ C: client, + Cfg: cfg, Catalog: discovery.NewServiceCatalogForConfig(client, cfg), Recorder: recorder, } @@ -75,7 +76,7 @@ func NewProfileReconciler(client client.Client, cfg *rest.Config, recorder recor func newObjectEnsurers(support *common.StateSupport) *objectEnsurers { return &objectEnsurers{ - deployment: common.NewObjectEnsurer(support.C, deploymentCreator), + deployment: common.NewObjectEnsurerWithPlatform(support.C, deploymentCreator), service: common.NewObjectEnsurer(support.C, serviceCreator), network: common.NewNoopObjectEnsurer(), definitionConfigMap: common.NewObjectEnsurer(support.C, workflowDefConfigMapCreator), @@ -86,7 +87,7 @@ func newObjectEnsurers(support *common.StateSupport) *objectEnsurers { func newObjectEnsurersOpenShift(support *common.StateSupport) *objectEnsurers { return &objectEnsurers{ - deployment: common.NewObjectEnsurer(support.C, deploymentCreator), + deployment: common.NewObjectEnsurerWithPlatform(support.C, deploymentCreator), service: common.NewObjectEnsurer(support.C, serviceCreator), network: common.NewObjectEnsurer(support.C, common.OpenShiftRouteCreator), definitionConfigMap: common.NewObjectEnsurer(support.C, workflowDefConfigMapCreator), @@ -108,7 +109,7 @@ func newStatusEnrichersOpenShift(support *common.StateSupport) *statusEnrichers } type objectEnsurers struct { - deployment common.ObjectEnsurer + deployment common.ObjectEnsurerWithPlatform service common.ObjectEnsurer network common.ObjectEnsurer definitionConfigMap common.ObjectEnsurer diff --git a/controllers/profiles/dev/states_dev.go b/controllers/profiles/dev/states_dev.go index f98e03eb2..17bd5a80a 100644 --- a/controllers/profiles/dev/states_dev.go +++ b/controllers/profiles/dev/states_dev.go @@ -70,8 +70,11 @@ func (e *ensureRunningWorkflowState) Do(ctx context.Context, workflow *operatora devBaseContainerImage := workflowdef.GetDefaultWorkflowDevModeImageTag() // check if the Platform available - pl, err := platform.GetActivePlatform(ctx, e.C, workflow.Namespace) - if err == nil && len(pl.Spec.DevMode.BaseImage) > 0 { + pl, err := platform.GetActivePlatform(context.TODO(), e.C, workflow.Namespace) + if err != nil { + return ctrl.Result{Requeue: false}, objs, err + } + if pl != nil && len(pl.Spec.DevMode.BaseImage) > 0 { devBaseContainerImage = pl.Spec.DevMode.BaseImage } userPropsCM, _, err := e.ensurers.userPropsConfigMap.Ensure(ctx, workflow) @@ -93,8 +96,8 @@ func (e *ensureRunningWorkflowState) Do(ctx context.Context, workflow *operatora return ctrl.Result{RequeueAfter: constants.RequeueAfterFailure}, objs, nil } - deployment, _, err := e.ensurers.deployment.Ensure(ctx, workflow, - deploymentMutateVisitor(workflow), + deployment, _, err := e.ensurers.deployment.Ensure(ctx, workflow, pl, + deploymentMutateVisitor(workflow, pl), common.ImageDeploymentMutateVisitor(workflow, devBaseContainerImage), mountDevConfigMapsMutateVisitor(workflow, flowDefCM.(*corev1.ConfigMap), userPropsCM.(*corev1.ConfigMap), managedPropsCM.(*corev1.ConfigMap), externalCM)) if err != nil { @@ -114,6 +117,12 @@ func (e *ensureRunningWorkflowState) Do(ctx context.Context, workflow *operatora } objs = append(objs, route) + if knativeObjs, err := common.NewKnativeEventingHandler(e.StateSupport).Ensure(ctx, workflow); err != nil { + return ctrl.Result{RequeueAfter: constants.RequeueAfterFailure}, objs, err + } else { + objs = append(objs, knativeObjs...) + } + // First time reconciling this object, mark as wait for deployment if workflow.Status.GetTopLevelCondition().IsUnknown() { klog.V(log.I).InfoS("Workflow is in WaitingForDeployment Condition") diff --git a/controllers/profiles/prod/deployment_handler.go b/controllers/profiles/prod/deployment_handler.go index 3b693429d..af999e5aa 100644 --- a/controllers/profiles/prod/deployment_handler.go +++ b/controllers/profiles/prod/deployment_handler.go @@ -67,7 +67,8 @@ func (d *deploymentReconciler) reconcileWithBuiltImage(ctx context.Context, work d.ensurers.deployment.Ensure( ctx, workflow, - d.getDeploymentMutateVisitors(workflow, image, userPropsCM.(*v1.ConfigMap), managedPropsCM.(*v1.ConfigMap))..., + pl, + d.getDeploymentMutateVisitors(workflow, pl, image, userPropsCM.(*v1.ConfigMap), managedPropsCM.(*v1.ConfigMap))..., ) if err != nil { workflow.Status.Manager().MarkFalse(api.RunningConditionType, api.DeploymentUnavailableReason, "Unable to perform the deploy due to ", err) @@ -82,7 +83,12 @@ func (d *deploymentReconciler) reconcileWithBuiltImage(ctx context.Context, work return reconcile.Result{}, nil, err } + knativeObjs, err := common.NewKnativeEventingHandler(d.StateSupport).Ensure(ctx, workflow) + if err != nil { + return ctrl.Result{RequeueAfter: constants.RequeueAfterFailure}, nil, err + } objs := []client.Object{deployment, service, managedPropsCM} + objs = append(objs, knativeObjs...) if deploymentOp == controllerutil.OperationResultCreated { workflow.Status.Manager().MarkFalse(api.RunningConditionType, api.WaitingForDeploymentReason, "") @@ -106,18 +112,19 @@ func (d *deploymentReconciler) reconcileWithBuiltImage(ctx context.Context, work func (d *deploymentReconciler) getDeploymentMutateVisitors( workflow *operatorapi.SonataFlow, + plf *operatorapi.SonataFlowPlatform, image string, userPropsCM *v1.ConfigMap, managedPropsCM *v1.ConfigMap) []common.MutateVisitor { if utils.IsOpenShift() { - return []common.MutateVisitor{common.DeploymentMutateVisitor(workflow), + return []common.MutateVisitor{common.DeploymentMutateVisitor(workflow, plf), mountProdConfigMapsMutateVisitor(workflow, userPropsCM, managedPropsCM), addOpenShiftImageTriggerDeploymentMutateVisitor(workflow, image), common.ImageDeploymentMutateVisitor(workflow, image), common.RolloutDeploymentIfCMChangedMutateVisitor(workflow, userPropsCM, managedPropsCM), } } - return []common.MutateVisitor{common.DeploymentMutateVisitor(workflow), + return []common.MutateVisitor{common.DeploymentMutateVisitor(workflow, plf), common.ImageDeploymentMutateVisitor(workflow, image), mountProdConfigMapsMutateVisitor(workflow, userPropsCM, managedPropsCM), common.RolloutDeploymentIfCMChangedMutateVisitor(workflow, userPropsCM, managedPropsCM)} diff --git a/controllers/profiles/prod/profile_prod.go b/controllers/profiles/prod/profile_prod.go index f5046d05f..b33532685 100644 --- a/controllers/profiles/prod/profile_prod.go +++ b/controllers/profiles/prod/profile_prod.go @@ -22,6 +22,7 @@ package prod import ( "time" + "github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata" "k8s.io/client-go/rest" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/discovery" @@ -29,7 +30,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common" ) @@ -52,7 +52,7 @@ const ( // ReconciliationState that needs access to it must include this struct as an attribute and initialize it in the profile builder. // Use newObjectEnsurers to facilitate building this struct type objectEnsurers struct { - deployment common.ObjectEnsurer + deployment common.ObjectEnsurerWithPlatform service common.ObjectEnsurer userPropsConfigMap common.ObjectEnsurer managedPropsConfigMap common.ObjectEnsurerWithPlatform @@ -60,7 +60,7 @@ type objectEnsurers struct { func newObjectEnsurers(support *common.StateSupport) *objectEnsurers { return &objectEnsurers{ - deployment: common.NewObjectEnsurer(support.C, common.DeploymentCreator), + deployment: common.NewObjectEnsurerWithPlatform(support.C, common.DeploymentCreator), service: common.NewObjectEnsurer(support.C, common.ServiceCreator), userPropsConfigMap: common.NewObjectEnsurer(support.C, common.UserPropsConfigMapCreator), managedPropsConfigMap: common.NewObjectEnsurerWithPlatform(support.C, common.ManagedPropsConfigMapCreator), @@ -72,6 +72,7 @@ func newObjectEnsurers(support *common.StateSupport) *objectEnsurers { func NewProfileReconciler(client client.Client, cfg *rest.Config, recorder record.EventRecorder) profiles.ProfileReconciler { support := &common.StateSupport{ C: client, + Cfg: cfg, Catalog: discovery.NewServiceCatalogForConfig(client, cfg), Recorder: recorder, } @@ -93,6 +94,7 @@ func NewProfileReconciler(client client.Client, cfg *rest.Config, recorder recor func NewProfileForOpsReconciler(client client.Client, cfg *rest.Config, recorder record.EventRecorder) profiles.ProfileReconciler { support := &common.StateSupport{ C: client, + Cfg: cfg, Catalog: discovery.NewServiceCatalogForConfig(client, cfg), Recorder: recorder, } diff --git a/controllers/profiles/prod/profile_prod_test.go b/controllers/profiles/prod/profile_prod_test.go index 24a23a0bc..5ca93a97f 100644 --- a/controllers/profiles/prod/profile_prod_test.go +++ b/controllers/profiles/prod/profile_prod_test.go @@ -35,11 +35,15 @@ import ( clientruntime "sigs.k8s.io/controller-runtime/pkg/client" ) +var ( + emptyPlatform = &operatorapi.SonataFlowPlatform{} +) + func Test_Reconciler_ProdOps(t *testing.T) { workflow := test.GetBaseSonataFlowWithProdOpsProfile(t.Name()) workflow.Spec.PodTemplate.PodSpec.InitContainers = append(workflow.Spec.PodTemplate.PodSpec.InitContainers, corev1.Container{ Name: "check-postgres", - Image: "registry.access.redhat.com/ubi9/ubi-minimal:latest", + Image: "registry.access.redhat.com/ubi9/ubi-micro:latest", Command: []string{"sh", "-c", "until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo \"Waiting for postgres server\"; sleep 3; done;"}, }) client := test.NewSonataFlowClientBuilder(). @@ -74,7 +78,7 @@ func Test_Reconciler_ProdCustomPod(t *testing.T) { workflow := test.GetBaseSonataFlowWithProdProfile(t.Name()) workflow.Spec.PodTemplate.PodSpec.InitContainers = append(workflow.Spec.PodTemplate.PodSpec.InitContainers, corev1.Container{ Name: "check-postgres", - Image: "registry.access.redhat.com/ubi9/ubi-minimal:latest", + Image: "registry.access.redhat.com/ubi9/ubi-micro:latest", Command: []string{"sh", "-c", "until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo \"Waiting for postgres server\"; sleep 3; done;"}, }) workflow.Status.Manager().MarkTrue(api.BuiltConditionType) diff --git a/controllers/profiles/profile.go b/controllers/profiles/profile.go index da981c75f..794ca7a70 100644 --- a/controllers/profiles/profile.go +++ b/controllers/profiles/profile.go @@ -22,10 +22,11 @@ package profiles import ( "context" + "github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata" + "github.com/apache/incubator-kie-kogito-serverless-operator/workflowproj" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata" operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" ) @@ -73,20 +74,5 @@ type ReconciliationState interface { PostReconcile(ctx context.Context, workflow *operatorapi.SonataFlow) error } -// IsDevProfile detects if the workflow is using the Dev profile or not -func IsDevProfile(workflow *operatorapi.SonataFlow) bool { - profile := workflow.Annotations[metadata.Profile] - if len(profile) == 0 { - return false - } - return metadata.ProfileType(profile) == metadata.DevProfile -} - -// IsProdProfile detects if the workflow is using the Prod profile or not -func IsProdProfile(workflow *operatorapi.SonataFlow) bool { - profile := workflow.Annotations[metadata.Profile] - if len(profile) == 0 { - return false - } - return metadata.ProfileType(profile) == metadata.ProdProfile -} +// IsDevProfile is an alias for workflowproj.IsDevProfile +var IsDevProfile = workflowproj.IsDevProfile diff --git a/controllers/sonataflow_controller.go b/controllers/sonataflow_controller.go index e635f3b3d..447e386f6 100644 --- a/controllers/sonataflow_controller.go +++ b/controllers/sonataflow_controller.go @@ -94,7 +94,6 @@ func (r *SonataFlowReconciler) Reconcile(ctx context.Context, req ctrl.Request) klog.V(log.I).InfoS("Ignoring request because resource is not assigned to current operator") return reconcile.Result{}, nil } - return profiles.NewReconciler(r.Client, r.Config, r.Recorder, workflow).Reconcile(ctx, workflow) } diff --git a/controllers/sonataflowplatform_controller.go b/controllers/sonataflowplatform_controller.go index c0ccd3991..a26db2fbe 100644 --- a/controllers/sonataflowplatform_controller.go +++ b/controllers/sonataflowplatform_controller.go @@ -203,11 +203,13 @@ func (r *SonataFlowPlatformReconciler) SonataFlowPlatformUpdateStatus(ctx contex }, } - tpsDI := services.NewDataIndexHandler(target) - tpsDI.SetServiceUrlInStatus(sfPlatform) + if sfcPlatform.Spec.Capabilities != nil && contains(sfcPlatform.Spec.Capabilities.Workflows, clusterplatform.PlatformServices) { + tpsDI := services.NewDataIndexHandler(target) + tpsDI.SetServiceUrlInStatus(sfPlatform) - tpsJS := services.NewJobServiceHandler(target) - tpsJS.SetServiceUrlInStatus(sfPlatform) + tpsJS := services.NewJobServiceHandler(target) + tpsJS.SetServiceUrlInStatus(sfPlatform) + } } else { target.Status.ClusterPlatformRef = nil } @@ -273,3 +275,12 @@ func (r *SonataFlowPlatformReconciler) platformRequests(ctx context.Context, sfc } return requests } + +func contains(slice []operatorapi.WorkFlowCapability, s operatorapi.WorkFlowCapability) bool { + for _, a := range slice { + if a == s { + return true + } + } + return false +} diff --git a/controllers/sonataflowplatform_controller_test.go b/controllers/sonataflowplatform_controller_test.go index 11e5bb932..abfffb20e 100644 --- a/controllers/sonataflowplatform_controller_test.go +++ b/controllers/sonataflowplatform_controller_test.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" + "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/clusterplatform" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/platform/services" "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/constants" "github.com/apache/incubator-kie-kogito-serverless-operator/test" @@ -77,7 +78,6 @@ func TestSonataFlowPlatformController(t *testing.T) { assert.Equal(t, "quay.io/kiegroup", ksp.Spec.Build.Config.Registry.Address) assert.Equal(t, "regcred", ksp.Spec.Build.Config.Registry.Secret) assert.Equal(t, v1alpha08.OperatorBuildStrategy, ksp.Spec.Build.Config.BuildStrategy) - assert.Nil(t, ksp.Spec.Services) assert.Equal(t, v1alpha08.PlatformClusterKubernetes, ksp.Status.Cluster) assert.Equal(t, v1alpha08.PlatformCreatingReason, ksp.Status.GetTopLevelCondition().Reason) @@ -137,9 +137,9 @@ func TestSonataFlowPlatformController(t *testing.T) { assert.NotContains(t, dep.Spec.Template.Spec.Containers[0].Env, env) // Check with persistence set - ksp.Spec.Services.DataIndex.Persistence = &v1alpha08.PersistenceOptions{PostgreSql: &v1alpha08.PersistencePostgreSql{ - SecretRef: v1alpha08.PostgreSqlSecretOptions{Name: "test"}, - ServiceRef: &v1alpha08.PostgreSqlServiceOptions{Name: "test"}, + ksp.Spec.Services.DataIndex.Persistence = &v1alpha08.PersistenceOptionsSpec{PostgreSQL: &v1alpha08.PersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "test"}, + ServiceRef: &v1alpha08.PostgreSQLServiceOptions{SQLServiceOptions: &v1alpha08.SQLServiceOptions{Name: "test"}}, }} // Ensure correct container overriding anything set in PodSpec ksp.Spec.Services.DataIndex.PodTemplate.Container = v1alpha08.ContainerSpec{TerminationMessagePath: "testing"} @@ -224,8 +224,8 @@ func TestSonataFlowPlatformController(t *testing.T) { // Check with persistence set url := "jdbc:postgresql://host:1234/database?currentSchema=data-index-service" - ksp.Spec.Services.DataIndex.Persistence = &v1alpha08.PersistenceOptions{PostgreSql: &v1alpha08.PersistencePostgreSql{ - SecretRef: v1alpha08.PostgreSqlSecretOptions{Name: "test"}, + ksp.Spec.Services.DataIndex.Persistence = &v1alpha08.PersistenceOptionsSpec{PostgreSQL: &v1alpha08.PersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "test"}, JdbcUrl: url, }} // Ensure correct container overriding anything set in PodSpec @@ -250,6 +250,217 @@ func TestSonataFlowPlatformController(t *testing.T) { assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, env2) }) + var ( + postgreSQLPort int = 5432 + ) + t.Run("verify that persistence options are correctly reconciled when defined in the platform", func(t *testing.T) { + namespace := t.Name() + // Create a SonataFlowPlatform object with metadata and spec. + ksp := test.GetBasePlatformInReadyPhase(namespace) + // Check with persistence set + ksp.Spec = v1alpha08.SonataFlowPlatformSpec{ + Services: &v1alpha08.ServicesPlatformSpec{ + DataIndex: &v1alpha08.ServiceSpec{}, + JobService: &v1alpha08.ServiceSpec{}, + }, + Persistence: &v1alpha08.PlatformPersistenceOptionsSpec{ + PostgreSQL: &v1alpha08.PlatformPersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "generic", UserKey: "POSTGRESQL_USER", PasswordKey: "POSTGRESQL_PASSWORD"}, + ServiceRef: &v1alpha08.SQLServiceOptions{ + Name: "postgresql", + Namespace: "default", + Port: &postgreSQLPort, + DatabaseName: "sonataflow"}, + }, + }, + } + + // Create a fake client to mock API calls. + cl := test.NewKogitoClientBuilderWithOpenShift().WithRuntimeObjects(ksp).WithStatusSubresource(ksp).Build() + // Create a SonataFlowPlatformReconciler object with the scheme and fake client. + r := &SonataFlowPlatformReconciler{cl, cl, cl.Scheme(), &rest.Config{}, &record.FakeRecorder{}} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: ksp.Name, + Namespace: ksp.Namespace, + }, + } + _, err := r.Reconcile(context.TODO(), req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + dbSourceKind := corev1.EnvVar{ + Name: "QUARKUS_DATASOURCE_DB_KIND", + Value: constants.PersistenceTypePostgreSQL, + } + dbSourceDIURL := corev1.EnvVar{ + Name: "QUARKUS_DATASOURCE_JDBC_URL", + Value: "jdbc:postgresql://postgresql.default:5432/sonataflow?currentSchema=sonataflow-platform-data-index-service", + } + dbSourceJSURL := corev1.EnvVar{ + Name: "QUARKUS_DATASOURCE_JDBC_URL", + Value: "jdbc:postgresql://postgresql.default:5432/sonataflow?currentSchema=sonataflow-platform-jobs-service", + } + dbUsername := corev1.EnvVar{ + Name: "QUARKUS_DATASOURCE_USERNAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "generic"}, + Key: "POSTGRESQL_USER", + }, + }, + } + dbPassword := corev1.EnvVar{ + Name: "QUARKUS_DATASOURCE_PASSWORD", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "generic"}, + Key: "POSTGRESQL_PASSWORD", + }, + }, + } + // Check Data Index deployment to ensure it contains references to the persistence values defined in the platform CR + dep := &appsv1.Deployment{} + di := services.NewDataIndexHandler(ksp) + assert.NoError(t, cl.Get(context.TODO(), types.NamespacedName{Name: di.GetServiceName(), Namespace: ksp.Namespace}, dep)) + assert.Len(t, dep.Spec.Template.Spec.Containers, 1) + assert.Equal(t, di.GetServiceImageName(constants.PersistenceTypePostgreSQL), dep.Spec.Template.Spec.Containers[0].Image) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbSourceKind) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbUsername) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbPassword) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbSourceDIURL) + + js := services.NewJobServiceHandler(ksp) + assert.NoError(t, cl.Get(context.TODO(), types.NamespacedName{Name: js.GetServiceName(), Namespace: ksp.Namespace}, dep)) + assert.Len(t, dep.Spec.Template.Spec.Containers, 1) + assert.Equal(t, js.GetServiceImageName(constants.PersistenceTypePostgreSQL), dep.Spec.Template.Spec.Containers[0].Image) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbSourceKind) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbUsername) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbPassword) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbSourceJSURL) + }) + + t.Run("verify that persistence options are correctly reconciled when defined in the platform and overwriten in the services spec", func(t *testing.T) { + namespace := t.Name() + // Create a SonataFlowPlatform object with metadata and spec. + ksp := test.GetBasePlatformInReadyPhase(namespace) + // Check with persistence set + urlDI := "jdbc:postgresql://localhost:5432/database?currentSchema=data-index-service" + urlJS := "jdbc:postgresql://localhost:5432/database?currentSchema=job-service" + ksp.Spec = v1alpha08.SonataFlowPlatformSpec{ + Services: &v1alpha08.ServicesPlatformSpec{ + DataIndex: &v1alpha08.ServiceSpec{ + Persistence: &v1alpha08.PersistenceOptionsSpec{ + PostgreSQL: &v1alpha08.PersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "dataIndex"}, + JdbcUrl: urlDI, + }, + }, + }, + JobService: &v1alpha08.ServiceSpec{ + Persistence: &v1alpha08.PersistenceOptionsSpec{ + PostgreSQL: &v1alpha08.PersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "job"}, + JdbcUrl: urlJS, + }, + }, + }, + }, + Persistence: &v1alpha08.PlatformPersistenceOptionsSpec{ + PostgreSQL: &v1alpha08.PlatformPersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "generic", UserKey: "POSTGRESQL_USER", PasswordKey: "POSTGRESQL_PASSWORD"}, + ServiceRef: &v1alpha08.SQLServiceOptions{Name: "postgresql", Namespace: "default", Port: &postgreSQLPort, DatabaseName: "sonataflow"}, + }, + }, + } + + // Create a fake client to mock API calls. + cl := test.NewKogitoClientBuilderWithOpenShift().WithRuntimeObjects(ksp).WithStatusSubresource(ksp).Build() + // Create a SonataFlowPlatformReconciler object with the scheme and fake client. + r := &SonataFlowPlatformReconciler{cl, cl, cl.Scheme(), &rest.Config{}, &record.FakeRecorder{}} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: ksp.Name, + Namespace: ksp.Namespace, + }, + } + _, err := r.Reconcile(context.TODO(), req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + dbSourceKind := corev1.EnvVar{ + Name: "QUARKUS_DATASOURCE_DB_KIND", + Value: constants.PersistenceTypePostgreSQL, + } + dbDIUsername := corev1.EnvVar{ + Name: "QUARKUS_DATASOURCE_USERNAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "dataIndex"}, + Key: "POSTGRESQL_USER", + }, + }, + } + dbDIPassword := corev1.EnvVar{ + Name: "QUARKUS_DATASOURCE_PASSWORD", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "dataIndex"}, + Key: "POSTGRESQL_PASSWORD", + }, + }, + } + dbJSUsername := corev1.EnvVar{ + Name: "QUARKUS_DATASOURCE_USERNAME", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "job"}, + Key: "POSTGRESQL_USER", + }, + }, + } + dbJSPassword := corev1.EnvVar{ + Name: "QUARKUS_DATASOURCE_PASSWORD", + Value: "", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: "job"}, + Key: "POSTGRESQL_PASSWORD", + }, + }, + } + // Check Data Index deployment to ensure it contains references to the persistence values defined in the platform CR + dep := &appsv1.Deployment{} + di := services.NewDataIndexHandler(ksp) + assert.NoError(t, cl.Get(context.TODO(), types.NamespacedName{Name: di.GetServiceName(), Namespace: ksp.Namespace}, dep)) + assert.Len(t, dep.Spec.Template.Spec.Containers, 1) + assert.Equal(t, di.GetServiceImageName(constants.PersistenceTypePostgreSQL), dep.Spec.Template.Spec.Containers[0].Image) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbSourceKind) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbDIUsername) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbDIPassword) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{Name: "QUARKUS_DATASOURCE_JDBC_URL", Value: urlDI}) + + js := services.NewJobServiceHandler(ksp) + assert.NoError(t, cl.Get(context.TODO(), types.NamespacedName{Name: js.GetServiceName(), Namespace: ksp.Namespace}, dep)) + assert.Len(t, dep.Spec.Template.Spec.Containers, 1) + assert.Equal(t, js.GetServiceImageName(constants.PersistenceTypePostgreSQL), dep.Spec.Template.Spec.Containers[0].Image) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbSourceKind) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbJSUsername) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, dbJSPassword) + assert.Contains(t, dep.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{Name: "QUARKUS_DATASOURCE_JDBC_URL", Value: urlJS}) + }) + // Job Service tests t.Run("verify that a basic reconcile with job service & persistence is performed without error", func(t *testing.T) { namespace := t.Name() @@ -302,9 +513,9 @@ func TestSonataFlowPlatformController(t *testing.T) { assert.NotContains(t, dep.Spec.Template.Spec.Containers[0].Env, envDataIndex) // Check with persistence set - ksp.Spec.Services.JobService.Persistence = &v1alpha08.PersistenceOptions{PostgreSql: &v1alpha08.PersistencePostgreSql{ - SecretRef: v1alpha08.PostgreSqlSecretOptions{Name: "test"}, - ServiceRef: &v1alpha08.PostgreSqlServiceOptions{Name: "test"}, + ksp.Spec.Services.JobService.Persistence = &v1alpha08.PersistenceOptionsSpec{PostgreSQL: &v1alpha08.PersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "test"}, + ServiceRef: &v1alpha08.PostgreSQLServiceOptions{SQLServiceOptions: &v1alpha08.SQLServiceOptions{Name: "test"}}, }} // Ensure correct container overriding anything set in PodSpec ksp.Spec.Services.JobService.PodTemplate.Container = v1alpha08.ContainerSpec{TerminationMessagePath: "testing"} @@ -387,8 +598,8 @@ func TestSonataFlowPlatformController(t *testing.T) { // Check with persistence set url := "jdbc:postgresql://host:1234/database?currentSchema=data-index-service" - ksp.Spec.Services.JobService.Persistence = &v1alpha08.PersistenceOptions{PostgreSql: &v1alpha08.PersistencePostgreSql{ - SecretRef: v1alpha08.PostgreSqlSecretOptions{Name: "test"}, + ksp.Spec.Services.JobService.Persistence = &v1alpha08.PersistenceOptionsSpec{PostgreSQL: &v1alpha08.PersistencePostgreSQL{ + SecretRef: v1alpha08.PostgreSQLSecretOptions{Name: "test"}, JdbcUrl: url, }} // Ensure correct container overriding anything set in PodSpec @@ -543,6 +754,8 @@ func TestSonataFlowPlatformController(t *testing.T) { assert.Equal(t, kscp.Name, ksp.Status.ClusterPlatformRef.Name) assert.Equal(t, kscp.Spec.PlatformRef.Name, ksp.Status.ClusterPlatformRef.PlatformRef.Name) assert.Equal(t, kscp.Spec.PlatformRef.Namespace, ksp.Status.ClusterPlatformRef.PlatformRef.Namespace) + assert.NotNil(t, kscp.Spec.Capabilities) + assert.Contains(t, kscp.Spec.Capabilities.Workflows, clusterplatform.PlatformServices) assert.NotNil(t, ksp.Status.ClusterPlatformRef) assert.Nil(t, ksp.Status.ClusterPlatformRef.Services) @@ -593,5 +806,25 @@ func TestSonataFlowPlatformController(t *testing.T) { assert.Equal(t, kscp.Spec.PlatformRef.Name, ksp2.Status.ClusterPlatformRef.PlatformRef.Name) assert.Equal(t, kscp.Spec.PlatformRef.Namespace, ksp2.Status.ClusterPlatformRef.PlatformRef.Namespace) assert.Nil(t, ksp2.Status.ClusterPlatformRef.Services) + + kscp.Spec.Capabilities = &v1alpha08.SonataFlowClusterPlatformCapSpec{} + assert.NoError(t, cl.Update(context.TODO(), kscp)) + _, err = cr.Reconcile(context.TODO(), cReq) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + _, err = r.Reconcile(context.TODO(), req2) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + assert.NoError(t, cl.Get(context.TODO(), types.NamespacedName{Name: kscp.Name}, kscp)) + assert.NoError(t, cl.Get(context.TODO(), types.NamespacedName{Name: ksp2.Name, Namespace: ksp2.Namespace}, ksp2)) + + assert.NotNil(t, kscp.Spec.Capabilities) + assert.Empty(t, kscp.Spec.Capabilities.Workflows) + assert.NotNil(t, ksp2.Status.ClusterPlatformRef) + assert.Nil(t, ksp2.Status.ClusterPlatformRef.Services) }) } diff --git a/controllers/workflowdef/image.go b/controllers/workflowdef/image.go index 982cf10ad..760a233e3 100644 --- a/controllers/workflowdef/image.go +++ b/controllers/workflowdef/image.go @@ -20,7 +20,6 @@ package workflowdef import ( - "github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata" "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" "github.com/apache/incubator-kie-kogito-serverless-operator/version" ) @@ -33,12 +32,19 @@ const ( defaultOperatorImage = "quay.io/kiegroup/kogito-serverless-operator" ) -// GetWorkflowAppImageNameTag retrieve the tag for the image based on the Workflow based annotation, :latest otherwise +// GetWorkflowAppImageNameTag returns the image name with tag to use for the image to be produced for a given workflow. +// Before, we generated the tags based on the workflow version annotation, however this produced the following undesired +// effects. Empirically, it was detected that, if we deploy a workflow several times, for instance, the workflow is deleted +// for a modification, and then deployed again. When the build cycle is produced, etc., if the workflow version +// remains the same, e.g. 1.0.0, the bits for the new image are not written in the respective registry (because an image +// with the given tag already exists), and thus, when the workflow executes the old bits are executed. +// To avoid this, the workflow version must be changed, for example to 2.0.0, and thus the subsequent image will have +// a different tag, and the expected bits will be stored at the registry and finally executed. +// This workflow version bump must be produced by the users, but we don't have control over this. +// So by now, considering that the operator images build is oriented to "dev" and "preview" scenarios, and +// not for "production" scenarios, we decided to use "latest" as the tag. In that way, we ensure that the last image +// produced bits will be used to execute a given workflow. func GetWorkflowAppImageNameTag(w *v1alpha08.SonataFlow) string { - v := w.Annotations[metadata.Version] - if v != "" { - return w.Name + ":" + v - } return w.Name + ":" + latestImageTag } diff --git a/controllers/workflowdef/services.go b/controllers/workflowdef/services.go new file mode 100644 index 000000000..03db8ef02 --- /dev/null +++ b/controllers/workflowdef/services.go @@ -0,0 +1,29 @@ +// Copyright 2024 Apache Software Foundation (ASF) +// +// 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 workflowdef + +import ( + operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" + cncfmodel "github.com/serverlessworkflow/sdk-go/v2/model" +) + +func ContainsEventKind(workflow *operatorapi.SonataFlow, eventKind cncfmodel.EventKind) bool { + for _, event := range workflow.Spec.Flow.Events { + if event.Kind == eventKind { + return true + } + } + return false +} diff --git a/controllers/workflows/constants.go b/controllers/workflows/constants.go new file mode 100644 index 000000000..afcf157a1 --- /dev/null +++ b/controllers/workflows/constants.go @@ -0,0 +1,24 @@ +// Copyright 2024 Apache Software Foundation (ASF) +// +// 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 workflows + +const ( + QuarkusFlywayMigrateAtStart string = "quarkus.flyway.migrate-at-start" + QuarkusDatasourceJDBCURL string = "quarkus.datasource.jdbc.url" + KogitoPersistenceType string = "kogito.persistence.type" + JDBCPersistenceType string = "jdbc" + KogitoPersistenceQueryTimeoutMillis string = "kogito.persistence.query.timeout.millis" + KogitoPersistenceProtoMarshaller string = "kogito.persistence.proto.marshaller" +) diff --git a/go.mod b/go.mod index 29cd71aab..0dba2eeea 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/blendle/zapdriver v1.3.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cloudevents/sdk-go/v2 v2.4.1 // indirect + github.com/cloudevents/sdk-go/v2 v2.15.2 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect diff --git a/go.sum b/go.sum index 23f262155..422a7db53 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,9 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudevents/conformance v0.2.0/go.mod h1:rHKDwylBH89Rns6U3wL9ww8bg9/4GbwRCDNuyoC6bcc= github.com/cloudevents/sdk-go/observability/opencensus/v2 v2.4.1/go.mod h1:lhEpxMrIUkeu9rVRgoAbyqZ8GR8Hd3DUy+thHUxAHoI= -github.com/cloudevents/sdk-go/v2 v2.4.1 h1:rZJoz9QVLbWQmnvLPDFEmv17Czu+CfSPwMO6lhJ72xQ= github.com/cloudevents/sdk-go/v2 v2.4.1/go.mod h1:MZiMwmAh5tGj+fPFvtHv9hKurKqXtdB9haJYMJ/7GJY= +github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc= +github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6tjmaQBSvxcv4uE= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= diff --git a/go.work.sum b/go.work.sum index c178d4e49..ac93bf1bb 100644 --- a/go.work.sum +++ b/go.work.sum @@ -704,6 +704,8 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/cloudevents/conformance v0.2.0 h1:NvSXOKlagcsOWMEbi8U7Ex/0oQ4JZE1HQ45bnxYf2zk= github.com/cloudevents/sdk-go/observability/opencensus/v2 v2.4.1 h1:UHjY9+DJyjELyFA8vU/KHHXix1F1z7QLFskzdJZkP+0= +github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc= +github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6tjmaQBSvxcv4uE= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= @@ -1115,7 +1117,6 @@ github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-migrate/migrate/v4 v4.6.2 h1:LDDOHo/q1W5UDj6PbkxdCv7lv9yunyZHXvxuwDkGo3k= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= @@ -1315,7 +1316,6 @@ github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kshvakov/clickhouse v1.3.5 h1:PDTYk9VYgbjPAWry3AoDREeMgOVUFij6bh6IjlloHL0= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= @@ -1446,7 +1446,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446 h1:/NRJ5vAYo github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 h1:Lt9DzQALzHoDwMBGJ6v8ObDPR0dzr2a6sXTB1Fq7IHs= github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= @@ -1615,7 +1614,7 @@ go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnw go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= @@ -1634,7 +1633,6 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -1772,7 +1770,6 @@ google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFl google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:CCviP9RmpZ1mxVr8MUjCnSiY09IbAXZxhLE6EhHIdPU= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= diff --git a/hack/ci/create-kind-cluster-with-registry.sh b/hack/ci/create-kind-cluster-with-registry.sh index bb341a114..2feb2c254 100755 --- a/hack/ci/create-kind-cluster-with-registry.sh +++ b/hack/ci/create-kind-cluster-with-registry.sh @@ -64,9 +64,11 @@ fi # # We want a consistent name that works from both ends, so we tell containerd to # alias localhost:${reg_port} to the registry container when pulling images -REGISTRY_DIR="/etc/containerd/certs.d/172.18.0.3:5000" -# retrieve IP address of the container connected to the cluster network + +# Retrieve IP address of the container connected to the cluster network IP_ADDRESS=$(docker inspect --format='{{(index (index .NetworkSettings.Networks "kind") ).IPAddress}}' ${reg_name}) + +REGISTRY_DIR="/etc/containerd/certs.d/${IP_ADDRESS}:5000" for node in $(kind get nodes); do docker exec "${node}" mkdir -p "${REGISTRY_DIR}" cat < 0 { + cmd := exec.Command("kubectl", "delete", "namespace", targetNamespace, "--wait") + _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + } + if len(targetNamespace2) > 0 { + cmd := exec.Command("kubectl", "delete", "namespace", targetNamespace2, "--wait") + _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + } + } + }) + var _ = Context("with supporting services enabled", func() { + + DescribeTable("against a platform in a separate namespace", func(testcaseDir string, profile string, persistenceType string, withServices bool) { + By("Deploy the SonataFlowPlatform CR") + var manifests []byte + EventuallyWithOffset(1, func() error { + var err error + cmd := exec.Command("kubectl", "kustomize", filepath.Join(projectDir, + test.GetSonataFlowE2EPlatformServicesDirectory(), profile, clusterWideEphemeral)) + manifests, err = utils.Run(cmd) + return err + }, time.Minute, time.Second).Should(Succeed()) + + cmd := exec.Command("kubectl", "create", "-n", targetNamespace, "-f", "-") + cmd.Stdin = bytes.NewBuffer(manifests) + _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + + By("Wait for SonatatFlowPlatform CR in " + targetNamespace + " to be ready") + // wait for platform to be ready + EventuallyWithOffset(1, func() error { + cmd = exec.Command("kubectl", "wait", "sfplatform", "-n", targetNamespace, "sonataflow-platform", "--for", "condition=Succeed", "--timeout=5s") + _, err = utils.Run(cmd) + return err + }, 20*time.Minute, 5).Should(Succeed()) + EventuallyWithOffset(1, func() []byte { + cmd = exec.Command("kubectl", "get", "sfplatform", "-n", targetNamespace, "sonataflow-platform", "-o", "jsonpath='{.status.clusterPlatformRef}'") + returnedValue, _ := utils.Run(cmd) + println(string(returnedValue)) + return returnedValue + }, 20*time.Minute, 5).Should(Equal([]byte("''"))) + + By("Evaluate status of SonataFlowClusterPlatform CR") + cmd = exec.Command("kubectl", "patch", "SonataFlowClusterPlatform", "cluster", "--type", "merge", "-p", `{"spec": {"platformRef": {"namespace": "`+targetNamespace+`"}}}`) + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + EventuallyWithOffset(1, func() error { + cmd = exec.Command("kubectl", "wait", "SonataFlowClusterPlatform", "cluster", "--for", "condition=Succeed", "--timeout=5s") + _, err = utils.Run(cmd) + return err + }, 20*time.Minute, 5).Should(Succeed()) + + if withServices { + By("Deploy SonatatFlowPlatform CR with services configured in " + targetNamespace2) + EventuallyWithOffset(1, func() error { + var err error + cmd := exec.Command("kubectl", "kustomize", filepath.Join(projectDir, + testcaseDir, profile, persistenceType)) + manifests, err = utils.Run(cmd) + return err + }, time.Minute, time.Second).Should(Succeed()) + + cmd = exec.Command("kubectl", "create", "-n", targetNamespace2, "-f", "-") + cmd.Stdin = bytes.NewBuffer(manifests) + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + + By("Wait for SonatatFlowPlatform CR in " + targetNamespace2 + " to be ready") + // wait for platform to be ready + EventuallyWithOffset(1, func() error { + cmd = exec.Command("kubectl", "wait", "sfplatform", "-n", targetNamespace2, "sonataflow-platform", "--for", "condition=Succeed", "--timeout=5s") + _, err = utils.Run(cmd) + return err + }, 20*time.Minute, 5).Should(Succeed()) + EventuallyWithOffset(1, func() []byte { + cmd = exec.Command("kubectl", "get", "sfplatform", "-n", targetNamespace2, "sonataflow-platform", "-o", "jsonpath='{.status.clusterPlatformRef}'") + returnedValue, _ := utils.Run(cmd) + return returnedValue + }, 20*time.Minute, 5).Should(Not(Equal([]byte("''")))) + EventuallyWithOffset(1, func() []byte { + cmd = exec.Command("kubectl", "get", "sfplatform", "-n", targetNamespace2, "sonataflow-platform", "-o", "jsonpath='{.status.clusterPlatformRef.services}'") + returnedValue, _ := utils.Run(cmd) + return returnedValue + }, 20*time.Minute, 5).Should(Equal([]byte("''"))) + } else { + EventuallyWithOffset(1, func() error { + var err error + cmd := exec.Command("kubectl", "kustomize", filepath.Join(projectDir, + testcaseDir, profile, persistenceType)) + manifests, err = utils.Run(cmd) + return err + }, time.Minute, time.Second).Should(Succeed()) + + cmd = exec.Command("kubectl", "create", "-n", targetNamespace2, "-f", "-") + cmd.Stdin = bytes.NewBuffer(manifests) + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + + By("Wait for SonatatFlowPlatform CR in " + targetNamespace2 + " to be ready") + dataIndexServiceUrl := services.GenerateServiceURL(constants.KogitoServiceURLProtocol, targetNamespace, "sonataflow-platform-"+constants.DataIndexServiceName) + jobServiceUrl := services.GenerateServiceURL(constants.KogitoServiceURLProtocol, targetNamespace, "sonataflow-platform-"+constants.JobServiceName) + // wait for platform to be ready + EventuallyWithOffset(1, func() error { + cmd = exec.Command("kubectl", "wait", "sfplatform", "-n", targetNamespace2, "sonataflow-platform", "--for", "condition=Succeed", "--timeout=5s") + _, err = utils.Run(cmd) + return err + }, 20*time.Minute, 5).Should(Succeed()) + EventuallyWithOffset(1, func() []byte { + cmd = exec.Command("kubectl", "get", "sfplatform", "-n", targetNamespace2, "sonataflow-platform", "-o", "jsonpath='{.status.clusterPlatformRef.services.dataIndexRef.url}'") + returnedValue, _ := utils.Run(cmd) + return returnedValue + }, 20*time.Minute, 5).Should(Equal([]byte("'" + dataIndexServiceUrl + "'"))) + EventuallyWithOffset(1, func() []byte { + cmd = exec.Command("kubectl", "get", "sfplatform", "-n", targetNamespace2, "sonataflow-platform", "-o", "jsonpath='{.status.clusterPlatformRef.services.jobServiceRef.url}'") + returnedValue, _ := utils.Run(cmd) + return returnedValue + }, 20*time.Minute, 5).Should(Equal([]byte("'" + jobServiceUrl + "'"))) + } + cmd = exec.Command("kubectl", "delete", "SonataFlowClusterPlatform", "cluster", "--wait") + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + }, + Entry("without services configured", test.GetSonataFlowE2EPlatformNoServicesDirectory(), dev, ephemeral, false), + Entry("with services configured", test.GetSonataFlowE2EPlatformServicesDirectory(), dev, ephemeral, true), + ) + + DescribeTable("against a platform in a separate namespace", func(testcaseDir string, profile string, persistenceType string) { + By("Deploy the SonataFlowPlatform CR") + var manifests []byte + EventuallyWithOffset(1, func() error { + var err error + cmd := exec.Command("kubectl", "kustomize", filepath.Join(projectDir, + test.GetSonataFlowE2EPlatformServicesDirectory(), profile, clusterWideEphemeral)) + manifests, err = utils.Run(cmd) + return err + }, time.Minute, time.Second).Should(Succeed()) + + cmd := exec.Command("kubectl", "create", "-n", targetNamespace, "-f", "-") + cmd.Stdin = bytes.NewBuffer(manifests) + _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + + By("Wait for SonatatFlowPlatform CR in " + targetNamespace + " to be ready") + // wait for platform to be ready + EventuallyWithOffset(1, func() error { + cmd = exec.Command("kubectl", "wait", "sfplatform", "-n", targetNamespace, "sonataflow-platform", "--for", "condition=Succeed", "--timeout=5s") + _, err = utils.Run(cmd) + return err + }, 20*time.Minute, 5).Should(Succeed()) + EventuallyWithOffset(1, func() []byte { + cmd = exec.Command("kubectl", "get", "sfplatform", "-n", targetNamespace, "sonataflow-platform", "-o", "jsonpath='{.status.clusterPlatformRef}'") + returnedValue, _ := utils.Run(cmd) + println(string(returnedValue)) + return returnedValue + }, 20*time.Minute, 5).Should(Equal([]byte("''"))) + + By("Evaluate status of SonataFlowClusterPlatform CR") + cmd = exec.Command("kubectl", "patch", "SonataFlowClusterPlatform", "cluster", "--type", "merge", "-p", `{"spec": {"platformRef": {"namespace": "`+targetNamespace+`"}}}`) + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + EventuallyWithOffset(1, func() error { + cmd = exec.Command("kubectl", "wait", "SonataFlowClusterPlatform", "cluster", "--for", "condition=Succeed", "--timeout=5s") + _, err = utils.Run(cmd) + return err + }, 20*time.Minute, 5).Should(Succeed()) + + EventuallyWithOffset(1, func() error { + var err error + cmd := exec.Command("kubectl", "kustomize", filepath.Join(projectDir, + testcaseDir, profile, persistenceType)) + manifests, err = utils.Run(cmd) + return err + }, time.Minute, time.Second).Should(Succeed()) + + cmd = exec.Command("kubectl", "create", "-n", targetNamespace2, "-f", "-") + cmd.Stdin = bytes.NewBuffer(manifests) + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + + By("Wait for SonatatFlowPlatform CR in " + targetNamespace2 + " to be ready") + // wait for platform to be ready + EventuallyWithOffset(1, func() error { + cmd = exec.Command("kubectl", "wait", "sfplatform", "-n", targetNamespace2, "sonataflow-platform", "--for", "condition=Succeed", "--timeout=5s") + _, err = utils.Run(cmd) + return err + }, 20*time.Minute, 5).Should(Succeed()) + EventuallyWithOffset(1, func() []byte { + cmd = exec.Command("kubectl", "get", "sfplatform", "-n", targetNamespace2, "sonataflow-platform", "-o", "jsonpath='{.status.clusterPlatformRef}'") + returnedValue, _ := utils.Run(cmd) + return returnedValue + }, 20*time.Minute, 5).Should(Not(Equal([]byte("''")))) + EventuallyWithOffset(1, func() []byte { + cmd = exec.Command("kubectl", "get", "sfplatform", "-n", targetNamespace2, "sonataflow-platform", "-o", "jsonpath='{.status.clusterPlatformRef.services}'") + returnedValue, _ := utils.Run(cmd) + return returnedValue + }, 20*time.Minute, 5).Should(Equal([]byte("''"))) + + cmd = exec.Command("kubectl", "delete", "SonataFlowClusterPlatform", "cluster", "--wait") + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + }, + Entry("with only Data Index configured", test.GetSonataFlowE2EPlatformServicesDirectory(), dev, ephemeralDataIndex), + Entry("with only Job Service configured", test.GetSonataFlowE2EPlatformServicesDirectory(), dev, ephemeralJobService), + ) + }) +}) diff --git a/test/e2e/helpers.go b/test/e2e/helpers.go index 28e747df3..d2dd8e83b 100644 --- a/test/e2e/helpers.go +++ b/test/e2e/helpers.go @@ -44,8 +44,9 @@ type health struct { } type check struct { - Name string `json:"name"` - Status string `json:"status"` + Name string `json:"name"` + Status string `json:"status"` + Data map[string]string `json:"data"` } var ( @@ -84,6 +85,7 @@ func verifyHealthStatusInPod(name string, namespace string) { Expect(h.Status).To(Equal(upStatus)) return } + if len(errs.Error()) > 0 { errs = fmt.Errorf("%v; %w", err, errs) } else { @@ -99,10 +101,21 @@ func getHealthStatusInContainer(podName string, containerName string, ns string) cmd := exec.Command("kubectl", "exec", "-t", podName, "-n", ns, "-c", containerName, "--", "curl", "-s", "localhost:8080/q/health") output, err := utils.Run(cmd) Expect(err).NotTo(HaveOccurred()) - err = json.Unmarshal(output, &h) + // On Apache CI Nodes, does not return valid JSON, hence we match first and last brackets by index and extract it + stringOutput := string(output) + startIndex := strings.Index(stringOutput, "{") + endIndex := strings.LastIndex(stringOutput, "}") + if startIndex == 0 { + stringOutput = stringOutput[startIndex : endIndex+1] + } else { + stringOutput = stringOutput[startIndex-1 : endIndex+1] + } + fmt.Printf("Parsed following JSON object from health Endpoint response: %v\n", stringOutput) + err = json.Unmarshal([]byte(stringOutput), &h) if err != nil { return nil, fmt.Errorf("failed to execute curl command against health endpoint in container %s:%v with output %s", containerName, err, output) } + GinkgoWriter.Println(fmt.Sprintf("Health status:\n%s", string(output))) return &h, nil } func verifyWorkflowIsInRunningStateInNamespace(workflowName string, ns string) bool { diff --git a/test/e2e/platform_test.go b/test/e2e/platform_test.go index 49afd5aed..00f51039c 100644 --- a/test/e2e/platform_test.go +++ b/test/e2e/platform_test.go @@ -15,8 +15,6 @@ package e2e import ( - //nolint:golint - //nolint:revive "bytes" "fmt" "math/rand" @@ -38,10 +36,13 @@ import ( ) const ( - ephemeral = "ephemeral" - postgreSQL = "postgreSQL" - dev = "dev" - production = "prod" + ephemeral = "ephemeral" + postgreSQL = "postgreSQL" + dev = "dev" + production = "prod" + clusterWideEphemeral = "cluster-wide-ephemeral" + ephemeralDataIndex = "ephemeral-data-index" + ephemeralJobService = "ephemeral-job-service" ) var _ = Describe("Validate the persistence", Ordered, func() { @@ -87,7 +88,7 @@ var _ = Describe("Validate the persistence", Ordered, func() { cmd = exec.Command("kubectl", "wait", "pod", "-n", targetNamespace, "-l", "app=sonataflow-platform", "--for", "condition=Ready", "--timeout=5s") _, err = utils.Run(cmd) return err - }, 10*time.Minute, 5).Should(Succeed()) + }, 20*time.Minute, 5).Should(Succeed()) By("Evaluate status of service's health endpoint") cmd = exec.Command("kubectl", "get", "pod", "-l", "app=sonataflow-platform", "-n", targetNamespace, "-ojsonpath={.items[*].metadata.name}") output, err := utils.Run(cmd) @@ -113,14 +114,47 @@ var _ = Describe("Validate the persistence", Ordered, func() { Expect(sf).NotTo(BeEmpty(), "sonataflow name is empty") EventuallyWithOffset(1, func() bool { return verifyWorkflowIsInRunningStateInNamespace(sf, targetNamespace) - }, 5*time.Minute, 5).Should(BeTrue()) + }, 10*time.Minute, 5).Should(BeTrue()) } }, Entry("with both Job Service and Data Index and ephemeral persistence and the workflow in a dev profile", test.GetSonataFlowE2EPlatformServicesDirectory(), dev, ephemeral), - XEntry("with both Job Service and Data Index and ephemeral persistence and the workflow in a production profile", test.GetSonataFlowE2EPlatformServicesDirectory(), production, ephemeral), + Entry("with both Job Service and Data Index and ephemeral persistence and the workflow in a production profile", test.GetSonataFlowE2EPlatformServicesDirectory(), production, ephemeral), Entry("with both Job Service and Data Index and postgreSQL persistence and the workflow in a dev profile", test.GetSonataFlowE2EPlatformServicesDirectory(), dev, postgreSQL), - XEntry("with both Job Service and Data Index and postgreSQL persistence and the workflow in a production profile", test.GetSonataFlowE2EPlatformServicesDirectory(), production, postgreSQL), + Entry("with both Job Service and Data Index and postgreSQL persistence and the workflow in a production profile", test.GetSonataFlowE2EPlatformServicesDirectory(), production, postgreSQL), ) }) + + DescribeTable("when deploying a SonataFlowPlatform CR with PostgreSQL Persistence", func(testcaseDir string) { + By("Deploy the CR") + var manifests []byte + EventuallyWithOffset(1, func() error { + var err error + cmd := exec.Command("kubectl", "kustomize", testcaseDir) + manifests, err = utils.Run(cmd) + return err + }, time.Minute, time.Second).Should(Succeed()) + cmd := exec.Command("kubectl", "create", "-n", targetNamespace, "-f", "-") + cmd.Stdin = bytes.NewBuffer(manifests) + _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + By("Wait for SonatatFlowPlatform CR to complete deployment") + // wait for service deployments to be ready + EventuallyWithOffset(1, func() error { + cmd = exec.Command("kubectl", "wait", "pod", "-n", targetNamespace, "-l", "app=sonataflow-platform", "--for", "condition=Ready", "--timeout=5s") + _, err = utils.Run(cmd) + return err + }, 10*time.Minute, 5).Should(Succeed()) + By("Evaluate status of all service's health endpoint") + cmd = exec.Command("kubectl", "get", "pod", "-l", "app=sonataflow-platform", "-n", targetNamespace, "-ojsonpath={.items[*].metadata.name}") + output, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + for _, pn := range strings.Split(string(output), " ") { + verifyHealthStatusInPod(pn, targetNamespace) + } + }, + Entry("and both Job Service and Data Index using the persistence from platform CR", test.GetSonataFlowE2EPlatformPersistenceSampleDataDirectory("generic_from_platform_cr")), + Entry("and both Job Service and Data Index using the one defined in each service, discarding the one from the platform CR", test.GetSonataFlowE2EPlatformPersistenceSampleDataDirectory("overwritten_by_services")), + ) + }) diff --git a/test/e2e/workflow_test.go b/test/e2e/workflow_test.go index e05c843f7..30a0ae2fd 100644 --- a/test/e2e/workflow_test.go +++ b/test/e2e/workflow_test.go @@ -68,7 +68,7 @@ var _ = Describe("SonataFlow Operator", Ordered, func() { "test/testdata/"+test.SonataFlowSimpleOpsYamlCR), "-n", targetNamespace) _, err := utils.Run(cmd) return err - }, 2*time.Minute, time.Second).Should(Succeed()) + }, 3*time.Minute, time.Second).Should(Succeed()) By("check the workflow is in running state") EventuallyWithOffset(1, func() bool { return verifyWorkflowIsInRunningState("simple", targetNamespace) }, 15*time.Minute, 30*time.Second).Should(BeTrue()) @@ -78,7 +78,7 @@ var _ = Describe("SonataFlow Operator", Ordered, func() { "test/testdata/"+test.SonataFlowSimpleOpsYamlCR), "-n", targetNamespace) _, err := utils.Run(cmd) return err - }, 2*time.Minute, time.Second).Should(Succeed()) + }, 3*time.Minute, time.Second).Should(Succeed()) }) It("should successfully deploy the Greeting Workflow in prod mode and verify if it's running", func() { @@ -88,7 +88,7 @@ var _ = Describe("SonataFlow Operator", Ordered, func() { "test/testdata/"+test.SonataFlowGreetingsDataInputSchemaConfig), "-n", targetNamespace) _, err := utils.Run(cmd) return err - }, 2*time.Minute, time.Second).Should(Succeed()) + }, 3*time.Minute, time.Second).Should(Succeed()) By("creating an instance of the SonataFlow Operand(CR)") EventuallyWithOffset(1, func() error { @@ -96,7 +96,7 @@ var _ = Describe("SonataFlow Operator", Ordered, func() { "test/testdata/"+test.SonataFlowGreetingsWithDataInputSchemaCR), "-n", targetNamespace) _, err := utils.Run(cmd) return err - }, 2*time.Minute, time.Second).Should(Succeed()) + }, 3*time.Minute, time.Second).Should(Succeed()) By("check the workflow is in running state") EventuallyWithOffset(1, func() bool { return verifyWorkflowIsInRunningState("greeting", targetNamespace) }, 15*time.Minute, 30*time.Second).Should(BeTrue()) @@ -106,7 +106,7 @@ var _ = Describe("SonataFlow Operator", Ordered, func() { "test/testdata/"+test.SonataFlowGreetingsWithDataInputSchemaCR), "-n", targetNamespace) _, err := utils.Run(cmd) return err - }, 2*time.Minute, time.Second).Should(Succeed()) + }, 3*time.Minute, time.Second).Should(Succeed()) }) It("should successfully deploy the orderprocessing workflow in devmode and verify if it's running", func() { @@ -117,7 +117,7 @@ var _ = Describe("SonataFlow Operator", Ordered, func() { test.GetSonataFlowE2eOrderProcessingFolder()), "-n", targetNamespace) _, err := utils.Run(cmd) return err - }, 2*time.Minute, time.Second).Should(Succeed()) + }, 3*time.Minute, time.Second).Should(Succeed()) By("check the workflow is in running state") EventuallyWithOffset(1, func() bool { return verifyWorkflowIsInRunningState("orderprocessing", targetNamespace) }, 10*time.Minute, 30*time.Second).Should(BeTrue()) @@ -135,7 +135,7 @@ var _ = Describe("SonataFlow Operator", Ordered, func() { test.GetSonataFlowE2eOrderProcessingFolder()), "-n", targetNamespace) _, err := utils.Run(cmd) return err - }, 2*time.Minute, time.Second).Should(Succeed()) + }, 3*time.Minute, time.Second).Should(Succeed()) }) }) @@ -144,6 +144,10 @@ var _ = Describe("SonataFlow Operator", Ordered, func() { var _ = Describe("Validate the persistence ", Ordered, func() { + const ( + dbConnectionName = "Database connections health check" + defaultDataCheck = "" + ) var ( ns string ) @@ -164,7 +168,7 @@ var _ = Describe("Validate the persistence ", Ordered, func() { }) - DescribeTable("when deploying a SonataFlow CR with PostgreSQL persistence", func(testcaseDir string) { + DescribeTable("when deploying a SonataFlow CR with PostgreSQL persistence", func(testcaseDir string, withPersistence bool) { By("Deploy the CR") var manifests []byte EventuallyWithOffset(1, func() error { @@ -180,14 +184,14 @@ var _ = Describe("Validate the persistence ", Ordered, func() { By("Wait for SonatatFlow CR to complete deployment") // wait for service deployments to be ready EventuallyWithOffset(1, func() error { - cmd = exec.Command("kubectl", "wait", "pod", "-n", ns, "-l", "sonataflow.org/workflow-app=callbackstatetimeouts", "--for", "condition=Ready", "--timeout=5s") + cmd = exec.Command("kubectl", "wait", "pod", "-n", ns, "-l", "sonataflow.org/workflow-app", "--for", "condition=Ready", "--timeout=5s") out, err := utils.Run(cmd) GinkgoWriter.Printf("%s\n", string(out)) return err - }, 10*time.Minute, 5).Should(Succeed()) + }, 12*time.Minute, 5).Should(Succeed()) By("Evaluate status of the workflow's pod database connection health endpoint") - cmd = exec.Command("kubectl", "get", "pod", "-l", "sonataflow.org/workflow-app=callbackstatetimeouts", "-n", ns, "-ojsonpath={.items[*].metadata.name}") + cmd = exec.Command("kubectl", "get", "pod", "-l", "sonataflow.org/workflow-app", "-n", ns, "-ojsonpath={.items[*].metadata.name}") output, err := utils.Run(cmd) Expect(err).NotTo(HaveOccurred()) EventuallyWithOffset(1, func() bool { @@ -198,16 +202,26 @@ var _ = Describe("Validate the persistence ", Ordered, func() { } Expect(h.Status).To(Equal(upStatus), "Pod health is not UP") for _, c := range h.Checks { - if c.Name == "Database connections health check" { + if c.Name == dbConnectionName { Expect(c.Status).To(Equal(upStatus), "Pod's database connection is not UP") - return true + if withPersistence { + Expect(c.Data[defaultDataCheck]).To(Equal(upStatus), "Pod's 'default' database data is not UP") + return true + } else { + Expect(defaultDataCheck).NotTo(BeElementOf(c.Data), "Pod's 'default' database data check exists in health manifest") + return true + } } } } return false - }, 10*time.Minute).Should(BeTrue()) + }, 1*time.Minute).Should(BeTrue()) }, - Entry("defined in the workflow from an existing kubernetes service as a reference", test.GetSonataFlowE2EWorkflowPersistenceSampleDataDirectory("by_service")), + Entry("defined in the workflow from an existing kubernetes service as a reference", test.GetSonataFlowE2EWorkflowPersistenceSampleDataDirectory("by_service"), true), + Entry("defined in the workflow and from the sonataflow platform", test.GetSonataFlowE2EWorkflowPersistenceSampleDataDirectory("from_platform_overwritten_by_service"), true), + Entry("defined from the sonataflow platform as reference and with DI and JS", test.GetSonataFlowE2EWorkflowPersistenceSampleDataDirectory("from_platform_with_di_and_js_services"), true), + Entry("defined from the sonataflow platform as reference and without DI and JS", test.GetSonataFlowE2EWorkflowPersistenceSampleDataDirectory("from_platform_without_di_and_js_services"), true), + Entry("defined from the sonataflow platform as reference but not required by the workflow", test.GetSonataFlowE2EWorkflowPersistenceSampleDataDirectory("from_platform_with_no_persistence_required"), false), ) }) diff --git a/test/testdata/persistence/workflow/by_service/02-sonataflow_platform.yaml b/test/testdata/platform/noservices/dev/ephemeral/02-sonataflow_platform.yaml similarity index 80% rename from test/testdata/persistence/workflow/by_service/02-sonataflow_platform.yaml rename to test/testdata/platform/noservices/dev/ephemeral/02-sonataflow_platform.yaml index 5867f2d60..c86ba5127 100644 --- a/test/testdata/persistence/workflow/by_service/02-sonataflow_platform.yaml +++ b/test/testdata/platform/noservices/dev/ephemeral/02-sonataflow_platform.yaml @@ -20,8 +20,8 @@ spec: build: template: buildArgs: - - name: QUARKUS_EXTENSIONS - value: org.kie.kogito:kogito-addons-quarkus-jobs-knative-eventing:999-SNAPSHOT,org.kie.kogito:kogito-addons-quarkus-persistence-jdbc:999-SNAPSHOT,io.quarkus:quarkus-jdbc-postgresql:3.2.9.Final,io.quarkus:quarkus-agroal:3.2.9.Final + - name: QUARKUS_EXTENSION + value: org.kie.kogito:kogito-addons-quarkus-jobs-knative-eventing:999-SNAPSHOT config: strategyOptions: KanikoBuildCacheEnabled: "true" diff --git a/test/testdata/platform/noservices/dev/ephemeral/kustomization.yaml b/test/testdata/platform/noservices/dev/ephemeral/kustomization.yaml new file mode 100644 index 000000000..5441bfce8 --- /dev/null +++ b/test/testdata/platform/noservices/dev/ephemeral/kustomization.yaml @@ -0,0 +1,20 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +resources: +- 02-sonataflow_platform.yaml + +sortOptions: + order: fifo + diff --git a/test/testdata/platform/noservices/dev/ephemeral/sonataflow/03-sonataflow_callbackstatetimeouts.sw.yaml b/test/testdata/platform/noservices/dev/ephemeral/sonataflow/03-sonataflow_callbackstatetimeouts.sw.yaml new file mode 100644 index 000000000..8bc8a77de --- /dev/null +++ b/test/testdata/platform/noservices/dev/ephemeral/sonataflow/03-sonataflow_callbackstatetimeouts.sw.yaml @@ -0,0 +1,81 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + name: callbackstatetimeouts + annotations: + sonataflow.org/description: Callback State Timeouts Example k8s + sonataflow.org/version: 0.0.1 + sonataflow.org/profile: dev +spec: + flow: + start: PrintStartMessage + events: + - name: callbackEvent + source: '' + type: callback_event_type + functions: + - name: systemOut + type: custom + operation: sysout + states: + - name: PrintStartMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has started.\"}" + transition: CallbackState + - name: CallbackState + type: callback + action: + name: callbackAction + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has executed the callbackFunction.\"}" + eventRef: callbackEvent + transition: CheckEventArrival + timeouts: + eventTimeout: PT30S + - name: CheckEventArrival + type: switch + dataConditions: + - condition: "${ .eventData != null }" + transition: EventArrived + defaultCondition: + transition: EventNotArrived + - name: EventArrived + type: inject + data: + exitMessage: "The callback event has arrived." + transition: PrintExitMessage + - name: EventNotArrived + type: inject + data: + exitMessage: "The callback event has not arrived, and the timeout has overdue." + transition: PrintExitMessage + - name: PrintExitMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has finalized. \" + .exitMessage + \" eventData: \" + .eventData}" + end: true diff --git a/test/testdata/persistence/workflow/by_service/01-postgres.yaml b/test/testdata/platform/persistence/generic_from_platform_cr/01-postgres.yaml similarity index 100% rename from test/testdata/persistence/workflow/by_service/01-postgres.yaml rename to test/testdata/platform/persistence/generic_from_platform_cr/01-postgres.yaml diff --git a/test/testdata/platform/persistence/generic_from_platform_cr/02-sonataflow_platform.yaml b/test/testdata/platform/persistence/generic_from_platform_cr/02-sonataflow_platform.yaml new file mode 100644 index 000000000..d746ac94b --- /dev/null +++ b/test/testdata/platform/persistence/generic_from_platform_cr/02-sonataflow_platform.yaml @@ -0,0 +1,46 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowPlatform +metadata: + name: sonataflow-platform +spec: + persistence: + postgresql: + secretRef: + name: postgres-secrets + userKey: POSTGRES_USER + passwordKey: POSTGRES_PASSWORD + serviceRef: + name: postgres + port: 5432 + databaseName: sonataflow + services: + dataIndex: + enabled: false + podTemplate: + initContainers: + - name: init-postgres + image: registry.access.redhat.com/ubi9/ubi-micro:latest + imagePullPolicy: IfNotPresent + command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] + jobService: + enabled: false + podTemplate: + initContainers: + - name: init-postgres + image: registry.access.redhat.com/ubi9/ubi-micro:latest + imagePullPolicy: IfNotPresent + command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] diff --git a/test/testdata/platform/persistence/generic_from_platform_cr/kustomization.yaml b/test/testdata/platform/persistence/generic_from_platform_cr/kustomization.yaml new file mode 100644 index 000000000..d3fd127c7 --- /dev/null +++ b/test/testdata/platform/persistence/generic_from_platform_cr/kustomization.yaml @@ -0,0 +1,32 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +resources: +- 01-postgres.yaml +- 02-sonataflow_platform.yaml + +generatorOptions: + disableNameSuffixHash: true + +secretGenerator: + - name: postgres-secrets + literals: + - POSTGRES_USER=sonataflow + - POSTGRES_PASSWORD=sonataflow + - POSTGRES_DATABASE=sonataflow + - PGDATA=/var/lib/pgsql/data/userdata + +sortOptions: + order: fifo + diff --git a/test/testdata/platform/persistence/overwritten_by_services/01-postgres.yaml b/test/testdata/platform/persistence/overwritten_by_services/01-postgres.yaml new file mode 100644 index 000000000..662de4c7b --- /dev/null +++ b/test/testdata/platform/persistence/overwritten_by_services/01-postgres.yaml @@ -0,0 +1,86 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: postgres + template: + metadata: + labels: + app.kubernetes.io/name: postgres + spec: + containers: + - name: postgres + image: postgres:13.2-alpine + imagePullPolicy: 'IfNotPresent' + ports: + - containerPort: 5432 + volumeMounts: + - name: storage + mountPath: /var/lib/postgresql/data + envFrom: + - secretRef: + name: postgres-secrets + readinessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + livenessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + resources: + limits: + memory: "256Mi" + cpu: "500m" + volumes: + - name: storage + persistentVolumeClaim: + claimName: postgres-pvc +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + selector: + app.kubernetes.io/name: postgres + ports: + - port: 5432 diff --git a/test/testdata/platform/persistence/overwritten_by_services/02-sonataflow_platform.yaml b/test/testdata/platform/persistence/overwritten_by_services/02-sonataflow_platform.yaml new file mode 100644 index 000000000..90e361b86 --- /dev/null +++ b/test/testdata/platform/persistence/overwritten_by_services/02-sonataflow_platform.yaml @@ -0,0 +1,65 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowPlatform +metadata: + name: sonataflow-platform +spec: + build: + config: + strategyOptions: + KanikoBuildCacheEnabled: "true" + persistence: + postgresql: + secretRef: + name: postgres-secrets + userKey: USER + passwordKey: PASSWORD + serviceRef: + name: no-db-exists + port: 5432 + databaseName: find-me + services: + dataIndex: + enabled: false + persistence: + postgresql: + jdbcUrl: jdbc:postgresql://postgres:5432/sonataflow?currentSchema=data-index-service + secretRef: + name: postgres-secrets + userKey: POSTGRES_USER + passwordKey: POSTGRES_PASSWORD + podTemplate: + initContainers: + - name: init-postgres + image: registry.access.redhat.com/ubi9/ubi-micro:latest + imagePullPolicy: IfNotPresent + command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] + jobService: + enabled: false + persistence: + postgresql: + jdbcUrl: jdbc:postgresql://postgres:5432/sonataflow?currentSchema=jobs-service + secretRef: + name: postgres-secrets + userKey: POSTGRES_USER + passwordKey: POSTGRES_PASSWORD + podTemplate: + initContainers: + - name: init-postgres + image: registry.access.redhat.com/ubi9/ubi-micro:latest + imagePullPolicy: IfNotPresent + command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] + diff --git a/test/testdata/platform/persistence/overwritten_by_services/kustomization.yaml b/test/testdata/platform/persistence/overwritten_by_services/kustomization.yaml new file mode 100644 index 000000000..d3fd127c7 --- /dev/null +++ b/test/testdata/platform/persistence/overwritten_by_services/kustomization.yaml @@ -0,0 +1,32 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +resources: +- 01-postgres.yaml +- 02-sonataflow_platform.yaml + +generatorOptions: + disableNameSuffixHash: true + +secretGenerator: + - name: postgres-secrets + literals: + - POSTGRES_USER=sonataflow + - POSTGRES_PASSWORD=sonataflow + - POSTGRES_DATABASE=sonataflow + - PGDATA=/var/lib/pgsql/data/userdata + +sortOptions: + order: fifo + diff --git a/test/testdata/platform/services/dev/cluster-wide-ephemeral/01-sonataflow_clusterplatform.yaml b/test/testdata/platform/services/dev/cluster-wide-ephemeral/01-sonataflow_clusterplatform.yaml new file mode 100644 index 000000000..57e9735db --- /dev/null +++ b/test/testdata/platform/services/dev/cluster-wide-ephemeral/01-sonataflow_clusterplatform.yaml @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowClusterPlatform +metadata: + name: cluster +spec: + platformRef: + name: sonataflow-platform + namespace: test-ns diff --git a/test/testdata/platform/services/dev/cluster-wide-ephemeral/02-sonataflow_platform.yaml b/test/testdata/platform/services/dev/cluster-wide-ephemeral/02-sonataflow_platform.yaml new file mode 100644 index 000000000..c7048fff6 --- /dev/null +++ b/test/testdata/platform/services/dev/cluster-wide-ephemeral/02-sonataflow_platform.yaml @@ -0,0 +1,32 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowPlatform +metadata: + name: sonataflow-platform +spec: + build: + template: + buildArgs: + - name: QUARKUS_EXTENSION + value: org.kie.kogito:kogito-addons-quarkus-jobs-knative-eventing:999-SNAPSHOT + config: + strategyOptions: + KanikoBuildCacheEnabled: "true" + services: + dataIndex: + enabled: true + jobService: + enabled: true diff --git a/test/testdata/platform/services/dev/cluster-wide-ephemeral/kustomization.yaml b/test/testdata/platform/services/dev/cluster-wide-ephemeral/kustomization.yaml new file mode 100644 index 000000000..b6e60d37b --- /dev/null +++ b/test/testdata/platform/services/dev/cluster-wide-ephemeral/kustomization.yaml @@ -0,0 +1,17 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +resources: +- 01-sonataflow_clusterplatform.yaml +- 02-sonataflow_platform.yaml diff --git a/test/testdata/platform/services/dev/ephemeral-data-index/02-sonataflow_platform.yaml b/test/testdata/platform/services/dev/ephemeral-data-index/02-sonataflow_platform.yaml new file mode 100644 index 000000000..9c09a82d5 --- /dev/null +++ b/test/testdata/platform/services/dev/ephemeral-data-index/02-sonataflow_platform.yaml @@ -0,0 +1,30 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowPlatform +metadata: + name: sonataflow-platform +spec: + build: + template: + buildArgs: + - name: QUARKUS_EXTENSION + value: org.kie.kogito:kogito-addons-quarkus-jobs-knative-eventing:999-SNAPSHOT + config: + strategyOptions: + KanikoBuildCacheEnabled: "true" + services: + dataIndex: + enabled: false diff --git a/test/testdata/platform/services/dev/ephemeral-data-index/kustomization.yaml b/test/testdata/platform/services/dev/ephemeral-data-index/kustomization.yaml new file mode 100644 index 000000000..5441bfce8 --- /dev/null +++ b/test/testdata/platform/services/dev/ephemeral-data-index/kustomization.yaml @@ -0,0 +1,20 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +resources: +- 02-sonataflow_platform.yaml + +sortOptions: + order: fifo + diff --git a/test/testdata/platform/services/dev/ephemeral-data-index/sonataflow/03-sonataflow_callbackstatetimeouts.sw.yaml b/test/testdata/platform/services/dev/ephemeral-data-index/sonataflow/03-sonataflow_callbackstatetimeouts.sw.yaml new file mode 100644 index 000000000..8bc8a77de --- /dev/null +++ b/test/testdata/platform/services/dev/ephemeral-data-index/sonataflow/03-sonataflow_callbackstatetimeouts.sw.yaml @@ -0,0 +1,81 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + name: callbackstatetimeouts + annotations: + sonataflow.org/description: Callback State Timeouts Example k8s + sonataflow.org/version: 0.0.1 + sonataflow.org/profile: dev +spec: + flow: + start: PrintStartMessage + events: + - name: callbackEvent + source: '' + type: callback_event_type + functions: + - name: systemOut + type: custom + operation: sysout + states: + - name: PrintStartMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has started.\"}" + transition: CallbackState + - name: CallbackState + type: callback + action: + name: callbackAction + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has executed the callbackFunction.\"}" + eventRef: callbackEvent + transition: CheckEventArrival + timeouts: + eventTimeout: PT30S + - name: CheckEventArrival + type: switch + dataConditions: + - condition: "${ .eventData != null }" + transition: EventArrived + defaultCondition: + transition: EventNotArrived + - name: EventArrived + type: inject + data: + exitMessage: "The callback event has arrived." + transition: PrintExitMessage + - name: EventNotArrived + type: inject + data: + exitMessage: "The callback event has not arrived, and the timeout has overdue." + transition: PrintExitMessage + - name: PrintExitMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has finalized. \" + .exitMessage + \" eventData: \" + .eventData}" + end: true diff --git a/test/testdata/platform/services/dev/ephemeral-job-service/02-sonataflow_platform.yaml b/test/testdata/platform/services/dev/ephemeral-job-service/02-sonataflow_platform.yaml new file mode 100644 index 000000000..9e0bd3ad8 --- /dev/null +++ b/test/testdata/platform/services/dev/ephemeral-job-service/02-sonataflow_platform.yaml @@ -0,0 +1,30 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowPlatform +metadata: + name: sonataflow-platform +spec: + build: + template: + buildArgs: + - name: QUARKUS_EXTENSION + value: org.kie.kogito:kogito-addons-quarkus-jobs-knative-eventing:999-SNAPSHOT + config: + strategyOptions: + KanikoBuildCacheEnabled: "true" + services: + jobService: + enabled: false diff --git a/test/testdata/platform/services/dev/ephemeral-job-service/kustomization.yaml b/test/testdata/platform/services/dev/ephemeral-job-service/kustomization.yaml new file mode 100644 index 000000000..5441bfce8 --- /dev/null +++ b/test/testdata/platform/services/dev/ephemeral-job-service/kustomization.yaml @@ -0,0 +1,20 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +resources: +- 02-sonataflow_platform.yaml + +sortOptions: + order: fifo + diff --git a/test/testdata/platform/services/dev/ephemeral-job-service/sonataflow/03-sonataflow_callbackstatetimeouts.sw.yaml b/test/testdata/platform/services/dev/ephemeral-job-service/sonataflow/03-sonataflow_callbackstatetimeouts.sw.yaml new file mode 100644 index 000000000..8bc8a77de --- /dev/null +++ b/test/testdata/platform/services/dev/ephemeral-job-service/sonataflow/03-sonataflow_callbackstatetimeouts.sw.yaml @@ -0,0 +1,81 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + name: callbackstatetimeouts + annotations: + sonataflow.org/description: Callback State Timeouts Example k8s + sonataflow.org/version: 0.0.1 + sonataflow.org/profile: dev +spec: + flow: + start: PrintStartMessage + events: + - name: callbackEvent + source: '' + type: callback_event_type + functions: + - name: systemOut + type: custom + operation: sysout + states: + - name: PrintStartMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has started.\"}" + transition: CallbackState + - name: CallbackState + type: callback + action: + name: callbackAction + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has executed the callbackFunction.\"}" + eventRef: callbackEvent + transition: CheckEventArrival + timeouts: + eventTimeout: PT30S + - name: CheckEventArrival + type: switch + dataConditions: + - condition: "${ .eventData != null }" + transition: EventArrived + defaultCondition: + transition: EventNotArrived + - name: EventArrived + type: inject + data: + exitMessage: "The callback event has arrived." + transition: PrintExitMessage + - name: EventNotArrived + type: inject + data: + exitMessage: "The callback event has not arrived, and the timeout has overdue." + transition: PrintExitMessage + - name: PrintExitMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has finalized. \" + .exitMessage + \" eventData: \" + .eventData}" + end: true diff --git a/test/testdata/platform/services/dev/postgreSQL/02-sonataflow_platform.yaml b/test/testdata/platform/services/dev/postgreSQL/02-sonataflow_platform.yaml index 15fd97df9..77d95c969 100644 --- a/test/testdata/platform/services/dev/postgreSQL/02-sonataflow_platform.yaml +++ b/test/testdata/platform/services/dev/postgreSQL/02-sonataflow_platform.yaml @@ -35,7 +35,7 @@ spec: podTemplate: initContainers: - name: init-postgres - image: registry.access.redhat.com/ubi9/ubi-minimal:latest + image: registry.access.redhat.com/ubi9/ubi-micro:latest imagePullPolicy: IfNotPresent command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] jobService: @@ -50,6 +50,6 @@ spec: podTemplate: initContainers: - name: init-postgres - image: registry.access.redhat.com/ubi9/ubi-minimal:latest + image: registry.access.redhat.com/ubi9/ubi-micro:latest imagePullPolicy: IfNotPresent command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] diff --git a/test/testdata/platform/services/prod/ephemeral/02-sonataflow_platform.yaml b/test/testdata/platform/services/prod/ephemeral/02-sonataflow_platform.yaml index 8f77082bc..52b7d11f7 100644 --- a/test/testdata/platform/services/prod/ephemeral/02-sonataflow_platform.yaml +++ b/test/testdata/platform/services/prod/ephemeral/02-sonataflow_platform.yaml @@ -18,10 +18,6 @@ metadata: name: sonataflow-platform spec: build: - template: - buildArgs: - - name: QUARKUS_EXTENSION - value: org.kie.kogito:kogito-addons-quarkus-jobs-knative-eventing:999-SNAPSHOT config: strategyOptions: KanikoBuildCacheEnabled: "true" @@ -30,4 +26,3 @@ spec: enabled: true jobService: enabled: true - diff --git a/test/testdata/platform/services/prod/postgreSQL/02-sonataflow_platform.yaml b/test/testdata/platform/services/prod/postgreSQL/02-sonataflow_platform.yaml index 75acb6d7d..9248f57b7 100644 --- a/test/testdata/platform/services/prod/postgreSQL/02-sonataflow_platform.yaml +++ b/test/testdata/platform/services/prod/postgreSQL/02-sonataflow_platform.yaml @@ -18,10 +18,9 @@ metadata: name: sonataflow-platform spec: build: - template: - buildArgs: - - name: QUARKUS_EXTENSIONS - value: org.kie.kogito:kogito-addons-quarkus-jobs-knative-eventing:999-SNAPSHOT + config: + strategyOptions: + KanikoBuildCacheEnabled: "true" services: dataIndex: enabled: true @@ -35,7 +34,7 @@ spec: podTemplate: initContainers: - name: init-postgres - image: registry.access.redhat.com/ubi9/ubi-minimal:latest + image: registry.access.redhat.com/ubi9/ubi-micro:latest imagePullPolicy: IfNotPresent command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] jobService: @@ -50,6 +49,6 @@ spec: podTemplate: initContainers: - name: init-postgres - image: registry.access.redhat.com/ubi9/ubi-minimal:latest + image: registry.access.redhat.com/ubi9/ubi-micro:latest imagePullPolicy: IfNotPresent command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] diff --git a/test/testdata/sonataflow.org_v1alpha08_sonataflow_vet_event.yaml b/test/testdata/sonataflow.org_v1alpha08_sonataflow_vet_event.yaml new file mode 100644 index 000000000..05e3a5b42 --- /dev/null +++ b/test/testdata/sonataflow.org_v1alpha08_sonataflow_vet_event.yaml @@ -0,0 +1,72 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + name: vet + annotations: + sonataflow.org/description: Vet service call via events + sonataflow.org/version: 0.0.1 +spec: + sink: + ref: + name: default + namespace: default + apiVersion: eventing.knative.dev/v1 + kind: Broker + flow: + events: + - name: MakeVetAppointment + source: VetServiceSource + type: events.vet.appointments + kind: produced + - name: VetAppointmentInfo + source: VetServiceSource + type: events.vet.appointments + - name: VetAppointmentRequestReceived + source: checkAccountInfo + type: events.vet.appointments.request + functions: + - name: StoreNewPatientInfo + operation: specs/services.yaml#checkAccountInfo + states: + - name: AppointmentRequestReceived + type: event + onEvents: + - eventRefs: + - VetAppointmentRequestReceived + actions: + - name: checkAccount + functionRef: + refName: checkAccountInfo + arguments: + account: "${ .accountId }" + transition: MakeVetAppointmentState + - name: MakeVetAppointmentState + type: callback + action: + name: MakeAppointmentAction + eventRef: + triggerEventRef: MakeVetAppointment + data: "${ .patientInfo }" + eventRef: VetAppointmentInfo + timeouts: + stateExecTimeout: PT15M + eventDataFilter: + toStateData: .test + end: true diff --git a/test/testdata/workflow/persistence/by_service/01-postgres.yaml b/test/testdata/workflow/persistence/by_service/01-postgres.yaml new file mode 100644 index 000000000..662de4c7b --- /dev/null +++ b/test/testdata/workflow/persistence/by_service/01-postgres.yaml @@ -0,0 +1,86 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: postgres + template: + metadata: + labels: + app.kubernetes.io/name: postgres + spec: + containers: + - name: postgres + image: postgres:13.2-alpine + imagePullPolicy: 'IfNotPresent' + ports: + - containerPort: 5432 + volumeMounts: + - name: storage + mountPath: /var/lib/postgresql/data + envFrom: + - secretRef: + name: postgres-secrets + readinessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + livenessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + resources: + limits: + memory: "256Mi" + cpu: "500m" + volumes: + - name: storage + persistentVolumeClaim: + claimName: postgres-pvc +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + selector: + app.kubernetes.io/name: postgres + ports: + - port: 5432 diff --git a/test/testdata/workflow/persistence/by_service/02-sonataflow_platform.yaml b/test/testdata/workflow/persistence/by_service/02-sonataflow_platform.yaml new file mode 100644 index 000000000..2de0372b9 --- /dev/null +++ b/test/testdata/workflow/persistence/by_service/02-sonataflow_platform.yaml @@ -0,0 +1,27 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowPlatform +metadata: + name: sonataflow-platform +spec: + build: + template: + buildArgs: + - name: QUARKUS_EXTENSIONS + value: org.kie.kogito:kogito-addons-quarkus-persistence-jdbc:999-SNAPSHOT,io.quarkus:quarkus-jdbc-postgresql:3.2.9.Final,io.quarkus:quarkus-agroal:3.2.9.Final + config: + strategyOptions: + KanikoBuildCacheEnabled: "true" diff --git a/test/testdata/persistence/workflow/by_service/03-sonataflow_callbackstatetimeouts.sw.yaml b/test/testdata/workflow/persistence/by_service/03-sonataflow_callbackstatetimeouts.sw.yaml similarity index 98% rename from test/testdata/persistence/workflow/by_service/03-sonataflow_callbackstatetimeouts.sw.yaml rename to test/testdata/workflow/persistence/by_service/03-sonataflow_callbackstatetimeouts.sw.yaml index 9d0fcbc75..bb64d2753 100644 --- a/test/testdata/persistence/workflow/by_service/03-sonataflow_callbackstatetimeouts.sw.yaml +++ b/test/testdata/workflow/persistence/by_service/03-sonataflow_callbackstatetimeouts.sw.yaml @@ -40,7 +40,6 @@ spec: - name: init-postgres image: registry.access.redhat.com/ubi9/ubi-micro:latest imagePullPolicy: IfNotPresent - #TODO: https://issues.redhat.com/browse/KOGITO-9840 command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] flow: start: PrintStartMessage diff --git a/test/testdata/persistence/workflow/by_service/kustomization.yaml b/test/testdata/workflow/persistence/by_service/kustomization.yaml similarity index 100% rename from test/testdata/persistence/workflow/by_service/kustomization.yaml rename to test/testdata/workflow/persistence/by_service/kustomization.yaml diff --git a/test/testdata/workflow/persistence/from_platform_overwritten_by_service/01-postgres.yaml b/test/testdata/workflow/persistence/from_platform_overwritten_by_service/01-postgres.yaml new file mode 100644 index 000000000..662de4c7b --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_overwritten_by_service/01-postgres.yaml @@ -0,0 +1,86 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: postgres + template: + metadata: + labels: + app.kubernetes.io/name: postgres + spec: + containers: + - name: postgres + image: postgres:13.2-alpine + imagePullPolicy: 'IfNotPresent' + ports: + - containerPort: 5432 + volumeMounts: + - name: storage + mountPath: /var/lib/postgresql/data + envFrom: + - secretRef: + name: postgres-secrets + readinessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + livenessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + resources: + limits: + memory: "256Mi" + cpu: "500m" + volumes: + - name: storage + persistentVolumeClaim: + claimName: postgres-pvc +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + selector: + app.kubernetes.io/name: postgres + ports: + - port: 5432 diff --git a/test/testdata/workflow/persistence/from_platform_overwritten_by_service/02-sonataflow_platform.yaml b/test/testdata/workflow/persistence/from_platform_overwritten_by_service/02-sonataflow_platform.yaml new file mode 100644 index 000000000..e342b1c18 --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_overwritten_by_service/02-sonataflow_platform.yaml @@ -0,0 +1,37 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowPlatform +metadata: + name: sonataflow-platform +spec: + persistence: + postgresql: + secretRef: + name: my-secret + userKey: MY_USER + passwordKey: MY_PASSWORD + serviceRef: + name: db_not_defined + port: 3456 + databaseName: db_name + build: + template: + buildArgs: + - name: QUARKUS_EXTENSIONS + value: org.kie.kogito:kogito-addons-quarkus-persistence-jdbc:999-SNAPSHOT,io.quarkus:quarkus-jdbc-postgresql:3.2.9.Final,io.quarkus:quarkus-agroal:3.2.9.Final + config: + strategyOptions: + KanikoBuildCacheEnabled: "true" diff --git a/test/testdata/workflow/persistence/from_platform_overwritten_by_service/03-sonataflow_callbackstatetimeouts.sw.yaml b/test/testdata/workflow/persistence/from_platform_overwritten_by_service/03-sonataflow_callbackstatetimeouts.sw.yaml new file mode 100644 index 000000000..bb64d2753 --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_overwritten_by_service/03-sonataflow_callbackstatetimeouts.sw.yaml @@ -0,0 +1,101 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + name: callbackstatetimeouts + annotations: + sonataflow.org/description: Callback State Timeouts Example k8s + sonataflow.org/version: 0.0.1 +spec: + persistence: + postgresql: + secretRef: + name: postgres-secrets + userKey: POSTGRES_USER + passwordKey: POSTGRES_PASSWORD + serviceRef: + name: postgres + port: 5432 + databaseName: sonataflow + databaseSchema: callbackstatetimeouts + podTemplate: + container: + env: + - name: QUARKUS_FLYWAY_MIGRATE_AT_START + value: "true" + initContainers: + - name: init-postgres + image: registry.access.redhat.com/ubi9/ubi-micro:latest + imagePullPolicy: IfNotPresent + command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] + flow: + start: PrintStartMessage + events: + - name: callbackEvent + source: '' + type: callback_event_type + functions: + - name: systemOut + type: custom + operation: sysout + states: + - name: PrintStartMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has started.\"}" + transition: CallbackState + - name: CallbackState + type: callback + action: + name: callbackAction + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has executed the callbackFunction.\"}" + eventRef: callbackEvent + transition: CheckEventArrival + timeouts: + eventTimeout: PT30S + - name: CheckEventArrival + type: switch + dataConditions: + - condition: "${ .eventData != null }" + transition: EventArrived + defaultCondition: + transition: EventNotArrived + - name: EventArrived + type: inject + data: + exitMessage: "The callback event has arrived." + transition: PrintExitMessage + - name: EventNotArrived + type: inject + data: + exitMessage: "The callback event has not arrived, and the timeout has overdue." + transition: PrintExitMessage + - name: PrintExitMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has finalized. \" + .exitMessage + \" eventData: \" + .eventData}" + end: true diff --git a/test/testdata/workflow/persistence/from_platform_overwritten_by_service/kustomization.yaml b/test/testdata/workflow/persistence/from_platform_overwritten_by_service/kustomization.yaml new file mode 100644 index 000000000..b7f587bcc --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_overwritten_by_service/kustomization.yaml @@ -0,0 +1,33 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +resources: +- 01-postgres.yaml +- 02-sonataflow_platform.yaml +- 03-sonataflow_callbackstatetimeouts.sw.yaml + +generatorOptions: + disableNameSuffixHash: true + +secretGenerator: + - name: postgres-secrets + literals: + - POSTGRES_USER=sonataflow + - POSTGRES_PASSWORD=sonataflow + - POSTGRES_DATABASE=sonataflow + - PGDATA=/var/lib/pgsql/data/userdata + +sortOptions: + order: fifo + diff --git a/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/01-postgres.yaml b/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/01-postgres.yaml new file mode 100644 index 000000000..662de4c7b --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/01-postgres.yaml @@ -0,0 +1,86 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: postgres + template: + metadata: + labels: + app.kubernetes.io/name: postgres + spec: + containers: + - name: postgres + image: postgres:13.2-alpine + imagePullPolicy: 'IfNotPresent' + ports: + - containerPort: 5432 + volumeMounts: + - name: storage + mountPath: /var/lib/postgresql/data + envFrom: + - secretRef: + name: postgres-secrets + readinessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + livenessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + resources: + limits: + memory: "256Mi" + cpu: "500m" + volumes: + - name: storage + persistentVolumeClaim: + claimName: postgres-pvc +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + selector: + app.kubernetes.io/name: postgres + ports: + - port: 5432 diff --git a/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/02-sonataflow_platform.yaml b/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/02-sonataflow_platform.yaml new file mode 100644 index 000000000..66eec55a4 --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/02-sonataflow_platform.yaml @@ -0,0 +1,55 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowPlatform +metadata: + name: sonataflow-platform +spec: + persistence: + postgresql: + secretRef: + name: postgres-secrets + userKey: POSTGRES_USER + passwordKey: POSTGRES_PASSWORD + serviceRef: + name: postgres + port: 5432 + databaseName: sonataflow + build: + template: + buildArgs: + - name: QUARKUS_EXTENSIONS + value: org.kie.kogito:kogito-addons-quarkus-persistence-jdbc:999-SNAPSHOT,io.quarkus:quarkus-jdbc-postgresql:3.2.9.Final,io.quarkus:quarkus-agroal:3.2.9.Final + config: + strategyOptions: + KanikoBuildCacheEnabled: "true" + services: + dataIndex: + enabled: true + podTemplate: + initContainers: + - name: init-postgres + image: registry.access.redhat.com/ubi9/ubi-micro:latest + imagePullPolicy: IfNotPresent + command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] + jobService: + enabled: true + podTemplate: + initContainers: + - name: init-postgres + image: registry.access.redhat.com/ubi9/ubi-micro:latest + imagePullPolicy: IfNotPresent + command: [ 'sh', '-c', 'until (echo 1 > /dev/tcp/postgres.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local/5432) >/dev/null 2>&1; do echo "Waiting for postgres server"; sleep 3; done;' ] + diff --git a/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/03-sonataflow_callbackstatetimeouts.sw.yaml b/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/03-sonataflow_callbackstatetimeouts.sw.yaml new file mode 100644 index 000000000..1d4bdbb96 --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/03-sonataflow_callbackstatetimeouts.sw.yaml @@ -0,0 +1,85 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + name: callbackstatetimeouts + annotations: + sonataflow.org/description: Callback State Timeouts Example k8s + sonataflow.org/version: 0.0.1 +spec: + podTemplate: + container: + env: + - name: QUARKUS_FLYWAY_MIGRATE_AT_START + value: "true" + flow: + start: PrintStartMessage + events: + - name: callbackEvent + source: '' + type: callback_event_type + functions: + - name: systemOut + type: custom + operation: sysout + states: + - name: PrintStartMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has started.\"}" + transition: CallbackState + - name: CallbackState + type: callback + action: + name: callbackAction + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has executed the callbackFunction.\"}" + eventRef: callbackEvent + transition: CheckEventArrival + timeouts: + eventTimeout: PT30S + - name: CheckEventArrival + type: switch + dataConditions: + - condition: "${ .eventData != null }" + transition: EventArrived + defaultCondition: + transition: EventNotArrived + - name: EventArrived + type: inject + data: + exitMessage: "The callback event has arrived." + transition: PrintExitMessage + - name: EventNotArrived + type: inject + data: + exitMessage: "The callback event has not arrived, and the timeout has overdue." + transition: PrintExitMessage + - name: PrintExitMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has finalized. \" + .exitMessage + \" eventData: \" + .eventData}" + end: true diff --git a/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/kustomization.yaml b/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/kustomization.yaml new file mode 100644 index 000000000..b7f587bcc --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_with_di_and_js_services/kustomization.yaml @@ -0,0 +1,33 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +resources: +- 01-postgres.yaml +- 02-sonataflow_platform.yaml +- 03-sonataflow_callbackstatetimeouts.sw.yaml + +generatorOptions: + disableNameSuffixHash: true + +secretGenerator: + - name: postgres-secrets + literals: + - POSTGRES_USER=sonataflow + - POSTGRES_PASSWORD=sonataflow + - POSTGRES_DATABASE=sonataflow + - PGDATA=/var/lib/pgsql/data/userdata + +sortOptions: + order: fifo + diff --git a/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/01-postgres.yaml b/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/01-postgres.yaml new file mode 100644 index 000000000..662de4c7b --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/01-postgres.yaml @@ -0,0 +1,86 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: postgres + template: + metadata: + labels: + app.kubernetes.io/name: postgres + spec: + containers: + - name: postgres + image: postgres:13.2-alpine + imagePullPolicy: 'IfNotPresent' + ports: + - containerPort: 5432 + volumeMounts: + - name: storage + mountPath: /var/lib/postgresql/data + envFrom: + - secretRef: + name: postgres-secrets + readinessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + livenessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + resources: + limits: + memory: "256Mi" + cpu: "500m" + volumes: + - name: storage + persistentVolumeClaim: + claimName: postgres-pvc +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + selector: + app.kubernetes.io/name: postgres + ports: + - port: 5432 diff --git a/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/02-sonataflow_platform.yaml b/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/02-sonataflow_platform.yaml new file mode 100644 index 000000000..f61f4a849 --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/02-sonataflow_platform.yaml @@ -0,0 +1,37 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowPlatform +metadata: + name: sonataflow-platform +spec: + persistence: + postgresql: + secretRef: + name: postgres-secrets + userKey: POSTGRES_USER + passwordKey: POSTGRES_PASSWORD + serviceRef: + name: postgres + port: 5432 + databaseName: sonataflow + build: + template: + buildArgs: + - name: QUARKUS_EXTENSIONS + value: org.kie.kogito:kogito-addons-quarkus-persistence-jdbc:999-SNAPSHOT,io.quarkus:quarkus-jdbc-postgresql:3.2.9.Final,io.quarkus:quarkus-agroal:3.2.9.Final + config: + strategyOptions: + KanikoBuildCacheEnabled: "true" diff --git a/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/03-sonataflow_callbackstatetimeouts-no-persistence.sw.yaml b/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/03-sonataflow_callbackstatetimeouts-no-persistence.sw.yaml new file mode 100644 index 000000000..7a8226c62 --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/03-sonataflow_callbackstatetimeouts-no-persistence.sw.yaml @@ -0,0 +1,86 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + name: callbackstatetimeouts-no-persistence + annotations: + sonataflow.org/description: Callback State Timeouts Example k8s + sonataflow.org/version: 0.0.1 +spec: + persistence: {} + podTemplate: + container: + env: + - name: QUARKUS_FLYWAY_MIGRATE_AT_START + value: "true" + flow: + start: PrintStartMessage + events: + - name: callbackEvent + source: '' + type: callback_event_type + functions: + - name: systemOut + type: custom + operation: sysout + states: + - name: PrintStartMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has started.\"}" + transition: CallbackState + - name: CallbackState + type: callback + action: + name: callbackAction + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has executed the callbackFunction.\"}" + eventRef: callbackEvent + transition: CheckEventArrival + timeouts: + eventTimeout: PT30S + - name: CheckEventArrival + type: switch + dataConditions: + - condition: "${ .eventData != null }" + transition: EventArrived + defaultCondition: + transition: EventNotArrived + - name: EventArrived + type: inject + data: + exitMessage: "The callback event has arrived." + transition: PrintExitMessage + - name: EventNotArrived + type: inject + data: + exitMessage: "The callback event has not arrived, and the timeout has overdue." + transition: PrintExitMessage + - name: PrintExitMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has finalized. \" + .exitMessage + \" eventData: \" + .eventData}" + end: true diff --git a/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/kustomization.yaml b/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/kustomization.yaml new file mode 100644 index 000000000..0a410cde1 --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_with_no_persistence_required/kustomization.yaml @@ -0,0 +1,33 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +resources: +- 01-postgres.yaml +- 02-sonataflow_platform.yaml +- 03-sonataflow_callbackstatetimeouts-no-persistence.sw.yaml + +generatorOptions: + disableNameSuffixHash: true + +secretGenerator: + - name: postgres-secrets + literals: + - POSTGRES_USER=sonataflow + - POSTGRES_PASSWORD=sonataflow + - POSTGRES_DATABASE=sonataflow + - PGDATA=/var/lib/pgsql/data/userdata + +sortOptions: + order: fifo + diff --git a/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/01-postgres.yaml b/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/01-postgres.yaml new file mode 100644 index 000000000..662de4c7b --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/01-postgres.yaml @@ -0,0 +1,86 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: postgres + template: + metadata: + labels: + app.kubernetes.io/name: postgres + spec: + containers: + - name: postgres + image: postgres:13.2-alpine + imagePullPolicy: 'IfNotPresent' + ports: + - containerPort: 5432 + volumeMounts: + - name: storage + mountPath: /var/lib/postgresql/data + envFrom: + - secretRef: + name: postgres-secrets + readinessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + livenessProbe: + exec: + command: ["pg_isready"] + initialDelaySeconds: 15 + timeoutSeconds: 2 + resources: + limits: + memory: "256Mi" + cpu: "500m" + volumes: + - name: storage + persistentVolumeClaim: + claimName: postgres-pvc +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: postgres + name: postgres +spec: + selector: + app.kubernetes.io/name: postgres + ports: + - port: 5432 diff --git a/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/02-sonataflow_platform.yaml b/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/02-sonataflow_platform.yaml new file mode 100644 index 000000000..6dc311d62 --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/02-sonataflow_platform.yaml @@ -0,0 +1,37 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlowPlatform +metadata: + name: sonataflow-platform +spec: + persistence: + postgresql: + secretRef: + name: postgres-secrets + userKey: POSTGRES_USER + passwordKey: POSTGRES_PASSWORD + serviceRef: + name: postgres + port: 5432 + databaseName: sonataflow + build: + template: + buildArgs: + - name: QUARKUS_EXTENSIONS + value: org.kie.kogito:kogito-addons-quarkus-persistence-jdbc:999-SNAPSHOT,io.quarkus:quarkus-jdbc-postgresql:3.2.9.Final,io.quarkus:quarkus-agroal:3.2.9.Final + config: + strategyOptions: + KanikoBuildCacheEnabled: "true" diff --git a/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/03-sonataflow_callbackstatetimeouts.sw.yaml b/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/03-sonataflow_callbackstatetimeouts.sw.yaml new file mode 100644 index 000000000..1d4bdbb96 --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/03-sonataflow_callbackstatetimeouts.sw.yaml @@ -0,0 +1,85 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +apiVersion: sonataflow.org/v1alpha08 +kind: SonataFlow +metadata: + name: callbackstatetimeouts + annotations: + sonataflow.org/description: Callback State Timeouts Example k8s + sonataflow.org/version: 0.0.1 +spec: + podTemplate: + container: + env: + - name: QUARKUS_FLYWAY_MIGRATE_AT_START + value: "true" + flow: + start: PrintStartMessage + events: + - name: callbackEvent + source: '' + type: callback_event_type + functions: + - name: systemOut + type: custom + operation: sysout + states: + - name: PrintStartMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has started.\"}" + transition: CallbackState + - name: CallbackState + type: callback + action: + name: callbackAction + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has executed the callbackFunction.\"}" + eventRef: callbackEvent + transition: CheckEventArrival + timeouts: + eventTimeout: PT30S + - name: CheckEventArrival + type: switch + dataConditions: + - condition: "${ .eventData != null }" + transition: EventArrived + defaultCondition: + transition: EventNotArrived + - name: EventArrived + type: inject + data: + exitMessage: "The callback event has arrived." + transition: PrintExitMessage + - name: EventNotArrived + type: inject + data: + exitMessage: "The callback event has not arrived, and the timeout has overdue." + transition: PrintExitMessage + - name: PrintExitMessage + type: operation + actions: + - name: printSystemOut + functionRef: + refName: systemOut + arguments: + message: "${\"callback-state-timeouts: \" + $WORKFLOW.instanceId + \" has finalized. \" + .exitMessage + \" eventData: \" + .eventData}" + end: true diff --git a/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/kustomization.yaml b/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/kustomization.yaml new file mode 100644 index 000000000..b7f587bcc --- /dev/null +++ b/test/testdata/workflow/persistence/from_platform_without_di_and_js_services/kustomization.yaml @@ -0,0 +1,33 @@ +# Copyright 2024 Apache Software Foundation (ASF) +# +# 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. + +resources: +- 01-postgres.yaml +- 02-sonataflow_platform.yaml +- 03-sonataflow_callbackstatetimeouts.sw.yaml + +generatorOptions: + disableNameSuffixHash: true + +secretGenerator: + - name: postgres-secrets + literals: + - POSTGRES_USER=sonataflow + - POSTGRES_PASSWORD=sonataflow + - POSTGRES_DATABASE=sonataflow + - PGDATA=/var/lib/pgsql/data/userdata + +sortOptions: + order: fifo + diff --git a/test/yaml.go b/test/yaml.go index d1c4006b9..8c3692a6f 100644 --- a/test/yaml.go +++ b/test/yaml.go @@ -44,6 +44,7 @@ const ( SonataFlowGreetingsWithDataInputSchemaCR = "sonataflow.org_v1alpha08_sonataflow_greetings_datainput.yaml" SonataFlowGreetingsWithStaticResourcesCR = "sonataflow.org_v1alpha08_sonataflow-metainf.yaml" SonataFlowSimpleOpsYamlCR = "sonataflow.org_v1alpha08_sonataflow-simpleops.yaml" + SonataFlowVetWithEventCR = "sonataflow.org_v1alpha08_sonataflow_vet_event.yaml" SonataFlowGreetingsDataInputSchemaConfig = "v1_configmap_greetings_datainput.yaml" SonataFlowGreetingsStaticFilesConfig = "v1_configmap_greetings_staticfiles.yaml" sonataFlowPlatformYamlCR = "sonataflow.org_v1alpha08_sonataflowplatform.yaml" @@ -202,6 +203,10 @@ func GetBaseSonataFlow(namespace string, options ...*func(*operatorapi.SonataFlo return NewSonataFlow(sonataFlowSampleYamlCR, namespace) } +func GetVetEventSonataFlow(namespace string) *operatorapi.SonataFlow { + return GetSonataFlow(SonataFlowVetWithEventCR, namespace) +} + func GetBaseSonataFlowWithDevProfile(namespace string) *operatorapi.SonataFlow { return NewSonataFlow(sonataFlowSampleYamlCR, namespace, SetDevProfile) } @@ -262,8 +267,17 @@ func GetSonataFlowE2eOrderProcessingFolder() string { func GetSonataFlowE2EPlatformServicesDirectory() string { return filepath.Join(getTestDataDir(), "platform", "services") } + +func GetSonataFlowE2EPlatformNoServicesDirectory() string { + return filepath.Join(getTestDataDir(), "platform", "noservices") +} + +func GetSonataFlowE2EPlatformPersistenceSampleDataDirectory(subdir string) string { + return filepath.Join(getTestDataDir(), "platform", "persistence", subdir) +} + func GetSonataFlowE2EWorkflowPersistenceSampleDataDirectory(subdir string) string { - return filepath.Join(getTestDataDir(), "persistence", "workflow", subdir) + return filepath.Join(getTestDataDir(), "workflow", "persistence", subdir) } // getTestDataDir gets the testdata directory containing every sample out there from test/testdata. diff --git a/testbdd/go.mod b/testbdd/go.mod index 01d9d91e0..ad885f54f 100644 --- a/testbdd/go.mod +++ b/testbdd/go.mod @@ -40,7 +40,7 @@ require ( github.com/blendle/zapdriver v1.3.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cloudevents/sdk-go/v2 v2.4.1 // indirect + github.com/cloudevents/sdk-go/v2 v2.15.2 // indirect github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect diff --git a/testbdd/go.sum b/testbdd/go.sum index 0001c2e33..09f220a4f 100644 --- a/testbdd/go.sum +++ b/testbdd/go.sum @@ -150,8 +150,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudevents/conformance v0.2.0/go.mod h1:rHKDwylBH89Rns6U3wL9ww8bg9/4GbwRCDNuyoC6bcc= github.com/cloudevents/sdk-go/observability/opencensus/v2 v2.4.1/go.mod h1:lhEpxMrIUkeu9rVRgoAbyqZ8GR8Hd3DUy+thHUxAHoI= -github.com/cloudevents/sdk-go/v2 v2.4.1 h1:rZJoz9QVLbWQmnvLPDFEmv17Czu+CfSPwMO6lhJ72xQ= github.com/cloudevents/sdk-go/v2 v2.4.1/go.mod h1:MZiMwmAh5tGj+fPFvtHv9hKurKqXtdB9haJYMJ/7GJY= +github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= diff --git a/utils/kubernetes/volumes.go b/utils/kubernetes/volumes.go index 4c7855f1c..71b56d620 100644 --- a/utils/kubernetes/volumes.go +++ b/utils/kubernetes/volumes.go @@ -101,19 +101,26 @@ func VolumeMountAdd(volumeMount []corev1.VolumeMount, name, mountPath string) [] // AddOrReplaceVolume adds or removes the given volumes to the PodSpec. // If there's already a volume with the same name, it's replaced. func AddOrReplaceVolume(podSpec *corev1.PodSpec, volumes ...corev1.Volume) { - // indexing the volumes to add to the podSpec we avoid O(n) operation when searching for a volume to replace - volumesToAdd := make(map[string]corev1.Volume, len(volumes)) + // volumes iterated here are read/constructed by the caller following the order defined in the original CRD, and that + // order must be preserved. If not preserved, in the reconciliation cycles an order change in the volumes might be + // interpreted as configuration change in the original resource, causing undesired side effects like creating + // a new ReplicaSet for a deployment with the subsequent pods spawning reported here. + volumesToAdd := make([]corev1.Volume, 0) + wasAdded := false for _, volume := range volumes { - volumesToAdd[volume.Name] = volume - } - // replace - for i := range podSpec.Volumes { - if vol, ok := volumesToAdd[podSpec.Volumes[i].Name]; ok { - podSpec.Volumes[i] = vol - delete(volumesToAdd, vol.Name) + wasAdded = false + for i := 0; !wasAdded && i < len(podSpec.Volumes); i++ { + if volume.Name == podSpec.Volumes[i].Name { + // replace existing + podSpec.Volumes[i] = volume + wasAdded = true + } + } + if !wasAdded { + // remember to add it later in order + volumesToAdd = append(volumesToAdd, volume) } } - // append what's left for _, volume := range volumesToAdd { podSpec.Volumes = append(podSpec.Volumes, volume) } @@ -121,18 +128,26 @@ func AddOrReplaceVolume(podSpec *corev1.PodSpec, volumes ...corev1.Volume) { // AddOrReplaceVolumeMount same as AddOrReplaceVolume, but with VolumeMounts in a specific container func AddOrReplaceVolumeMount(containerIndex int, podSpec *corev1.PodSpec, mounts ...corev1.VolumeMount) { - mountsToAdd := make(map[string]corev1.VolumeMount, len(mounts)) + // analogous to AddOrReplaceVolume function, the processing must be realized en order. + // see: AddOrReplaceVolume + mountsToAdd := make([]corev1.VolumeMount, 0) + wasAdded := false + container := &podSpec.Containers[containerIndex] for _, mount := range mounts { - mountsToAdd[mount.Name] = mount - } - for i := range podSpec.Containers[containerIndex].VolumeMounts { - if mount, ok := mountsToAdd[podSpec.Containers[containerIndex].VolumeMounts[i].Name]; ok { - podSpec.Containers[containerIndex].VolumeMounts[i] = mount - delete(mountsToAdd, mount.Name) + wasAdded = false + for i := 0; !wasAdded && i < len(container.VolumeMounts); i++ { + if mount.Name == container.VolumeMounts[i].Name { + // replace existing + container.VolumeMounts[i] = mount + wasAdded = true + } + } + if !wasAdded { + // remember to add it later in order + mountsToAdd = append(mountsToAdd, mount) } } - // append what's left for _, mount := range mountsToAdd { - podSpec.Containers[containerIndex].VolumeMounts = append(podSpec.Containers[containerIndex].VolumeMounts, mount) + container.VolumeMounts = append(container.VolumeMounts, mount) } } diff --git a/utils/kubernetes/volumes_test.go b/utils/kubernetes/volumes_test.go index 618e62779..6b296c671 100644 --- a/utils/kubernetes/volumes_test.go +++ b/utils/kubernetes/volumes_test.go @@ -15,7 +15,6 @@ package kubernetes import ( - "sort" "testing" "github.com/stretchr/testify/assert" @@ -46,9 +45,6 @@ func TestReplaceOrAddVolume(t *testing.T) { AddOrReplaceVolume(&podSpec, volumes...) assert.Len(t, podSpec.Volumes, 3) - sort.Slice(podSpec.Volumes, func(i, j int) bool { - return podSpec.Volumes[i].Name < podSpec.Volumes[j].Name - }) assert.Equal(t, "cmA", podSpec.Volumes[0].ConfigMap.Name) assert.Equal(t, "cmB", podSpec.Volumes[1].ConfigMap.Name) assert.Equal(t, "cmC", podSpec.Volumes[2].ConfigMap.Name) @@ -69,9 +65,6 @@ func TestReplaceOrAddVolume_Append(t *testing.T) { AddOrReplaceVolume(&podSpec, volumes...) assert.Len(t, podSpec.Volumes, 2) - sort.Slice(podSpec.Volumes, func(i, j int) bool { - return podSpec.Volumes[i].Name < podSpec.Volumes[j].Name - }) assert.Equal(t, "cm1", podSpec.Volumes[0].ConfigMap.Name) assert.Equal(t, "cmB", podSpec.Volumes[1].ConfigMap.Name) } @@ -90,9 +83,6 @@ func TestReplaceOrAddVolume_EmptyVolumes(t *testing.T) { AddOrReplaceVolume(&podSpec, volumes...) assert.Len(t, podSpec.Volumes, 2) - sort.Slice(podSpec.Volumes, func(i, j int) bool { - return podSpec.Volumes[i].Name < podSpec.Volumes[j].Name - }) assert.Equal(t, "cm1", podSpec.Volumes[0].ConfigMap.Name) assert.Equal(t, "cm2", podSpec.Volumes[1].ConfigMap.Name) } @@ -114,9 +104,6 @@ func TestReplaceOrAddVolume_EmptyPodVolumes(t *testing.T) { AddOrReplaceVolume(&podSpec, volumes...) assert.Len(t, podSpec.Volumes, 3) - sort.Slice(podSpec.Volumes, func(i, j int) bool { - return podSpec.Volumes[i].Name < podSpec.Volumes[j].Name - }) assert.Equal(t, "cmA", podSpec.Volumes[0].ConfigMap.Name) assert.Equal(t, "cmB", podSpec.Volumes[1].ConfigMap.Name) assert.Equal(t, "cmC", podSpec.Volumes[2].ConfigMap.Name) @@ -137,9 +124,6 @@ func TestAddOrReplaceVolumeMount(t *testing.T) { AddOrReplaceVolumeMount(0, &podSpec, mounts...) assert.Len(t, podSpec.Containers[0].VolumeMounts, 2) - sort.Slice(podSpec.Containers[0].VolumeMounts, func(i, j int) bool { - return podSpec.Containers[0].VolumeMounts[i].Name < podSpec.Containers[0].VolumeMounts[j].Name - }) assert.Equal(t, "/dev", podSpec.Containers[0].VolumeMounts[0].MountPath) assert.Equal(t, "/tmp/any/path", podSpec.Containers[0].VolumeMounts[1].MountPath) } diff --git a/version/version.go b/version/version.go index c3f8102ae..942dc66c4 100644 --- a/version/version.go +++ b/version/version.go @@ -25,11 +25,11 @@ import ( const ( // Current version - OperatorVersion = "2.0.0-snapshot" + OperatorVersion = "999.0.0-snapshot" // Should not be changed snapshotSuffix = "snapshot" - latestVersion = "2.0.0-snapshot" + latestVersion = "999.0.0-snapshot" ) func IsSnapshot() bool { diff --git a/workflowproj/io.go b/workflowproj/io.go index 39b637076..152d37bee 100644 --- a/workflowproj/io.go +++ b/workflowproj/io.go @@ -42,11 +42,11 @@ func ensurePath(path string) error { return nil } -func saveAsKubernetesManifest(object client.Object, savePath, prefix string) error { +func saveAsKubernetesManifest(object client.Object, savePath string, prefix int) error { if reflect.ValueOf(object).IsNil() { return nil } - filename := strings.ToLower(fmt.Sprintf("%s%s_%s%s", + filename := strings.ToLower(fmt.Sprintf("%02d-%s_%s%s", prefix, object.GetObjectKind().GroupVersionKind().Kind, object.GetName(), diff --git a/workflowproj/operator.go b/workflowproj/operator.go index 33b4ca672..de6615d53 100644 --- a/workflowproj/operator.go +++ b/workflowproj/operator.go @@ -28,7 +28,6 @@ import ( "github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata" operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08" - "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles" ) const ( @@ -74,10 +73,10 @@ func GetWorkflowManagedPropertiesConfigMapName(workflow *operatorapi.SonataFlow) return workflow.Name + workflowManagedConfigMapNameSuffix } -// GetWorkflowManagedPropertiesConfigMapName gets the default ConfigMap name that holds the managed application property for the given workflow +// GetManagedPropertiesFileName gets the default ConfigMap name that holds the managed application property for the given workflow func GetManagedPropertiesFileName(workflow *operatorapi.SonataFlow) string { profile := metadata.ProdProfile - if profiles.IsDevProfile(workflow) { + if IsDevProfile(workflow) { profile = metadata.DevProfile } return fmt.Sprintf("application-%s.properties", profile) diff --git a/workflowproj/workflowproj.go b/workflowproj/workflowproj.go index afabd7e5c..79359e4b2 100644 --- a/workflowproj/workflowproj.go +++ b/workflowproj/workflowproj.go @@ -153,18 +153,21 @@ func (w *workflowProjectHandler) SaveAsKubernetesManifests(path string) error { if err := w.parseRawProject(); err != nil { return err } - fileCount := 1 - if err := saveAsKubernetesManifest(w.project.Workflow, path, fmt.Sprintf("%02d-", 1)); err != nil { - return err + fileCount := 0 + if w.project.Properties != nil { + fileCount++ + if err := saveAsKubernetesManifest(w.project.Properties, path, fileCount); err != nil { + return err + } } - for i, r := range w.project.Resources { - fileCount = i + 1 - if err := saveAsKubernetesManifest(r, path, fmt.Sprintf("%02d-", fileCount)); err != nil { + for _, r := range w.project.Resources { + fileCount++ + if err := saveAsKubernetesManifest(r, path, fileCount); err != nil { return err } } fileCount++ - if err := saveAsKubernetesManifest(w.project.Properties, path, fmt.Sprintf("%02d-", fileCount)); err != nil { + if err := saveAsKubernetesManifest(w.project.Workflow, path, fileCount); err != nil { return err } return nil @@ -298,3 +301,12 @@ func (w *workflowProjectHandler) addResourceConfigMapToProject(cm *corev1.Config } return nil } + +// IsDevProfile detects if the workflow is using the Dev profile or not +func IsDevProfile(workflow *operatorapi.SonataFlow) bool { + profile := workflow.Annotations[metadata.Profile] + if len(profile) == 0 { + return false + } + return metadata.ProfileType(profile) == metadata.DevProfile +} diff --git a/workflowproj/workflowproj_test.go b/workflowproj/workflowproj_test.go index c1207527f..1c04c602a 100644 --- a/workflowproj/workflowproj_test.go +++ b/workflowproj/workflowproj_test.go @@ -28,8 +28,11 @@ import ( "strings" "testing" + "k8s.io/apimachinery/pkg/runtime/schema" + "github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata" "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes/scheme" ) @@ -113,10 +116,21 @@ func Test_Handler_WorkflowMinimalAndPropsAndSpecAndGeneric(t *testing.T) { assert.Equal(t, "02-hello-resources", proj.Resources[1].Name) assert.Equal(t, proj.Workflow.Spec.Resources.ConfigMaps[0].ConfigMap.Name, proj.Resources[0].Name) assert.Equal(t, proj.Workflow.Spec.Resources.ConfigMaps[1].ConfigMap.Name, proj.Resources[1].Name) - assert.NotEmpty(t, proj.Resources[0].Data, fmt.Sprintf("Data in proj.Resources[0] is empty %+v", proj.Resources[0])) - assert.NotEmpty(t, proj.Resources[1].Data, fmt.Sprintf("Data in proj.Resources[1] is empty %+v", proj.Resources[1])) - assert.NotEmpty(t, proj.Resources[0].Data["myopenapi.json"]) - assert.NotEmpty(t, proj.Resources[1].Data["input.json"]) + data, err := getResourceDataWithFileName(proj.Resources, "myopenapi.json") + assert.NoError(t, err) + assert.NotEmpty(t, data) + data, err = getResourceDataWithFileName(proj.Resources, "input.json") + assert.NoError(t, err) + assert.NotEmpty(t, data) +} + +func getResourceDataWithFileName(cms []*corev1.ConfigMap, fileName string) (string, error) { + for i := range cms { + if data, ok := cms[i].Data[fileName]; ok { + return data, nil + } + } + return "", fmt.Errorf("No configmap found with data containing filename %s", fileName) } func Test_Handler_WorklflowServiceAndPropsAndSpec_SaveAs(t *testing.T) { @@ -140,17 +154,34 @@ func Test_Handler_WorklflowServiceAndPropsAndSpec_SaveAs(t *testing.T) { assert.NoError(t, err) assert.Len(t, files, 4) - for _, f := range files { - if strings.HasSuffix(f.Name(), yamlFileExt) { - contents, err := os.ReadFile(path.Join(tmpPath, f.Name())) - assert.NoError(t, err) - decode := scheme.Codecs.UniversalDeserializer().Decode - k8sObj, _, err := decode(contents, nil, nil) - assert.NoError(t, err) - assert.NotNil(t, k8sObj) - assert.NotEmpty(t, k8sObj.GetObjectKind().GroupVersionKind().String()) - } + expectedFiles := []string{ + "01-configmap_service-props.yaml", + "02-configmap_01-service-resources.yaml", + "03-configmap_02-service-resources.yaml", + "04-sonataflow_service.yaml", } + expectedKinds := []schema.GroupVersionKind{ + {Group: "", Version: "v1", Kind: "ConfigMap"}, + {Group: "", Version: "v1", Kind: "ConfigMap"}, + {Group: "", Version: "v1", Kind: "ConfigMap"}, + {Group: "sonataflow.org", Version: "v1alpha08", Kind: "SonataFlow"}, + } + + for i := 0; i < len(files); i++ { + assert.Equal(t, files[i].Name(), expectedFiles[i]) + assertIsK8sObject(t, tmpPath, files[i].Name(), expectedKinds[i]) + } +} + +func assertIsK8sObject(t *testing.T, basePath string, fileName string, gvk schema.GroupVersionKind) { + contents, err := os.ReadFile(path.Join(basePath, fileName)) + assert.NoError(t, err) + decode := scheme.Codecs.UniversalDeserializer().Decode + k8sObj, _, err := decode(contents, nil, nil) + assert.NoError(t, err) + assert.NotNil(t, k8sObj) + assert.NotEmpty(t, k8sObj.GetObjectKind().GroupVersionKind().String()) + assert.Equal(t, gvk, k8sObj.GetObjectKind().GroupVersionKind()) } func Test_Handler_WorkflowService_SaveAs(t *testing.T) { @@ -170,6 +201,9 @@ func Test_Handler_WorkflowService_SaveAs(t *testing.T) { for _, f := range files { if strings.HasSuffix(f.Name(), yamlFileExt) { + // we have only one file produced in these test cases + prefix := fmt.Sprintf("%02d-", 1) + assert.True(t, strings.HasPrefix(f.Name(), prefix)) contents, err := os.ReadFile(path.Join(tmpPath, f.Name())) assert.NoError(t, err) decode := scheme.Codecs.UniversalDeserializer().Decode