Skip to content

Commit fa85d0a

Browse files
ricardozaninirgdoliveira
authored andcommitted
Fix apache#560 - Check if the .spec.flow has changed before building (apache#564)
* Fix apache#560 - Check if the .spec.flow has changed before building Signed-off-by: Ricardo Zanini <[email protected]> * Use CRC32 instead of comparing flows directly Signed-off-by: Ricardo Zanini <[email protected]> --------- Signed-off-by: Ricardo Zanini <[email protected]>
1 parent 0d36c77 commit fa85d0a

File tree

12 files changed

+132
-37
lines changed

12 files changed

+132
-37
lines changed

api/v1alpha08/sonataflow_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ type SonataFlowStatus struct {
200200
// Triggers list of triggers created for the SonataFlow
201201
//+operator-sdk:csv:customresourcedefinitions:type=status,displayName="triggers"
202202
Triggers []SonataFlowTriggerRef `json:"triggers,omitempty"`
203+
//+operator-sdk:csv:customresourcedefinitions:type=status,displayName="flowRevision"
204+
FlowCRC uint32 `json:"flowCRC,omitempty"`
203205
}
204206

205207
// SonataFlowTriggerRef defines a trigger created for the SonataFlow.

bundle/manifests/sonataflow.org_sonataflows.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10050,6 +10050,9 @@ spec:
1005010050
endpoint:
1005110051
description: Endpoint is an externally accessible URL of the workflow
1005210052
type: string
10053+
flowCRC:
10054+
format: int32
10055+
type: integer
1005310056
lastTimeRecoverAttempt:
1005410057
format: date-time
1005510058
type: string

config/crd/bases/sonataflow.org_sonataflows.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10050,6 +10050,9 @@ spec:
1005010050
endpoint:
1005110051
description: Endpoint is an externally accessible URL of the workflow
1005210052
type: string
10053+
flowCRC:
10054+
format: int32
10055+
type: integer
1005310056
lastTimeRecoverAttempt:
1005410057
format: date-time
1005510058
type: string

config/manifests/bases/sonataflow-operator.clusterserviceversion.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ spec:
239239
- description: Endpoint is an externally accessible URL of the workflow
240240
displayName: endpoint
241241
path: endpoint
242+
- displayName: flowRevision
243+
path: flowCRC
242244
- displayName: lastTimeRecoverAttempt
243245
path: lastTimeRecoverAttempt
244246
- description: Platform displays which platform is being used by this workflow

internal/controller/profiles/common/reconciler.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"context"
2424
"fmt"
2525

26+
"github.com/apache/incubator-kie-kogito-serverless-operator/utils"
27+
2628
"k8s.io/client-go/rest"
2729

2830
"github.com/apache/incubator-kie-kogito-serverless-operator/internal/controller/discovery"
@@ -56,6 +58,10 @@ func (s *StateSupport) PerformStatusUpdate(ctx context.Context, workflow *operat
5658
return false, err
5759
}
5860
workflow.Status.ObservedGeneration = workflow.Generation
61+
workflow.Status.FlowCRC, err = utils.Crc32Checksum(workflow.Spec.Flow)
62+
if err != nil {
63+
return false, err
64+
}
5965
services.SetServiceUrlsInWorkflowStatus(pl, workflow)
6066
if workflow.Status.Platform == nil {
6167
workflow.Status.Platform = &operatorapi.SonataFlowPlatformRef{}

internal/controller/profiles/preview/profile_preview_test.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func Test_deployWorkflowReconciliationHandler_handleObjects(t *testing.T) {
181181
assert.Equal(t, serviceMonitor.Spec.Endpoints[0].Path, "/q/metrics")
182182
}
183183

184-
func Test_GenerationAnnotationCheck(t *testing.T) {
184+
func Test_WorkflowChangedCheck(t *testing.T) {
185185
// we load a workflow with metadata.generation to 0
186186
workflow := test.GetBaseSonataFlow(t.Name())
187187
platform := test.GetBasePlatformInReadyPhase(t.Name())
@@ -199,15 +199,14 @@ func Test_GenerationAnnotationCheck(t *testing.T) {
199199
assert.NotNil(t, result)
200200
assert.Len(t, objects, 3)
201201

202-
// then we load a workflow with metadata.generation set to 1
202+
// then we load the current workflow
203203
workflowChanged := &operatorapi.SonataFlow{}
204204
err = client.Get(context.TODO(), clientruntime.ObjectKeyFromObject(workflow), workflowChanged)
205205
assert.NoError(t, err)
206-
//we set the generation to 1
207-
workflowChanged.Generation = int64(1)
208-
err = client.Update(context.TODO(), workflowChanged)
209-
assert.NoError(t, err)
210-
// reconcile
206+
//we change something within the flow
207+
workflowChanged.Spec.Flow.AutoRetries = true
208+
209+
// reconcile -> the one in the k8s DB is different, so there's a change.
211210
handler = &deployWithBuildWorkflowState{
212211
StateSupport: fakeReconcilerSupport(client),
213212
ensurers: NewObjectEnsurers(&common.StateSupport{C: client}),

internal/controller/profiles/preview/states_preview.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"fmt"
2525
"sort"
2626

27+
"github.com/apache/incubator-kie-kogito-serverless-operator/utils"
2728
corev1 "k8s.io/api/core/v1"
2829
"k8s.io/apimachinery/pkg/api/errors"
2930
"k8s.io/apimachinery/pkg/labels"
@@ -40,7 +41,6 @@ import (
4041
"github.com/apache/incubator-kie-kogito-serverless-operator/internal/controller/profiles/common"
4142
"github.com/apache/incubator-kie-kogito-serverless-operator/internal/controller/profiles/common/constants"
4243
"github.com/apache/incubator-kie-kogito-serverless-operator/log"
43-
kubeutil "github.com/apache/incubator-kie-kogito-serverless-operator/utils/kubernetes"
4444
"github.com/apache/incubator-kie-kogito-serverless-operator/workflowproj"
4545
)
4646

@@ -209,7 +209,11 @@ func (h *deployWithBuildWorkflowState) Do(ctx context.Context, workflow *operato
209209
return ctrl.Result{}, nil, err
210210
}
211211

212-
if h.isWorkflowChanged(workflow) { // Let's check that the 2 resWorkflowDef definition are different
212+
hasChanged, err := h.isWorkflowChanged(workflow)
213+
if err != nil {
214+
return ctrl.Result{}, nil, err
215+
}
216+
if hasChanged { // Let's check that the 2 resWorkflowDef definition are different
213217
if err = buildManager.MarkToRestart(build); err != nil {
214218
return ctrl.Result{}, nil, err
215219
}
@@ -235,13 +239,18 @@ func (h *deployWithBuildWorkflowState) PostReconcile(ctx context.Context, workfl
235239
return h.cleanupOutdatedRevisions(ctx, workflow)
236240
}
237241

238-
// isWorkflowChanged marks the workflow status as unknown to require a new build reconciliation
239-
func (h *deployWithBuildWorkflowState) isWorkflowChanged(workflow *operatorapi.SonataFlow) bool {
240-
generation := kubeutil.GetLastGeneration(workflow.Namespace, workflow.Name, h.C, context.TODO())
241-
if generation > workflow.Status.ObservedGeneration {
242-
return true
242+
// isWorkflowChanged checks whether the contents of .spec.flow of the given workflow has changed.
243+
func (h *deployWithBuildWorkflowState) isWorkflowChanged(workflow *operatorapi.SonataFlow) (bool, error) {
244+
// Added this guard for backward compatibility for workflows deployed with a previous operator version, so we won't kick thousands of builds on users' cluster.
245+
// After this reconciliation cycle, the CRC should be updated
246+
if workflow.Status.FlowCRC == 0 {
247+
return false, nil
243248
}
244-
return false
249+
actualCRC, err := utils.Crc32Checksum(workflow.Spec.Flow)
250+
if err != nil {
251+
return false, err
252+
}
253+
return actualCRC != workflow.Status.FlowCRC, nil
245254
}
246255

247256
func (h *deployWithBuildWorkflowState) cleanupOutdatedRevisions(ctx context.Context, workflow *operatorapi.SonataFlow) error {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package preview
19+
20+
import (
21+
"testing"
22+
23+
"github.com/apache/incubator-kie-kogito-serverless-operator/utils"
24+
25+
"github.com/apache/incubator-kie-kogito-serverless-operator/internal/controller/profiles/common"
26+
"github.com/apache/incubator-kie-kogito-serverless-operator/test"
27+
"github.com/serverlessworkflow/sdk-go/v2/model"
28+
"github.com/stretchr/testify/assert"
29+
)
30+
31+
func Test_deployWithBuildWorkflowState_isWorkflowChanged(t *testing.T) {
32+
workflow1 := test.GetBaseSonataFlow(t.Name())
33+
workflow2 := test.GetBaseSonataFlow(t.Name())
34+
workflow1.Status.FlowCRC, _ = utils.Crc32Checksum(workflow1.Spec.Flow)
35+
workflow2.Status.FlowCRC, _ = utils.Crc32Checksum(workflow2.Spec.Flow)
36+
deployWithBuildWorkflowState := &deployWithBuildWorkflowState{
37+
StateSupport: &common.StateSupport{C: test.NewSonataFlowClientBuilder().WithRuntimeObjects(workflow1).Build()},
38+
}
39+
40+
hasChanged, err := deployWithBuildWorkflowState.isWorkflowChanged(workflow2)
41+
assert.NoError(t, err)
42+
assert.False(t, hasChanged)
43+
44+
// change workflow2
45+
workflow2.Spec.Flow.Metadata = model.Metadata{
46+
"string": model.Object{
47+
StringValue: "test",
48+
},
49+
}
50+
51+
hasChanged, err = deployWithBuildWorkflowState.isWorkflowChanged(workflow2)
52+
assert.NoError(t, err)
53+
assert.True(t, hasChanged)
54+
}

operator.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27477,6 +27477,9 @@ spec:
2747727477
endpoint:
2747827478
description: Endpoint is an externally accessible URL of the workflow
2747927479
type: string
27480+
flowCRC:
27481+
format: int32
27482+
type: integer
2748027483
lastTimeRecoverAttempt:
2748127484
format: date-time
2748227485
type: string

test/yaml.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727
"runtime"
2828
"strings"
2929

30+
"github.com/apache/incubator-kie-kogito-serverless-operator/utils"
31+
3032
"github.com/apache/incubator-kie-kogito-serverless-operator/api"
3133
operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08"
3234
"github.com/apache/incubator-kie-kogito-serverless-operator/log"
@@ -71,6 +73,7 @@ func GetSonataFlow(testFile, namespace string) *operatorapi.SonataFlow {
7173
GetKubernetesResource(testFile, ksw)
7274
klog.V(log.D).InfoS("Successfully read KSW", "ksw", spew.Sprint(ksw))
7375
ksw.Namespace = namespace
76+
ksw.Status.FlowCRC, _ = utils.Crc32Checksum(ksw.Spec.Flow)
7477
return ksw
7578
}
7679

0 commit comments

Comments
 (0)