// Report allows a migration minion to report if it successfully // completed its activities for a given migration phase. func (c *Client) Report(migrationId string, phase migration.Phase, success bool) error { args := params.MinionReport{ MigrationId: migrationId, Phase: phase.String(), Success: success, } err := c.caller.FacadeCall("Report", args, nil) return errors.Trace(err) }
// 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 }
// SubmitMinionReport implements ModelMigration. func (mig *modelMigration) SubmitMinionReport(tag names.Tag, phase migration.Phase, success bool) error { globalKey, err := agentTagToGlobalKey(tag) if err != nil { return errors.Trace(err) } docID := mig.minionReportId(phase, globalKey) doc := modelMigMinionSyncDoc{ Id: docID, MigrationId: mig.Id(), Phase: phase.String(), EntityKey: globalKey, Time: mig.st.clock.Now().UnixNano(), Success: success, } ops := []txn.Op{{ C: migrationsMinionSyncC, Id: docID, Insert: doc, Assert: txn.DocMissing, }} err = mig.st.runTransaction(ops) if errors.Cause(err) == txn.ErrAborted { coll, closer := mig.st.getCollection(migrationsMinionSyncC) defer closer() var existingDoc modelMigMinionSyncDoc err := coll.FindId(docID).Select(bson.M{"success": 1}).One(&existingDoc) if err != nil { return errors.Annotate(err, "checking existing report") } if existingDoc.Success != success { return errors.Errorf("conflicting reports received for %s/%s/%s", mig.Id(), phase.String(), tag) } return nil } else if err != nil { return errors.Trace(err) } return nil }
func (mig *modelMigration) minionReportId(phase migration.Phase, globalKey string) string { return fmt.Sprintf("%s:%s:%s", mig.Id(), phase.String(), globalKey) }
// SetPhase implements Client. func (c *client) SetPhase(phase migration.Phase) error { args := params.SetMigrationPhaseArgs{ Phase: phase.String(), } return c.caller.FacadeCall("SetPhase", args, nil) }