github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	"strings"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/mgo/v3"
    14  	"github.com/juju/mgo/v3/bson"
    15  	"github.com/juju/mgo/v3/txn"
    16  	"github.com/juju/names/v5"
    17  	"gopkg.in/macaroon.v2"
    18  
    19  	"github.com/juju/juju/core/migration"
    20  	"github.com/juju/juju/core/permission"
    21  	"github.com/juju/juju/core/status"
    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  	// Attempt returns the migration attempt identifier. This
    38  	// increments for each migration attempt for the model.
    39  	Attempt() int
    40  
    41  	// StartTime returns the time when the migration was started.
    42  	StartTime() time.Time
    43  
    44  	// SuccessTime returns the time when the migration reached
    45  	// SUCCESS.
    46  	SuccessTime() time.Time
    47  
    48  	// EndTime returns the time when the migration reached DONE or
    49  	// REAPFAILED.
    50  	EndTime() time.Time
    51  
    52  	// Phase returns the migration's phase.
    53  	Phase() (migration.Phase, error)
    54  
    55  	// PhaseChangedTime returns the time when the migration's phase
    56  	// last changed.
    57  	PhaseChangedTime() time.Time
    58  
    59  	// StatusMessage returns human readable text about the current
    60  	// progress of the migration.
    61  	StatusMessage() string
    62  
    63  	// InitiatedBy returns username the initiated the migration.
    64  	InitiatedBy() string
    65  
    66  	// TargetInfo returns the details required to connect to the
    67  	// migration's target controller.
    68  	TargetInfo() (*migration.TargetInfo, error)
    69  
    70  	// SetPhase sets the phase of the migration. An error will be
    71  	// returned if the new phase does not follow the current phase or
    72  	// if the migration is no longer active.
    73  	SetPhase(nextPhase migration.Phase) error
    74  
    75  	// SetStatusMessage sets some human readable text about the
    76  	// current progress of the migration.
    77  	SetStatusMessage(text string) error
    78  
    79  	// SubmitMinionReport records a report from a migration minion
    80  	// worker about the success or failure to complete its actions for
    81  	// a given migration phase.
    82  	SubmitMinionReport(tag names.Tag, phase migration.Phase, success bool) error
    83  
    84  	// MinionReports returns details of the minions that have reported
    85  	// success or failure for the current migration phase, as well as
    86  	// those which are yet to report.
    87  	MinionReports() (*MinionReports, error)
    88  
    89  	// WatchMinionReports returns a notify watcher which triggers when
    90  	// a migration minion has reported back about the success or failure
    91  	// of its actions for the current migration phase.
    92  	WatchMinionReports() (NotifyWatcher, error)
    93  
    94  	// Refresh updates the contents of the ModelMigration from the
    95  	// underlying state.
    96  	Refresh() error
    97  
    98  	// ModelUserAccess returns the type of access that the given tag had to
    99  	// the model prior to it being migrated.
   100  	ModelUserAccess(names.Tag) permission.Access
   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  	// The attempt number of the model migration for this model.
   131  	Attempt int `bson:"attempt"`
   132  
   133  	// InitiatedBy holds the username of the user that triggered the
   134  	// migration. It should be in "user@domain" format.
   135  	InitiatedBy string `bson:"initiated-by"`
   136  
   137  	// TargetController holds the UUID of the target controller.
   138  	TargetController string `bson:"target-controller"`
   139  
   140  	// An optional alias for the controller the model got migrated to.
   141  	TargetControllerAlias string `bson:"target-controller-alias"`
   142  
   143  	// TargetAddrs holds the host:port values for the target API
   144  	// server.
   145  	TargetAddrs []string `bson:"target-addrs"`
   146  
   147  	// TargetCACert holds the certificate to validate the target API
   148  	// server's TLS certificate.
   149  	TargetCACert string `bson:"target-cacert"`
   150  
   151  	// TargetAuthTag holds a string representation of the tag to
   152  	// authenticate to the target controller with.
   153  	TargetAuthTag string `bson:"target-entity"`
   154  
   155  	// TargetPassword holds the password to use with TargetAuthTag
   156  	// when authenticating.
   157  	TargetPassword string `bson:"target-password,omitempty"`
   158  
   159  	// TargetMacaroons holds the macaroons to use with TargetAuthTag
   160  	// when authenticating.
   161  	TargetMacaroons string `bson:"target-macaroons,omitempty"`
   162  
   163  	// The list of users and their access-level to the model being migrated.
   164  	ModelUsers []modelMigUserDoc `bson:"model-users,omitempty"`
   165  }
   166  
   167  type modelMigUserDoc struct {
   168  	UserID string            `bson:"user_id"`
   169  	Access permission.Access `bson:"access"`
   170  }
   171  
   172  // modelMigStatusDoc tracks the progress of a migration attempt for a
   173  // model. These are written into migrationsStatusC.
   174  //
   175  // There is exactly one document in migrationsStatusC for each
   176  // document in migrationsC. Separating them allows for watching
   177  // for new model migrations without being woken up for each model
   178  // migration status change.
   179  type modelMigStatusDoc struct {
   180  	// These are the same as the ids as migrationsC.
   181  	// "uuid:sequence".
   182  	Id string `bson:"_id"`
   183  
   184  	// StartTime holds the time the migration started (stored as per
   185  	// UnixNano).
   186  	StartTime int64 `bson:"start-time"`
   187  
   188  	// StartTime holds the time the migration reached the SUCCESS
   189  	// phase (stored as per UnixNano).
   190  	SuccessTime int64 `bson:"success-time"`
   191  
   192  	// EndTime holds the time the migration reached a terminal (end)
   193  	// phase (stored as per UnixNano).
   194  	EndTime int64 `bson:"end-time"`
   195  
   196  	// Phase holds the current migration phase. This should be one of
   197  	// the string representations of the core/migrations.Phase
   198  	// constants.
   199  	Phase string `bson:"phase"`
   200  
   201  	// PhaseChangedTime holds the time that Phase last changed (stored
   202  	// as per UnixNano).
   203  	PhaseChangedTime int64 `bson:"phase-changed-time"`
   204  
   205  	// StatusMessage holds a human readable message about the
   206  	// migration's progress.
   207  	StatusMessage string `bson:"status-message"`
   208  }
   209  
   210  type modelMigMinionSyncDoc struct {
   211  	Id          string `bson:"_id"`
   212  	MigrationId string `bson:"migration-id"`
   213  	Phase       string `bson:"phase"`
   214  	EntityKey   string `bson:"entity-key"`
   215  	Time        int64  `bson:"time"`
   216  	Success     bool   `bson:"success"`
   217  }
   218  
   219  // Id implements ModelMigration.
   220  func (mig *modelMigration) Id() string {
   221  	return mig.doc.Id
   222  }
   223  
   224  // ModelUUID implements ModelMigration.
   225  func (mig *modelMigration) ModelUUID() string {
   226  	return mig.doc.ModelUUID
   227  }
   228  
   229  // Attempt implements ModelMigration.
   230  func (mig *modelMigration) Attempt() int {
   231  	return mig.doc.Attempt
   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  		ControllerAlias: mig.doc.TargetControllerAlias,
   286  		Addrs:           mig.doc.TargetAddrs,
   287  		CACert:          mig.doc.TargetCACert,
   288  		AuthTag:         authTag,
   289  		Password:        mig.doc.TargetPassword,
   290  		Macaroons:       macs,
   291  	}, nil
   292  }
   293  
   294  // SetPhase implements ModelMigration.
   295  func (mig *modelMigration) SetPhase(nextPhase migration.Phase) error {
   296  	now := mig.st.clock().Now().UnixNano()
   297  
   298  	phase, err := mig.Phase()
   299  	if err != nil {
   300  		return errors.Trace(err)
   301  	}
   302  
   303  	if nextPhase == phase {
   304  		return nil // Already at that phase. Nothing to do.
   305  	}
   306  	if !phase.CanTransitionTo(nextPhase) {
   307  		return errors.Errorf("illegal phase change: %s -> %s", phase, nextPhase)
   308  	}
   309  
   310  	nextDoc := mig.statusDoc
   311  	nextDoc.Phase = nextPhase.String()
   312  	nextDoc.PhaseChangedTime = now
   313  	update := bson.M{
   314  		"phase":              nextDoc.Phase,
   315  		"phase-changed-time": now,
   316  	}
   317  	if nextPhase == migration.SUCCESS {
   318  		nextDoc.SuccessTime = now
   319  		update["success-time"] = now
   320  	}
   321  
   322  	ops, err := migStatusHistoryAndOps(mig.st, nextPhase, now, mig.StatusMessage())
   323  	if err != nil {
   324  		return errors.Trace(err)
   325  	}
   326  
   327  	// If the migration aborted, make the model active again.
   328  	if nextPhase == migration.ABORTDONE {
   329  		ops = append(ops, txn.Op{
   330  			C:      modelsC,
   331  			Id:     mig.doc.ModelUUID,
   332  			Assert: txn.DocExists,
   333  			Update: bson.M{
   334  				"$set": bson.M{"migration-mode": MigrationModeNone},
   335  			},
   336  		})
   337  	}
   338  
   339  	// Set end timestamps and mark migration as no longer active if a
   340  	// terminal phase is hit.
   341  	if nextPhase.IsTerminal() {
   342  		nextDoc.EndTime = now
   343  		update["end-time"] = now
   344  		ops = append(ops, txn.Op{
   345  			C:      migrationsActiveC,
   346  			Id:     mig.doc.ModelUUID,
   347  			Assert: txn.DocExists,
   348  			Remove: true,
   349  		})
   350  	}
   351  
   352  	ops = append(ops, txn.Op{
   353  		C:      migrationsStatusC,
   354  		Id:     mig.statusDoc.Id,
   355  		Update: bson.M{"$set": update},
   356  		// Ensure phase hasn't changed underneath us
   357  		Assert: bson.M{"phase": mig.statusDoc.Phase},
   358  	})
   359  
   360  	if err := mig.st.db().RunTransaction(ops); err == txn.ErrAborted {
   361  		return errors.New("phase already changed")
   362  	} else if err != nil {
   363  		return errors.Annotate(err, "failed to update phase")
   364  	}
   365  
   366  	mig.statusDoc = nextDoc
   367  	return nil
   368  }
   369  
   370  // migStatusHistoryAndOps sets the model's status history and returns ops for
   371  // setting model status according to the phase and message.
   372  func migStatusHistoryAndOps(st *State, phase migration.Phase, now int64, msg string) ([]txn.Op, error) {
   373  	switch phase {
   374  	case migration.REAP, migration.DONE:
   375  		// if we're reaping/have reaped the model, setting status on it is both
   376  		// pointless and potentially problematic.
   377  		return nil, nil
   378  	}
   379  	model, err := st.Model()
   380  	if err != nil {
   381  		return nil, errors.Trace(err)
   382  	}
   383  	globalKey := model.globalKey()
   384  	modelStatus := status.Busy
   385  	if phase.IsTerminal() {
   386  		modelStatus = status.Available
   387  	}
   388  	if msg != "" {
   389  		msg = "migrating: " + msg
   390  	}
   391  	doc := statusDoc{
   392  		Status:     modelStatus,
   393  		StatusInfo: msg,
   394  		Updated:    now,
   395  	}
   396  
   397  	ops, err := statusSetOps(st.db(), doc, globalKey)
   398  	if err != nil {
   399  		return nil, errors.Trace(err)
   400  	}
   401  	_, _ = probablyUpdateStatusHistory(st.db(), globalKey, doc)
   402  	return ops, nil
   403  }
   404  
   405  // SetStatusMessage implements ModelMigration.
   406  func (mig *modelMigration) SetStatusMessage(text string) error {
   407  	phase, err := mig.Phase()
   408  	if err != nil {
   409  		return errors.Trace(err)
   410  	}
   411  
   412  	ops, err := migStatusHistoryAndOps(mig.st, phase, mig.st.clock().Now().UnixNano(), text)
   413  	if err != nil {
   414  		return errors.Trace(err)
   415  	}
   416  
   417  	ops = append(ops, txn.Op{
   418  		C:      migrationsStatusC,
   419  		Id:     mig.statusDoc.Id,
   420  		Update: bson.M{"$set": bson.M{"status-message": text}},
   421  		Assert: txn.DocExists,
   422  	})
   423  	if err := mig.st.db().RunTransaction(ops); err != nil {
   424  		return errors.Annotate(err, "failed to set migration status")
   425  	}
   426  	mig.statusDoc.StatusMessage = text
   427  	return nil
   428  }
   429  
   430  // SubmitMinionReport implements ModelMigration.
   431  func (mig *modelMigration) SubmitMinionReport(tag names.Tag, phase migration.Phase, success bool) error {
   432  	globalKey, err := agentTagToGlobalKey(tag)
   433  	if err != nil {
   434  		return errors.Trace(err)
   435  	}
   436  	docID := mig.minionReportId(phase, globalKey)
   437  	doc := modelMigMinionSyncDoc{
   438  		Id:          docID,
   439  		MigrationId: mig.Id(),
   440  		Phase:       phase.String(),
   441  		EntityKey:   globalKey,
   442  		Time:        mig.st.clock().Now().UnixNano(),
   443  		Success:     success,
   444  	}
   445  	ops := []txn.Op{{
   446  		C:      migrationsMinionSyncC,
   447  		Id:     docID,
   448  		Insert: doc,
   449  		Assert: txn.DocMissing,
   450  	}}
   451  	err = mig.st.db().RunTransaction(ops)
   452  	if errors.Cause(err) == txn.ErrAborted {
   453  		coll, closer := mig.st.db().GetCollection(migrationsMinionSyncC)
   454  		defer closer()
   455  		var existingDoc modelMigMinionSyncDoc
   456  		err := coll.FindId(docID).Select(bson.M{"success": 1}).One(&existingDoc)
   457  		if err != nil {
   458  			return errors.Annotate(err, "checking existing report")
   459  		}
   460  		if existingDoc.Success != success {
   461  			return errors.Errorf("conflicting reports received for %s/%s/%s",
   462  				mig.Id(), phase.String(), tag)
   463  		}
   464  		return nil
   465  	} else if err != nil {
   466  		return errors.Trace(err)
   467  	}
   468  	return nil
   469  }
   470  
   471  // MinionReports implements ModelMigration.
   472  func (mig *modelMigration) MinionReports() (*MinionReports, error) {
   473  	all, err := mig.getAllAgents()
   474  	if err != nil {
   475  		return nil, errors.Trace(err)
   476  	}
   477  
   478  	phase, err := mig.Phase()
   479  	if err != nil {
   480  		return nil, errors.Annotate(err, "retrieving phase")
   481  	}
   482  
   483  	coll, closer := mig.st.db().GetCollection(migrationsMinionSyncC)
   484  	defer closer()
   485  	query := coll.Find(bson.M{"_id": bson.M{
   486  		"$regex": "^" + mig.minionReportId(phase, ".+"),
   487  	}})
   488  	query = query.Select(bson.M{
   489  		"entity-key": 1,
   490  		"success":    1,
   491  	})
   492  	var docs []bson.M
   493  	if err := query.All(&docs); err != nil {
   494  		return nil, errors.Annotate(err, "retrieving minion reports")
   495  	}
   496  
   497  	succeeded := names.NewSet()
   498  	failed := names.NewSet()
   499  	for _, doc := range docs {
   500  		entityKey, ok := doc["entity-key"].(string)
   501  		if !ok {
   502  			return nil, errors.Errorf("unexpected entity-key %v", doc["entity-key"])
   503  		}
   504  		tag, err := globalKeyToAgentTag(entityKey)
   505  		if err != nil {
   506  			return nil, errors.Trace(err)
   507  		}
   508  		success, ok := doc["success"].(bool)
   509  		if !ok {
   510  			return nil, errors.Errorf("unexpected success value: %v", doc["success"])
   511  		}
   512  		if success {
   513  			succeeded.Add(tag)
   514  		} else {
   515  			failed.Add(tag)
   516  		}
   517  	}
   518  
   519  	unknown := all.Difference(succeeded).Difference(failed)
   520  
   521  	return &MinionReports{
   522  		Succeeded: succeeded.Values(),
   523  		Failed:    failed.Values(),
   524  		Unknown:   unknown.Values(),
   525  	}, nil
   526  }
   527  
   528  // WatchMinionReports implements ModelMigration.
   529  func (mig *modelMigration) WatchMinionReports() (NotifyWatcher, error) {
   530  	phase, err := mig.Phase()
   531  	if err != nil {
   532  		return nil, errors.Annotate(err, "retrieving phase")
   533  	}
   534  	prefix := mig.minionReportId(phase, "")
   535  	filter := func(rawId interface{}) bool {
   536  		id, ok := rawId.(string)
   537  		if !ok {
   538  			return false
   539  		}
   540  		return strings.HasPrefix(id, prefix)
   541  	}
   542  	return newNotifyCollWatcher(mig.st, migrationsMinionSyncC, filter), nil
   543  }
   544  
   545  func (mig *modelMigration) minionReportId(phase migration.Phase, globalKey string) string {
   546  	return fmt.Sprintf("%s:%s:%s", mig.Id(), phase.String(), globalKey)
   547  }
   548  
   549  func (mig *modelMigration) getAllAgents() (names.Set, error) {
   550  	agentTags := names.NewSet()
   551  	machineTags, err := mig.loadAgentTags(machinesC, "machineid",
   552  		func(id string) names.Tag { return names.NewMachineTag(id) },
   553  	)
   554  	if err != nil {
   555  		return nil, errors.Annotate(err, "loading machine tags")
   556  	}
   557  	agentTags = agentTags.Union(machineTags)
   558  
   559  	m, err := mig.st.Model()
   560  	if err != nil {
   561  		return nil, errors.Trace(err)
   562  	}
   563  	if m.Type() != ModelTypeCAAS {
   564  		unitTags, err := mig.loadAgentTags(unitsC, "name",
   565  			func(name string) names.Tag { return names.NewUnitTag(name) },
   566  		)
   567  		if err != nil {
   568  			return nil, errors.Annotate(err, "loading unit names")
   569  		}
   570  
   571  		return agentTags.Union(unitTags), nil
   572  	}
   573  
   574  	applicationTags, err := mig.loadAgentTags(applicationsC, "name",
   575  		func(name string) names.Tag { return names.NewApplicationTag(name) },
   576  	)
   577  	if err != nil {
   578  		return nil, errors.Annotate(err, "loading application names")
   579  	}
   580  	for _, applicationTag := range applicationTags.Values() {
   581  		app, err := mig.st.Application(applicationTag.Id())
   582  		if err != nil {
   583  			return nil, errors.Trace(err)
   584  		}
   585  		isSidecar, err := app.IsSidecar()
   586  		if err != nil {
   587  			return nil, errors.Trace(err)
   588  		}
   589  		if !isSidecar {
   590  			agentTags.Add(applicationTag)
   591  			continue
   592  		}
   593  		unitNames, err := app.UnitNames()
   594  		if err != nil {
   595  			return nil, errors.Trace(err)
   596  		}
   597  		for _, unitName := range unitNames {
   598  			agentTags.Add(names.NewUnitTag(unitName))
   599  		}
   600  	}
   601  	return agentTags, nil
   602  }
   603  
   604  func (mig *modelMigration) loadAgentTags(collName, fieldName string, convert func(string) names.Tag) (
   605  	names.Set, error,
   606  ) {
   607  	// During migrations we know that nothing there are no machines or
   608  	// units being provisioned or destroyed so a simple query of the
   609  	// collections will do.
   610  	coll, closer := mig.st.db().GetCollection(collName)
   611  	defer closer()
   612  	var docs []bson.M
   613  	err := coll.Find(nil).Select(bson.M{fieldName: 1}).All(&docs)
   614  	if err != nil {
   615  		return nil, errors.Trace(err)
   616  	}
   617  
   618  	out := names.NewSet()
   619  	for _, doc := range docs {
   620  		v, ok := doc[fieldName].(string)
   621  		if !ok {
   622  			return nil, errors.Errorf("invalid %s value: %v", fieldName, doc[fieldName])
   623  		}
   624  		out.Add(convert(v))
   625  	}
   626  	return out, nil
   627  }
   628  
   629  // Refresh implements ModelMigration.
   630  func (mig *modelMigration) Refresh() error {
   631  	// Only the status document is updated. The modelMigDoc is static
   632  	// after creation.
   633  	statusColl, closer := mig.st.db().GetCollection(migrationsStatusC)
   634  	defer closer()
   635  	var statusDoc modelMigStatusDoc
   636  	err := statusColl.FindId(mig.doc.Id).One(&statusDoc)
   637  	if err == mgo.ErrNotFound {
   638  		return errors.NotFoundf("migration status")
   639  	} else if err != nil {
   640  		return errors.Annotate(err, "migration status lookup failed")
   641  	}
   642  
   643  	mig.statusDoc = statusDoc
   644  	return nil
   645  }
   646  
   647  // ModelUserAccess implements ModelMigration.
   648  func (mig *modelMigration) ModelUserAccess(tag names.Tag) permission.Access {
   649  	id := tag.Id()
   650  	for _, user := range mig.doc.ModelUsers {
   651  		if user.UserID == id {
   652  			return user.Access
   653  		}
   654  	}
   655  
   656  	return permission.NoAccess
   657  }
   658  
   659  // MigrationSpec holds the information required to create a
   660  // ModelMigration instance.
   661  type MigrationSpec struct {
   662  	InitiatedBy names.UserTag
   663  	TargetInfo  migration.TargetInfo
   664  }
   665  
   666  // Validate returns an error if the MigrationSpec contains bad
   667  // data. Nil is returned otherwise.
   668  func (spec *MigrationSpec) Validate() error {
   669  	if !names.IsValidUser(spec.InitiatedBy.Id()) {
   670  		return errors.NotValidf("InitiatedBy")
   671  	}
   672  	return spec.TargetInfo.Validate()
   673  }
   674  
   675  // CreateMigration initialises state that tracks a model migration. It
   676  // will return an error if there is already a model migration in
   677  // progress.
   678  func (st *State) CreateMigration(spec MigrationSpec) (ModelMigration, error) {
   679  	if st.IsController() {
   680  		return nil, errors.New("controllers can't be migrated")
   681  	}
   682  	if err := spec.Validate(); err != nil {
   683  		return nil, errors.Trace(err)
   684  	}
   685  	if err := checkTargetController(st, spec.TargetInfo.ControllerTag); err != nil {
   686  		return nil, errors.Trace(err)
   687  	}
   688  
   689  	now := st.clock().Now().UnixNano()
   690  	modelUUID := st.ModelUUID()
   691  	var doc modelMigDoc
   692  	var statusDoc modelMigStatusDoc
   693  
   694  	msg := "starting"
   695  	ops, err := migStatusHistoryAndOps(st, migration.QUIESCE, now, msg)
   696  	if err != nil {
   697  		return nil, errors.Trace(err)
   698  	}
   699  
   700  	buildTxn := func(int) ([]txn.Op, error) {
   701  		model, err := st.Model()
   702  		if err != nil {
   703  			return nil, errors.Annotate(err, "failed to load model")
   704  		}
   705  		if model.Life() != Alive {
   706  			return nil, errors.New("model is not alive")
   707  		}
   708  
   709  		if isActive, err := st.IsMigrationActive(); err != nil {
   710  			return nil, errors.Trace(err)
   711  		} else if isActive {
   712  			return nil, errors.New("already in progress")
   713  		}
   714  
   715  		macsJSON, err := macaroonsToJSON(spec.TargetInfo.Macaroons)
   716  		if err != nil {
   717  			return nil, errors.Trace(err)
   718  		}
   719  
   720  		attempt, err := sequence(st, "modelmigration")
   721  		if err != nil {
   722  			return nil, errors.Trace(err)
   723  		}
   724  
   725  		userDocs, err := modelUserDocs(model)
   726  		if err != nil {
   727  			return nil, errors.Trace(err)
   728  		}
   729  
   730  		id := fmt.Sprintf("%s:%d", modelUUID, attempt)
   731  		doc = modelMigDoc{
   732  			Id:                    id,
   733  			ModelUUID:             modelUUID,
   734  			Attempt:               attempt,
   735  			InitiatedBy:           spec.InitiatedBy.Id(),
   736  			TargetController:      spec.TargetInfo.ControllerTag.Id(),
   737  			TargetControllerAlias: spec.TargetInfo.ControllerAlias,
   738  			TargetAddrs:           spec.TargetInfo.Addrs,
   739  			TargetCACert:          spec.TargetInfo.CACert,
   740  			TargetAuthTag:         spec.TargetInfo.AuthTag.String(),
   741  			TargetPassword:        spec.TargetInfo.Password,
   742  			TargetMacaroons:       macsJSON,
   743  			ModelUsers:            userDocs,
   744  		}
   745  
   746  		statusDoc = modelMigStatusDoc{
   747  			Id:               id,
   748  			StartTime:        now,
   749  			Phase:            migration.QUIESCE.String(),
   750  			PhaseChangedTime: now,
   751  			StatusMessage:    msg,
   752  		}
   753  
   754  		ops := append(ops, []txn.Op{{
   755  			C:      migrationsC,
   756  			Id:     doc.Id,
   757  			Assert: txn.DocMissing,
   758  			Insert: &doc,
   759  		}, {
   760  			C:      migrationsStatusC,
   761  			Id:     statusDoc.Id,
   762  			Assert: txn.DocMissing,
   763  			Insert: &statusDoc,
   764  		}, {
   765  			C:      migrationsActiveC,
   766  			Id:     modelUUID,
   767  			Assert: txn.DocMissing,
   768  			Insert: bson.M{"id": doc.Id},
   769  		}, {
   770  			C:      modelsC,
   771  			Id:     modelUUID,
   772  			Assert: txn.DocExists,
   773  			Update: bson.M{"$set": bson.M{
   774  				"migration-mode": MigrationModeExporting,
   775  			}},
   776  		}, model.assertActiveOp(),
   777  		}...)
   778  		return ops, nil
   779  	}
   780  	if err := st.db().Run(buildTxn); err != nil {
   781  		return nil, errors.Annotate(err, "failed to create migration")
   782  	}
   783  
   784  	return &modelMigration{
   785  		doc:       doc,
   786  		statusDoc: statusDoc,
   787  		st:        st,
   788  	}, nil
   789  }
   790  
   791  func modelUserDocs(m *Model) ([]modelMigUserDoc, error) {
   792  	users, err := m.Users()
   793  	if err != nil {
   794  		return nil, err
   795  	}
   796  
   797  	var docs []modelMigUserDoc
   798  	for _, user := range users {
   799  		docs = append(docs, modelMigUserDoc{
   800  			UserID: user.UserTag.Id(),
   801  			Access: user.Access,
   802  		})
   803  	}
   804  
   805  	return docs, nil
   806  }
   807  
   808  func macaroonsToJSON(m []macaroon.Slice) (string, error) {
   809  	if len(m) == 0 {
   810  		return "", nil
   811  	}
   812  	j, err := json.Marshal(m)
   813  	if err != nil {
   814  		return "", errors.Annotate(err, "marshalling macaroons")
   815  	}
   816  	return string(j), nil
   817  }
   818  
   819  func jsonToMacaroons(raw string) ([]macaroon.Slice, error) {
   820  	if raw == "" {
   821  		return nil, nil
   822  	}
   823  	var macs []macaroon.Slice
   824  	if err := json.Unmarshal([]byte(raw), &macs); err != nil {
   825  		return nil, errors.Annotate(err, "unmarshalling macaroon")
   826  	}
   827  	return macs, nil
   828  }
   829  
   830  func checkTargetController(st *State, targetControllerTag names.ControllerTag) error {
   831  	if targetControllerTag.Id() == st.ControllerUUID() {
   832  		return errors.New("model already attached to target controller")
   833  	}
   834  	return nil
   835  }
   836  
   837  // LatestMigration returns the most recent ModelMigration (if any) for a model
   838  // that has not been removed from the state. Callers interested in
   839  // ModelMigrations for models that have been removed after a successful
   840  // migration to another controller should use CompletedMigration
   841  // instead.
   842  func (st *State) LatestMigration() (ModelMigration, error) {
   843  	mig, phase, err := st.latestMigration(st.ModelUUID())
   844  	if err != nil {
   845  		return nil, err
   846  	}
   847  
   848  	// Hide previous migrations for models which have been migrated
   849  	// away from a model and then migrated back.
   850  	if phase == migration.DONE {
   851  		model, err := st.Model()
   852  		if err != nil {
   853  			return nil, errors.Trace(err)
   854  		}
   855  		if model.MigrationMode() == MigrationModeNone {
   856  			return nil, errors.NotFoundf("migration")
   857  		}
   858  	}
   859  
   860  	return mig, nil
   861  }
   862  
   863  // CompletedMigration returns the most recent migration for this state's
   864  // model if it reached the DONE phase and caused the model to be relocated.
   865  func (st *State) CompletedMigration() (ModelMigration, error) {
   866  	mig, err := st.CompletedMigrationForModel(st.ModelUUID())
   867  	return mig, errors.Trace(err)
   868  }
   869  
   870  // CompletedMigrationForModel returns the most recent migration for the
   871  // input model UUID if it reached the DONE phase and caused the model
   872  // to be relocated.
   873  func (st *State) CompletedMigrationForModel(modelUUID string) (ModelMigration, error) {
   874  	mig, phase, err := st.latestMigration(modelUUID)
   875  	if err != nil {
   876  		return nil, errors.Trace(err)
   877  	}
   878  	// Return NotFound if the model still modelExists or the migration is not
   879  	// flagged as completed.
   880  	modelExists, err := st.ModelExists(modelUUID)
   881  	if err != nil {
   882  		return nil, errors.Trace(err)
   883  	}
   884  	if phase != migration.DONE || modelExists {
   885  		return nil, errors.NotFoundf("migration")
   886  	}
   887  
   888  	return mig, nil
   889  }
   890  
   891  // latestMigration returns the most recent ModelMigration for a model
   892  // (if any).
   893  func (st *State) latestMigration(modelUUID string) (ModelMigration, migration.Phase, error) {
   894  	migColl, closer := st.db().GetCollection(migrationsC)
   895  	defer closer()
   896  	query := migColl.Find(bson.M{"model-uuid": modelUUID})
   897  	query = query.Sort("-attempt").Limit(1)
   898  	mig, err := st.migrationFromQuery(query)
   899  	if err != nil {
   900  		return nil, migration.UNKNOWN, errors.Trace(err)
   901  	}
   902  
   903  	// Hide previous migrations for models which have been migrated
   904  	// away from a model and then migrated back.
   905  	phase, err := mig.Phase()
   906  	if err != nil {
   907  		return nil, migration.UNKNOWN, errors.Trace(err)
   908  	}
   909  	return mig, phase, nil
   910  }
   911  
   912  // Migration retrieves a specific ModelMigration by its id. See also
   913  // LatestMigration and LatestCompletedMigration.
   914  func (st *State) Migration(id string) (ModelMigration, error) {
   915  	migColl, closer := st.db().GetCollection(migrationsC)
   916  	defer closer()
   917  	mig, err := st.migrationFromQuery(migColl.FindId(id))
   918  	if err != nil {
   919  		return nil, errors.Trace(err)
   920  	}
   921  	return mig, nil
   922  }
   923  
   924  func (st *State) migrationFromQuery(query mongo.Query) (ModelMigration, error) {
   925  	var doc modelMigDoc
   926  	err := query.One(&doc)
   927  	if err == mgo.ErrNotFound {
   928  		return nil, errors.NotFoundf("migration")
   929  	} else if err != nil {
   930  		return nil, errors.Annotate(err, "migration lookup failed")
   931  	}
   932  
   933  	statusColl, closer := st.db().GetCollection(migrationsStatusC)
   934  	defer closer()
   935  	var statusDoc modelMigStatusDoc
   936  	err = statusColl.FindId(doc.Id).One(&statusDoc)
   937  	if err == mgo.ErrNotFound {
   938  		return nil, errors.NotFoundf("migration status")
   939  	} else if err != nil {
   940  		return nil, errors.Annotate(err, "migration status lookup failed")
   941  	}
   942  
   943  	return &modelMigration{
   944  		doc:       doc,
   945  		statusDoc: statusDoc,
   946  		st:        st,
   947  	}, nil
   948  }
   949  
   950  // IsMigrationActive returns true if a migration is in progress for
   951  // the model associated with the State.
   952  func (st *State) IsMigrationActive() (bool, error) {
   953  	return IsMigrationActive(st, st.ModelUUID())
   954  }
   955  
   956  // IsMigrationActive returns true if a migration is in progress for
   957  // the model with the given UUID. The State provided need not be for
   958  // the model in question.
   959  func IsMigrationActive(st *State, modelUUID string) (bool, error) {
   960  	active, closer := st.db().GetCollection(migrationsActiveC)
   961  	defer closer()
   962  	n, err := active.FindId(modelUUID).Count()
   963  	if err != nil {
   964  		return false, errors.Trace(err)
   965  	}
   966  	return n > 0, nil
   967  }
   968  
   969  func unixNanoToTime0(i int64) time.Time {
   970  	if i == 0 {
   971  		return time.Time{}
   972  	}
   973  	return time.Unix(0, i)
   974  }
   975  
   976  func agentTagToGlobalKey(tag names.Tag) (string, error) {
   977  	switch t := tag.(type) {
   978  	case names.MachineTag:
   979  		return machineGlobalKey(t.Id()), nil
   980  	case names.UnitTag:
   981  		return unitAgentGlobalKey(t.Id()), nil
   982  	case names.ApplicationTag:
   983  		return applicationGlobalKey(t.Id()), nil
   984  	default:
   985  		return "", errors.Errorf("%s is not an agent tag", tag)
   986  	}
   987  }
   988  
   989  func globalKeyToAgentTag(key string) (names.Tag, error) {
   990  	parts := strings.SplitN(key, "#", 2)
   991  	if len(parts) != 2 {
   992  		return nil, errors.NotValidf("global key %q", key)
   993  	}
   994  	keyType, keyId := parts[0], parts[1]
   995  	switch keyType {
   996  	case "m":
   997  		return names.NewMachineTag(keyId), nil
   998  	case "u":
   999  		return names.NewUnitTag(keyId), nil
  1000  	case "a":
  1001  		return names.NewApplicationTag(keyId), nil
  1002  	default:
  1003  		return nil, errors.NotValidf("global key type %q", keyType)
  1004  	}
  1005  }