// SetPhase implements ModelMigration. func (mig *modelMigration) SetPhase(nextPhase migration.Phase) error { now := GetClock().Now().UnixNano() phase, err := mig.Phase() if err != nil { return errors.Trace(err) } if nextPhase == phase { return nil // Already at that phase. Nothing to do. } if !phase.CanTransitionTo(nextPhase) { return errors.Errorf("illegal phase change: %s -> %s", phase, nextPhase) } nextDoc := mig.statusDoc nextDoc.Phase = nextPhase.String() nextDoc.PhaseChangedTime = now update := bson.M{ "phase": nextDoc.Phase, "phase-changed-time": now, } if nextPhase == migration.SUCCESS { nextDoc.SuccessTime = now update["success-time"] = now } var ops []txn.Op if nextPhase.IsTerminal() { nextDoc.EndTime = now update["end-time"] = now ops = append(ops, txn.Op{ C: migrationsActiveC, Id: mig.doc.ModelUUID, Assert: txn.DocExists, Remove: true, }) } ops = append(ops, txn.Op{ C: migrationsStatusC, Id: mig.statusDoc.Id, Update: bson.M{"$set": update}, // Ensure phase hasn't changed underneath us Assert: bson.M{"phase": mig.statusDoc.Phase}, }) if err := mig.st.runTransaction(ops); err == txn.ErrAborted { return errors.New("phase already changed") } else if err != nil { return errors.Annotate(err, "failed to update phase") } mig.statusDoc = nextDoc return nil }
// IsTerminal returns true when the given phase means a migration has // finished (successfully or otherwise). func IsTerminal(phase migration.Phase) bool { return phase.IsTerminal() }