github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/modelmigration.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/utils/set"
    15  	"gopkg.in/juju/names.v2"
    16  	"gopkg.in/macaroon.v1"
    17  	"gopkg.in/mgo.v2"
    18  	"gopkg.in/mgo.v2/bson"
    19  	"gopkg.in/mgo.v2/txn"
    20  
    21  	"github.com/juju/juju/core/migration"
    22  	"github.com/juju/juju/mongo"
    23  )
    24  
    25  // This file contains functionality for managing the state documents
    26  // used by Juju to track model migrations.
    27  
    28  // ModelMigration represents the state of an migration attempt for a
    29  // model.
    30  type ModelMigration interface {
    31  	// Id returns a unique identifier for the model migration.
    32  	Id() string
    33  
    34  	// ModelUUID returns the UUID for the model being migrated.
    35  	ModelUUID() string
    36  
    37  	// ExternalControl returns true if the model migration should be
    38  	// managed by an external process.
    39  	ExternalControl() bool
    40  
    41  	// Attempt returns the migration attempt identifier. This
    42  	// increments for each migration attempt for the model.
    43  	Attempt() (int, error)
    44  
    45  	// StartTime returns the time when the migration was started.
    46  	StartTime() time.Time
    47  
    48  	// SuccessTime returns the time when the migration reached
    49  	// SUCCESS.
    50  	SuccessTime() time.Time
    51  
    52  	// EndTime returns the time when the migration reached DONE or
    53  	// REAPFAILED.
    54  	EndTime() time.Time
    55  
    56  	// Phase returns the migration's phase.
    57  	Phase() (migration.Phase, error)
    58  
    59  	// PhaseChangedTime returns the time when the migration's phase
    60  	// last changed.
    61  	PhaseChangedTime() time.Time
    62  
    63  	// StatusMessage returns human readable text about the current
    64  	// progress of the migration.
    65  	StatusMessage() string
    66  
    67  	// InitiatedBy returns username the initiated the migration.
    68  	InitiatedBy() string
    69  
    70  	// TargetInfo returns the details required to connect to the
    71  	// migration's target controller.
    72  	TargetInfo() (*migration.TargetInfo, error)
    73  
    74  	// SetPhase sets the phase of the migration. An error will be
    75  	// returned if the new phase does not follow the current phase or
    76  	// if the migration is no longer active.
    77  	SetPhase(nextPhase migration.Phase) error
    78  
    79  	// SetStatusMessage sets some human readable text about the
    80  	// current progress of the migration.
    81  	SetStatusMessage(text string) error
    82  
    83  	// SubmitMinionReport records a report from a migration minion
    84  	// worker about the success or failure to complete its actions for
    85  	// a given migration phase.
    86  	SubmitMinionReport(tag names.Tag, phase migration.Phase, success bool) error
    87  
    88  	// MinionReports returns details of the minions that have reported
    89  	// success or failure for the current migration phase, as well as
    90  	// those which are yet to report.
    91  	MinionReports() (*MinionReports, error)
    92  
    93  	// WatchMinionReports returns a notify watcher which triggers when
    94  	// a migration minion has reported back about the success or failure
    95  	// of its actions for the current migration phase.
    96  	WatchMinionReports() (NotifyWatcher, error)
    97  
    98  	// Refresh updates the contents of the ModelMigration from the
    99  	// underlying state.
   100  	Refresh() error
   101  }
   102  
   103  // MinionReports indicates the sets of agents whose migration minion
   104  // workers have completed the current migration phase, have failed to
   105  // complete the current migration phase, or are yet to report
   106  // regarding the current migration phase.
   107  type MinionReports struct {
   108  	Succeeded []names.Tag
   109  	Failed    []names.Tag
   110  	Unknown   []names.Tag
   111  }
   112  
   113  // modelMigration is an implementation of ModelMigration.
   114  type modelMigration struct {
   115  	st        *State
   116  	doc       modelMigDoc
   117  	statusDoc modelMigStatusDoc
   118  }
   119  
   120  // modelMigDoc holds parameters of a migration attempt for a
   121  // model. These are written into migrationsC.
   122  type modelMigDoc struct {
   123  	// Id holds migration document key. It has the format
   124  	// "uuid:sequence".
   125  	Id string `bson:"_id"`
   126  
   127  	// The UUID of the model being migrated.
   128  	ModelUUID string `bson:"model-uuid"`
   129  
   130  	// InitiatedBy holds the username of the user that triggered the
   131  	// migration. It should be in "user@domain" format.
   132  	InitiatedBy string `bson:"initiated-by"`
   133  
   134  	// ExternalControl is true if the migration will be controlled by
   135  	// an external process, instead of the migrationmaster worker.
   136  	ExternalControl bool `bson:"external-control"`
   137  
   138  	// TargetController holds the UUID of the target controller.
   139  	TargetController string `bson:"target-controller"`
   140  
   141  	// TargetAddrs holds the host:port values for the target API
   142  	// server.
   143  	TargetAddrs []string `bson:"target-addrs"`
   144  
   145  	// TargetCACert holds the certificate to validate the target API
   146  	// server's TLS certificate.
   147  	TargetCACert string `bson:"target-cacert"`
   148  
   149  	// TargetAuthTag holds a string representation of the tag to
   150  	// authenticate to the target controller with.
   151  	TargetAuthTag string `bson:"target-entity"`
   152  
   153  	// TargetPassword holds the password to use with TargetAuthTag
   154  	// when authenticating.
   155  	TargetPassword string `bson:"target-password,omitempty"`
   156  
   157  	// TargetMacaroons holds the macaroons to use with TargetAuthTag
   158  	// when authenticating.
   159  	TargetMacaroons string `bson:"target-macaroons,omitempty"`
   160  }
   161  
   162  // modelMigStatusDoc tracks the progress of a migration attempt for a
   163  // model. These are written into migrationsStatusC.
   164  //
   165  // There is exactly one document in migrationsStatusC for each
   166  // document in migrationsC. Separating them allows for watching
   167  // for new model migrations without being woken up for each model
   168  // migration status change.
   169  type modelMigStatusDoc struct {
   170  	// These are the same as the ids as migrationsC.
   171  	// "uuid:sequence".
   172  	Id string `bson:"_id"`
   173  
   174  	// StartTime holds the time the migration started (stored as per
   175  	// UnixNano).
   176  	StartTime int64 `bson:"start-time"`
   177  
   178  	// StartTime holds the time the migration reached the SUCCESS
   179  	// phase (stored as per UnixNano).
   180  	SuccessTime int64 `bson:"success-time"`
   181  
   182  	// EndTime holds the time the migration reached a terminal (end)
   183  	// phase (stored as per UnixNano).
   184  	EndTime int64 `bson:"end-time"`
   185  
   186  	// Phase holds the current migration phase. This should be one of
   187  	// the string representations of the core/migrations.Phase
   188  	// constants.
   189  	Phase string `bson:"phase"`
   190  
   191  	// PhaseChangedTime holds the time that Phase last changed (stored
   192  	// as per UnixNano).
   193  	PhaseChangedTime int64 `bson:"phase-changed-time"`
   194  
   195  	// StatusMessage holds a human readable message about the
   196  	// migration's progress.
   197  	StatusMessage string `bson:"status-message"`
   198  }
   199  
   200  type modelMigMinionSyncDoc struct {
   201  	Id          string `bson:"_id"`
   202  	MigrationId string `bson:"migration-id"`
   203  	Phase       string `bson:"phase"`
   204  	EntityKey   string `bson:"entity-key"`
   205  	Time        int64  `bson:"time"`
   206  	Success     bool   `bson:"success"`
   207  }
   208  
   209  // Id implements ModelMigration.
   210  func (mig *modelMigration) Id() string {
   211  	return mig.doc.Id
   212  }
   213  
   214  // ModelUUID implements ModelMigration.
   215  func (mig *modelMigration) ModelUUID() string {
   216  	return mig.doc.ModelUUID
   217  }
   218  
   219  // ExternalControl implements ModelMigration.
   220  func (mig *modelMigration) ExternalControl() bool {
   221  	return mig.doc.ExternalControl
   222  }
   223  
   224  // Attempt implements ModelMigration.
   225  func (mig *modelMigration) Attempt() (int, error) {
   226  	attempt, err := strconv.Atoi(mig.st.localID(mig.doc.Id))
   227  	if err != nil {
   228  		// This really shouldn't happen.
   229  		return -1, errors.Errorf("invalid migration id: %v", mig.doc.Id)
   230  	}
   231  	return attempt, nil
   232  }
   233  
   234  // StartTime implements ModelMigration.
   235  func (mig *modelMigration) StartTime() time.Time {
   236  	return unixNanoToTime0(mig.statusDoc.StartTime)
   237  }
   238  
   239  // SuccessTime implements ModelMigration.
   240  func (mig *modelMigration) SuccessTime() time.Time {
   241  	return unixNanoToTime0(mig.statusDoc.SuccessTime)
   242  }
   243  
   244  // EndTime implements ModelMigration.
   245  func (mig *modelMigration) EndTime() time.Time {
   246  	return unixNanoToTime0(mig.statusDoc.EndTime)
   247  }
   248  
   249  // Phase implements ModelMigration.
   250  func (mig *modelMigration) Phase() (migration.Phase, error) {
   251  	phase, ok := migration.ParsePhase(mig.statusDoc.Phase)
   252  	if !ok {
   253  		return phase, errors.Errorf("invalid phase in DB: %v", mig.statusDoc.Phase)
   254  	}
   255  	return phase, nil
   256  }
   257  
   258  // PhaseChangedTime implements ModelMigration.
   259  func (mig *modelMigration) PhaseChangedTime() time.Time {
   260  	return unixNanoToTime0(mig.statusDoc.PhaseChangedTime)
   261  }
   262  
   263  // StatusMessage implements ModelMigration.
   264  func (mig *modelMigration) StatusMessage() string {
   265  	return mig.statusDoc.StatusMessage
   266  }
   267  
   268  // InitiatedBy implements ModelMigration.
   269  func (mig *modelMigration) InitiatedBy() string {
   270  	return mig.doc.InitiatedBy
   271  }
   272  
   273  // TargetInfo implements ModelMigration.
   274  func (mig *modelMigration) TargetInfo() (*migration.TargetInfo, error) {
   275  	authTag, err := names.ParseUserTag(mig.doc.TargetAuthTag)
   276  	if err != nil {
   277  		return nil, errors.Trace(err)
   278  	}
   279  	macs, err := jsonToMacaroons(mig.doc.TargetMacaroons)
   280  	if err != nil {
   281  		return nil, errors.Trace(err)
   282  	}
   283  	return &migration.TargetInfo{
   284  		ControllerTag: names.NewControllerTag(mig.doc.TargetController),
   285  		Addrs:         mig.doc.TargetAddrs,
   286  		CACert:        mig.doc.TargetCACert,
   287  		AuthTag:       authTag,
   288  		Password:      mig.doc.TargetPassword,
   289  		Macaroons:     macs,
   290  	}, nil
   291  }
   292  
   293  // SetPhase implements ModelMigration.
   294  func (mig *modelMigration) SetPhase(nextPhase migration.Phase) error {
   295  	now := mig.st.clock.Now().UnixNano()
   296  
   297  	phase, err := mig.Phase()
   298  	if err != nil {
   299  		return errors.Trace(err)
   300  	}
   301  
   302  	if nextPhase == phase {
   303  		return nil // Already at that phase. Nothing to do.
   304  	}
   305  	if !phase.CanTransitionTo(nextPhase) {
   306  		return errors.Errorf("illegal phase change: %s -> %s", phase, nextPhase)
   307  	}
   308  
   309  	nextDoc := mig.statusDoc
   310  	nextDoc.Phase = nextPhase.String()
   311  	nextDoc.PhaseChangedTime = now
   312  	update := bson.M{
   313  		"phase":              nextDoc.Phase,
   314  		"phase-changed-time": now,
   315  	}
   316  	if nextPhase == migration.SUCCESS {
   317  		nextDoc.SuccessTime = now
   318  		update["success-time"] = now
   319  	}
   320  	var ops []txn.Op
   321  
   322  	// If the migration aborted, make the model active again.
   323  	if nextPhase == migration.ABORTDONE {
   324  		ops = append(ops, txn.Op{
   325  			C:      modelsC,
   326  			Id:     mig.doc.ModelUUID,
   327  			Assert: txn.DocExists,
   328  			Update: bson.M{
   329  				"$set": bson.M{"migration-mode": MigrationModeNone},
   330  			},
   331  		})
   332  	}
   333  
   334  	// Set end timestamps and mark migration as no longer active if a
   335  	// terminal phase is hit.
   336  	if nextPhase.IsTerminal() {
   337  		nextDoc.EndTime = now
   338  		update["end-time"] = now
   339  		ops = append(ops, txn.Op{
   340  			C:      migrationsActiveC,
   341  			Id:     mig.doc.ModelUUID,
   342  			Assert: txn.DocExists,
   343  			Remove: true,
   344  		})
   345  	}
   346  
   347  	ops = append(ops, txn.Op{
   348  		C:      migrationsStatusC,
   349  		Id:     mig.statusDoc.Id,
   350  		Update: bson.M{"$set": update},
   351  		// Ensure phase hasn't changed underneath us
   352  		Assert: bson.M{"phase": mig.statusDoc.Phase},
   353  	})
   354  
   355  	if err := mig.st.runTransaction(ops); err == txn.ErrAborted {
   356  		return errors.New("phase already changed")
   357  	} else if err != nil {
   358  		return errors.Annotate(err, "failed to update phase")
   359  	}
   360  
   361  	mig.statusDoc = nextDoc
   362  	return nil
   363  }
   364  
   365  // SetStatusMessage implements ModelMigration.
   366  func (mig *modelMigration) SetStatusMessage(text string) error {
   367  	ops := []txn.Op{{
   368  		C:      migrationsStatusC,
   369  		Id:     mig.statusDoc.Id,
   370  		Update: bson.M{"$set": bson.M{"status-message": text}},
   371  		Assert: txn.DocExists,
   372  	}}
   373  	if err := mig.st.runTransaction(ops); err != nil {
   374  		return errors.Annotate(err, "failed to set migration status")
   375  	}
   376  	mig.statusDoc.StatusMessage = text
   377  	return nil
   378  }
   379  
   380  // SubmitMinionReport implements ModelMigration.
   381  func (mig *modelMigration) SubmitMinionReport(tag names.Tag, phase migration.Phase, success bool) error {
   382  	globalKey, err := agentTagToGlobalKey(tag)
   383  	if err != nil {
   384  		return errors.Trace(err)
   385  	}
   386  	docID := mig.minionReportId(phase, globalKey)
   387  	doc := modelMigMinionSyncDoc{
   388  		Id:          docID,
   389  		MigrationId: mig.Id(),
   390  		Phase:       phase.String(),
   391  		EntityKey:   globalKey,
   392  		Time:        mig.st.clock.Now().UnixNano(),
   393  		Success:     success,
   394  	}
   395  	ops := []txn.Op{{
   396  		C:      migrationsMinionSyncC,
   397  		Id:     docID,
   398  		Insert: doc,
   399  		Assert: txn.DocMissing,
   400  	}}
   401  	err = mig.st.runTransaction(ops)
   402  	if errors.Cause(err) == txn.ErrAborted {
   403  		coll, closer := mig.st.getCollection(migrationsMinionSyncC)
   404  		defer closer()
   405  		var existingDoc modelMigMinionSyncDoc
   406  		err := coll.FindId(docID).Select(bson.M{"success": 1}).One(&existingDoc)
   407  		if err != nil {
   408  			return errors.Annotate(err, "checking existing report")
   409  		}
   410  		if existingDoc.Success != success {
   411  			return errors.Errorf("conflicting reports received for %s/%s/%s",
   412  				mig.Id(), phase.String(), tag)
   413  		}
   414  		return nil
   415  	} else if err != nil {
   416  		return errors.Trace(err)
   417  	}
   418  	return nil
   419  }
   420  
   421  // MinionReports implements ModelMigration.
   422  func (mig *modelMigration) MinionReports() (*MinionReports, error) {
   423  	all, err := mig.getAllAgents()
   424  	if err != nil {
   425  		return nil, errors.Trace(err)
   426  	}
   427  
   428  	phase, err := mig.Phase()
   429  	if err != nil {
   430  		return nil, errors.Annotate(err, "retrieving phase")
   431  	}
   432  
   433  	coll, closer := mig.st.getCollection(migrationsMinionSyncC)
   434  	defer closer()
   435  	query := coll.Find(bson.M{"_id": bson.M{
   436  		"$regex": "^" + mig.minionReportId(phase, ".+"),
   437  	}})
   438  	query = query.Select(bson.M{
   439  		"entity-key": 1,
   440  		"success":    1,
   441  	})
   442  	var docs []bson.M
   443  	if err := query.All(&docs); err != nil {
   444  		return nil, errors.Annotate(err, "retrieving minion reports")
   445  	}
   446  
   447  	succeeded := set.NewTags()
   448  	failed := set.NewTags()
   449  	for _, doc := range docs {
   450  		entityKey, ok := doc["entity-key"].(string)
   451  		if !ok {
   452  			return nil, errors.Errorf("unexpected entity-key %v", doc["entity-key"])
   453  		}
   454  		tag, err := globalKeyToAgentTag(entityKey)
   455  		if err != nil {
   456  			return nil, errors.Trace(err)
   457  		}
   458  		success, ok := doc["success"].(bool)
   459  		if !ok {
   460  			return nil, errors.Errorf("unexpected success value: %v", doc["success"])
   461  		}
   462  		if success {
   463  			succeeded.Add(tag)
   464  		} else {
   465  			failed.Add(tag)
   466  		}
   467  	}
   468  
   469  	unknown := all.Difference(succeeded).Difference(failed)
   470  
   471  	return &MinionReports{
   472  		Succeeded: succeeded.Values(),
   473  		Failed:    failed.Values(),
   474  		Unknown:   unknown.Values(),
   475  	}, nil
   476  }
   477  
   478  // WatchMinionReports implements ModelMigration.
   479  func (mig *modelMigration) WatchMinionReports() (NotifyWatcher, error) {
   480  	phase, err := mig.Phase()
   481  	if err != nil {
   482  		return nil, errors.Annotate(err, "retrieving phase")
   483  	}
   484  	prefix := mig.minionReportId(phase, "")
   485  	filter := func(rawId interface{}) bool {
   486  		id, ok := rawId.(string)
   487  		if !ok {
   488  			return false
   489  		}
   490  		return strings.HasPrefix(id, prefix)
   491  	}
   492  	return newNotifyCollWatcher(mig.st, migrationsMinionSyncC, filter), nil
   493  }
   494  
   495  func (mig *modelMigration) minionReportId(phase migration.Phase, globalKey string) string {
   496  	return fmt.Sprintf("%s:%s:%s", mig.Id(), phase.String(), globalKey)
   497  }
   498  
   499  func (mig *modelMigration) getAllAgents() (set.Tags, error) {
   500  	machineTags, err := mig.loadAgentTags(machinesC, "machineid",
   501  		func(id string) names.Tag { return names.NewMachineTag(id) },
   502  	)
   503  	if err != nil {
   504  		return nil, errors.Annotate(err, "loading machine tags")
   505  	}
   506  
   507  	unitTags, err := mig.loadAgentTags(unitsC, "name",
   508  		func(name string) names.Tag { return names.NewUnitTag(name) },
   509  	)
   510  	if err != nil {
   511  		return nil, errors.Annotate(err, "loading unit names")
   512  	}
   513  
   514  	return machineTags.Union(unitTags), nil
   515  }
   516  
   517  func (mig *modelMigration) loadAgentTags(collName, fieldName string, convert func(string) names.Tag) (
   518  	set.Tags, error,
   519  ) {
   520  	// During migrations we know that nothing there are no machines or
   521  	// units being provisioned or destroyed so a simple query of the
   522  	// collections will do.
   523  	coll, closer := mig.st.getCollection(collName)
   524  	defer closer()
   525  	var docs []bson.M
   526  	err := coll.Find(nil).Select(bson.M{fieldName: 1}).All(&docs)
   527  	if err != nil {
   528  		return nil, errors.Trace(err)
   529  	}
   530  
   531  	out := set.NewTags()
   532  	for _, doc := range docs {
   533  		v, ok := doc[fieldName].(string)
   534  		if !ok {
   535  			return nil, errors.Errorf("invalid %s value: %v", fieldName, doc[fieldName])
   536  		}
   537  		out.Add(convert(v))
   538  	}
   539  	return out, nil
   540  }
   541  
   542  // Refresh implements ModelMigration.
   543  func (mig *modelMigration) Refresh() error {
   544  	// Only the status document is updated. The modelMigDoc is static
   545  	// after creation.
   546  	statusColl, closer := mig.st.getCollection(migrationsStatusC)
   547  	defer closer()
   548  	var statusDoc modelMigStatusDoc
   549  	err := statusColl.FindId(mig.doc.Id).One(&statusDoc)
   550  	if err == mgo.ErrNotFound {
   551  		return errors.NotFoundf("migration status")
   552  	} else if err != nil {
   553  		return errors.Annotate(err, "migration status lookup failed")
   554  	}
   555  
   556  	mig.statusDoc = statusDoc
   557  	return nil
   558  }
   559  
   560  // MigrationSpec holds the information required to create a
   561  // ModelMigration instance.
   562  type MigrationSpec struct {
   563  	InitiatedBy     names.UserTag
   564  	TargetInfo      migration.TargetInfo
   565  	ExternalControl bool
   566  }
   567  
   568  // Validate returns an error if the MigrationSpec contains bad
   569  // data. Nil is returned otherwise.
   570  func (spec *MigrationSpec) Validate() error {
   571  	if !names.IsValidUser(spec.InitiatedBy.Id()) {
   572  		return errors.NotValidf("InitiatedBy")
   573  	}
   574  	return spec.TargetInfo.Validate()
   575  }
   576  
   577  // CreateMigration initialises state that tracks a model migration. It
   578  // will return an error if there is already a model migration in
   579  // progress.
   580  func (st *State) CreateMigration(spec MigrationSpec) (ModelMigration, error) {
   581  	if st.IsController() {
   582  		return nil, errors.New("controllers can't be migrated")
   583  	}
   584  	if err := spec.Validate(); err != nil {
   585  		return nil, errors.Trace(err)
   586  	}
   587  	if err := checkTargetController(st, spec.TargetInfo.ControllerTag); err != nil {
   588  		return nil, errors.Trace(err)
   589  	}
   590  
   591  	now := st.clock.Now().UnixNano()
   592  	modelUUID := st.ModelUUID()
   593  	var doc modelMigDoc
   594  	var statusDoc modelMigStatusDoc
   595  	buildTxn := func(int) ([]txn.Op, error) {
   596  		model, err := st.Model()
   597  		if err != nil {
   598  			return nil, errors.Annotate(err, "failed to load model")
   599  		}
   600  		if model.Life() != Alive {
   601  			return nil, errors.New("model is not alive")
   602  		}
   603  
   604  		if isActive, err := st.IsMigrationActive(); err != nil {
   605  			return nil, errors.Trace(err)
   606  		} else if isActive {
   607  			return nil, errors.New("already in progress")
   608  		}
   609  
   610  		macsJSON, err := macaroonsToJSON(spec.TargetInfo.Macaroons)
   611  		if err != nil {
   612  			return nil, errors.Trace(err)
   613  		}
   614  
   615  		seq, err := st.sequence("modelmigration")
   616  		if err != nil {
   617  			return nil, errors.Trace(err)
   618  		}
   619  
   620  		id := fmt.Sprintf("%s:%d", modelUUID, seq)
   621  		doc = modelMigDoc{
   622  			Id:               id,
   623  			ModelUUID:        modelUUID,
   624  			InitiatedBy:      spec.InitiatedBy.Id(),
   625  			ExternalControl:  spec.ExternalControl,
   626  			TargetController: spec.TargetInfo.ControllerTag.Id(),
   627  			TargetAddrs:      spec.TargetInfo.Addrs,
   628  			TargetCACert:     spec.TargetInfo.CACert,
   629  			TargetAuthTag:    spec.TargetInfo.AuthTag.String(),
   630  			TargetPassword:   spec.TargetInfo.Password,
   631  			TargetMacaroons:  macsJSON,
   632  		}
   633  		statusDoc = modelMigStatusDoc{
   634  			Id:               id,
   635  			StartTime:        now,
   636  			Phase:            migration.QUIESCE.String(),
   637  			PhaseChangedTime: now,
   638  			StatusMessage:    "starting",
   639  		}
   640  		return []txn.Op{{
   641  			C:      migrationsC,
   642  			Id:     doc.Id,
   643  			Assert: txn.DocMissing,
   644  			Insert: &doc,
   645  		}, {
   646  			C:      migrationsStatusC,
   647  			Id:     statusDoc.Id,
   648  			Assert: txn.DocMissing,
   649  			Insert: &statusDoc,
   650  		}, {
   651  			C:      migrationsActiveC,
   652  			Id:     modelUUID,
   653  			Assert: txn.DocMissing,
   654  			Insert: bson.M{"id": doc.Id},
   655  		}, {
   656  			C:      modelsC,
   657  			Id:     modelUUID,
   658  			Assert: txn.DocExists,
   659  			Update: bson.M{"$set": bson.M{
   660  				"migration-mode": MigrationModeExporting,
   661  			}},
   662  		}, model.assertActiveOp(),
   663  		}, nil
   664  	}
   665  	if err := st.run(buildTxn); err != nil {
   666  		return nil, errors.Annotate(err, "failed to create migration")
   667  	}
   668  
   669  	return &modelMigration{
   670  		doc:       doc,
   671  		statusDoc: statusDoc,
   672  		st:        st,
   673  	}, nil
   674  }
   675  
   676  func macaroonsToJSON(m []macaroon.Slice) (string, error) {
   677  	if len(m) == 0 {
   678  		return "", nil
   679  	}
   680  	j, err := json.Marshal(m)
   681  	if err != nil {
   682  		return "", errors.Annotate(err, "marshalling macaroons")
   683  	}
   684  	return string(j), nil
   685  }
   686  
   687  func jsonToMacaroons(raw string) ([]macaroon.Slice, error) {
   688  	if raw == "" {
   689  		return nil, nil
   690  	}
   691  	var macs []macaroon.Slice
   692  	if err := json.Unmarshal([]byte(raw), &macs); err != nil {
   693  		return nil, errors.Annotate(err, "unmarshalling macaroon")
   694  	}
   695  	return macs, nil
   696  }
   697  
   698  func checkTargetController(st *State, targetControllerTag names.ControllerTag) error {
   699  	currentController, err := st.ControllerModel()
   700  	if err != nil {
   701  		return errors.Annotate(err, "failed to load existing controller model")
   702  	}
   703  	if targetControllerTag == currentController.ControllerTag() {
   704  		return errors.New("model already attached to target controller")
   705  	}
   706  	return nil
   707  }
   708  
   709  // LatestMigration returns the most recent ModelMigration for a model
   710  // (if any).
   711  func (st *State) LatestMigration() (ModelMigration, error) {
   712  	migColl, closer := st.getCollection(migrationsC)
   713  	defer closer()
   714  	query := migColl.Find(bson.M{"model-uuid": st.ModelUUID()})
   715  	query = query.Sort("-_id").Limit(1)
   716  	mig, err := st.migrationFromQuery(query)
   717  	if err != nil {
   718  		return nil, errors.Trace(err)
   719  	}
   720  
   721  	// Hide previous migrations for models which have been migrated
   722  	// away from a model and then migrated back.
   723  	phase, err := mig.Phase()
   724  	if err != nil {
   725  		return nil, errors.Trace(err)
   726  	}
   727  	if phase == migration.DONE {
   728  		model, err := st.Model()
   729  		if err != nil {
   730  			return nil, errors.Trace(err)
   731  		}
   732  		if model.MigrationMode() == MigrationModeNone {
   733  			return nil, errors.NotFoundf("migration")
   734  		}
   735  	}
   736  
   737  	return mig, nil
   738  }
   739  
   740  // Migration retrieves a specific ModelMigration by its id. See also
   741  // LatestMigration.
   742  func (st *State) Migration(id string) (ModelMigration, error) {
   743  	migColl, closer := st.getCollection(migrationsC)
   744  	defer closer()
   745  	mig, err := st.migrationFromQuery(migColl.FindId(id))
   746  	if err != nil {
   747  		return nil, errors.Trace(err)
   748  	}
   749  	return mig, nil
   750  }
   751  
   752  func (st *State) migrationFromQuery(query mongo.Query) (ModelMigration, error) {
   753  	var doc modelMigDoc
   754  	err := query.One(&doc)
   755  	if err == mgo.ErrNotFound {
   756  		return nil, errors.NotFoundf("migration")
   757  	} else if err != nil {
   758  		return nil, errors.Annotate(err, "migration lookup failed")
   759  	}
   760  
   761  	statusColl, closer := st.getCollection(migrationsStatusC)
   762  	defer closer()
   763  	var statusDoc modelMigStatusDoc
   764  	err = statusColl.FindId(doc.Id).One(&statusDoc)
   765  	if err == mgo.ErrNotFound {
   766  		return nil, errors.NotFoundf("migration status")
   767  	} else if err != nil {
   768  		return nil, errors.Annotate(err, "migration status lookup failed")
   769  	}
   770  
   771  	return &modelMigration{
   772  		doc:       doc,
   773  		statusDoc: statusDoc,
   774  		st:        st,
   775  	}, nil
   776  }
   777  
   778  // IsMigrationActive returns true if a migration is in progress for
   779  // the model associated with the State.
   780  func (st *State) IsMigrationActive() (bool, error) {
   781  	return IsMigrationActive(st, st.ModelUUID())
   782  }
   783  
   784  // IsMigrationActive returns true if a migration is in progress for
   785  // the model with the given UUID. The State provided need not be for
   786  // the model in question.
   787  func IsMigrationActive(st *State, modelUUID string) (bool, error) {
   788  	active, closer := st.getCollection(migrationsActiveC)
   789  	defer closer()
   790  	n, err := active.FindId(modelUUID).Count()
   791  	if err != nil {
   792  		return false, errors.Trace(err)
   793  	}
   794  	return n > 0, nil
   795  }
   796  
   797  func unixNanoToTime0(i int64) time.Time {
   798  	if i == 0 {
   799  		return time.Time{}
   800  	}
   801  	return time.Unix(0, i)
   802  }
   803  
   804  func agentTagToGlobalKey(tag names.Tag) (string, error) {
   805  	switch t := tag.(type) {
   806  	case names.MachineTag:
   807  		return machineGlobalKey(t.Id()), nil
   808  	case names.UnitTag:
   809  		return unitAgentGlobalKey(t.Id()), nil
   810  	default:
   811  		return "", errors.Errorf("%s is not an agent tag", tag)
   812  	}
   813  }
   814  
   815  func globalKeyToAgentTag(key string) (names.Tag, error) {
   816  	parts := strings.SplitN(key, "#", 2)
   817  	if len(parts) != 2 {
   818  		return nil, errors.NotValidf("global key %q", key)
   819  	}
   820  	keyType, keyId := parts[0], parts[1]
   821  	switch keyType {
   822  	case "m":
   823  		return names.NewMachineTag(keyId), nil
   824  	case "u":
   825  		return names.NewUnitTag(keyId), nil
   826  	default:
   827  		return nil, errors.NotValidf("global key type %q", keyType)
   828  	}
   829  }