Skip to content

Commit 7088e31

Browse files
shuo-wuderekbit
authored andcommitted
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 70d4a40 commit 7088e31

1 file changed

Lines changed: 83 additions & 19 deletions

File tree

controller/volume_controller.go

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4146,23 +4146,85 @@ 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 for some paths helps inform the caller that it should wait for the existing invalid replica disappears 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+
for _, r := range rList {
4162+
isReplicaValid := isReplicaValidForMigration(pathToOldRs[path], r)
4163+
if chosenReplica == nil {
4164+
chosenReplica = r
4165+
isChosenReplicaValid = isReplicaValid
4166+
isChosenReplicaRunning = r.Status.CurrentState == longhorn.InstanceStateRunning
4167+
}
4168+
4169+
if isReplicaValid {
4170+
if !isChosenReplicaValid { // prefer a valid new replica
4171+
chosenReplica = r
4172+
isChosenReplicaValid = true
4173+
isChosenReplicaRunning = r.Status.CurrentState == longhorn.InstanceStateRunning
4174+
} else {
4175+
if !isChosenReplicaRunning && r.Status.CurrentState == longhorn.InstanceStateRunning { // when both are valid, prefer a running replica
4176+
chosenReplica = r
4177+
isChosenReplicaValid = true
4178+
isChosenReplicaRunning = true
4179+
}
4180+
}
4181+
} else { // for invalid replicas, do cleanup immediately
4182+
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)
4183+
if err := c.migrationReplicaCleanup(r, rs, v.Spec.DataEngine); err != nil {
4184+
return nil, err
4185+
}
4186+
}
41564187
}
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)
4188+
4189+
// Even if there are multiple valid or running migration replicas, we still need to clean up the extra ones.
4190+
// 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.
4191+
for _, r := range rList {
4192+
if r.Name != chosenReplica.Name {
4193+
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)
4194+
if err := c.migrationReplicaCleanup(r, rs, v.Spec.DataEngine); err != nil {
4195+
return nil, err
4196+
}
41614197
}
4162-
} else {
4163-
r.Spec.MigrationEngineName = ""
4198+
}
4199+
4200+
if chosenReplica != nil {
4201+
migrationReplicas[path] = chosenReplica
41644202
}
41654203
}
4204+
4205+
return migrationReplicas, nil
4206+
}
4207+
4208+
// isReplicaValidForMigration checks whether a new replica is considered valid if:
4209+
// 1. it is not being deleted;
4210+
// 2. its desired state is `running`;
4211+
// 3. it has never failed to start. During migration, we always delete failed replicas and create new ones, rather than rebuilding them.
4212+
func isReplicaValidForMigration(oldR, newR *longhorn.Replica) bool {
4213+
matchTheOldReplica := oldR != nil && newR.Spec.DesireState == oldR.Spec.DesireState
4214+
return matchTheOldReplica &&
4215+
newR.DeletionTimestamp == nil && newR.Spec.DesireState == longhorn.InstanceStateRunning &&
4216+
newR.Status.CurrentState != longhorn.InstanceStateError && newR.Status.CurrentState != longhorn.InstanceStateUnknown && newR.Status.CurrentState != longhorn.InstanceStateStopping &&
4217+
newR.Spec.LastFailedAt == ""
4218+
}
4219+
4220+
func (c *VolumeController) migrationReplicaCleanup(r *longhorn.Replica, rs map[string]*longhorn.Replica, dataEngineType longhorn.DataEngineType) error {
4221+
if types.IsDataEngineV1(dataEngineType) {
4222+
if err := c.deleteReplica(r, rs); err != nil {
4223+
return errors.Wrapf(err, "failed to delete migration replica %v", r.Name)
4224+
}
4225+
} else {
4226+
r.Spec.MigrationEngineName = ""
4227+
}
41664228
return nil
41674229
}
41684230

@@ -4391,7 +4453,7 @@ func (c *VolumeController) prepareReplicasAndEngineForMigration(v *longhorn.Volu
43914453

43924454
// Sync migration replicas with old replicas
43934455
currentAvailableReplicas := map[string]*longhorn.Replica{}
4394-
migrationReplicas := map[string]*longhorn.Replica{}
4456+
migrationReplicaLists := map[string][]*longhorn.Replica{}
43954457
for _, r := range rs {
43964458
isUnavailable, err := c.IsReplicaUnavailable(r)
43974459
if err != nil {
@@ -4417,10 +4479,11 @@ func (c *VolumeController) prepareReplicasAndEngineForMigration(v *longhorn.Volu
44174479
default:
44184480
log.Warnf("Unexpected mode %v for the current replica %v, will ignore it and continue migration", currentEngine.Status.ReplicaModeMap[r.Name], r.Name)
44194481
}
4420-
} else if r.Spec.EngineName == migrationEngine.Name {
4421-
migrationReplicas[dataPath] = r
4422-
} else if r.Spec.MigrationEngineName == migrationEngine.Name {
4423-
migrationReplicas[dataPath] = r
4482+
} else if r.Spec.EngineName == migrationEngine.Name || r.Spec.MigrationEngineName == migrationEngine.Name {
4483+
if migrationReplicaLists[dataPath] == nil {
4484+
migrationReplicaLists[dataPath] = []*longhorn.Replica{}
4485+
}
4486+
migrationReplicaLists[dataPath] = append(migrationReplicaLists[dataPath], r)
44244487
} else {
44254488
log.Warnf("During migration found unknown replica with engine %v, will directly remove it", r.Spec.EngineName)
44264489
if err := c.deleteReplica(r, rs); err != nil {
@@ -4429,7 +4492,8 @@ func (c *VolumeController) prepareReplicasAndEngineForMigration(v *longhorn.Volu
44294492
}
44304493
}
44314494

4432-
if err := c.deleteInvalidMigrationReplicas(rs, currentAvailableReplicas, migrationReplicas, v); err != nil {
4495+
migrationReplicas, err := c.deleteInvalidMigrationReplicas(rs, currentAvailableReplicas, migrationReplicaLists, v)
4496+
if err != nil {
44334497
return false, false, err
44344498
}
44354499

0 commit comments

Comments
 (0)