blob: 79c8832945571a77127665859e229fc6201c251b [file] [log] [blame]
// Copyright 2023 Red Hat, Inc. and/or its affiliates
//
// 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 profiles
import (
"context"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
operatorapi "github.com/kiegroup/kogito-serverless-operator/api/v1alpha08"
"github.com/kiegroup/kogito-serverless-operator/workflowproj"
"github.com/kiegroup/kogito-serverless-operator/controllers/workflowdef"
"github.com/kiegroup/kogito-serverless-operator/utils"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
clientruntime "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kiegroup/kogito-serverless-operator/api"
"github.com/kiegroup/kogito-serverless-operator/test"
)
func Test_OverrideStartupProbe(t *testing.T) {
workflow := test.GetBaseSonataFlow(t.Name())
client := test.NewKogitoClientBuilder().WithRuntimeObjects(workflow).WithStatusSubresource(workflow).Build()
config := &rest.Config{}
devReconciler := newDevProfileReconciler(client, config)
result, err := devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
// get the deployment, change the probe and reconcile it again
newThreshold := int32(5) //yes we have to force the type for the assertion below
deployment := test.MustGetDeployment(t, client, workflow)
assert.Equal(t, int32(healthFailureThresholdDevMode), deployment.Spec.Template.Spec.Containers[0].StartupProbe.FailureThreshold)
deployment.Spec.Template.Spec.Containers[0].StartupProbe.FailureThreshold = newThreshold
assert.NoError(t, client.Update(context.TODO(), deployment))
// reconcile and fetch from the cluster
result, err = devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
deployment = test.MustGetDeployment(t, client, workflow)
assert.Equal(t, newThreshold, deployment.Spec.Template.Spec.Containers[0].StartupProbe.FailureThreshold)
}
func Test_recoverFromFailureNoDeployment(t *testing.T) {
workflow := test.GetBaseSonataFlow(t.Name())
workflowID := clientruntime.ObjectKeyFromObject(workflow)
workflow.Status.Manager().MarkFalse(api.RunningConditionType, api.DeploymentFailureReason, "")
client := test.NewKogitoClientBuilder().WithRuntimeObjects(workflow).WithStatusSubresource(workflow).Build()
config := &rest.Config{}
reconciler := newDevProfileReconciler(client, config)
// we are in failed state and have no objects
result, err := reconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
// the recover state tried to clear the conditions of our workflow, so we can try reconciling it again
workflow = test.MustGetWorkflow(t, client, workflowID)
assert.True(t, workflow.Status.GetTopLevelCondition().IsUnknown())
result, err = reconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
// the deployment should be there
_ = test.MustGetDeployment(t, client, workflow)
// we failed again, but now we have the deployment
workflow = test.MustGetWorkflow(t, client, workflowID)
workflow.Status.Manager().MarkFalse(api.RunningConditionType, api.DeploymentFailureReason, "")
err = client.Status().Update(context.TODO(), workflow)
assert.NoError(t, err)
// the fake client won't update the deployment status condition since we don't have a deployment controller
// our state will think that we don't have a deployment available yet, so it will try to reset the pods
result, err = reconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
workflow = test.MustGetWorkflow(t, client, workflowID)
workflow.Status.Manager().MarkFalse(api.RunningConditionType, api.DeploymentFailureReason, "")
assert.Equal(t, 1, workflow.Status.RecoverFailureAttempts)
deployment := test.MustGetDeployment(t, client, workflow)
assert.NotEmpty(t, deployment.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"])
}
func Test_newDevProfile(t *testing.T) {
workflow := test.GetBaseSonataFlow(t.Name())
client := test.NewKogitoClientBuilder().WithRuntimeObjects(workflow).WithStatusSubresource(workflow).Build()
config := &rest.Config{}
devReconciler := newDevProfileReconciler(client, config)
result, err := devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
// check if the objects have been created
deployment := test.MustGetDeployment(t, client, workflow)
assert.Equal(t, workflowdef.GetDefaultWorkflowDevModeImageTag(), deployment.Spec.Template.Spec.Containers[0].Image)
assert.NotNil(t, deployment.Spec.Template.Spec.Containers[0].LivenessProbe)
assert.NotNil(t, deployment.Spec.Template.Spec.Containers[0].ReadinessProbe)
assert.NotNil(t, deployment.Spec.Template.Spec.Containers[0].StartupProbe)
defCM := test.MustGetConfigMap(t, client, workflow)
assert.NotEmpty(t, defCM.Data[workflow.Name+workflowdef.KogitoWorkflowJSONFileExt])
assert.Equal(t, configMapWorkflowDefMountPath, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath)
assert.Equal(t, "", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].SubPath) //https://kubernetes.io/docs/concepts/configuration/configmap/#mounted-configmaps-are-updated-automatically
assert.Equal(t, quarkusDevConfigMountPath+"/workflows", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath)
propCM := &v1.ConfigMap{}
_ = client.Get(context.TODO(), types.NamespacedName{Namespace: workflow.Namespace, Name: workflowproj.GetWorkflowPropertiesConfigMapName(workflow)}, propCM)
assert.NotEmpty(t, propCM.Data[workflowproj.ApplicationPropertiesFileName])
assert.Equal(t, quarkusDevConfigMountPath, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath)
assert.Equal(t, "", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[1].SubPath) //https://kubernetes.io/docs/concepts/configuration/configmap/#mounted-configmaps-are-updated-automatically
assert.Contains(t, propCM.Data[workflowproj.ApplicationPropertiesFileName], "quarkus.http.port")
service := test.MustGetService(t, client, workflow)
assert.Equal(t, int32(defaultHTTPWorkflowPortInt), service.Spec.Ports[0].TargetPort.IntVal)
workflow.Status.Manager().MarkTrue(api.RunningConditionType)
err = client.Status().Update(context.TODO(), workflow)
assert.NoError(t, err)
// Mess with the object
service.Spec.Ports[0].TargetPort = intstr.FromInt(9090)
err = client.Update(context.TODO(), service)
assert.NoError(t, err)
// reconcile again
result, err = devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
// check if the reconciliation ensures the object correctly
service = test.MustGetService(t, client, workflow)
assert.Equal(t, int32(defaultHTTPWorkflowPortInt), service.Spec.Ports[0].TargetPort.IntVal)
// now with the deployment
deployment = test.MustGetDeployment(t, client, workflow)
deployment.Spec.Template.Spec.Containers[0].Image = "default"
err = client.Update(context.TODO(), deployment)
assert.NoError(t, err)
propCM = &v1.ConfigMap{}
_ = client.Get(context.TODO(), types.NamespacedName{Namespace: workflow.Namespace, Name: workflowproj.GetWorkflowPropertiesConfigMapName(workflow)}, propCM)
assert.NotEmpty(t, propCM.Data[workflowproj.ApplicationPropertiesFileName])
assert.Contains(t, propCM.Data[workflowproj.ApplicationPropertiesFileName], "quarkus.http.port")
// reconcile
workflow.Status.Manager().MarkTrue(api.RunningConditionType)
err = client.Status().Update(context.TODO(), workflow)
assert.NoError(t, err)
result, err = devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
deployment = test.MustGetDeployment(t, client, workflow)
assert.Equal(t, workflowdef.GetDefaultWorkflowDevModeImageTag(), deployment.Spec.Template.Spec.Containers[0].Image)
}
func Test_devProfileImageDefaultsNoPlatform(t *testing.T) {
workflow := test.GetBaseSonataFlowWithDevProfile(t.Name())
client := test.NewKogitoClientBuilder().WithRuntimeObjects(workflow).WithStatusSubresource(workflow).Build()
config := &rest.Config{}
devReconciler := newDevProfileReconciler(client, config)
result, err := devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
// check if the objects have been created
deployment := test.MustGetDeployment(t, client, workflow)
assert.Equal(t, workflowdef.GetDefaultWorkflowDevModeImageTag(), deployment.Spec.Template.Spec.Containers[0].Image)
}
func Test_devProfileWithImageSnapshotOverrideWithPlatform(t *testing.T) {
workflow := test.GetBaseSonataFlowWithDevProfile(t.Name())
platform := test.GetBasePlatformWithDevBaseImageInReadyPhase(workflow.Namespace)
client := test.NewKogitoClientBuilder().WithRuntimeObjects(workflow, platform).WithStatusSubresource(workflow, platform).Build()
config := &rest.Config{}
devReconciler := newDevProfileReconciler(client, config)
result, err := devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
// check if the objects have been created
deployment := test.MustGetDeployment(t, client, workflow)
assert.Equal(t, "quay.io/customgroup/custom-swf-builder-nightly:42.43.7", deployment.Spec.Template.Spec.Containers[0].Image)
}
func Test_devProfileWithWPlatformWithoutDevBaseImageAndWithBaseImage(t *testing.T) {
workflow := test.GetBaseSonataFlowWithDevProfile(t.Name())
platform := test.GetBasePlatformWithBaseImageInReadyPhase(workflow.Namespace)
client := test.NewKogitoClientBuilder().WithRuntimeObjects(workflow, platform).WithStatusSubresource(workflow, platform).Build()
config := &rest.Config{}
devReconciler := newDevProfileReconciler(client, config)
result, err := devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
// check if the objects have been created
deployment := test.MustGetDeployment(t, client, workflow)
assert.Equal(t, workflowdef.GetDefaultWorkflowDevModeImageTag(), deployment.Spec.Template.Spec.Containers[0].Image)
}
func Test_devProfileWithPlatformWithoutDevBaseImageAndWithoutBaseImage(t *testing.T) {
workflow := test.GetBaseSonataFlowWithDevProfile(t.Name())
platform := test.GetBasePlatformInReadyPhase(workflow.Namespace)
client := test.NewKogitoClientBuilder().WithRuntimeObjects(workflow, platform).WithStatusSubresource(workflow, platform).Build()
config := &rest.Config{}
devReconciler := newDevProfileReconciler(client, config)
result, err := devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
// check if the objects have been created
deployment := test.MustGetDeployment(t, client, workflow)
assert.Equal(t, workflowdef.GetDefaultWorkflowDevModeImageTag(), deployment.Spec.Template.Spec.Containers[0].Image)
}
func Test_newDevProfileWithExternalConfigMaps(t *testing.T) {
configmapName := "mycamel-configmap"
workflow := test.GetBaseSonataFlowWithDevProfile(t.Name())
workflow.Spec.Resources.ConfigMaps = append(workflow.Spec.Resources.ConfigMaps,
operatorapi.ConfigMapWorkflowResource{ConfigMap: v1.LocalObjectReference{Name: configmapName}, WorkflowPath: "routes"})
config := &rest.Config{}
client := test.NewKogitoClientBuilder().WithRuntimeObjects(workflow).WithStatusSubresource(workflow).Build()
devReconciler := newDevProfileReconciler(client, config)
camelXmlRouteFileName := "camelroute-xml"
xmlRoute := `<route routeConfigurationId="xmlError">
<from uri="timer:xml?period=5s"/>
<log message="I am XML"/>
<throwException exceptionType="java.lang.Exception" message="Some kind of XML error"/>
</route>`
camelYamlRouteFileName := "camelroute-yaml"
yamlRoute := `- from:
uri: direct:numberToWords
steps:
- bean:
beanType: java.math.BigInteger
method: valueOf
- setHeader:
name: operationName
constant: NumberToWords
- toD:
uri: cxf://{{com.dataaccess.webservicesserver.url}}?serviceClass=com.dataaccess.webservicesserver.NumberConversionSoapType&wsdlURL=/wsdl/numberconversion.wsdl`
cmData := make(map[string]string)
cmData[camelXmlRouteFileName] = xmlRoute
cmUser := createConfigMapBase("Test_newDevProfileWithExternalConfigMaps", "mycamel-configmap", cmData)
errCreate := client.Create(context.Background(), cmUser)
assert.Nil(t, errCreate)
result, err := devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
// check if the objects have been created
deployment := test.MustGetDeployment(t, client, workflow)
assert.Equal(t, 3, len(deployment.Spec.Template.Spec.Containers[0].VolumeMounts))
assert.Equal(t, 3, len(deployment.Spec.Template.Spec.Volumes))
wd := deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0]
props := deployment.Spec.Template.Spec.Containers[0].VolumeMounts[1]
extCamel := deployment.Spec.Template.Spec.Containers[0].VolumeMounts[2]
assert.Equal(t, configMapWorkflowDefVolumeName, wd.Name)
assert.Equal(t, configMapWorkflowDefMountPath, wd.MountPath)
assert.Equal(t, configMapResourcesVolumeName, props.Name)
assert.Equal(t, quarkusDevConfigMountPath, props.MountPath)
assert.Equal(t, configMapExternalResourcesVolumeNamePrefix+"routes", extCamel.Name)
assert.Equal(t, extCamel.MountPath, quarkusDevConfigMountPath+"/routes")
cmData[camelYamlRouteFileName] = yamlRoute
errUpdate := client.Update(context.Background(), cmUser)
assert.Nil(t, errUpdate)
// reconcile again
workflow.Status.Manager().MarkTrue(api.RunningConditionType)
err = client.Update(context.TODO(), workflow)
assert.NoError(t, err)
result, err = devReconciler.Reconcile(context.TODO(), workflow)
deployment = test.MustGetDeployment(t, client, workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
//Now we expect 4 volumes mount wd, props camelroute.xml and camelroute.yaml
deployment = test.MustGetDeployment(t, client, workflow)
assert.Equal(t, 3, len(deployment.Spec.Template.Spec.Containers[0].VolumeMounts))
assert.Equal(t, 3, len(deployment.Spec.Template.Spec.Volumes))
extCamelRouteOne := deployment.Spec.Template.Spec.Containers[0].VolumeMounts[2]
assert.Equal(t, configMapExternalResourcesVolumeNamePrefix+"routes", extCamelRouteOne.Name)
assert.Equal(t, quarkusDevConfigMountPath+"/routes", extCamelRouteOne.MountPath)
workflow.Status.Manager().MarkTrue(api.RunningConditionType)
err = client.Update(context.TODO(), workflow)
assert.NoError(t, err)
result, err = devReconciler.Reconcile(context.TODO(), workflow)
deployment = test.MustGetDeployment(t, client, workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
deployment = test.MustGetDeployment(t, client, workflow)
assert.Equal(t, 3, len(deployment.Spec.Template.Spec.Containers[0].VolumeMounts))
assert.Equal(t, 3, len(deployment.Spec.Template.Spec.Volumes))
// remove the external configmaps without removing the labels
errDel := client.Delete(context.Background(), cmUser)
assert.Nil(t, errDel)
// reconcile
workflow.Status.Manager().MarkTrue(api.RunningConditionType)
err = client.Status().Update(context.TODO(), workflow)
assert.NoError(t, err)
result, err = devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, api.ExternalResourcesNotFoundReason, workflow.Status.GetTopLevelCondition().Reason)
// delete the link
workflow.Spec.Resources.ConfigMaps = nil
assert.NoError(t, client.Update(context.TODO(), workflow))
result, err = devReconciler.Reconcile(context.TODO(), workflow)
assert.NoError(t, err)
assert.NotNil(t, result)
deployment = test.MustGetDeployment(t, client, workflow)
assert.Equal(t, 2, len(deployment.Spec.Template.Spec.Volumes))
assert.Equal(t, 2, len(deployment.Spec.Template.Spec.Containers[0].VolumeMounts))
wd = deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0]
assert.Equal(t, wd.Name, configMapWorkflowDefVolumeName)
assert.Equal(t, wd.MountPath, configMapWorkflowDefMountPath)
}
func createConfigMapBase(namespace string, name string, cmData map[string]string) clientruntime.Object {
cm := &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Immutable: utils.Pbool(false),
Data: cmData,
}
return cm
}