Repository: apecloud/kubeblocks

Issue:
[BUG]the last ordinal pod can not be online
Using yaml's below to create a cluster.

```
apiVersion: apps.kubeblocks.io/v1
kind: Cluster
metadata:
  name: kafka2-ext-zk-descriptor
  namespace: default
spec:
  terminationPolicy: Delete
  # Specifies the name of the ClusterDefinition to use when creating a Cluster.
  # Note: DO NOT UPDATE THIS FIELD
  # The value must be `kafaka` to create a Kafka Cluster
  clusterDef: kafka
  # Specifies the name of the ClusterTopology to be used when creating the
  # Cluster.
  # - combined: combined Kafka controller (KRaft) and broker in one Component
  # - combined_monitor: combined mode with monitor component
  # - separated: separated KRaft and Broker Components.
  # - separated_monitor: separated mode with monitor component
  # Valid options are: [combined,combined_monitor,separated,separated_monitor,kafka2-external-zk]
  topology: kafka2-external-zk
  services:
    - name: bootstrap
      serviceName: bootstrap
      componentSelector: kafka-broker
      spec:
        type: ClusterIP
        ports:
          - name: kafka-client
            targetPort: 9092
            port: 9092
  componentSpecs:
    - name: kafka-broker
      componentDef: kafka27-broker
      serviceRefs:
        - name: kafkaZookeeper
          namespace: default
          serviceDescriptor: kafka-cluster-zookeeper-service
      tls: false
      replicas: 3
      env:
        - name: KB_BROKER_DIRECT_POD_ACCESS
          value: 'true'
        - name: KAFKA_HEAP_OPTS
          value: "-Xmx500m -Xms500m"
        - name: KB_KAFKA_ZK_SUB_PATH
          value: "test"   # set sub path to use for sharing zk, default value is $(CLUSTER_NAME), set empty string for root
      resources:
        limits:
          cpu: '0.5'
          memory: 0.5Gi
        requests:
          cpu: '0.5'
          memory: 0.5Gi
      volumeClaimTemplates:
        - name: data
          spec:
            accessModes:
              - ReadWriteOnce
            resources:
              requests:
                storage: 20Gi
        - name: metadata
          spec:
            storageClassName: null
            accessModes:
              - ReadWriteOnce
            resources:
              requests:
                storage: 5Gi
---
apiVersion: apps.kubeblocks.io/v1
kind: ServiceDescriptor
metadata:
  name: kafka-cluster-zookeeper-service
  namespace: default
spec:
  # Specifies the type or nature of the service.
  # Should represent a well-known application cluster type, such as {mysql, redis, zookeeper}.
  serviceKind: zookeeper
  # Represents the version of the service reference.
  serviceVersion: 3.8.5
  # Represents the endpoint of the service connection credential.
  endpoint:
    # your external zk endpoints here
    value: "zookeeper-cluster-zookeeper-0.zookeeper-cluster-zookeeper-headless.default:2181,zookeeper-cluster-zookeeper-1.zookeeper-cluster-zookeeper-headless.default:2181,zookeeper-cluster-zookeeper-2.zookeeper-cluster-zookeeper-headless.default:2181"
  # Represents the port of the service connection credential.
  port:
    value: "2181"
```

Using below yaml to do an `OpsRequest` to offline the last ordinal pod.

```
apiVersion: operations.kubeblocks.io/v1alpha1
kind: OpsRequest
metadata:
  generateName: offline-instance-
spec:
  clusterName: kafka2-ext-zk-descriptor
  horizontalScaling:
  - componentName: kafka-broker
    scaleIn:
      onlineInstancesToOffline:
      - kafka2-ext-zk-descriptor-kafka-broker-2
  type: HorizontalScaling
```

Using below yaml to do an `OpsRequest` to try to online the last ordinal pod.

```
apiVersion: operations.kubeblocks.io/v1alpha1
kind: OpsRequest
metadata:
  generateName: online-instance-
spec:
  clusterName: kafka2-ext-zk-descriptor
  horizontalScaling:
  - componentName: kafka-broker
    scaleOut:
      offlineInstancesToOnline:
      - kafka2-ext-zk-descriptor-kafka-broker-2
  type: HorizontalScaling
```

Then `OpsRequest` of online will stuck.

```
k get ops
NAME                           TYPE                CLUSTER                    STATUS    PROGRESS   AGE
offline-instance-6vkml         HorizontalScaling   kafka2-ext-zk-descriptor   Succeed   1/1        7m1s
online-instance-znk4k          HorizontalScaling   kafka2-ext-zk-descriptor   Running   0/1        6m10s
```

`kafka2-ext-zk-descriptor-kafka-broker-2` can not be onlined.

```
k get pod
NAME                                      READY   STATUS    RESTARTS     AGE
kafka2-ext-zk-descriptor-kafka-broker-0   2/2     Running   0            8m43s
kafka2-ext-zk-descriptor-kafka-broker-1   2/2     Running   0            8m43s
```

Cluster's `offlineInstances` is cleaned but `replicas` does not increment.

```
k get cluster
NAME                       CLUSTER-DEFINITION   TERMINATION-POLICY   STATUS     AGE
kafka2-ext-zk-descriptor   kafka                Delete               Running    10m
k get cluster kafka2-ext-zk-descriptor -oyaml|grep -C5 replicas
    - name: KB_KAFKA_ZK_SUB_PATH
      value: test
    name: kafka-broker
    replicas: 2
    resources:
      limits:
        cpu: 500m
        memory: 512Mi
      requests:
```

KB version: v1.0.0-beta.21
K8S version: v1.30.4

Discussion:
@leon-inf: @openhands-agent It looks like that when the user specifies offlineInstancesToOnline during scale-out, the ops controller only modifies the offlineInstances in the cluster API without adjusting the replicas, resulting in no new replicas being created. Please help try to fix it.
@apecloud-bot: [OpenHands](https://github.com/All-Hands-AI/OpenHands) started fixing the issue! You can monitor the progress [here](https://github.com/apecloud/kubeblocks/actions/runs/14165771810).@leon-inf: @openhands-agent There is a minor issue with the fix in #9144. If the user explicitly sets new replicas, then we can no longer modify it any more. Only when the user does not set replicas but only specifies offlineInstancesToOnline, We can modify it appropriately.
@apecloud-bot: [OpenHands](https://github.com/All-Hands-AI/OpenHands) started fixing the issue! You can monitor the progress [here](https://github.com/apecloud/kubeblocks/actions/runs/14166008788).@lancelot1989: instance template will also hit this issue, even if specifying replicasChanger

![Image](https://github.com/user-attachments/assets/84b6aad1-62b8-44c8-bbad-35011f9c0452)

Solution patch:
diff --git a/pkg/controller/kubebuilderx/plan_builder.go b/pkg/controller/kubebuilderx/plan_builder.go
index 0e9aa876b..67a3a72d2 100644
--- a/pkg/controller/kubebuilderx/plan_builder.go
+++ b/pkg/controller/kubebuilderx/plan_builder.go
@@ -191,11 +191,9 @@ func buildOrderedVertices(transCtx *transformContext, currentTree *ObjectTree, d
 			}
 		}
 	}
-	finalizer := currentTree.GetFinalizer()
 	deleteOrphanObjects := func() {
 		for name := range deleteSet {
 			object := oldSnapshot[name]
-			keepFinalizer(object, finalizer)
 			v := model.NewObjectVertex(nil, object, model.ActionDeletePtr(), inDataContext4G())
 			findAndAppend(v)
 		}
@@ -216,21 +214,6 @@ func buildOrderedVertices(transCtx *transformContext, currentTree *ObjectTree, d
 	return vertices
 }
 
-func keepFinalizer(object client.Object, finalizer string) {
-	var finalizers []string
-	if len(finalizer) > 0 {
-		finalizers = append(finalizers, finalizer)
-	}
-	object.SetFinalizers(finalizers)
-}
-
-func getRemainingFinalizer(obj client.Object) string {
-	if len(obj.GetFinalizers()) > 0 {
-		return obj.GetFinalizers()[0]
-	}
-	return ""
-}
-
 // Plan implementation
 
 func (p *Plan) Execute() error {
@@ -314,7 +297,10 @@ func (b *PlanBuilder) patchObject(ctx context.Context, vertex *model.ObjectVerte
 }
 
 func (b *PlanBuilder) deleteObject(ctx context.Context, vertex *model.ObjectVertex) error {
-	finalizer := getRemainingFinalizer(vertex.Obj)
+	var finalizer string
+	if b.currentTree != nil {
+		finalizer = b.currentTree.GetFinalizer()
+	}
 	if len(finalizer) > 0 && controllerutil.RemoveFinalizer(vertex.Obj, finalizer) {
 		err := b.cli.Update(ctx, vertex.Obj, clientOption(vertex))
 		if err != nil && !apierrors.IsNotFound(err) {
diff --git a/pkg/operations/horizontal_scaling.go b/pkg/operations/horizontal_scaling.go
index cceaa2200..f1561b940 100644
--- a/pkg/operations/horizontal_scaling.go
+++ b/pkg/operations/horizontal_scaling.go
@@ -30,7 +30,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
-	appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1"
 	opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1"
 	"github.com/apecloud/kubeblocks/pkg/constant"
 	intctrlcomp "github.com/apecloud/kubeblocks/pkg/controller/component"
@@ -226,16 +225,6 @@ func (hs horizontalScalingOpsHandler) getCreateAndDeletePodSet(opsRes *OpsResour
 			deletePodSet[k] = appsv1.GetInstanceTemplateName(clusterName, fullCompName, k)
 		}
 	}
-	if horizontalScaling.ScaleIn != nil && len(horizontalScaling.ScaleIn.OnlineInstancesToOffline) > 0 {
-		for _, v := range horizontalScaling.ScaleIn.OnlineInstancesToOffline {
-			deletePodSet[v] = appsv1alpha1.GetInstanceTemplateName(clusterName, fullCompName, v)
-		}
-	}
-	if horizontalScaling.ScaleOut != nil && len(horizontalScaling.ScaleOut.OfflineInstancesToOnline) > 0 {
-		for _, v := range horizontalScaling.ScaleOut.OfflineInstancesToOnline {
-			createPodSet[v] = appsv1alpha1.GetInstanceTemplateName(clusterName, fullCompName, v)
-		}
-	}
 	if opsRes.OpsRequest.Status.Phase == opsv1alpha1.OpsCancellingPhase {
 		// when cancelling this opsRequest, revert the changes.
 		return deletePodSet, createPodSet, nil
@@ -372,6 +361,7 @@ func (hs horizontalScalingOpsHandler) autoSyncReplicaChanges(
 				continue
 			}
 			if _, ok := insTplMap[k]; !ok {
+				// auto sync the replicaChanges for the instance template if the replicaChanges is not specified.
 				replicaChanger.Instances = append(replicaChanger.Instances, opsv1alpha1.InstanceReplicasTemplate{Name: k, ReplicaChanges: v})
 				allReplicaChanges += v
 			}
@@ -392,24 +382,66 @@ func (hs horizontalScalingOpsHandler) autoSyncReplicaChanges(
 	}
 	scaleOut := horizontalScaling.ScaleOut
 	if scaleOut != nil {
-		// get the pod set when removing the specified instances from offlineInstances slice
-		podSet, err := intctrlcomp.GenerateAllPodNamesToSet(compReplicas, compInstanceTpls, compExpectOfflineInstances,
-			opsRes.Cluster.Name, horizontalScaling.ComponentName)
+		onlineInsCountMap, err := hs.getToOnlineInsCountMap(opsRes, horizontalScaling, compReplicas, compInstanceTpls, compExpectOfflineInstances)
 		if err != nil {
 			return err
 		}
-		onlineInsCountMap := map[string]int32{}
-		for _, insName := range scaleOut.OfflineInstancesToOnline {
+		scaleOut.Instances, scaleOut.ReplicaChanges = getSyncedInstancesAndReplicaChanges(onlineInsCountMap, scaleOut.ReplicaChanger, scaleOut.NewInstances)
+	}
+	return nil
+}
+
+func (hs horizontalScalingOpsHandler) getToOnlineInsCountMap(
+	opsRes *OpsResource,
+	horizontalScaling opsv1alpha1.HorizontalScaling,
+	compReplicas int32,
+	compInstanceTpls []appsv1.InstanceTemplate,
+	compExpectOfflineInstances []string) (map[string]int32, error) {
+	if horizontalScaling.ScaleOut.ReplicaChanges != nil || len(horizontalScaling.ScaleOut.OfflineInstancesToOnline) == 0 {
+		return nil, nil
+	}
+	compInstanceTplsClone := slices.Clone(compInstanceTpls)
+	slices.Sort(horizontalScaling.ScaleOut.OfflineInstancesToOnline)
+	// 1. Automatically synchronize replicaChanges based on the specified OfflineInstancesToOnline
+	offlineInsMap := map[string][]string{}
+	instanceTplChangesMap := map[string]int32{}
+	for _, tplChange := range horizontalScaling.ScaleOut.ReplicaChanger.Instances {
+		instanceTplChangesMap[tplChange.Name] = tplChange.ReplicaChanges
+	}
+	for _, insName := range horizontalScaling.ScaleOut.OfflineInstancesToOnline {
+		insTplName := appsv1.GetInstanceTemplateName(opsRes.Cluster.Name, horizontalScaling.ComponentName, insName)
+		if _, ok := instanceTplChangesMap[insTplName]; ok {
+			// Ignore the template instance that has already been specified in the replica changes.
+			continue
+		}
+		offlineInsMap[insTplName] = append(offlineInsMap[insTplName], insName)
+		compReplicas += 1
+	}
+	for i := range compInstanceTplsClone {
+		tplName := compInstanceTplsClone[i].Name
+		if insNames, ok := offlineInsMap[tplName]; ok {
+			compInstanceTplsClone[i].Replicas = pointer.Int32(compInstanceTplsClone[i].GetReplicas() + int32(len(insNames)))
+		}
+	}
+	// 2. obtain the updated Pod set after synchronization replicas.
+	podSet, err := intctrlcomp.GenerateAllPodNamesToSet(compReplicas, compInstanceTplsClone, compExpectOfflineInstances,
+		opsRes.Cluster.Name, horizontalScaling.ComponentName)
+	if err != nil {
+		return nil, err
+	}
+	// 3. count the number of online instances for each instance template.
+	onlineInsCountMap := map[string]int32{}
+	for insTplName, insNames := range offlineInsMap {
+		for _, insName := range insNames {
+			// Once replica synchronization is complete, sequentially process each instance in the toOnline list to confirm its successful creation.
+			// If any instance fails to come online, break it.
 			if _, ok := podSet[insName]; !ok {
-				//  if the specified instance will not be created, continue
-				continue
+				break
 			}
-			insTplName := appsv1.GetInstanceTemplateName(opsRes.Cluster.Name, horizontalScaling.ComponentName, insName)
 			onlineInsCountMap[insTplName]++
 		}
-		scaleOut.Instances, scaleOut.ReplicaChanges = getSyncedInstancesAndReplicaChanges(onlineInsCountMap, scaleOut.ReplicaChanger, scaleOut.NewInstances)
 	}
-	return nil
+	return onlineInsCountMap, nil
 }
 
 // getCompExpectReplicas gets the expected replicas for the component.



Test patch:
diff --git a/controllers/workloads/instanceset_controller_test.go b/controllers/workloads/instanceset_controller_test.go
index bcd6591d4..70c819532 100644
--- a/controllers/workloads/instanceset_controller_test.go
+++ b/controllers/workloads/instanceset_controller_test.go
@@ -197,7 +197,9 @@ var _ = Describe("InstanceSet Controller", func() {
 
 			By("delete the ITS object")
 			Expect(k8sClient.Delete(ctx, itsObj)).Should(Succeed())
-			Eventually(testapps.CheckObjExists(&testCtx, itsKey, &workloads.InstanceSet{}, false)).Should(Succeed())
+
+			By("check its object NOT deleted")
+			Consistently(testapps.CheckObjExists(&testCtx, itsKey, &workloads.InstanceSet{}, true)).Should(Succeed())
 
 			By("check pods deleted")
 			podKey := types.NamespacedName{
@@ -206,12 +208,16 @@ var _ = Describe("InstanceSet Controller", func() {
 			}
 			Eventually(testapps.CheckObjExists(&testCtx, podKey, &corev1.Pod{}, false)).Should(Succeed())
 
-			By("check PVCs deleted")
+			By("check PVCs deleted, but the pvc-protection finalizer prevent the pvc to be deleted physically")
 			pvcKey := types.NamespacedName{
 				Namespace: itsObj.Namespace,
 				Name:      fmt.Sprintf("%s-%s-0", pvc.Name, itsObj.Name),
 			}
-			Eventually(testapps.CheckObjExists(&testCtx, pvcKey, &corev1.PersistentVolumeClaim{}, false)).Should(Succeed())
+			Eventually(testapps.CheckObj(&testCtx, pvcKey, func(g Gomega, pvc *corev1.PersistentVolumeClaim) {
+				g.Expect(pvc.DeletionTimestamp).ShouldNot(BeNil())
+				g.Expect(pvc.Finalizers).To(HaveLen(1))
+				g.Expect(pvc.Finalizers[0]).To(Equal("kubernetes.io/pvc-protection"))
+			})).Should(Succeed())
 		})
 
 		It("when deleted - retain", func() {
@@ -233,12 +239,14 @@ var _ = Describe("InstanceSet Controller", func() {
 			}
 			Eventually(testapps.CheckObjExists(&testCtx, podKey, &corev1.Pod{}, false)).Should(Succeed())
 
-			By("check PVCs retained")
+			By("check PVCs retained and not deleted")
 			pvcKey := types.NamespacedName{
 				Namespace: itsObj.Namespace,
 				Name:      fmt.Sprintf("%s-%s-0", pvc.Name, itsObj.Name),
 			}
-			Eventually(testapps.CheckObjExists(&testCtx, pvcKey, &corev1.PersistentVolumeClaim{}, true)).Should(Succeed())
+			Consistently(testapps.CheckObj(&testCtx, pvcKey, func(g Gomega, pvc *corev1.PersistentVolumeClaim) {
+				g.Expect(pvc.DeletionTimestamp).Should(BeNil())
+			})).Should(Succeed())
 		})
 
 		It("when scaled - delete", func() {
@@ -261,12 +269,16 @@ var _ = Describe("InstanceSet Controller", func() {
 			}
 			Eventually(testapps.CheckObjExists(&testCtx, podKey, &corev1.Pod{}, false)).Should(Succeed())
 
-			By("check PVCs deleted")
+			By("check PVCs deleted, but the pvc-protection finalizer prevent the pvc to be deleted physically")
 			pvcKey := types.NamespacedName{
 				Namespace: itsObj.Namespace,
 				Name:      fmt.Sprintf("%s-%s-0", pvc.Name, itsObj.Name),
 			}
-			Eventually(testapps.CheckObjExists(&testCtx, pvcKey, &corev1.PersistentVolumeClaim{}, false)).Should(Succeed())
+			Eventually(testapps.CheckObj(&testCtx, pvcKey, func(g Gomega, pvc *corev1.PersistentVolumeClaim) {
+				g.Expect(pvc.DeletionTimestamp).ShouldNot(BeNil())
+				g.Expect(pvc.Finalizers).To(HaveLen(1))
+				g.Expect(pvc.Finalizers[0]).To(Equal("kubernetes.io/pvc-protection"))
+			})).Should(Succeed())
 		})
 
 		It("when scaled - retain", func() {
@@ -289,12 +301,14 @@ var _ = Describe("InstanceSet Controller", func() {
 			}
 			Eventually(testapps.CheckObjExists(&testCtx, podKey, &corev1.Pod{}, false)).Should(Succeed())
 
-			By("check PVCs retained")
+			By("check PVCs retained and not deleted")
 			pvcKey := types.NamespacedName{
 				Namespace: itsObj.Namespace,
 				Name:      fmt.Sprintf("%s-%s-0", pvc.Name, itsObj.Name),
 			}
-			Eventually(testapps.CheckObjExists(&testCtx, pvcKey, &corev1.PersistentVolumeClaim{}, true)).Should(Succeed())
+			Consistently(testapps.CheckObj(&testCtx, pvcKey, func(g Gomega, pvc *corev1.PersistentVolumeClaim) {
+				g.Expect(pvc.DeletionTimestamp).Should(BeNil())
+			})).Should(Succeed())
 		})
 	})
 
diff --git a/pkg/controller/kubebuilderx/plan_builder_test.go b/pkg/controller/kubebuilderx/plan_builder_test.go
index 0d8bc43a0..281586dc5 100644
--- a/pkg/controller/kubebuilderx/plan_builder_test.go
+++ b/pkg/controller/kubebuilderx/plan_builder_test.go
@@ -128,20 +128,21 @@ var _ = Describe("plan builder test", func() {
 				Obj:    its,
 				Action: model.ActionDeletePtr(),
 			}
-			k8sMock.EXPECT().
-				Update(gomock.Any(), gomock.Any(), gomock.Any()).
-				DoAndReturn(func(_ context.Context, obj *workloads.InstanceSet, _ ...client.UpdateOption) error {
-					Expect(obj).ShouldNot(BeNil())
-					Expect(obj.Finalizers).Should(HaveLen(0))
-					return nil
-				}).Times(1)
+			// k8sMock.EXPECT().
+			//	Update(gomock.Any(), gomock.Any(), gomock.Any()).
+			//	DoAndReturn(func(_ context.Context, obj *workloads.InstanceSet, _ ...client.UpdateOption) error {
+			//		Expect(obj).ShouldNot(BeNil())
+			//		Expect(obj.Finalizers).Should(HaveLen(0))
+			//		return nil
+			//	}).Times(1)
 			k8sMock.EXPECT().
 				Delete(gomock.Any(), gomock.Any(), gomock.Any()).
 				DoAndReturn(func(_ context.Context, obj *workloads.InstanceSet, _ ...client.DeleteOption) error {
 					Expect(obj).ShouldNot(BeNil())
 					Expect(obj.Namespace).Should(Equal(its.Namespace))
 					Expect(obj.Name).Should(Equal(its.Name))
-					Expect(obj.Finalizers).Should(HaveLen(0))
+					Expect(obj.Finalizers).Should(HaveLen(1))
+					Expect(obj.Finalizers[0]).Should(Equal(finalizer))
 					return nil
 				}).Times(1)
 			Expect(planBuilder.defaultWalkFunc(v)).Should(Succeed())
diff --git a/pkg/operations/horizontal_scaling_test.go b/pkg/operations/horizontal_scaling_test.go
index ecd44485c..70015c777 100644
--- a/pkg/operations/horizontal_scaling_test.go
+++ b/pkg/operations/horizontal_scaling_test.go
@@ -310,7 +310,7 @@ var _ = Describe("HorizontalScaling OpsRequest", func() {
 				Expect(ok).Should(BeTrue())
 			}
 
-			By("mock specified pods deleted")
+			By("mock specified pods deleted or created")
 			mockHScale(podList)
 			testapps.MockInstanceSetStatus(testCtx, opsRes.Cluster, defaultCompName)
 			checkOpsRequestPhaseIsSucceed(reqCtx, opsRes)
@@ -404,6 +404,30 @@ var _ = Describe("HorizontalScaling OpsRequest", func() {
 			Expect(*compSpec.Instances[0].Replicas).Should(BeEquivalentTo(2))
 		})
 
+		It("test online the boundary ordinal the specified pod of the instance template and auto-sync replicaChanges", func() {
+			offlineInstanceName := fmt.Sprintf("%s-%s-%s-1", clusterName, defaultCompName, insTplName)
+			offlineInstanceName2 := fmt.Sprintf("%s-%s-2", clusterName, defaultCompName)
+			offlineInstances := []string{offlineInstanceName, offlineInstanceName2}
+			opsRes := testHScaleWithSpecifiedPod(func(cluster *appsv1.Cluster) {
+				setClusterCompSpec(cluster, []appsv1.InstanceTemplate{
+					{Name: insTplName, Replicas: pointer.Int32(1)},
+				}, offlineInstances)
+			}, opsv1alpha1.HorizontalScaling{
+				ScaleOut: &opsv1alpha1.ScaleOut{
+					OfflineInstancesToOnline: offlineInstances,
+				},
+			}, []string{}, func(podList []*corev1.Pod) {
+				By("create the specified pod " + offlineInstanceName)
+				testapps.MockInstanceSetPod(&testCtx, nil, clusterName, defaultCompName, offlineInstanceName, "follower")
+				testapps.MockInstanceSetPod(&testCtx, nil, clusterName, defaultCompName, offlineInstanceName2, "follower")
+			}, false)
+			Expect(opsRes.OpsRequest.Status.Progress).Should(Equal("2/2"))
+			By("expect replicas to 4")
+			compSpec := opsRes.Cluster.Spec.GetComponentByName(defaultCompName)
+			Expect(compSpec.Replicas).Should(BeEquivalentTo(5))
+			Expect(*compSpec.Instances[0].Replicas).Should(BeEquivalentTo(2))
+		})
+
 		It("test offline and online the specified pod and auto-sync replicaChanges", func() {
 			onlinePodName := fmt.Sprintf("%s-%s-1", clusterName, defaultCompName)
 			offlinePodName := fmt.Sprintf("%s-%s-%s-0", clusterName, defaultCompName, insTplName)
@@ -509,26 +533,6 @@ var _ = Describe("HorizontalScaling OpsRequest", func() {
 			Expect(opsRes.Cluster.Spec.GetComponentByName(defaultCompName).Replicas).Should(BeEquivalentTo(3))
 		})
 
-		It("test offline the specified pod but it is not exist", func() {
-			By("init operations resources with 3 CLusterDefinition/ClusterVersion/Hybrid components Cluster/consensus Pods")
-			opsRes, _, _ := initOperationsResources(compDefName, clusterName)
-			testapps.MockInstanceSetComponent(&testCtx, clusterName, defaultCompName)
-			reqCtx := intctrlutil.RequestCtx{Ctx: ctx}
-
-			By("offline the specified pod but it is not exist")
-			offlineInsName := fmt.Sprintf("%s-%s-4", clusterName, defaultCompName)
-			_ = createOpsAndToCreatingPhase(reqCtx, opsRes, opsv1alpha1.HorizontalScaling{
-				ScaleIn: &opsv1alpha1.ScaleIn{
-					OnlineInstancesToOffline: []string{offlineInsName},
-				},
-			}, true)
-			Eventually(testops.GetOpsRequestPhase(&testCtx, client.ObjectKeyFromObject(opsRes.OpsRequest))).Should(Equal(opsv1alpha1.OpsCreatingPhase))
-			Expect(opsRes.Cluster.Spec.GetComponentByName(defaultCompName).Replicas).Should(BeEquivalentTo(3))
-			By("expect for opsRequest phase is Succeed after pods has been scaled and progress is 1/1")
-			checkOpsRequestPhaseIsSucceed(reqCtx, opsRes)
-			Expect(opsRes.OpsRequest.Status.Progress).Should(Equal("1/1"), fmt.Sprintf("info: %v", opsRes.OpsRequest))
-		})
-
 		It("test offline two specified pods with same pod name with ignore validate", func() {
 			By("init operations resources with CLusterDefinition/ClusterVersion/Hybrid components Cluster/consensus Pods")
 			opsRes, _, _ := initOperationsResources(compDefName, clusterName)