@@ -4146,23 +4146,87 @@ func (c *VolumeController) createAndStartMatchingReplicas(v *longhorn.Volume,
41464146 return nil
41474147}
41484148
4149- func (c * VolumeController ) deleteInvalidMigrationReplicas (rs , pathToOldRs , pathToNewRs map [string ]* longhorn.Replica , v * longhorn.Volume ) error {
4150- for path , r := range pathToNewRs {
4151- matchTheOldReplica := pathToOldRs [path ] != nil && r .Spec .DesireState == pathToOldRs [path ].Spec .DesireState
4152- newReplicaIsAvailable := r .DeletionTimestamp == nil && r .Spec .DesireState == longhorn .InstanceStateRunning &&
4153- r .Status .CurrentState != longhorn .InstanceStateError && r .Status .CurrentState != longhorn .InstanceStateUnknown && r .Status .CurrentState != longhorn .InstanceStateStopping
4154- if matchTheOldReplica && newReplicaIsAvailable {
4155- continue
4149+ // deleteInvalidMigrationReplicas selects one replica for each path and delete the rests.
4150+ //
4151+ // Notice that the selected replica may not be valid.
4152+ // Returning invalid replicas (with deletion timestamp set) for some paths helps inform the caller that it should wait for the existing invalid replicas completely disappearing before creating a new one.
4153+ func (c * VolumeController ) deleteInvalidMigrationReplicas (rs , pathToOldRs map [string ]* longhorn.Replica , pathToNewRLists map [string ][]* longhorn.Replica , v * longhorn.Volume ) (map [string ]* longhorn.Replica , error ) {
4154+ migrationReplicas := make (map [string ]* longhorn.Replica )
4155+ for path , rList := range pathToNewRLists {
4156+ sort .Slice (rList , func (i , j int ) bool {
4157+ return rList [i ].Name < rList [j ].Name
4158+ })
4159+ var chosenReplica * longhorn.Replica
4160+ isChosenReplicaValid , isChosenReplicaRunning := false , false
4161+ removedReplicaMap := make (map [string ]bool )
4162+ for _ , r := range rList {
4163+ isReplicaValid := isReplicaValidForMigration (pathToOldRs [path ], r )
4164+ if chosenReplica == nil {
4165+ chosenReplica = r
4166+ isChosenReplicaValid = isReplicaValid
4167+ isChosenReplicaRunning = r .Status .CurrentState == longhorn .InstanceStateRunning
4168+ }
4169+
4170+ if isReplicaValid {
4171+ if ! isChosenReplicaValid { // prefer a valid new replica
4172+ chosenReplica = r
4173+ isChosenReplicaValid = true
4174+ isChosenReplicaRunning = r .Status .CurrentState == longhorn .InstanceStateRunning
4175+ } else {
4176+ if ! isChosenReplicaRunning && r .Status .CurrentState == longhorn .InstanceStateRunning { // when both are valid, prefer a running replica
4177+ chosenReplica = r
4178+ isChosenReplicaValid = true
4179+ isChosenReplicaRunning = true
4180+ }
4181+ }
4182+ } else { // for invalid replicas, do cleanup immediately
4183+ c .logger .Infof ("Cleaning up the invalid migration replica %v (data engine %v) since it is state %s, or it is last failed at %s, or there is no matching old replica in path %v" , r .Name , v .Spec .DataEngine , r .Status .CurrentState , r .Spec .LastFailedAt , path )
4184+ if err := c .migrationReplicaCleanup (r , rs , v .Spec .DataEngine ); err != nil {
4185+ return nil , err
4186+ }
4187+ removedReplicaMap [r .Name ] = true
4188+ }
41564189 }
4157- delete (pathToNewRs , path )
4158- if types .IsDataEngineV1 (v .Spec .DataEngine ) {
4159- if err := c .deleteReplica (r , rs ); err != nil {
4160- return errors .Wrapf (err , "failed to delete the new replica %v when there is no matching old replica in path %v" , r .Name , path )
4190+
4191+ // Even if there are multiple valid or running migration replicas, we still need to clean up the extra ones.
4192+ // Otherwise, those extra replicas for the same path would lead to the mismatching between migration replica list and the old replica list, then cause the migration engine restart.
4193+ for _ , r := range rList {
4194+ if ! removedReplicaMap [r .Name ] && r .Name != chosenReplica .Name {
4195+ c .logger .Infof ("Cleaning up the extra migration replica %v (data engine %v) since another migration replica %v is chosen for path %v" , r .Name , v .Spec .DataEngine , chosenReplica .Name , path )
4196+ if err := c .migrationReplicaCleanup (r , rs , v .Spec .DataEngine ); err != nil {
4197+ return nil , err
4198+ }
41614199 }
4162- } else {
4163- r .Spec .MigrationEngineName = ""
4200+ }
4201+
4202+ if chosenReplica != nil {
4203+ migrationReplicas [path ] = chosenReplica
41644204 }
41654205 }
4206+
4207+ return migrationReplicas , nil
4208+ }
4209+
4210+ // isReplicaValidForMigration checks whether a new replica is considered valid if:
4211+ // 1. it is not being deleted;
4212+ // 2. its desired state is `running`;
4213+ // 3. it has never failed to start. During migration, we always delete failed replicas and create new ones, rather than rebuilding them.
4214+ func isReplicaValidForMigration (oldR , newR * longhorn.Replica ) bool {
4215+ matchTheOldReplica := oldR != nil && newR .Spec .DesireState == oldR .Spec .DesireState
4216+ return matchTheOldReplica &&
4217+ newR .DeletionTimestamp == nil && newR .Spec .DesireState == longhorn .InstanceStateRunning &&
4218+ newR .Status .CurrentState != longhorn .InstanceStateError && newR .Status .CurrentState != longhorn .InstanceStateUnknown && newR .Status .CurrentState != longhorn .InstanceStateStopping &&
4219+ newR .Spec .LastFailedAt == ""
4220+ }
4221+
4222+ func (c * VolumeController ) migrationReplicaCleanup (r * longhorn.Replica , rs map [string ]* longhorn.Replica , dataEngineType longhorn.DataEngineType ) error {
4223+ if types .IsDataEngineV1 (dataEngineType ) {
4224+ if err := c .deleteReplica (r , rs ); err != nil {
4225+ return errors .Wrapf (err , "failed to delete migration replica %v" , r .Name )
4226+ }
4227+ } else {
4228+ r .Spec .MigrationEngineName = ""
4229+ }
41664230 return nil
41674231}
41684232
@@ -4391,7 +4455,7 @@ func (c *VolumeController) prepareReplicasAndEngineForMigration(v *longhorn.Volu
43914455
43924456 // Sync migration replicas with old replicas
43934457 currentAvailableReplicas := map [string ]* longhorn.Replica {}
4394- migrationReplicas := map [string ]* longhorn.Replica {}
4458+ migrationReplicaLists := map [string ][ ]* longhorn.Replica {}
43954459 for _ , r := range rs {
43964460 isUnavailable , err := c .IsReplicaUnavailable (r )
43974461 if err != nil {
@@ -4417,10 +4481,11 @@ func (c *VolumeController) prepareReplicasAndEngineForMigration(v *longhorn.Volu
44174481 default :
44184482 log .Warnf ("Unexpected mode %v for the current replica %v, will ignore it and continue migration" , currentEngine .Status .ReplicaModeMap [r .Name ], r .Name )
44194483 }
4420- } else if r .Spec .EngineName == migrationEngine .Name {
4421- migrationReplicas [dataPath ] = r
4422- } else if r .Spec .MigrationEngineName == migrationEngine .Name {
4423- migrationReplicas [dataPath ] = r
4484+ } else if r .Spec .EngineName == migrationEngine .Name || r .Spec .MigrationEngineName == migrationEngine .Name {
4485+ if migrationReplicaLists [dataPath ] == nil {
4486+ migrationReplicaLists [dataPath ] = []* longhorn.Replica {}
4487+ }
4488+ migrationReplicaLists [dataPath ] = append (migrationReplicaLists [dataPath ], r )
44244489 } else {
44254490 log .Warnf ("During migration found unknown replica with engine %v, will directly remove it" , r .Spec .EngineName )
44264491 if err := c .deleteReplica (r , rs ); err != nil {
@@ -4429,7 +4494,8 @@ func (c *VolumeController) prepareReplicasAndEngineForMigration(v *longhorn.Volu
44294494 }
44304495 }
44314496
4432- if err := c .deleteInvalidMigrationReplicas (rs , currentAvailableReplicas , migrationReplicas , v ); err != nil {
4497+ migrationReplicas , err := c .deleteInvalidMigrationReplicas (rs , currentAvailableReplicas , migrationReplicaLists , v )
4498+ if err != nil {
44334499 return false , false , err
44344500 }
44354501
0 commit comments