Skip to content

Commit 0d3a0fd

Browse files
committed
fix: clean up extra new replicas and avoid creating more duplicates during migration
Longhorn 11397 Signed-off-by: Shuo Wu <shuo.wu@suse.com>
1 parent dcde9ad commit 0d3a0fd

1 file changed

Lines changed: 85 additions & 19 deletions

File tree

controller/volume_controller.go

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)