Skip to content

Commit 6e9ab73

Browse files
wangyeleiapecloud-bot
authored andcommitted
fix: ops always is pending if the dependent ops is pending but behind… (#10118)
(cherry picked from commit 70fac03)
1 parent d6a62ea commit 6e9ab73

2 files changed

Lines changed: 123 additions & 9 deletions

File tree

pkg/operations/ops_util_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2020
package operations
2121

2222
import (
23+
"fmt"
2324
"time"
2425

2526
. "github.com/onsi/ginkgo/v2"
@@ -350,5 +351,91 @@ var _ = Describe("OpsUtil functions", func() {
350351
_, _ = GetOpsManager().Do(reqCtx, k8sClient, opsRes)
351352
Eventually(testops.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(opsv1alpha1.OpsCreatingPhase))
352353
})
354+
355+
It("Test swapOpsWithDependentBefore when force ops enqueued before dependent ops", func() {
356+
By("init operations resources ")
357+
opsRes, _, _ := initOperationsResources(compDefName, clusterName)
358+
testapps.MockInstanceSetComponent(&testCtx, clusterName, defaultCompName)
359+
360+
By("create a force opsRequest that depends on another ops")
361+
ops1 := createHorizontalScaling(clusterName, opsv1alpha1.HorizontalScaling{
362+
ComponentOps: opsv1alpha1.ComponentOps{ComponentName: defaultCompName},
363+
ScaleIn: &opsv1alpha1.ScaleIn{
364+
ReplicaChanger: opsv1alpha1.ReplicaChanger{ReplicaChanges: pointer.Int32(1)},
365+
},
366+
}, false)
367+
ops2 := createHorizontalScaling(clusterName, opsv1alpha1.HorizontalScaling{
368+
ComponentOps: opsv1alpha1.ComponentOps{ComponentName: defaultCompName},
369+
ScaleOut: &opsv1alpha1.ScaleOut{
370+
ReplicaChanger: opsv1alpha1.ReplicaChanger{ReplicaChanges: pointer.Int32(1)},
371+
},
372+
}, false)
373+
ops3 := createHorizontalScaling(clusterName, opsv1alpha1.HorizontalScaling{
374+
ComponentOps: opsv1alpha1.ComponentOps{ComponentName: defaultCompName},
375+
ScaleOut: &opsv1alpha1.ScaleOut{
376+
ReplicaChanger: opsv1alpha1.ReplicaChanger{ReplicaChanges: pointer.Int32(1)},
377+
},
378+
}, false)
379+
ops3.Annotations = map[string]string{constant.OpsDependentOnSuccessfulOpsAnnoKey: fmt.Sprintf("%s,%s", ops1.Name, ops2.Name)}
380+
ops3.Spec.Force = true
381+
382+
By("manually add ops3 to cluster annotation first with InQueue=false")
383+
ops3Recorder := opsv1alpha1.OpsRecorder{
384+
Name: ops3.Name,
385+
Type: opsv1alpha1.HorizontalScalingType,
386+
InQueue: false,
387+
QueueBySelf: false,
388+
}
389+
Expect(testapps.ChangeObj(&testCtx, opsRes.Cluster, func(cluster *appsv1.Cluster) {
390+
opsutil.SetOpsRequestToCluster(cluster, []opsv1alpha1.OpsRecorder{ops3Recorder})
391+
})).Should(Succeed())
392+
393+
By("ops2 should be in queue")
394+
opsRequestSlice, _ := opsutil.GetOpsRequestSliceFromCluster(opsRes.Cluster)
395+
Expect(len(opsRequestSlice)).Should(Equal(1))
396+
Expect(opsRequestSlice[0].Name).Should(Equal(ops3.Name))
397+
Expect(opsRequestSlice[0].InQueue).Should(BeFalse())
398+
399+
By("now create ops1 and add it to cluster annotation after ops2")
400+
ops1Recorder := opsv1alpha1.OpsRecorder{
401+
Name: ops1.Name,
402+
Type: opsv1alpha1.HorizontalScalingType,
403+
InQueue: true,
404+
QueueBySelf: false,
405+
}
406+
ops2Recorder := opsv1alpha1.OpsRecorder{
407+
Name: ops2.Name,
408+
Type: opsv1alpha1.HorizontalScalingType,
409+
InQueue: true,
410+
QueueBySelf: false,
411+
}
412+
Expect(testapps.ChangeObj(&testCtx, opsRes.Cluster, func(cluster *appsv1.Cluster) {
413+
opsutil.SetOpsRequestToCluster(cluster, []opsv1alpha1.OpsRecorder{ops3Recorder, ops1Recorder, ops2Recorder})
414+
})).Should(Succeed())
415+
416+
By("verify ops1 is after ops2 in the slice")
417+
opsRequestSlice, _ = opsutil.GetOpsRequestSliceFromCluster(opsRes.Cluster)
418+
Expect(len(opsRequestSlice)).Should(Equal(3))
419+
Expect(opsRequestSlice[0].Name).Should(Equal(ops3.Name))
420+
Expect(opsRequestSlice[1].Name).Should(Equal(ops1.Name))
421+
Expect(opsRequestSlice[2].Name).Should(Equal(ops2.Name))
422+
423+
By("simulate ops3 reconciliation that triggers swap")
424+
opsRes.OpsRequest = ops3
425+
ops3.Status.Phase = opsv1alpha1.OpsPendingPhase
426+
427+
reqCtx := intctrlutil.RequestCtx{Ctx: testCtx.Ctx}
428+
opsBehaviour := GetOpsManager().OpsMap[opsv1alpha1.HorizontalScalingType]
429+
opsRecorder, err := enqueueOpsRequestToClusterAnnotation(reqCtx.Ctx, k8sClient, opsRes, opsBehaviour)
430+
Expect(err).ShouldNot(HaveOccurred())
431+
Expect(opsRecorder).ShouldNot(BeNil())
432+
433+
By("verify ops1 and ops2 have been swapped")
434+
opsRequestSlice, _ = opsutil.GetOpsRequestSliceFromCluster(opsRes.Cluster)
435+
Expect(len(opsRequestSlice)).Should(Equal(3))
436+
Expect(opsRequestSlice[0].Name).Should(Equal(ops1.Name))
437+
Expect(opsRequestSlice[1].Name).Should(Equal(ops2.Name))
438+
Expect(opsRequestSlice[2].Name).Should(Equal(ops3.Name))
439+
})
353440
})
354441
})

pkg/operations/queue_util.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ import (
2323
"context"
2424
"fmt"
2525
"slices"
26+
"strings"
2627
"time"
2728

2829
apierrors "k8s.io/apimachinery/pkg/api/errors"
2930
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3031
"sigs.k8s.io/controller-runtime/pkg/client"
3132

3233
opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1"
34+
"github.com/apecloud/kubeblocks/pkg/constant"
3335
intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
3436
opsutil "github.com/apecloud/kubeblocks/pkg/operations/util"
3537
)
@@ -132,20 +134,45 @@ func enqueueOpsRequestToClusterAnnotation(ctx context.Context, cli client.Client
132134
}
133135
opsRequestSlice = append(opsRequestSlice, opsRecorder)
134136
default:
135-
if !opsRecorder.InQueue {
136-
// the opsRequest is already running.
137-
return &opsRecorder, nil
138-
}
139-
if !opsRes.OpsRequest.Spec.Force && existOtherRunningOps(opsRequestSlice, opsRecorder.Type, opsBehaviour) {
140-
// if exists other running opsRequest, return.
141-
return &opsRecorder, nil
137+
var doSwap bool
138+
opsRequestSlice, doSwap = swapOpsWithDependentBefore(opsRequestSlice, index, opsRes)
139+
// check if dependent ops are after current ops in the slice, if so swap positions and update the queue in the cluster annotation.
140+
if !doSwap {
141+
if !opsRecorder.InQueue {
142+
// the opsRequest is already running.
143+
return &opsRecorder, nil
144+
}
145+
if !opsRes.OpsRequest.Spec.Force && existOtherRunningOps(opsRequestSlice, opsRecorder.Type, opsBehaviour) {
146+
// if exists other running opsRequest, return.
147+
return &opsRecorder, nil
148+
}
149+
// mark to handle the next opsRequest
150+
opsRequestSlice[index].InQueue = false
142151
}
143-
// mark to handle the next opsRequest
144-
opsRequestSlice[index].InQueue = false
145152
}
146153
return &opsRecorder, opsutil.UpdateClusterOpsAnnotations(ctx, cli, opsRes.Cluster, opsRequestSlice)
147154
}
148155

156+
func swapOpsWithDependentBefore(opsRequestSlice []opsv1alpha1.OpsRecorder, currentIndex int, opsRes *OpsResource) ([]opsv1alpha1.OpsRecorder, bool) {
157+
dependentOpsStr := opsRes.OpsRequest.Annotations[constant.OpsDependentOnSuccessfulOpsAnnoKey]
158+
if dependentOpsStr == "" {
159+
return opsRequestSlice, false
160+
}
161+
dependentSet := make(map[string]struct{})
162+
for _, name := range strings.Split(dependentOpsStr, ",") {
163+
dependentSet[name] = struct{}{}
164+
}
165+
var doSwap bool
166+
for i := currentIndex + 1; i < len(opsRequestSlice); i++ {
167+
if _, ok := dependentSet[opsRequestSlice[i].Name]; ok {
168+
opsRequestSlice[currentIndex], opsRequestSlice[i] = opsRequestSlice[i], opsRequestSlice[currentIndex]
169+
currentIndex = i
170+
doSwap = true
171+
}
172+
}
173+
return opsRequestSlice, doSwap
174+
}
175+
149176
// existOtherRunningOps checks if exists other running opsRequest.
150177
func existOtherRunningOps(opsRecorderSlice []opsv1alpha1.OpsRecorder, opsType opsv1alpha1.OpsType, opsBehaviour OpsBehaviour) bool {
151178
for i := range opsRecorderSlice {

0 commit comments

Comments
 (0)