You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kie.apache.org by wm...@apache.org on 2024/02/02 08:10:55 UTC

(incubator-kie-kogito-serverless-operator) branch main updated: kie-kogito-serverless-operator-361: Add data-index and job service startupProbes to the workflow Deployment (#377)

This is an automated email from the ASF dual-hosted git repository.

wmedvedeo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-kogito-serverless-operator.git


The following commit(s) were added to refs/heads/main by this push:
     new 47c73db6 kie-kogito-serverless-operator-361: Add data-index and job service startupProbes to the workflow Deployment (#377)
47c73db6 is described below

commit 47c73db60dda8712b74de54934032c774125815d
Author: Walter Medvedeo <wm...@gmail.com>
AuthorDate: Fri Feb 2 09:10:51 2024 +0100

    kie-kogito-serverless-operator-361: Add data-index and job service startupProbes to the workflow Deployment (#377)
---
 controllers/platform/services/properties.go        |   6 +
 controllers/platform/services/services.go          |  17 +-
 .../profiles/common/constants/platform_services.go |  13 +-
 .../profiles/common/properties/application_test.go |  15 +-
 controllers/sonataflowplatform_controller_test.go  |   4 +-
 controllers/workflowdef/utils.go                   | 113 ++++++++
 controllers/workflowdef/utils_suite_test.go        |  32 +++
 controllers/workflowdef/utils_test.go              | 285 +++++++++++++++++++++
 8 files changed, 465 insertions(+), 20 deletions(-)

diff --git a/controllers/platform/services/properties.go b/controllers/platform/services/properties.go
index dbe6dc3f..c541f5b0 100644
--- a/controllers/platform/services/properties.go
+++ b/controllers/platform/services/properties.go
@@ -24,6 +24,8 @@ import (
 	"net/url"
 	"strings"
 
+	"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"
@@ -164,6 +166,7 @@ func GenerateDataIndexWorkflowProperties(workflow *operatorapi.SonataFlow, platf
 	if workflow != nil && !profiles.IsDevProfile(workflow) && di.IsServiceEnabled() {
 		props.Set(constants.KogitoProcessDefinitionsEventsEnabled, "true")
 		props.Set(constants.KogitoProcessInstancesEventsEnabled, "true")
+		props.Set(constants.KogitoDataIndexHealthCheckEnabled, "true")
 		di := NewDataIndexHandler(platform)
 		p, err := di.GenerateWorkflowProperties()
 		if err != nil {
@@ -186,6 +189,9 @@ func GenerateJobServiceWorkflowProperties(workflow *operatorapi.SonataFlow, plat
 	props.Set(constants.JobServiceRequestEventsURL, fmt.Sprintf("%s://localhost/v2/jobs/events", constants.JobServiceURLProtocol))
 	js := NewJobServiceHandler(platform)
 	if workflow != nil && !profiles.IsDevProfile(workflow) && js.IsServiceEnabled() {
+		if workflowdef.HasTimeouts(workflow) {
+			props.Set(constants.KogitoJobServiceHealthCheckEnabled, "true")
+		}
 		p, err := js.GenerateWorkflowProperties()
 		if err != nil {
 			return nil, err
diff --git a/controllers/platform/services/services.go b/controllers/platform/services/services.go
index e49679e5..05aedbb6 100644
--- a/controllers/platform/services/services.go
+++ b/controllers/platform/services/services.go
@@ -81,8 +81,6 @@ type PlatformServiceHandler interface {
 	GetLocalServiceBaseUrl() string
 	// GetServiceBaseUrl returns the base url of the service, based on whether using local or cluster-scoped service.
 	GetServiceBaseUrl() string
-	// GetServiceUrl returns the service url, based on whether using local or cluster-scoped service.
-	GetServiceUrl() string
 	// IsServiceEnabled returns true if the service is enabled in either the spec or the status.clusterPlatformRef.
 	IsServiceEnabled() bool
 	// SetServiceUrlInStatus sets the service url in status. if reconciled instance does not have service set in spec AND
@@ -150,10 +148,6 @@ func (d DataIndexHandler) IsServiceEnabled() bool {
 	return d.IsServiceEnabledInSpec() || d.isServiceEnabledInStatus()
 }
 
-func (d DataIndexHandler) GetServiceUrl() string {
-	return d.GetServiceBaseUrl() + constants.KogitoProcessInstancesEventsPath
-}
-
 func (d DataIndexHandler) GetServiceBaseUrl() string {
 	if d.IsServiceEnabledInSpec() {
 		return d.GetLocalServiceBaseUrl()
@@ -236,8 +230,9 @@ func (d DataIndexHandler) GetServiceCmName() string {
 func (d DataIndexHandler) GenerateWorkflowProperties() (*properties.Properties, error) {
 	props := properties.NewProperties()
 	if d.IsServiceEnabled() {
+		props.Set(constants.KogitoDataIndexURL, d.GetServiceBaseUrl())
 		props.Set(constants.KogitoProcessDefinitionsEventsURL, d.GetServiceBaseUrl()+constants.KogitoProcessDefinitionsEventsPath)
-		props.Set(constants.KogitoProcessInstancesEventsURL, d.GetServiceUrl())
+		props.Set(constants.KogitoProcessInstancesEventsURL, d.GetServiceBaseUrl()+constants.KogitoProcessInstancesEventsPath)
 	}
 	return props, nil
 }
@@ -313,10 +308,6 @@ func (j JobServiceHandler) IsServiceEnabled() bool {
 	return j.IsServiceEnabledInSpec() || j.isServiceEnabledInStatus()
 }
 
-func (j JobServiceHandler) GetServiceUrl() string {
-	return j.GetServiceBaseUrl() + constants.JobServiceURLPath
-}
-
 func (j JobServiceHandler) GetServiceBaseUrl() string {
 	if j.IsServiceEnabledInSpec() {
 		return j.GetLocalServiceBaseUrl()
@@ -411,8 +402,8 @@ func (j JobServiceHandler) GenerateServiceProperties() (*properties.Properties,
 func (j JobServiceHandler) GenerateWorkflowProperties() (*properties.Properties, error) {
 	props := properties.NewProperties()
 	if j.IsServiceEnabled() {
-		// add data source reactive URL
-		props.Set(constants.JobServiceRequestEventsURL, j.GetServiceUrl())
+		props.Set(constants.KogitoJobServiceURL, j.GetServiceBaseUrl())
+		props.Set(constants.JobServiceRequestEventsURL, j.GetServiceBaseUrl()+constants.JobServiceJobEventsPath)
 	}
 	return props, nil
 }
diff --git a/controllers/profiles/common/constants/platform_services.go b/controllers/profiles/common/constants/platform_services.go
index 3db52e16..e0f24927 100644
--- a/controllers/profiles/common/constants/platform_services.go
+++ b/controllers/profiles/common/constants/platform_services.go
@@ -32,7 +32,7 @@ const (
 	JobServiceStatusChangeEventsURL  = "mp.messaging.outgoing.kogito-job-service-job-status-events-http.url"
 	JobServiceURLProtocol            = "http"
 	JobServiceDataSourceReactiveURL  = "quarkus.datasource.reactive.url"
-	JobServiceURLPath                = "/v2/jobs/events"
+	JobServiceJobEventsPath          = "/v2/jobs/events"
 
 	KogitoProcessEventsProtocol           = "http"
 	KogitoProcessInstancesEventsURL       = "mp.messaging.outgoing.kogito-processinstances-events.url"
@@ -42,7 +42,16 @@ const (
 	KogitoProcessDefinitionsEventsEnabled = "kogito.events.processdefinitions.enabled"
 	KogitoProcessDefinitionsEventsPath    = "/definitions"
 	KogitoUserTasksEventsEnabled          = "kogito.events.usertasks.enabled"
-	KogitoEventsVariablesEnabled          = "kogito.events.variables.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"
+	// KogitoDataIndexURL configures the data index url, this value can be used internally by the workflow.
+	KogitoDataIndexURL = "kogito.data-index.url"
+	// KogitoJobServiceHealthCheckEnabled configures if a workflow must check for the job service availability as part
+	// of its start health check.
+	KogitoJobServiceHealthCheckEnabled = "kogito.jobs-service.health-enabled"
+	// KogitoJobServiceURL configures the jobs service, this value can be used internally by the workflow.
+	KogitoJobServiceURL                   = "kogito.jobs-service.url"
 	KogitoServiceURLProperty              = "kogito.service.url"
 	KogitoServiceURLProtocol              = "http"
 	DataIndexKafkaSmallRyeHealthProperty  = `quarkus.smallrye-health.check."io.quarkus.kafka.client.health.KafkaHealthCheck".enabled`
diff --git a/controllers/profiles/common/properties/application_test.go b/controllers/profiles/common/properties/application_test.go
index 2a95e967..543247dd 100644
--- a/controllers/profiles/common/properties/application_test.go
+++ b/controllers/profiles/common/properties/application_test.go
@@ -239,7 +239,7 @@ 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, 15, len(generatedProps.Keys()))
+	assert.Equal(t, 18, len(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, "http://"+platform.Name+"-"+constants.DataIndexServiceName+"."+platform.Namespace+"/processes", generatedProps.GetString(constants.KogitoProcessInstancesEventsURL, ""))
@@ -249,6 +249,9 @@ func Test_appPropertyHandler_WithServicesWithUserOverrides(t *testing.T) {
 	assert.Equal(t, "", generatedProps.GetString(constants.JobServiceDataSourceReactiveURL, ""))
 	assert.Equal(t, "", generatedProps.GetString(constants.JobServiceStatusChangeEvents, ""))
 	assert.Equal(t, "", generatedProps.GetString(constants.JobServiceStatusChangeEventsURL, ""))
+	assert.Equal(t, "true", generatedProps.GetString(constants.KogitoDataIndexHealthCheckEnabled, ""))
+	assert.Equal(t, "http://"+platform.Name+"-"+constants.DataIndexServiceName+"."+platform.Namespace, generatedProps.GetString(constants.KogitoDataIndexURL, ""))
+	assert.Equal(t, "http://"+platform.Name+"-"+constants.JobServiceName+"."+platform.Namespace, generatedProps.GetString(constants.KogitoJobServiceURL, ""))
 
 	// disabling data index bypasses config of outgoing events url
 	platform.Spec.Services.DataIndex.Enabled = nil
@@ -256,7 +259,7 @@ 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, 13, len(generatedProps.Keys()))
+	assert.Equal(t, 14, len(generatedProps.Keys()))
 	assert.Equal(t, "", generatedProps.GetString(constants.KogitoProcessDefinitionsEventsURL, ""))
 	assert.Equal(t, "false", generatedProps.GetString(constants.KogitoProcessDefinitionsEventsEnabled, ""))
 	assert.Equal(t, "", generatedProps.GetString(constants.KogitoProcessInstancesEventsURL, ""))
@@ -265,6 +268,7 @@ 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
@@ -465,6 +469,7 @@ func generateJobServiceWorkflowProductionProperties() *properties.Properties {
 	if jobServiceProdProperties == nil {
 		jobServiceProdProperties = properties.NewProperties()
 		jobServiceProdProperties.Set("kogito.service.url", "http://foo.default")
+		jobServiceProdProperties.Set("kogito.jobs-service.url", "http://foo-jobs-service.default")
 		jobServiceProdProperties.Set("quarkus.http.host", "0.0.0.0")
 		jobServiceProdProperties.Set("quarkus.http.port", "8080")
 		jobServiceProdProperties.Set("quarkus.kogito.devservices.enabled", "false")
@@ -489,7 +494,6 @@ func generateDataIndexWorkflowDevProperties() *properties.Properties {
 		dataIndexDevProperties.Set("quarkus.devservices.enabled", "false")
 		dataIndexDevProperties.Set("quarkus.kogito.devservices.enabled", "false")
 		dataIndexDevProperties.Set("org.kie.kogito.addons.knative.eventing.health-enabled", "false")
-		//TODO revisar, pero para el dev profile esto no va
 		dataIndexDevProperties.Set("mp.messaging.outgoing.kogito-job-service-job-request-events.connector", "quarkus-http")
 		dataIndexDevProperties.Set("mp.messaging.outgoing.kogito-job-service-job-request-events.url", "http://localhost/v2/jobs/events")
 		dataIndexDevProperties.Set("kogito.events.processdefinitions.enabled", "false")
@@ -504,6 +508,8 @@ func generateDataIndexWorkflowProductionProperties() *properties.Properties {
 	if dataIndexProdProperties == nil {
 		dataIndexProdProperties = properties.NewProperties()
 		dataIndexProdProperties.Set("kogito.service.url", "http://foo.default")
+		dataIndexProdProperties.Set("kogito.data-index.url", "http://foo-data-index-service.default")
+		dataIndexProdProperties.Set("kogito.data-index.health-enabled", "true")
 		dataIndexProdProperties.Set("quarkus.http.host", "0.0.0.0")
 		dataIndexProdProperties.Set("quarkus.http.port", "8080")
 		dataIndexProdProperties.Set("quarkus.devservices.enabled", "false")
@@ -544,6 +550,9 @@ func generateDataIndexAndJobServiceWorkflowProductionProperties() *properties.Pr
 	if dataIndexJobServiceProdProperties == nil {
 		dataIndexJobServiceProdProperties = properties.NewProperties()
 		dataIndexJobServiceProdProperties.Set("kogito.service.url", "http://foo.default")
+		dataIndexJobServiceProdProperties.Set("kogito.data-index.url", "http://foo-data-index-service.default")
+		dataIndexJobServiceProdProperties.Set("kogito.data-index.health-enabled", "true")
+		dataIndexJobServiceProdProperties.Set("kogito.jobs-service.url", "http://foo-jobs-service.default")
 		dataIndexJobServiceProdProperties.Set("quarkus.http.host", "0.0.0.0")
 		dataIndexJobServiceProdProperties.Set("quarkus.http.port", "8080")
 		dataIndexJobServiceProdProperties.Set("quarkus.kogito.devservices.enabled", "false")
diff --git a/controllers/sonataflowplatform_controller_test.go b/controllers/sonataflowplatform_controller_test.go
index 5594d644..11e5bb93 100644
--- a/controllers/sonataflowplatform_controller_test.go
+++ b/controllers/sonataflowplatform_controller_test.go
@@ -573,11 +573,11 @@ func TestSonataFlowPlatformController(t *testing.T) {
 		psDi := services.NewDataIndexHandler(ksp)
 		psDi2 := services.NewDataIndexHandler(ksp2)
 		assert.Equal(t, ksp2.Status.ClusterPlatformRef.Services.DataIndexRef.Url, psDi.GetLocalServiceBaseUrl())
-		assert.Equal(t, psDi.GetLocalServiceBaseUrl()+constants.KogitoProcessInstancesEventsPath, psDi2.GetServiceUrl())
+		assert.Equal(t, psDi.GetLocalServiceBaseUrl()+constants.KogitoProcessInstancesEventsPath, psDi2.GetServiceBaseUrl()+constants.KogitoProcessInstancesEventsPath)
 		psJs := services.NewJobServiceHandler(ksp)
 		psJs2 := services.NewJobServiceHandler(ksp2)
 		assert.Equal(t, ksp2.Status.ClusterPlatformRef.Services.JobServiceRef.Url, psJs.GetLocalServiceBaseUrl())
-		assert.Equal(t, psJs.GetLocalServiceBaseUrl()+constants.JobServiceURLPath, psJs2.GetServiceUrl())
+		assert.Equal(t, psJs.GetLocalServiceBaseUrl()+constants.JobServiceJobEventsPath, psJs2.GetServiceBaseUrl()+constants.JobServiceJobEventsPath)
 
 		ksp2.Spec.Services = &v1alpha08.ServicesPlatformSpec{}
 
diff --git a/controllers/workflowdef/utils.go b/controllers/workflowdef/utils.go
new file mode 100644
index 00000000..87a0356f
--- /dev/null
+++ b/controllers/workflowdef/utils.go
@@ -0,0 +1,113 @@
+/*
+ * 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 workflowdef
+
+import (
+	operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08"
+	"github.com/serverlessworkflow/sdk-go/v2/model"
+)
+
+// HasTimeouts returns true if current workflow has configured any of the SonataFlow supported timeouts, false
+// in any other case. This method might be reviewed when more timeouts are supported.
+func HasTimeouts(workflow *operatorapi.SonataFlow) bool {
+	flow := &workflow.Spec.Flow
+	hasTimeouts := HasWorkflowExecTimeout(flow) || HasWorkflowEventTimeout(flow)
+	for i := 0; !hasTimeouts && i < len(flow.States); i++ {
+		state := flow.States[i]
+		switch state.Type {
+		case model.StateTypeEvent:
+			hasTimeouts = HasEventStateTimeouts(state.EventState)
+		case model.StateTypeOperation:
+			hasTimeouts = HasOperationStateTimeouts(state.OperationState)
+		case model.StateTypeSwitch:
+			hasTimeouts = HasSwitchStateTimeouts(state.SwitchState)
+		case model.StateTypeSleep:
+			hasTimeouts = true
+		case model.StateTypeParallel:
+			hasTimeouts = HasParallelStateTimeouts(state.ParallelState)
+		case model.StateTypeForEach:
+			hasTimeouts = HasForEachStateTimeouts(state.ForEachState)
+		case model.StateTypeCallback:
+			hasTimeouts = HasCallbackStateTimeouts(state.CallbackState)
+		}
+	}
+	return hasTimeouts
+}
+
+func HasWorkflowEventTimeout(flow *operatorapi.Flow) bool {
+	return flow.Timeouts != nil && len(flow.Timeouts.EventTimeout) > 0
+}
+func HasWorkflowExecTimeout(flow *operatorapi.Flow) bool {
+	return flow.Timeouts != nil && flow.Timeouts.WorkflowExecTimeout != nil && len(flow.Timeouts.WorkflowExecTimeout.Duration) > 0
+}
+
+func HasEventStateTimeouts(state *model.EventState) bool {
+	if state.Timeouts != nil && len(state.Timeouts.EventTimeout) > 0 {
+		return true
+	}
+	for _, onEvent := range state.OnEvents {
+		if hasActionsWithSleep(&onEvent.Actions) {
+			return true
+		}
+	}
+	return false
+}
+
+func HasOperationStateTimeouts(state *model.OperationState) bool {
+	return hasActionsWithSleep(&state.Actions)
+}
+
+func HasSwitchStateTimeouts(state *model.SwitchState) bool {
+	return state.Timeouts != nil && len(state.Timeouts.EventTimeout) > 0
+}
+
+func HasParallelStateTimeouts(state *model.ParallelState) bool {
+	for _, branch := range state.Branches {
+		if hasBranchTimeouts(&branch) {
+			return true
+		}
+	}
+	return false
+}
+
+func hasBranchTimeouts(branch *model.Branch) bool {
+	return hasActionsWithSleep(&branch.Actions)
+}
+
+func HasForEachStateTimeouts(state *model.ForEachState) bool {
+	return hasActionsWithSleep(&state.Actions)
+}
+
+func HasCallbackStateTimeouts(state *model.CallbackState) bool {
+	return (state.Timeouts != nil && len(state.Timeouts.EventTimeout) > 0) || hasAnySleep(&state.Action)
+}
+
+func hasActionsWithSleep(actions *[]model.Action) bool {
+	for _, action := range *actions {
+		if hasAnySleep(&action) {
+			return true
+		}
+	}
+	return false
+}
+
+func hasAnySleep(action *model.Action) bool {
+	return action.Sleep != nil && (len(action.Sleep.Before) > 0 || len(action.Sleep.After) > 0)
+}
diff --git a/controllers/workflowdef/utils_suite_test.go b/controllers/workflowdef/utils_suite_test.go
new file mode 100644
index 00000000..aa3919a7
--- /dev/null
+++ b/controllers/workflowdef/utils_suite_test.go
@@ -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.
+ */
+
+package workflowdef
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+func TestProperties(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Utils Suite")
+}
diff --git a/controllers/workflowdef/utils_test.go b/controllers/workflowdef/utils_test.go
new file mode 100644
index 00000000..9e279b10
--- /dev/null
+++ b/controllers/workflowdef/utils_test.go
@@ -0,0 +1,285 @@
+/*
+ * 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 workflowdef
+
+import (
+	operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	cncfmodel "github.com/serverlessworkflow/sdk-go/v2/model"
+)
+
+var (
+	emptyDuration = ""
+	isoDuration   = "PT30S"
+)
+
+var _ = DescribeTable("Workflow has timeouts",
+	func(workflow *operatorapi.SonataFlow, expectedHasTimeouts bool) {
+		hasTimeouts := HasTimeouts(workflow)
+		Expect(hasTimeouts).Should(Equal(expectedHasTimeouts))
+	},
+	Entry("for a workflow with WorkflowExecTimeout", workflowWithWorkflowExecTimeout(&isoDuration), true),
+	Entry("for a workflow with empty WorkflowExecTimeout", workflowWithWorkflowExecTimeout(&emptyDuration), false),
+	Entry("for a workflow with nil WorkflowExecTimeout", workflowWithWorkflowExecTimeout(&emptyDuration), false),
+
+	Entry("for a workflow with WorkflowEventTimeout", workflowWithWorkflowEventStateTimeout(&isoDuration), true),
+	Entry("for a workflow with empty WorkflowEventTimeout", workflowWithWorkflowEventStateTimeout(&emptyDuration), false),
+	Entry("for a workflow with nil WorkflowEventTimeout", workflowWithWorkflowEventStateTimeout(nil), false),
+
+	Entry("for a workflow with EventState with timeouts", workflowWithEventStateWithTimeout(&isoDuration), true),
+	Entry("for a workflow with EventState empty timeouts", workflowWithEventStateWithTimeout(&emptyDuration), false),
+	Entry("for a workflow with EventState nil timeouts", workflowWithEventStateWithTimeout(&emptyDuration), false),
+	Entry("for a workflow with EventState with action sleep at before", workflowWithEventStateWithActionSleep(true, false), true),
+	Entry("for a workflow with EventState with action sleep at before", workflowWithEventStateWithActionSleep(false, true), true),
+
+	Entry("for a workflow with OperationState with action sleep at before", workflowWithEventStateWithActionSleep(true, false), true),
+	Entry("for a workflow with OperationState with with action sleep at after", workflowWithEventStateWithActionSleep(false, true), true),
+	Entry("for a workflow with OperationState with no action sleep", workflowWithEventStateWithActionSleep(false, false), false),
+
+	Entry("for a workflow with SwitchState with timeouts", workflowWithSwitchStateWithTimeout(&isoDuration), true),
+	Entry("for a workflow with SwitchState with empty timeouts", workflowWithSwitchStateWithTimeout(&emptyDuration), false),
+	Entry("for a workflow with SwitchState with nil timeouts", workflowWithSwitchStateWithTimeout(nil), false),
+
+	Entry("for a workflow with SleepState", workflowWithSleepState(), true),
+
+	Entry("for a workflow with ParallelState with branch with sleep at before", workflowWithParallelState(true, false), true),
+	Entry("for a workflow with ParallelState with branch with sleep at after", workflowWithParallelState(false, true), true),
+	Entry("for a workflow with ParallelState with branches with sleep at before and after", workflowWithParallelState(true, true), true),
+	Entry("for a workflow with ParallelState with no sleep branches", workflowWithParallelState(false, false), false),
+
+	Entry("for a workflow with ForEachState with action sleep at before", workflowWithForEachStateWithActionSleep(true, false), true),
+	Entry("for a workflow with ForEachState with with action sleep at after", workflowWithForEachStateWithActionSleep(false, true), true),
+	Entry("for a workflow with ForEachState with no action sleep", workflowWithForEachStateWithActionSleep(false, false), false),
+
+	Entry("for a workflow with CallbackState with timeouts", workflowWithCallbackStateTimeoutAndActionSleep(&isoDuration, nil, nil), true),
+	Entry("for a workflow with CallbackState with nil timeouts and before action sleep", workflowWithCallbackStateTimeoutAndActionSleep(nil, &isoDuration, nil), true),
+	Entry("for a workflow with CallbackState with nil timeouts and after action sleep", workflowWithCallbackStateTimeoutAndActionSleep(nil, nil, &isoDuration), true),
+	Entry("for a workflow with CallbackState with nil timeouts and no action sleep", workflowWithCallbackStateTimeoutAndActionSleep(nil, nil, nil), false),
+)
+
+func workflowWithWorkflowExecTimeout(duration *string) *operatorapi.SonataFlow {
+	wf := generateWorkflow()
+	if duration != nil {
+		wf.Spec.Flow.Timeouts = &cncfmodel.Timeouts{}
+		wf.Spec.Flow.Timeouts.WorkflowExecTimeout = &cncfmodel.WorkflowExecTimeout{
+			Duration: *duration,
+		}
+	}
+	return wf
+}
+
+func workflowWithWorkflowEventStateTimeout(duration *string) *operatorapi.SonataFlow {
+	wf := generateWorkflow()
+	if duration != nil {
+		wf.Spec.Flow.Timeouts = &cncfmodel.Timeouts{
+			EventTimeout: *duration,
+		}
+	}
+	return wf
+}
+
+func workflowWithEventStateWithTimeout(duration *string) *operatorapi.SonataFlow {
+	wf := generateWorkflow()
+	state := generateEventState()
+	if duration != nil {
+		state.EventState.Timeouts = &cncfmodel.EventStateTimeout{EventTimeout: *duration}
+	}
+	wf.Spec.Flow.States = []cncfmodel.State{*state}
+	return wf
+}
+
+func workflowWithEventStateWithActionSleep(before bool, after bool) *operatorapi.SonataFlow {
+	wf := generateWorkflow()
+	state := generateEventState()
+	wf.Spec.Flow.States = []cncfmodel.State{*state}
+	state.EventState.OnEvents = []cncfmodel.OnEvents{
+		{
+			Actions: generateActionsWithSleep(before, after),
+		},
+	}
+	return wf
+}
+
+func workflowWithOperationStateWithActionSleep(before bool, after bool) *operatorapi.SonataFlow {
+	wf := generateWorkflow()
+	state := generateOperationState()
+	wf.Spec.Flow.States = []cncfmodel.State{*state}
+	state.OperationState.Actions = generateActionsWithSleep(before, after)
+	return wf
+}
+
+func workflowWithSwitchStateWithTimeout(duration *string) *operatorapi.SonataFlow {
+	wf := generateWorkflow()
+	state := generateSwitchState()
+	wf.Spec.Flow.States = []cncfmodel.State{*state}
+	if duration != nil {
+		state.SwitchState.Timeouts = &cncfmodel.SwitchStateTimeout{
+			EventTimeout: *duration,
+		}
+	}
+	return wf
+}
+
+func workflowWithSleepState() *operatorapi.SonataFlow {
+	wf := generateWorkflow()
+	wf.Spec.Flow.States = []cncfmodel.State{*generateSleepState()}
+	return wf
+}
+
+func workflowWithParallelState(branchWithBeforeSleep bool, branchWithAfterSleep bool) *operatorapi.SonataFlow {
+	wf := generateWorkflow()
+	state := generateParallelState()
+	wf.Spec.Flow.States = []cncfmodel.State{*state}
+	if branchWithBeforeSleep {
+		branch := cncfmodel.Branch{
+			Actions: []cncfmodel.Action{{Sleep: &cncfmodel.Sleep{Before: "PT5S"}}},
+		}
+		state.ParallelState.Branches = append(state.ParallelState.Branches, branch)
+	}
+	if branchWithAfterSleep {
+		branch := cncfmodel.Branch{
+			Actions: []cncfmodel.Action{{Sleep: &cncfmodel.Sleep{After: "PT5S"}}},
+		}
+		state.ParallelState.Branches = append(state.ParallelState.Branches, branch)
+	}
+	return wf
+}
+
+func workflowWithForEachStateWithActionSleep(before bool, after bool) *operatorapi.SonataFlow {
+	wf := generateWorkflow()
+	state := generateForEachState()
+	wf.Spec.Flow.States = []cncfmodel.State{*state}
+	state.ForEachState.Actions = generateActionsWithSleep(before, after)
+	return wf
+}
+
+func workflowWithCallbackStateTimeoutAndActionSleep(duration *string, before *string, after *string) *operatorapi.SonataFlow {
+	wf := generateWorkflow()
+	state := generateCallbackState()
+	wf.Spec.Flow.States = []cncfmodel.State{*state}
+	if duration != nil {
+		state.CallbackState.Timeouts = &cncfmodel.CallbackStateTimeout{EventTimeout: *duration}
+	}
+	state.CallbackState.Action = cncfmodel.Action{}
+	if before != nil || after != nil {
+		state.CallbackState.Action.Sleep = &cncfmodel.Sleep{}
+		if before != nil {
+			state.CallbackState.Action.Sleep.Before = *before
+		}
+		if after != nil {
+			state.CallbackState.Action.Sleep.After = *after
+		}
+	}
+	return wf
+}
+
+func generateWorkflow() *operatorapi.SonataFlow {
+	wf := &operatorapi.SonataFlow{
+		Spec: operatorapi.SonataFlowSpec{
+			Flow: operatorapi.Flow{},
+		},
+	}
+	return wf
+}
+
+func generateEventState() *cncfmodel.State {
+	return &cncfmodel.State{
+		BaseState: cncfmodel.BaseState{
+			Type: cncfmodel.StateTypeEvent,
+		},
+		EventState: &cncfmodel.EventState{},
+	}
+}
+
+func generateOperationState() *cncfmodel.State {
+	return &cncfmodel.State{
+		BaseState: cncfmodel.BaseState{
+			Type: cncfmodel.StateTypeOperation,
+		},
+		OperationState: &cncfmodel.OperationState{},
+	}
+}
+
+func generateSwitchState() *cncfmodel.State {
+	return &cncfmodel.State{
+		BaseState: cncfmodel.BaseState{
+			Type: cncfmodel.StateTypeSwitch,
+		},
+		SwitchState: &cncfmodel.SwitchState{},
+	}
+}
+
+func generateSleepState() *cncfmodel.State {
+	return &cncfmodel.State{
+		BaseState: cncfmodel.BaseState{
+			Type: cncfmodel.StateTypeSleep,
+		},
+		SleepState: &cncfmodel.SleepState{},
+	}
+}
+
+func generateParallelState() *cncfmodel.State {
+	return &cncfmodel.State{
+		BaseState: cncfmodel.BaseState{
+			Type: cncfmodel.StateTypeParallel,
+		},
+		ParallelState: &cncfmodel.ParallelState{
+			Branches: []cncfmodel.Branch{},
+		},
+	}
+}
+
+func generateForEachState() *cncfmodel.State {
+	return &cncfmodel.State{
+		BaseState: cncfmodel.BaseState{
+			Type: cncfmodel.StateTypeForEach,
+		},
+		ForEachState: &cncfmodel.ForEachState{},
+	}
+}
+
+func generateCallbackState() *cncfmodel.State {
+	return &cncfmodel.State{
+		BaseState: cncfmodel.BaseState{
+			Type: cncfmodel.StateTypeCallback,
+		},
+		CallbackState: &cncfmodel.CallbackState{},
+	}
+}
+
+func generateActionsWithSleep(before bool, after bool) []cncfmodel.Action {
+	var actions []cncfmodel.Action
+	if before {
+		actions = append(actions, cncfmodel.Action{
+			Sleep: &cncfmodel.Sleep{
+				Before: "PT30S",
+			},
+		})
+	}
+	if after {
+		actions = append(actions, cncfmodel.Action{
+			Sleep: &cncfmodel.Sleep{
+				After: "PT30S",
+			},
+		})
+	}
+	return actions
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@kie.apache.org
For additional commands, e-mail: commits-help@kie.apache.org