github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/migrationmaster/worker_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package migrationmaster_test
     5  
     6  import (
     7  	"context"
     8  	"net/http"
     9  	"net/textproto"
    10  	"net/url"
    11  	"reflect"
    12  	"time"
    13  
    14  	"github.com/juju/clock/testclock"
    15  	"github.com/juju/errors"
    16  	"github.com/juju/loggo"
    17  	"github.com/juju/names/v5"
    18  	jujutesting "github.com/juju/testing"
    19  	jc "github.com/juju/testing/checkers"
    20  	"github.com/juju/utils/v3"
    21  	"github.com/juju/version/v2"
    22  	"github.com/juju/worker/v3"
    23  	"github.com/juju/worker/v3/workertest"
    24  	gc "gopkg.in/check.v1"
    25  	"gopkg.in/macaroon.v2"
    26  
    27  	"github.com/juju/juju/api"
    28  	"github.com/juju/juju/api/base"
    29  	apiclient "github.com/juju/juju/api/client/client"
    30  	"github.com/juju/juju/api/common"
    31  	apiservererrors "github.com/juju/juju/apiserver/errors"
    32  	coremigration "github.com/juju/juju/core/migration"
    33  	resourcetesting "github.com/juju/juju/core/resources/testing"
    34  	"github.com/juju/juju/core/watcher"
    35  	"github.com/juju/juju/migration"
    36  	"github.com/juju/juju/rpc/params"
    37  	coretesting "github.com/juju/juju/testing"
    38  	jujuversion "github.com/juju/juju/version"
    39  	"github.com/juju/juju/worker/fortress"
    40  	"github.com/juju/juju/worker/migrationmaster"
    41  )
    42  
    43  type Suite struct {
    44  	coretesting.BaseSuite
    45  	clock         *testclock.Clock
    46  	stub          *jujutesting.Stub
    47  	connection    *stubConnection
    48  	connectionErr error
    49  	facade        *stubMasterFacade
    50  	config        migrationmaster.Config
    51  }
    52  
    53  var _ = gc.Suite(&Suite{})
    54  
    55  var (
    56  	fakeModelBytes      = []byte("model")
    57  	sourceControllerTag = names.NewControllerTag("source-controller-uuid")
    58  	targetControllerTag = names.NewControllerTag("controller-uuid")
    59  	modelUUID           = "model-uuid"
    60  	modelTag            = names.NewModelTag(modelUUID)
    61  	modelName           = "model-name"
    62  	ownerTag            = names.NewUserTag("owner")
    63  	modelVersion        = version.MustParse("1.2.4")
    64  
    65  	// Define stub calls that commonly appear in tests here to allow reuse.
    66  	apiOpenControllerCall = jujutesting.StubCall{
    67  		FuncName: "apiOpen",
    68  		Args: []interface{}{
    69  			&api.Info{
    70  				Addrs:    []string{"1.2.3.4:5"},
    71  				CACert:   "cert",
    72  				Tag:      names.NewUserTag("admin"),
    73  				Password: "secret",
    74  			},
    75  			migration.ControllerDialOpts(),
    76  		},
    77  	}
    78  	importCall = jujutesting.StubCall{
    79  		FuncName: "MigrationTarget.Import",
    80  		Args: []interface{}{
    81  			params.SerializedModel{Bytes: fakeModelBytes},
    82  		},
    83  	}
    84  	activateCall = jujutesting.StubCall{
    85  		FuncName: "MigrationTarget.Activate",
    86  		Args: []interface{}{
    87  			params.ActivateModelArgs{
    88  				ModelTag:        modelTag.String(),
    89  				ControllerTag:   sourceControllerTag.String(),
    90  				ControllerAlias: "mycontroller",
    91  				SourceAPIAddrs:  []string{"source-addr"},
    92  				SourceCACert:    "cacert",
    93  				CrossModelUUIDs: []string{"related-model-uuid"},
    94  			},
    95  		},
    96  	}
    97  	checkMachinesCall = jujutesting.StubCall{
    98  		FuncName: "MigrationTarget.CheckMachines",
    99  		Args: []interface{}{
   100  			params.ModelArgs{ModelTag: modelTag.String()},
   101  		},
   102  	}
   103  	adoptResourcesCall = jujutesting.StubCall{
   104  		FuncName: "MigrationTarget.AdoptResources",
   105  		Args: []interface{}{
   106  			params.AdoptResourcesArgs{
   107  				ModelTag:                modelTag.String(),
   108  				SourceControllerVersion: jujuversion.Current,
   109  			},
   110  		},
   111  	}
   112  	latestLogTimeCall = jujutesting.StubCall{
   113  		FuncName: "MigrationTarget.LatestLogTime",
   114  		Args: []interface{}{
   115  			params.ModelArgs{ModelTag: modelTag.String()},
   116  		},
   117  	}
   118  	apiCloseCall = jujutesting.StubCall{FuncName: "Connection.Close"}
   119  	abortCall    = jujutesting.StubCall{
   120  		FuncName: "MigrationTarget.Abort",
   121  		Args: []interface{}{
   122  			params.ModelArgs{ModelTag: modelTag.String()},
   123  		},
   124  	}
   125  	watchStatusLockdownCalls = []jujutesting.StubCall{
   126  		{"facade.Watch", nil},
   127  		{"facade.MigrationStatus", nil},
   128  		{"guard.Lockdown", nil},
   129  	}
   130  	prechecksCalls = []jujutesting.StubCall{
   131  		{"facade.ModelInfo", nil},
   132  		{"facade.Prechecks", []interface{}{}},
   133  		apiOpenControllerCall,
   134  		{"MigrationTarget.Prechecks", []interface{}{params.MigrationModelInfo{
   135  			UUID:         modelUUID,
   136  			Name:         modelName,
   137  			OwnerTag:     ownerTag.String(),
   138  			AgentVersion: modelVersion,
   139  		}}},
   140  		apiCloseCall,
   141  	}
   142  	abortCalls = []jujutesting.StubCall{
   143  		{"facade.SetPhase", []interface{}{coremigration.ABORT}},
   144  		apiOpenControllerCall,
   145  		abortCall,
   146  		apiCloseCall,
   147  		{"facade.SetPhase", []interface{}{coremigration.ABORTDONE}},
   148  	}
   149  	openDestLogStreamCall = jujutesting.StubCall{FuncName: "ConnectControllerStream", Args: []interface{}{
   150  		"/migrate/logtransfer",
   151  		url.Values{},
   152  		http.Header{
   153  			textproto.CanonicalMIMEHeaderKey(params.MigrationModelHTTPHeader): {modelUUID},
   154  		},
   155  	}}
   156  )
   157  
   158  func (s *Suite) SetUpTest(c *gc.C) {
   159  	s.BaseSuite.SetUpTest(c)
   160  
   161  	s.clock = testclock.NewClock(time.Now())
   162  	s.stub = new(jujutesting.Stub)
   163  	s.connection = &stubConnection{
   164  		c:             c,
   165  		stub:          s.stub,
   166  		controllerTag: targetControllerTag,
   167  		logStream:     &mockStream{},
   168  		controllerVersion: params.ControllerVersionResults{
   169  			Version: "2.9.99",
   170  		},
   171  		facadeVersion: 2,
   172  	}
   173  	s.connectionErr = nil
   174  
   175  	s.facade = newStubMasterFacade(s.stub)
   176  
   177  	// The default worker Config used by most of the tests. Tests may
   178  	// tweak parts of this as needed.
   179  	s.config = migrationmaster.Config{
   180  		ModelUUID:       utils.MustNewUUID().String(),
   181  		Facade:          s.facade,
   182  		Guard:           newStubGuard(s.stub),
   183  		APIOpen:         s.apiOpen,
   184  		UploadBinaries:  nullUploadBinaries,
   185  		CharmDownloader: fakeCharmDownloader,
   186  		ToolsDownloader: fakeToolsDownloader,
   187  		Clock:           s.clock,
   188  	}
   189  }
   190  
   191  func (s *Suite) apiOpen(info *api.Info, dialOpts api.DialOpts) (api.Connection, error) {
   192  	s.stub.AddCall("apiOpen", info, dialOpts)
   193  	if s.connectionErr != nil {
   194  		return nil, s.connectionErr
   195  	}
   196  	return s.connection, nil
   197  }
   198  
   199  func (s *Suite) makeStatus(phase coremigration.Phase) coremigration.MigrationStatus {
   200  	return coremigration.MigrationStatus{
   201  		MigrationId:      "model-uuid:2",
   202  		ModelUUID:        "model-uuid",
   203  		Phase:            phase,
   204  		PhaseChangedTime: s.clock.Now(),
   205  		TargetInfo: coremigration.TargetInfo{
   206  			ControllerTag: targetControllerTag,
   207  			Addrs:         []string{"1.2.3.4:5"},
   208  			CACert:        "cert",
   209  			AuthTag:       names.NewUserTag("admin"),
   210  			Password:      "secret",
   211  		},
   212  	}
   213  }
   214  
   215  func (s *Suite) TestSuccessfulMigration(c *gc.C) {
   216  	s.facade.exportedResources = []coremigration.SerializedModelResource{{
   217  		ApplicationRevision: resourcetesting.NewResource(c, nil, "blob", "app", "").Resource,
   218  	}}
   219  
   220  	s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE))
   221  	s.facade.queueMinionReports(makeMinionReports(coremigration.QUIESCE))
   222  	s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION))
   223  	s.facade.queueMinionReports(makeMinionReports(coremigration.SUCCESS))
   224  	s.config.UploadBinaries = makeStubUploadBinaries(s.stub)
   225  
   226  	s.checkWorkerReturns(c, migrationmaster.ErrMigrated)
   227  
   228  	// Observe that the migration was seen, the model exported, an API
   229  	// connection to the target controller was made, the model was
   230  	// imported and then the migration completed.
   231  	assertExpectedCallArgs(c, s.stub, joinCalls(
   232  		// Wait for migration to start.
   233  		watchStatusLockdownCalls,
   234  		[]jujutesting.StubCall{
   235  			{"facade.MinionReportTimeout", nil},
   236  		},
   237  
   238  		// QUIESCE
   239  		prechecksCalls,
   240  		[]jujutesting.StubCall{
   241  			{"facade.WatchMinionReports", nil},
   242  			{"facade.MinionReports", nil},
   243  		},
   244  		prechecksCalls,
   245  		[]jujutesting.StubCall{
   246  			{"facade.SetPhase", []interface{}{coremigration.IMPORT}},
   247  
   248  			//IMPORT
   249  			{"facade.Export", nil},
   250  			apiOpenControllerCall,
   251  			importCall,
   252  			{"UploadBinaries", []interface{}{
   253  				[]string{"charm0", "charm1"},
   254  				fakeCharmDownloader,
   255  				map[version.Binary]string{
   256  					version.MustParseBinary("2.1.0-ubuntu-amd64"): "/tools/0",
   257  				},
   258  				fakeToolsDownloader,
   259  				s.facade.exportedResources,
   260  				s.facade,
   261  			}},
   262  			apiCloseCall, // for target controller
   263  			{"facade.SetPhase", []interface{}{coremigration.PROCESSRELATIONS}},
   264  
   265  			// PROCESSRELATIONS
   266  			{"facade.ProcessRelations", []interface{}{""}},
   267  			{"facade.SetPhase", []interface{}{coremigration.VALIDATION}},
   268  
   269  			// VALIDATION
   270  			{"facade.WatchMinionReports", nil},
   271  			{"facade.MinionReports", nil},
   272  			apiOpenControllerCall,
   273  			checkMachinesCall,
   274  			{"facade.SourceControllerInfo", nil},
   275  			activateCall,
   276  			apiCloseCall,
   277  			{"facade.SetPhase", []interface{}{coremigration.SUCCESS}},
   278  
   279  			// SUCCESS
   280  			{"facade.WatchMinionReports", nil},
   281  			{"facade.MinionReports", nil},
   282  			apiOpenControllerCall,
   283  			adoptResourcesCall,
   284  			apiCloseCall,
   285  			{"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}},
   286  
   287  			// LOGTRANSFER
   288  			apiOpenControllerCall,
   289  			latestLogTimeCall,
   290  			{"StreamModelLog", []interface{}{time.Time{}}},
   291  			openDestLogStreamCall,
   292  			{"facade.SetPhase", []interface{}{coremigration.REAP}},
   293  
   294  			// REAP
   295  			{"facade.Reap", nil},
   296  			{"facade.SetPhase", []interface{}{coremigration.DONE}},
   297  		}),
   298  	)
   299  }
   300  
   301  func (s *Suite) TestIncompatibleTarget(c *gc.C) {
   302  	s.connection.facadeVersion = 1
   303  	s.facade.exportedResources = []coremigration.SerializedModelResource{{
   304  		ApplicationRevision: resourcetesting.NewResource(c, nil, "blob", "app", "").Resource,
   305  	}}
   306  
   307  	s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE))
   308  	s.facade.queueMinionReports(makeMinionReports(coremigration.QUIESCE))
   309  	s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION))
   310  	s.facade.queueMinionReports(makeMinionReports(coremigration.SUCCESS))
   311  	s.config.UploadBinaries = makeStubUploadBinaries(s.stub)
   312  
   313  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   314  
   315  	// Observe that the migration was seen, the model exported, an API
   316  	// connection to the target controller was made, the model was
   317  	// imported and then the migration completed.
   318  	s.stub.CheckCalls(c, joinCalls(
   319  		// Wait for migration to start.
   320  		watchStatusLockdownCalls,
   321  		[]jujutesting.StubCall{
   322  			{"facade.MinionReportTimeout", nil},
   323  		},
   324  
   325  		// QUIESCE
   326  		[]jujutesting.StubCall{
   327  			{"facade.ModelInfo", nil},
   328  			{"facade.Prechecks", []interface{}{}},
   329  			apiOpenControllerCall,
   330  			{"facade.SourceControllerInfo", nil},
   331  			apiCloseCall,
   332  		},
   333  		abortCalls,
   334  	))
   335  }
   336  
   337  func (s *Suite) TestMigrationResume(c *gc.C) {
   338  	// Test that a partially complete migration can be resumed.
   339  	s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS))
   340  	s.facade.queueMinionReports(makeMinionReports(coremigration.SUCCESS))
   341  
   342  	s.checkWorkerReturns(c, migrationmaster.ErrMigrated)
   343  	s.stub.CheckCalls(c, joinCalls(
   344  		watchStatusLockdownCalls,
   345  		[]jujutesting.StubCall{
   346  			{"facade.MinionReportTimeout", nil},
   347  			{"facade.WatchMinionReports", nil},
   348  			{"facade.MinionReports", nil},
   349  			apiOpenControllerCall,
   350  			adoptResourcesCall,
   351  			apiCloseCall,
   352  			{"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}},
   353  			apiOpenControllerCall,
   354  			latestLogTimeCall,
   355  			{"StreamModelLog", []interface{}{time.Time{}}},
   356  			openDestLogStreamCall,
   357  			{"facade.SetPhase", []interface{}{coremigration.REAP}},
   358  			{"facade.Reap", nil},
   359  			{"facade.SetPhase", []interface{}{coremigration.DONE}},
   360  		},
   361  	))
   362  }
   363  
   364  func (s *Suite) TestPreviouslyAbortedMigration(c *gc.C) {
   365  	s.facade.queueStatus(s.makeStatus(coremigration.ABORTDONE))
   366  
   367  	w, err := migrationmaster.New(s.config)
   368  	c.Assert(err, jc.ErrorIsNil)
   369  	defer workertest.CleanKill(c, w)
   370  
   371  	s.waitForStubCalls(c, []string{
   372  		"facade.Watch",
   373  		"facade.MigrationStatus",
   374  		"guard.Unlock",
   375  	})
   376  }
   377  
   378  func (s *Suite) TestPreviouslyCompletedMigration(c *gc.C) {
   379  	s.facade.queueStatus(s.makeStatus(coremigration.DONE))
   380  	s.checkWorkerReturns(c, migrationmaster.ErrMigrated)
   381  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   382  		{"facade.Watch", nil},
   383  		{"facade.MigrationStatus", nil},
   384  	})
   385  }
   386  
   387  func (s *Suite) TestWatchFailure(c *gc.C) {
   388  	s.facade.watchErr = errors.New("boom")
   389  	s.checkWorkerErr(c, "watching for migration: boom")
   390  }
   391  
   392  func (s *Suite) TestStatusError(c *gc.C) {
   393  	s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE))
   394  	s.facade.statusErr = errors.New("splat")
   395  
   396  	s.checkWorkerErr(c, "retrieving migration status: splat")
   397  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   398  		{"facade.Watch", nil},
   399  		{"facade.MigrationStatus", nil},
   400  	})
   401  }
   402  
   403  func (s *Suite) TestStatusNotFound(c *gc.C) {
   404  	s.facade.statusErr = &params.Error{Code: params.CodeNotFound}
   405  	s.facade.triggerWatcher()
   406  
   407  	w, err := migrationmaster.New(s.config)
   408  	c.Assert(err, jc.ErrorIsNil)
   409  	defer workertest.CleanKill(c, w)
   410  
   411  	s.waitForStubCalls(c, []string{
   412  		"facade.Watch",
   413  		"facade.MigrationStatus",
   414  		"guard.Unlock",
   415  	})
   416  }
   417  
   418  func (s *Suite) TestUnlockError(c *gc.C) {
   419  	s.facade.statusErr = &params.Error{Code: params.CodeNotFound}
   420  	s.facade.triggerWatcher()
   421  	guard := newStubGuard(s.stub)
   422  	guard.unlockErr = errors.New("pow")
   423  	s.config.Guard = guard
   424  
   425  	s.checkWorkerErr(c, "pow")
   426  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   427  		{"facade.Watch", nil},
   428  		{"facade.MigrationStatus", nil},
   429  		{"guard.Unlock", nil},
   430  	})
   431  }
   432  
   433  func (s *Suite) TestLockdownError(c *gc.C) {
   434  	s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE))
   435  	guard := newStubGuard(s.stub)
   436  	guard.lockdownErr = errors.New("biff")
   437  	s.config.Guard = guard
   438  
   439  	s.checkWorkerErr(c, "biff")
   440  	s.stub.CheckCalls(c, watchStatusLockdownCalls)
   441  }
   442  
   443  func (s *Suite) TestQUIESCEMinionWaitWatchError(c *gc.C) {
   444  	s.checkMinionWaitWatchError(c, coremigration.QUIESCE)
   445  }
   446  
   447  func (s *Suite) TestQUIESCEMinionWaitGetError(c *gc.C) {
   448  	s.checkMinionWaitGetError(c, coremigration.QUIESCE)
   449  }
   450  
   451  func (s *Suite) TestQUIESCEFailedAgent(c *gc.C) {
   452  	s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE))
   453  	s.facade.queueMinionReports(coremigration.MinionReports{
   454  		MigrationId:    "model-uuid:2",
   455  		Phase:          coremigration.QUIESCE,
   456  		FailedMachines: []string{"42"}, // a machine failed
   457  	})
   458  
   459  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   460  
   461  	expectedCalls := joinCalls(
   462  		watchStatusLockdownCalls,
   463  		[]jujutesting.StubCall{
   464  			{"facade.MinionReportTimeout", nil},
   465  		},
   466  		prechecksCalls,
   467  		[]jujutesting.StubCall{
   468  			{"facade.WatchMinionReports", nil},
   469  			{"facade.MinionReports", nil},
   470  		},
   471  		abortCalls,
   472  	)
   473  
   474  	assertExpectedCallArgs(c, s.stub, expectedCalls)
   475  }
   476  
   477  func (s *Suite) TestQUIESCEWrongController(c *gc.C) {
   478  	s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE))
   479  	s.connection.controllerTag = names.NewControllerTag("another-controller")
   480  
   481  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   482  	s.stub.CheckCalls(c, joinCalls(
   483  		watchStatusLockdownCalls,
   484  		[]jujutesting.StubCall{
   485  			{"facade.MinionReportTimeout", nil},
   486  			{"facade.ModelInfo", nil},
   487  			{"facade.Prechecks", []interface{}{}},
   488  			apiOpenControllerCall,
   489  			apiCloseCall,
   490  		},
   491  		abortCalls,
   492  	))
   493  }
   494  
   495  func (s *Suite) TestQUIESCESourceChecksFail(c *gc.C) {
   496  	s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE))
   497  	s.facade.prechecksErr = errors.New("boom")
   498  
   499  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   500  	s.stub.CheckCalls(c, joinCalls(
   501  		watchStatusLockdownCalls,
   502  		[]jujutesting.StubCall{
   503  			{"facade.MinionReportTimeout", nil},
   504  			{"facade.ModelInfo", nil},
   505  			{"facade.Prechecks", []interface{}{}},
   506  		},
   507  		abortCalls,
   508  	))
   509  }
   510  
   511  func (s *Suite) TestQUIESCEModelInfoFail(c *gc.C) {
   512  	s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE))
   513  	s.facade.modelInfoErr = errors.New("boom")
   514  
   515  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   516  	s.stub.CheckCalls(c, joinCalls(
   517  		watchStatusLockdownCalls,
   518  		[]jujutesting.StubCall{
   519  			{"facade.MinionReportTimeout", nil},
   520  			{"facade.ModelInfo", nil},
   521  		},
   522  		abortCalls,
   523  	))
   524  }
   525  
   526  func (s *Suite) TestQUIESCETargetChecksFail(c *gc.C) {
   527  	s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE))
   528  	s.connection.prechecksErr = errors.New("boom")
   529  
   530  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   531  	assertExpectedCallArgs(c, s.stub, joinCalls(
   532  		watchStatusLockdownCalls,
   533  		[]jujutesting.StubCall{
   534  			{"facade.MinionReportTimeout", nil},
   535  		},
   536  		prechecksCalls,
   537  		abortCalls,
   538  	))
   539  }
   540  
   541  func (s *Suite) TestProcessRelationsFailure(c *gc.C) {
   542  	s.facade.queueStatus(s.makeStatus(coremigration.PROCESSRELATIONS))
   543  	s.facade.processRelationsErr = errors.New("boom")
   544  
   545  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   546  	s.stub.CheckCalls(c, joinCalls(
   547  		watchStatusLockdownCalls,
   548  		[]jujutesting.StubCall{
   549  			{"facade.MinionReportTimeout", nil},
   550  			{"facade.ProcessRelations", []interface{}{""}},
   551  		},
   552  		abortCalls,
   553  	))
   554  }
   555  
   556  func (s *Suite) TestExportFailure(c *gc.C) {
   557  	s.facade.queueStatus(s.makeStatus(coremigration.IMPORT))
   558  	s.facade.exportErr = errors.New("boom")
   559  
   560  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   561  	s.stub.CheckCalls(c, joinCalls(
   562  		watchStatusLockdownCalls,
   563  		[]jujutesting.StubCall{
   564  			{"facade.MinionReportTimeout", nil},
   565  			{"facade.Export", nil},
   566  		},
   567  		abortCalls,
   568  	))
   569  }
   570  
   571  func (s *Suite) TestAPIOpenFailure(c *gc.C) {
   572  	s.facade.queueStatus(s.makeStatus(coremigration.IMPORT))
   573  	s.connectionErr = errors.New("boom")
   574  
   575  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   576  	s.stub.CheckCalls(c, joinCalls(
   577  		watchStatusLockdownCalls,
   578  		[]jujutesting.StubCall{
   579  			{"facade.MinionReportTimeout", nil},
   580  			{"facade.Export", nil},
   581  			apiOpenControllerCall,
   582  			{"facade.SetPhase", []interface{}{coremigration.ABORT}},
   583  			apiOpenControllerCall,
   584  			{"facade.SetPhase", []interface{}{coremigration.ABORTDONE}},
   585  		},
   586  	))
   587  }
   588  
   589  func (s *Suite) TestImportFailure(c *gc.C) {
   590  	s.facade.queueStatus(s.makeStatus(coremigration.IMPORT))
   591  	s.connection.importErr = errors.New("boom")
   592  
   593  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   594  	s.stub.CheckCalls(c, joinCalls(
   595  		watchStatusLockdownCalls,
   596  		[]jujutesting.StubCall{
   597  			{"facade.MinionReportTimeout", nil},
   598  			{"facade.Export", nil},
   599  			apiOpenControllerCall,
   600  			importCall,
   601  			apiCloseCall,
   602  		},
   603  		abortCalls,
   604  	))
   605  }
   606  
   607  func (s *Suite) TestVALIDATIONMinionWaitWatchError(c *gc.C) {
   608  	s.checkMinionWaitWatchError(c, coremigration.VALIDATION)
   609  }
   610  
   611  func (s *Suite) TestVALIDATIONMinionWaitGetError(c *gc.C) {
   612  	s.checkMinionWaitGetError(c, coremigration.VALIDATION)
   613  }
   614  
   615  func (s *Suite) TestVALIDATIONFailedAgent(c *gc.C) {
   616  	// Set the last phase change status to be further back
   617  	// in time than the max wait time for minion reports.
   618  	sts := s.makeStatus(coremigration.VALIDATION)
   619  	sts.PhaseChangedTime = time.Now().Add(-20 * time.Minute)
   620  	s.facade.queueStatus(sts)
   621  
   622  	w, err := migrationmaster.New(s.config)
   623  	c.Assert(err, jc.ErrorIsNil)
   624  
   625  	// Queue the reports *after* the watcher is started.
   626  	// The test will only pass if the minion wait timeout
   627  	// is independent of the phase change time.
   628  	s.facade.queueMinionReports(coremigration.MinionReports{
   629  		MigrationId:    "model-uuid:2",
   630  		Phase:          coremigration.VALIDATION,
   631  		FailedMachines: []string{"42"}, // a machine failed
   632  	})
   633  
   634  	err = workertest.CheckKilled(c, w)
   635  	c.Check(errors.Cause(err), gc.Equals, migrationmaster.ErrInactive)
   636  	s.stub.CheckCalls(c, joinCalls(
   637  		watchStatusLockdownCalls,
   638  		[]jujutesting.StubCall{
   639  			{"facade.MinionReportTimeout", nil},
   640  			{"facade.WatchMinionReports", nil},
   641  			{"facade.MinionReports", nil},
   642  		},
   643  		abortCalls,
   644  	))
   645  }
   646  
   647  func (s *Suite) TestVALIDATIONCheckMachinesOneError(c *gc.C) {
   648  	s.facade.queueStatus(s.makeStatus(coremigration.VALIDATION))
   649  	s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION))
   650  
   651  	s.connection.machineErrs = []string{"been so strange"}
   652  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   653  	s.stub.CheckCalls(c, joinCalls(
   654  		watchStatusLockdownCalls,
   655  		[]jujutesting.StubCall{
   656  			{"facade.MinionReportTimeout", nil},
   657  			{"facade.WatchMinionReports", nil},
   658  			{"facade.MinionReports", nil},
   659  			apiOpenControllerCall,
   660  			checkMachinesCall,
   661  			apiCloseCall,
   662  		},
   663  		abortCalls,
   664  	))
   665  	lastMessages := s.facade.statuses[len(s.facade.statuses)-2:]
   666  	c.Assert(lastMessages, gc.DeepEquals, []string{
   667  		"machine sanity check failed, 1 error found",
   668  		"aborted, removing model from target controller: machine sanity check failed, 1 error found",
   669  	})
   670  }
   671  
   672  func (s *Suite) TestVALIDATIONCheckMachinesSeveralErrors(c *gc.C) {
   673  	s.facade.queueStatus(s.makeStatus(coremigration.VALIDATION))
   674  	s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION))
   675  	s.connection.machineErrs = []string{"been so strange", "lit up"}
   676  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   677  	s.stub.CheckCalls(c, joinCalls(
   678  		watchStatusLockdownCalls,
   679  		[]jujutesting.StubCall{
   680  			{"facade.MinionReportTimeout", nil},
   681  			{"facade.WatchMinionReports", nil},
   682  			{"facade.MinionReports", nil},
   683  			apiOpenControllerCall,
   684  			checkMachinesCall,
   685  			apiCloseCall,
   686  		},
   687  		abortCalls,
   688  	))
   689  	lastMessages := s.facade.statuses[len(s.facade.statuses)-2:]
   690  	c.Assert(lastMessages, gc.DeepEquals, []string{
   691  		"machine sanity check failed, 2 errors found",
   692  		"aborted, removing model from target controller: machine sanity check failed, 2 errors found",
   693  	})
   694  }
   695  
   696  func (s *Suite) TestVALIDATIONCheckMachinesOtherError(c *gc.C) {
   697  	s.facade.queueStatus(s.makeStatus(coremigration.VALIDATION))
   698  	s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION))
   699  	s.connection.checkMachineErr = errors.Errorf("something went bang")
   700  
   701  	s.checkWorkerReturns(c, s.connection.checkMachineErr)
   702  	s.stub.CheckCalls(c, joinCalls(
   703  		watchStatusLockdownCalls,
   704  		[]jujutesting.StubCall{
   705  			{"facade.MinionReportTimeout", nil},
   706  			{"facade.WatchMinionReports", nil},
   707  			{"facade.MinionReports", nil},
   708  			apiOpenControllerCall,
   709  			checkMachinesCall,
   710  			apiCloseCall,
   711  		},
   712  	))
   713  }
   714  
   715  func (s *Suite) TestSUCCESSMinionWaitWatchError(c *gc.C) {
   716  	s.checkMinionWaitWatchError(c, coremigration.SUCCESS)
   717  }
   718  
   719  func (s *Suite) TestSUCCESSMinionWaitGetError(c *gc.C) {
   720  	s.checkMinionWaitGetError(c, coremigration.SUCCESS)
   721  }
   722  
   723  func (s *Suite) TestSUCCESSMinionWaitFailedMachine(c *gc.C) {
   724  	// With the SUCCESS phase the master should wait for all reports,
   725  	// continuing even if some minions report failure.
   726  	s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS))
   727  	s.facade.queueMinionReports(coremigration.MinionReports{
   728  		MigrationId:    "model-uuid:2",
   729  		Phase:          coremigration.SUCCESS,
   730  		FailedMachines: []string{"42"},
   731  	})
   732  
   733  	s.checkWorkerReturns(c, migrationmaster.ErrMigrated)
   734  	s.stub.CheckCalls(c, joinCalls(
   735  		watchStatusLockdownCalls,
   736  		[]jujutesting.StubCall{
   737  			{"facade.MinionReportTimeout", nil},
   738  			{"facade.WatchMinionReports", nil},
   739  			{"facade.MinionReports", nil},
   740  			apiOpenControllerCall,
   741  			adoptResourcesCall,
   742  			apiCloseCall,
   743  			{"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}},
   744  			apiOpenControllerCall,
   745  			latestLogTimeCall,
   746  			{"StreamModelLog", []interface{}{time.Time{}}},
   747  			openDestLogStreamCall,
   748  			{"facade.SetPhase", []interface{}{coremigration.REAP}},
   749  			{"facade.Reap", nil},
   750  			{"facade.SetPhase", []interface{}{coremigration.DONE}},
   751  		},
   752  	))
   753  }
   754  
   755  func (s *Suite) TestSUCCESSMinionWaitFailedUnit(c *gc.C) {
   756  	// See note for TestMinionWaitFailedMachine above.
   757  	s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS))
   758  	s.facade.queueMinionReports(coremigration.MinionReports{
   759  		MigrationId:        "model-uuid:2",
   760  		Phase:              coremigration.SUCCESS,
   761  		FailedUnits:        []string{"foo/2"},
   762  		FailedApplications: []string{"bar"},
   763  	})
   764  
   765  	s.checkWorkerReturns(c, migrationmaster.ErrMigrated)
   766  	s.stub.CheckCalls(c, joinCalls(
   767  		watchStatusLockdownCalls,
   768  		[]jujutesting.StubCall{
   769  			{"facade.MinionReportTimeout", nil},
   770  			{"facade.WatchMinionReports", nil},
   771  			{"facade.MinionReports", nil},
   772  			apiOpenControllerCall,
   773  			adoptResourcesCall,
   774  			apiCloseCall,
   775  			{"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}},
   776  			apiOpenControllerCall,
   777  			latestLogTimeCall,
   778  			{"StreamModelLog", []interface{}{time.Time{}}},
   779  			openDestLogStreamCall,
   780  			{"facade.SetPhase", []interface{}{coremigration.REAP}},
   781  			{"facade.Reap", nil},
   782  			{"facade.SetPhase", []interface{}{coremigration.DONE}},
   783  		},
   784  	))
   785  }
   786  
   787  func (s *Suite) TestSUCCESSMinionWaitTimeout(c *gc.C) {
   788  	// The SUCCESS phase is special in that even if some minions fail
   789  	// to report the migration should continue. There's no turning
   790  	// back from SUCCESS.
   791  	s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS))
   792  
   793  	w, err := migrationmaster.New(s.config)
   794  	c.Assert(err, jc.ErrorIsNil)
   795  	defer workertest.DirtyKill(c, w)
   796  
   797  	select {
   798  	case <-s.clock.Alarms():
   799  	case <-time.After(coretesting.LongWait):
   800  		c.Fatal("timed out waiting for clock.After call")
   801  	}
   802  
   803  	// Move time ahead in order to trigger timeout.
   804  	s.clock.Advance(15 * time.Minute)
   805  
   806  	err = workertest.CheckKilled(c, w)
   807  	c.Assert(err, gc.Equals, migrationmaster.ErrMigrated)
   808  
   809  	s.stub.CheckCalls(c, joinCalls(
   810  		watchStatusLockdownCalls,
   811  		[]jujutesting.StubCall{
   812  			{"facade.MinionReportTimeout", nil},
   813  			{"facade.WatchMinionReports", nil},
   814  			apiOpenControllerCall,
   815  			adoptResourcesCall,
   816  			apiCloseCall,
   817  			{"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}},
   818  			apiOpenControllerCall,
   819  			latestLogTimeCall,
   820  			{"StreamModelLog", []interface{}{time.Time{}}},
   821  			openDestLogStreamCall,
   822  			{"facade.SetPhase", []interface{}{coremigration.REAP}},
   823  			{"facade.Reap", nil},
   824  			{"facade.SetPhase", []interface{}{coremigration.DONE}},
   825  		},
   826  	))
   827  }
   828  
   829  func (s *Suite) TestMinionWaitWrongPhase(c *gc.C) {
   830  	s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS))
   831  
   832  	// Have the phase in the minion reports be different from the
   833  	// migration status. This shouldn't happen but the migrationmaster
   834  	// should handle it.
   835  	s.facade.queueMinionReports(makeMinionReports(coremigration.IMPORT))
   836  
   837  	s.checkWorkerErr(c,
   838  		`minion reports phase \(IMPORT\) does not match migration phase \(SUCCESS\)`)
   839  }
   840  
   841  func (s *Suite) TestMinionWaitMigrationIdChanged(c *gc.C) {
   842  	s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS))
   843  
   844  	// Have the migration id in the minion reports be different from
   845  	// the migration status. This shouldn't happen but the
   846  	// migrationmaster should handle it.
   847  	s.facade.queueMinionReports(coremigration.MinionReports{
   848  		MigrationId: "blah",
   849  		Phase:       coremigration.SUCCESS,
   850  	})
   851  
   852  	s.checkWorkerErr(c,
   853  		"unexpected migration id in minion reports, got blah, expected model-uuid:2")
   854  }
   855  
   856  func (s *Suite) assertAPIConnectWithMacaroon(c *gc.C, authUser names.UserTag) {
   857  	// Use ABORT because it involves an API connection to the target
   858  	// and is convenient.
   859  	status := s.makeStatus(coremigration.ABORT)
   860  	status.TargetInfo.AuthTag = authUser
   861  
   862  	// Set up macaroon based auth to the target.
   863  	mac, err := macaroon.New([]byte("secret"), []byte("id"), "location", macaroon.LatestVersion)
   864  	c.Assert(err, jc.ErrorIsNil)
   865  	macs := []macaroon.Slice{{mac}}
   866  	status.TargetInfo.Password = ""
   867  	status.TargetInfo.Macaroons = macs
   868  
   869  	s.facade.queueStatus(status)
   870  
   871  	s.checkWorkerReturns(c, migrationmaster.ErrInactive)
   872  	var apiUser names.Tag
   873  	if authUser.IsLocal() {
   874  		apiUser = authUser
   875  	}
   876  	s.stub.CheckCalls(c, joinCalls(
   877  		watchStatusLockdownCalls,
   878  		[]jujutesting.StubCall{
   879  			{"facade.MinionReportTimeout", nil},
   880  			{
   881  				"apiOpen",
   882  				[]interface{}{
   883  					&api.Info{
   884  						Addrs:     []string{"1.2.3.4:5"},
   885  						CACert:    "cert",
   886  						Tag:       apiUser,
   887  						Macaroons: macs, // <---
   888  					},
   889  					migration.ControllerDialOpts(),
   890  				},
   891  			},
   892  			abortCall,
   893  			apiCloseCall,
   894  			{"facade.SetPhase", []interface{}{coremigration.ABORTDONE}},
   895  		},
   896  	))
   897  }
   898  
   899  func (s *Suite) TestAPIConnectWithMacaroonLocalUser(c *gc.C) {
   900  	s.assertAPIConnectWithMacaroon(c, names.NewUserTag("admin"))
   901  }
   902  
   903  func (s *Suite) TestAPIConnectWithMacaroonExternalUser(c *gc.C) {
   904  	s.assertAPIConnectWithMacaroon(c, names.NewUserTag("fred@external"))
   905  }
   906  
   907  func (s *Suite) TestLogTransferErrorOpeningTargetAPI(c *gc.C) {
   908  	s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER))
   909  	s.connectionErr = errors.New("people of earth")
   910  
   911  	s.checkWorkerReturns(c, s.connectionErr)
   912  	s.stub.CheckCalls(c, joinCalls(
   913  		watchStatusLockdownCalls,
   914  		[]jujutesting.StubCall{
   915  			{"facade.MinionReportTimeout", nil},
   916  			apiOpenControllerCall,
   917  		},
   918  	))
   919  }
   920  
   921  func (s *Suite) TestLogTransferErrorGettingStartTime(c *gc.C) {
   922  	s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER))
   923  	s.connection.latestLogErr = errors.New("tender vittles")
   924  
   925  	s.checkWorkerReturns(c, s.connection.latestLogErr)
   926  	s.stub.CheckCalls(c, joinCalls(
   927  		watchStatusLockdownCalls,
   928  		[]jujutesting.StubCall{
   929  			{"facade.MinionReportTimeout", nil},
   930  			apiOpenControllerCall,
   931  			latestLogTimeCall,
   932  		},
   933  	))
   934  }
   935  
   936  func (s *Suite) TestLogTransferErrorOpeningLogSource(c *gc.C) {
   937  	s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER))
   938  	s.facade.streamErr = errors.New("chicken bones")
   939  
   940  	s.checkWorkerReturns(c, s.facade.streamErr)
   941  	s.stub.CheckCalls(c, joinCalls(
   942  		watchStatusLockdownCalls,
   943  		[]jujutesting.StubCall{
   944  			{"facade.MinionReportTimeout", nil},
   945  			apiOpenControllerCall,
   946  			latestLogTimeCall,
   947  			{"StreamModelLog", []interface{}{time.Time{}}},
   948  		},
   949  	))
   950  }
   951  
   952  func (s *Suite) TestLogTransferErrorOpeningLogDest(c *gc.C) {
   953  	s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER))
   954  	s.connection.streamErr = errors.New("tule lake shuffle")
   955  
   956  	s.checkWorkerReturns(c, s.connection.streamErr)
   957  	s.stub.CheckCalls(c, joinCalls(
   958  		watchStatusLockdownCalls,
   959  		[]jujutesting.StubCall{
   960  			{"facade.MinionReportTimeout", nil},
   961  			apiOpenControllerCall,
   962  			latestLogTimeCall,
   963  			{"StreamModelLog", []interface{}{time.Time{}}},
   964  			openDestLogStreamCall,
   965  		},
   966  	))
   967  }
   968  
   969  func (s *Suite) TestLogTransferErrorWriting(c *gc.C) {
   970  	s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER))
   971  	s.facade.logMessages = func(d chan<- common.LogMessage) {
   972  		safeSend(c, d, common.LogMessage{Message: "the go team"})
   973  	}
   974  	s.connection.logStream.writeErr = errors.New("bottle rocket")
   975  	s.checkWorkerReturns(c, s.connection.logStream.writeErr)
   976  	s.stub.CheckCalls(c, joinCalls(
   977  		watchStatusLockdownCalls,
   978  		[]jujutesting.StubCall{
   979  			{"facade.MinionReportTimeout", nil},
   980  			apiOpenControllerCall,
   981  			latestLogTimeCall,
   982  			{"StreamModelLog", []interface{}{time.Time{}}},
   983  			openDestLogStreamCall,
   984  		},
   985  	))
   986  	c.Assert(s.connection.logStream.closeCount, gc.Equals, 1)
   987  }
   988  
   989  func (s *Suite) TestLogTransferSendsRecords(c *gc.C) {
   990  	t1, err := time.Parse("2006-01-02 15:04", "2016-11-28 16:11")
   991  	c.Assert(err, jc.ErrorIsNil)
   992  	s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER))
   993  	messages := []common.LogMessage{
   994  		{Message: "the go team"},
   995  		{Message: "joan as police woman"},
   996  		{
   997  			Entity:    "the mules",
   998  			Timestamp: t1,
   999  			Severity:  "warning",
  1000  			Module:    "this one",
  1001  			Location:  "nearby",
  1002  			Message:   "ham shank",
  1003  		},
  1004  	}
  1005  	s.facade.logMessages = func(d chan<- common.LogMessage) {
  1006  		for _, message := range messages {
  1007  			safeSend(c, d, message)
  1008  		}
  1009  	}
  1010  
  1011  	s.checkWorkerReturns(c, migrationmaster.ErrMigrated)
  1012  	s.stub.CheckCalls(c, joinCalls(
  1013  		watchStatusLockdownCalls,
  1014  		[]jujutesting.StubCall{
  1015  			{"facade.MinionReportTimeout", nil},
  1016  			apiOpenControllerCall,
  1017  			latestLogTimeCall,
  1018  			{"StreamModelLog", []interface{}{time.Time{}}},
  1019  			openDestLogStreamCall,
  1020  			{"facade.SetPhase", []interface{}{coremigration.REAP}},
  1021  			{"facade.Reap", nil},
  1022  			{"facade.SetPhase", []interface{}{coremigration.DONE}},
  1023  		},
  1024  	))
  1025  	c.Assert(s.connection.logStream.written, gc.DeepEquals, []params.LogRecord{
  1026  		{Message: "the go team"},
  1027  		{Message: "joan as police woman"},
  1028  		{
  1029  			Time:     t1,
  1030  			Module:   "this one",
  1031  			Location: "nearby",
  1032  			Level:    "warning",
  1033  			Message:  "ham shank",
  1034  			Entity:   "the mules",
  1035  		},
  1036  	})
  1037  	c.Assert(s.connection.logStream.closeCount, gc.Equals, 1)
  1038  }
  1039  
  1040  func (s *Suite) TestLogTransferReportsProgress(c *gc.C) {
  1041  	s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER))
  1042  	messages := []common.LogMessage{
  1043  		{Message: "captain beefheart"},
  1044  		{Message: "super furry animals"},
  1045  		{Message: "ezra furman"},
  1046  		{Message: "these new puritans"},
  1047  	}
  1048  	s.facade.logMessages = func(d chan<- common.LogMessage) {
  1049  		for _, message := range messages {
  1050  			safeSend(c, d, message)
  1051  			c.Assert(s.clock.WaitAdvance(20*time.Second, coretesting.LongWait, 1), jc.ErrorIsNil)
  1052  		}
  1053  	}
  1054  
  1055  	var logWriter loggo.TestWriter
  1056  	c.Assert(loggo.RegisterWriter("migrationmaster-tests", &logWriter), jc.ErrorIsNil)
  1057  	defer func() {
  1058  		_, _ = loggo.RemoveWriter("migrationmaster-tests")
  1059  		logWriter.Clear()
  1060  	}()
  1061  
  1062  	s.checkWorkerReturns(c, migrationmaster.ErrMigrated)
  1063  
  1064  	c.Assert(logWriter.Log()[:3], jc.LogMatches, []string{
  1065  		"successful, transferring logs to target controller \\(0 sent\\)",
  1066  		// This is a bit of a punt, but without accepting a range
  1067  		// we sometimes see this test failing on loaded test machines.
  1068  		"successful, transferring logs to target controller \\([23] sent\\)",
  1069  		"successful, transferr(ing|ed) logs to target controller \\([234] sent\\)",
  1070  	})
  1071  }
  1072  
  1073  func (s *Suite) TestLogTransfer_ChecksLatestTime(c *gc.C) {
  1074  	s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER))
  1075  	t := time.Date(2016, 12, 2, 10, 39, 10, 20, time.UTC)
  1076  	s.connection.latestLogTime = t
  1077  
  1078  	s.checkWorkerReturns(c, migrationmaster.ErrMigrated)
  1079  	s.stub.CheckCalls(c, joinCalls(
  1080  		watchStatusLockdownCalls,
  1081  		[]jujutesting.StubCall{
  1082  			{"facade.MinionReportTimeout", nil},
  1083  			apiOpenControllerCall,
  1084  			latestLogTimeCall,
  1085  			{"StreamModelLog", []interface{}{t}},
  1086  			openDestLogStreamCall,
  1087  			{"facade.SetPhase", []interface{}{coremigration.REAP}},
  1088  			{"facade.Reap", nil},
  1089  			{"facade.SetPhase", []interface{}{coremigration.DONE}},
  1090  		},
  1091  	))
  1092  }
  1093  
  1094  func safeSend(c *gc.C, d chan<- common.LogMessage, message common.LogMessage) {
  1095  	select {
  1096  	case d <- message:
  1097  	case <-time.After(coretesting.ShortWait):
  1098  		c.Fatalf("timed out sending log message")
  1099  	}
  1100  }
  1101  
  1102  func (s *Suite) checkWorkerReturns(c *gc.C, expected error) {
  1103  	err := s.runWorker(c)
  1104  	c.Check(errors.Cause(err), gc.Equals, expected)
  1105  }
  1106  
  1107  func (s *Suite) checkWorkerErr(c *gc.C, expected string) {
  1108  	err := s.runWorker(c)
  1109  	c.Check(err, gc.ErrorMatches, expected)
  1110  }
  1111  
  1112  func (s *Suite) runWorker(c *gc.C) error {
  1113  	w, err := migrationmaster.New(s.config)
  1114  	c.Assert(err, jc.ErrorIsNil)
  1115  	defer workertest.DirtyKill(c, w)
  1116  	return workertest.CheckKilled(c, w)
  1117  }
  1118  
  1119  func (s *Suite) waitForStubCalls(c *gc.C, expectedCallNames []string) {
  1120  	var callNames []string
  1121  	for a := coretesting.LongAttempt.Start(); a.Next(); {
  1122  		callNames = stubCallNames(s.stub)
  1123  		if reflect.DeepEqual(callNames, expectedCallNames) {
  1124  			return
  1125  		}
  1126  	}
  1127  	c.Fatalf("failed to see expected calls\nobtained: %v\nexpected: %v",
  1128  		callNames, expectedCallNames)
  1129  }
  1130  
  1131  func (s *Suite) checkMinionWaitWatchError(c *gc.C, phase coremigration.Phase) {
  1132  	s.facade.minionReportsWatchErr = errors.New("boom")
  1133  	s.facade.queueStatus(s.makeStatus(phase))
  1134  
  1135  	s.checkWorkerErr(c, "boom")
  1136  }
  1137  
  1138  func (s *Suite) checkMinionWaitGetError(c *gc.C, phase coremigration.Phase) {
  1139  	s.facade.queueStatus(s.makeStatus(phase))
  1140  
  1141  	s.facade.minionReportsErr = errors.New("boom")
  1142  	s.facade.triggerMinionReports()
  1143  
  1144  	s.checkWorkerErr(c, "boom")
  1145  }
  1146  
  1147  // assertExpectedCallArgs checks that the stub has been called with the
  1148  // expected arguments. It ignores the facade versions map on the Prechecks
  1149  // call because that's an implementation detail of the api facade, not the
  1150  // worker. As long as it's non-zero, otherwise we don't care.
  1151  func assertExpectedCallArgs(c *gc.C, stub *jujutesting.Stub, expectedCalls []jujutesting.StubCall) {
  1152  	stub.CheckCallNames(c, callNames(expectedCalls)...)
  1153  	for i, call := range expectedCalls {
  1154  		stubCall := stub.Calls()[i]
  1155  
  1156  		if call.FuncName == "MigrationTarget.Prechecks" {
  1157  			mc := jc.NewMultiChecker()
  1158  			mc.AddExpr("_.FacadeVersions", gc.Not(gc.HasLen), 0)
  1159  
  1160  			c.Assert(stubCall.Args, mc, call.Args, gc.Commentf("call %s", call.FuncName))
  1161  			continue
  1162  		}
  1163  
  1164  		c.Assert(stubCall, jc.DeepEquals, call, gc.Commentf("call %s", call.FuncName))
  1165  	}
  1166  }
  1167  
  1168  func stubCallNames(stub *jujutesting.Stub) []string {
  1169  	var out []string
  1170  	for _, call := range stub.Calls() {
  1171  		out = append(out, call.FuncName)
  1172  	}
  1173  	return out
  1174  }
  1175  
  1176  func newStubGuard(stub *jujutesting.Stub) *stubGuard {
  1177  	return &stubGuard{stub: stub}
  1178  }
  1179  
  1180  type stubGuard struct {
  1181  	stub        *jujutesting.Stub
  1182  	unlockErr   error
  1183  	lockdownErr error
  1184  }
  1185  
  1186  func (g *stubGuard) Lockdown(fortress.Abort) error {
  1187  	g.stub.AddCall("guard.Lockdown")
  1188  	return g.lockdownErr
  1189  }
  1190  
  1191  func (g *stubGuard) Unlock() error {
  1192  	g.stub.AddCall("guard.Unlock")
  1193  	return g.unlockErr
  1194  }
  1195  
  1196  func newStubMasterFacade(stub *jujutesting.Stub) *stubMasterFacade {
  1197  	return &stubMasterFacade{
  1198  		stub:           stub,
  1199  		watcherChanges: make(chan struct{}, 999),
  1200  
  1201  		// Give minionReportsChanges a larger-than-required buffer to
  1202  		// support waits at a number of phases.
  1203  		minionReportsChanges: make(chan struct{}, 999),
  1204  		minionReportTimeout:  15 * time.Minute,
  1205  	}
  1206  }
  1207  
  1208  type stubMasterFacade struct {
  1209  	migrationmaster.Facade
  1210  
  1211  	stub *jujutesting.Stub
  1212  
  1213  	watcherChanges chan struct{}
  1214  	watchErr       error
  1215  	status         []coremigration.MigrationStatus
  1216  	statusErr      error
  1217  
  1218  	prechecksErr        error
  1219  	modelInfoErr        error
  1220  	exportErr           error
  1221  	processRelationsErr error
  1222  
  1223  	logMessages func(chan<- common.LogMessage)
  1224  	streamErr   error
  1225  
  1226  	minionReportsChanges  chan struct{}
  1227  	minionReportsWatchErr error
  1228  	minionReports         []coremigration.MinionReports
  1229  	minionReportsErr      error
  1230  	minionReportTimeout   time.Duration
  1231  
  1232  	exportedResources []coremigration.SerializedModelResource
  1233  
  1234  	statuses []string
  1235  }
  1236  
  1237  func (f *stubMasterFacade) triggerWatcher() {
  1238  	select {
  1239  	case f.watcherChanges <- struct{}{}:
  1240  	default:
  1241  		panic("migration watcher channel unexpectedly closed")
  1242  	}
  1243  }
  1244  
  1245  func (f *stubMasterFacade) queueStatus(status coremigration.MigrationStatus) {
  1246  	f.status = append(f.status, status)
  1247  	f.triggerWatcher()
  1248  }
  1249  
  1250  func (f *stubMasterFacade) triggerMinionReports() {
  1251  	select {
  1252  	case f.minionReportsChanges <- struct{}{}:
  1253  	default:
  1254  		panic("minion reports watcher channel unexpectedly closed")
  1255  	}
  1256  }
  1257  
  1258  func (f *stubMasterFacade) queueMinionReports(r coremigration.MinionReports) {
  1259  	f.minionReports = append(f.minionReports, r)
  1260  	f.triggerMinionReports()
  1261  }
  1262  
  1263  func (f *stubMasterFacade) Watch() (watcher.NotifyWatcher, error) {
  1264  	f.stub.AddCall("facade.Watch")
  1265  	if f.watchErr != nil {
  1266  		return nil, f.watchErr
  1267  	}
  1268  	return newMockWatcher(f.watcherChanges), nil
  1269  }
  1270  
  1271  func (f *stubMasterFacade) MigrationStatus() (coremigration.MigrationStatus, error) {
  1272  	f.stub.AddCall("facade.MigrationStatus")
  1273  	if f.statusErr != nil {
  1274  		return coremigration.MigrationStatus{}, f.statusErr
  1275  	}
  1276  	if len(f.status) == 0 {
  1277  		panic("no status queued to report")
  1278  	}
  1279  	out := f.status[0]
  1280  	f.status = f.status[1:]
  1281  	return out, nil
  1282  }
  1283  
  1284  func (f *stubMasterFacade) WatchMinionReports() (watcher.NotifyWatcher, error) {
  1285  	f.stub.AddCall("facade.WatchMinionReports")
  1286  	if f.minionReportsWatchErr != nil {
  1287  		return nil, f.minionReportsWatchErr
  1288  	}
  1289  	return newMockWatcher(f.minionReportsChanges), nil
  1290  }
  1291  
  1292  func (f *stubMasterFacade) MinionReports() (coremigration.MinionReports, error) {
  1293  	f.stub.AddCall("facade.MinionReports")
  1294  	if f.minionReportsErr != nil {
  1295  		return coremigration.MinionReports{}, f.minionReportsErr
  1296  	}
  1297  	if len(f.minionReports) == 0 {
  1298  		return coremigration.MinionReports{}, errors.NotFoundf("reports")
  1299  
  1300  	}
  1301  	r := f.minionReports[0]
  1302  	f.minionReports = f.minionReports[1:]
  1303  	return r, nil
  1304  }
  1305  
  1306  func (f *stubMasterFacade) MinionReportTimeout() (time.Duration, error) {
  1307  	f.stub.AddCall("facade.MinionReportTimeout")
  1308  	return f.minionReportTimeout, nil
  1309  }
  1310  
  1311  func (f *stubMasterFacade) Prechecks() error {
  1312  	f.stub.AddCall("facade.Prechecks")
  1313  	return f.prechecksErr
  1314  }
  1315  
  1316  func (f *stubMasterFacade) ModelInfo() (coremigration.ModelInfo, error) {
  1317  	f.stub.AddCall("facade.ModelInfo")
  1318  	if f.modelInfoErr != nil {
  1319  		return coremigration.ModelInfo{}, f.modelInfoErr
  1320  	}
  1321  	return coremigration.ModelInfo{
  1322  		UUID:         modelUUID,
  1323  		Name:         modelName,
  1324  		Owner:        ownerTag,
  1325  		AgentVersion: modelVersion,
  1326  	}, nil
  1327  }
  1328  
  1329  func (f *stubMasterFacade) SourceControllerInfo() (coremigration.SourceControllerInfo, []string, error) {
  1330  	f.stub.AddCall("facade.SourceControllerInfo")
  1331  	return coremigration.SourceControllerInfo{
  1332  		ControllerTag:   sourceControllerTag,
  1333  		ControllerAlias: "mycontroller",
  1334  		Addrs:           []string{"source-addr"},
  1335  		CACert:          "cacert",
  1336  	}, []string{"related-model-uuid"}, nil
  1337  }
  1338  
  1339  func (f *stubMasterFacade) Export() (coremigration.SerializedModel, error) {
  1340  	f.stub.AddCall("facade.Export")
  1341  	if f.exportErr != nil {
  1342  		return coremigration.SerializedModel{}, f.exportErr
  1343  	}
  1344  	return coremigration.SerializedModel{
  1345  		Bytes:  fakeModelBytes,
  1346  		Charms: []string{"charm0", "charm1"},
  1347  		Tools: map[version.Binary]string{
  1348  			version.MustParseBinary("2.1.0-ubuntu-amd64"): "/tools/0",
  1349  		},
  1350  		Resources: f.exportedResources,
  1351  	}, nil
  1352  }
  1353  
  1354  func (f *stubMasterFacade) ProcessRelations(controllerAlias string) error {
  1355  	f.stub.AddCall("facade.ProcessRelations", controllerAlias)
  1356  	if f.processRelationsErr != nil {
  1357  		return f.processRelationsErr
  1358  	}
  1359  	return nil
  1360  }
  1361  
  1362  func (f *stubMasterFacade) SetPhase(phase coremigration.Phase) error {
  1363  	f.stub.AddCall("facade.SetPhase", phase)
  1364  	return nil
  1365  }
  1366  
  1367  func (f *stubMasterFacade) SetStatusMessage(message string) error {
  1368  	f.statuses = append(f.statuses, message)
  1369  	return nil
  1370  }
  1371  
  1372  func (f *stubMasterFacade) Reap() error {
  1373  	f.stub.AddCall("facade.Reap")
  1374  	return nil
  1375  }
  1376  
  1377  func (f *stubMasterFacade) StreamModelLog(_ context.Context, start time.Time) (<-chan common.LogMessage, error) {
  1378  	f.stub.AddCall("StreamModelLog", start)
  1379  	if f.streamErr != nil {
  1380  		return nil, f.streamErr
  1381  	}
  1382  	result := make(chan common.LogMessage)
  1383  	messageFunc := f.logMessages
  1384  	if messageFunc == nil {
  1385  		messageFunc = func(chan<- common.LogMessage) {}
  1386  	}
  1387  	go func() {
  1388  		defer close(result)
  1389  		messageFunc(result)
  1390  	}()
  1391  	return result, nil
  1392  }
  1393  
  1394  func newMockWatcher(changes chan struct{}) *mockWatcher {
  1395  	return &mockWatcher{
  1396  		Worker:  workertest.NewErrorWorker(nil),
  1397  		changes: changes,
  1398  	}
  1399  }
  1400  
  1401  type mockWatcher struct {
  1402  	worker.Worker
  1403  	changes chan struct{}
  1404  }
  1405  
  1406  func (w *mockWatcher) Changes() watcher.NotifyChannel {
  1407  	return w.changes
  1408  }
  1409  
  1410  type stubConnection struct {
  1411  	c *gc.C
  1412  	api.Connection
  1413  	stub                *jujutesting.Stub
  1414  	prechecksErr        error
  1415  	importErr           error
  1416  	processRelationsErr error
  1417  	controllerTag       names.ControllerTag
  1418  
  1419  	streamErr error
  1420  	logStream *mockStream
  1421  
  1422  	latestLogErr  error
  1423  	latestLogTime time.Time
  1424  
  1425  	machineErrs     []string
  1426  	checkMachineErr error
  1427  
  1428  	facadeVersion int
  1429  
  1430  	controllerVersion params.ControllerVersionResults
  1431  }
  1432  
  1433  func (c *stubConnection) BestFacadeVersion(string) int {
  1434  	return c.facadeVersion
  1435  }
  1436  
  1437  func (c *stubConnection) APICall(objType string, _ int, _, request string, args, response interface{}) error {
  1438  	c.stub.AddCall(objType+"."+request, args)
  1439  
  1440  	if objType == "MigrationTarget" {
  1441  		switch request {
  1442  		case "Prechecks":
  1443  			return c.prechecksErr
  1444  		case "Import":
  1445  			return c.importErr
  1446  		case "ProcessRelations":
  1447  			return c.processRelationsErr
  1448  		case "Activate", "AdoptResources":
  1449  			return nil
  1450  		case "LatestLogTime":
  1451  			responseTime := response.(*time.Time)
  1452  			// This is needed because even if a zero time comes back
  1453  			// from the API it will have a timezone attached.
  1454  			*responseTime = c.latestLogTime.In(time.UTC)
  1455  			return c.latestLogErr
  1456  		case "CheckMachines":
  1457  			results := response.(*params.ErrorResults)
  1458  			for _, msg := range c.machineErrs {
  1459  				results.Results = append(results.Results, params.ErrorResult{
  1460  					Error: apiservererrors.ServerError(errors.Errorf(msg)),
  1461  				})
  1462  			}
  1463  			return c.checkMachineErr
  1464  		}
  1465  	} else if objType == "Controller" {
  1466  		switch request {
  1467  		case "ControllerVersion":
  1468  			c.c.Logf("objType %q request %q, args %#v", objType, request, args)
  1469  			controllerVersion := response.(*params.ControllerVersionResults)
  1470  			*controllerVersion = c.controllerVersion
  1471  			return nil
  1472  		}
  1473  	}
  1474  	return errors.New("unexpected API call")
  1475  }
  1476  
  1477  func (c *stubConnection) Client() *apiclient.Client {
  1478  	// This is kinda crappy but the *Client doesn't have to be
  1479  	// functional...
  1480  	return new(apiclient.Client)
  1481  }
  1482  
  1483  func (c *stubConnection) Close() error {
  1484  	c.stub.AddCall("Connection.Close")
  1485  	return nil
  1486  }
  1487  
  1488  func (c *stubConnection) ControllerTag() names.ControllerTag {
  1489  	return c.controllerTag
  1490  }
  1491  
  1492  func (c *stubConnection) ConnectControllerStream(path string, attrs url.Values, headers http.Header) (base.Stream, error) {
  1493  	c.stub.AddCall("ConnectControllerStream", path, attrs, headers)
  1494  	if c.streamErr != nil {
  1495  		return nil, c.streamErr
  1496  	}
  1497  	return c.logStream, nil
  1498  }
  1499  
  1500  func makeStubUploadBinaries(stub *jujutesting.Stub) func(migration.UploadBinariesConfig) error {
  1501  	return func(config migration.UploadBinariesConfig) error {
  1502  		stub.AddCall(
  1503  			"UploadBinaries",
  1504  			config.Charms,
  1505  			config.CharmDownloader,
  1506  			config.Tools,
  1507  			config.ToolsDownloader,
  1508  			config.Resources,
  1509  			config.ResourceDownloader,
  1510  		)
  1511  		return nil
  1512  	}
  1513  }
  1514  
  1515  // nullUploadBinaries is a UploadBinaries variant which is intended to
  1516  // not get called.
  1517  func nullUploadBinaries(migration.UploadBinariesConfig) error {
  1518  	panic("should not get called")
  1519  }
  1520  
  1521  var fakeCharmDownloader = struct{ migration.CharmDownloader }{}
  1522  
  1523  var fakeToolsDownloader = struct{ migration.ToolsDownloader }{}
  1524  
  1525  func joinCalls(allCalls ...[]jujutesting.StubCall) (out []jujutesting.StubCall) {
  1526  	for _, calls := range allCalls {
  1527  		out = append(out, calls...)
  1528  	}
  1529  	return
  1530  }
  1531  
  1532  func callNames(calls []jujutesting.StubCall) []string {
  1533  	var out []string
  1534  	for _, call := range calls {
  1535  		out = append(out, call.FuncName)
  1536  	}
  1537  	return out
  1538  }
  1539  
  1540  func makeMinionReports(p coremigration.Phase) coremigration.MinionReports {
  1541  	return coremigration.MinionReports{
  1542  		MigrationId:  "model-uuid:2",
  1543  		Phase:        p,
  1544  		SuccessCount: 5,
  1545  		UnknownCount: 0,
  1546  	}
  1547  }
  1548  
  1549  type mockStream struct {
  1550  	base.Stream
  1551  	c          *gc.C
  1552  	written    []params.LogRecord
  1553  	writeErr   error
  1554  	closeCount int
  1555  }
  1556  
  1557  func (s *mockStream) WriteJSON(v interface{}) error {
  1558  	if s.writeErr != nil {
  1559  		return s.writeErr
  1560  	}
  1561  	rec, ok := v.(params.LogRecord)
  1562  	if !ok {
  1563  		s.c.Errorf("unexpected value written to stream: %v", v)
  1564  		return nil
  1565  	}
  1566  	s.written = append(s.written, rec)
  1567  	return nil
  1568  }
  1569  
  1570  func (s *mockStream) Close() error {
  1571  	s.closeCount++
  1572  	return nil
  1573  }