github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names"
    11  	jujutesting "github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/api"
    16  	masterapi "github.com/juju/juju/api/migrationmaster"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/core/migration"
    19  	coretesting "github.com/juju/juju/testing"
    20  	"github.com/juju/juju/watcher"
    21  	"github.com/juju/juju/worker"
    22  	"github.com/juju/juju/worker/dependency"
    23  	"github.com/juju/juju/worker/fortress"
    24  	"github.com/juju/juju/worker/migrationmaster"
    25  	"github.com/juju/juju/worker/workertest"
    26  )
    27  
    28  type Suite struct {
    29  	coretesting.BaseSuite
    30  	stub          *jujutesting.Stub
    31  	connection    *stubConnection
    32  	connectionErr error
    33  }
    34  
    35  var _ = gc.Suite(&Suite{})
    36  
    37  var (
    38  	fakeSerializedModel = []byte("model")
    39  	modelTagString      = names.NewModelTag("model-uuid").String()
    40  
    41  	// Define stub calls that commonly appear in tests here to allow reuse.
    42  	apiOpenCall = jujutesting.StubCall{
    43  		"apiOpen",
    44  		[]interface{}{
    45  			&api.Info{
    46  				Addrs:    []string{"1.2.3.4:5"},
    47  				CACert:   "cert",
    48  				Tag:      names.NewUserTag("admin"),
    49  				Password: "secret",
    50  			},
    51  			api.DialOpts{},
    52  		},
    53  	}
    54  	importCall = jujutesting.StubCall{
    55  		"APICall:MigrationTarget.Import",
    56  		[]interface{}{
    57  			params.SerializedModel{Bytes: fakeSerializedModel},
    58  		},
    59  	}
    60  	activateCall = jujutesting.StubCall{
    61  		"APICall:MigrationTarget.Activate",
    62  		[]interface{}{
    63  			params.ModelArgs{ModelTag: modelTagString},
    64  		},
    65  	}
    66  	connCloseCall = jujutesting.StubCall{"Connection.Close", nil}
    67  	abortCall     = jujutesting.StubCall{
    68  		"APICall:MigrationTarget.Abort",
    69  		[]interface{}{
    70  			params.ModelArgs{ModelTag: modelTagString},
    71  		},
    72  	}
    73  )
    74  
    75  func (s *Suite) SetUpTest(c *gc.C) {
    76  	s.BaseSuite.SetUpTest(c)
    77  
    78  	s.stub = new(jujutesting.Stub)
    79  	s.connection = &stubConnection{stub: s.stub}
    80  	s.connectionErr = nil
    81  	s.PatchValue(migrationmaster.ApiOpen, s.apiOpen)
    82  	s.PatchValue(migrationmaster.TempSuccessSleep, time.Millisecond)
    83  }
    84  
    85  func (s *Suite) apiOpen(info *api.Info, dialOpts api.DialOpts) (api.Connection, error) {
    86  	s.stub.AddCall("apiOpen", info, dialOpts)
    87  	if s.connectionErr != nil {
    88  		return nil, s.connectionErr
    89  	}
    90  	return s.connection, nil
    91  }
    92  
    93  func (s *Suite) triggerMigration(masterClient *stubMasterClient) {
    94  	masterClient.watcherChanges <- struct{}{}
    95  }
    96  
    97  func (s *Suite) TestSuccessfulMigration(c *gc.C) {
    98  	masterClient := newStubMasterClient(s.stub)
    99  	worker, err := migrationmaster.New(migrationmaster.Config{
   100  		Facade: masterClient,
   101  		Guard:  newStubGuard(s.stub),
   102  	})
   103  	c.Assert(err, jc.ErrorIsNil)
   104  	s.triggerMigration(masterClient)
   105  
   106  	err = workertest.CheckKilled(c, worker)
   107  	c.Assert(errors.Cause(err), gc.Equals, dependency.ErrUninstall)
   108  
   109  	// Observe that the migration was seen, the model exported, an API
   110  	// connection to the target controller was made, the model was
   111  	// imported and then the migration completed.
   112  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   113  		{"masterClient.Watch", nil},
   114  		{"masterClient.GetMigrationStatus", nil},
   115  		{"guard.Lockdown", nil},
   116  		{"masterClient.SetPhase", []interface{}{migration.READONLY}},
   117  		{"masterClient.SetPhase", []interface{}{migration.PRECHECK}},
   118  		{"masterClient.SetPhase", []interface{}{migration.IMPORT}},
   119  		{"masterClient.Export", nil},
   120  		apiOpenCall,
   121  		importCall,
   122  		connCloseCall,
   123  		{"masterClient.SetPhase", []interface{}{migration.VALIDATION}},
   124  		apiOpenCall,
   125  		activateCall,
   126  		connCloseCall,
   127  		{"masterClient.SetPhase", []interface{}{migration.SUCCESS}},
   128  		{"masterClient.SetPhase", []interface{}{migration.LOGTRANSFER}},
   129  		{"masterClient.SetPhase", []interface{}{migration.REAP}},
   130  		{"masterClient.SetPhase", []interface{}{migration.DONE}},
   131  	})
   132  }
   133  
   134  func (s *Suite) TestMigrationResume(c *gc.C) {
   135  	// Test that a partially complete migration can be resumed.
   136  
   137  	masterClient := newStubMasterClient(s.stub)
   138  	worker, err := migrationmaster.New(migrationmaster.Config{
   139  		Facade: masterClient,
   140  		Guard:  newStubGuard(s.stub),
   141  	})
   142  	c.Assert(err, jc.ErrorIsNil)
   143  	masterClient.status.Phase = migration.SUCCESS
   144  	s.triggerMigration(masterClient)
   145  
   146  	err = workertest.CheckKilled(c, worker)
   147  	c.Assert(errors.Cause(err), gc.Equals, dependency.ErrUninstall)
   148  
   149  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   150  		{"masterClient.Watch", nil},
   151  		{"masterClient.GetMigrationStatus", nil},
   152  		{"guard.Lockdown", nil},
   153  		{"masterClient.SetPhase", []interface{}{migration.LOGTRANSFER}},
   154  		{"masterClient.SetPhase", []interface{}{migration.REAP}},
   155  		{"masterClient.SetPhase", []interface{}{migration.DONE}},
   156  	})
   157  }
   158  
   159  func (s *Suite) TestPreviouslyAbortedMigration(c *gc.C) {
   160  	masterClient := newStubMasterClient(s.stub)
   161  	masterClient.status.Phase = migration.ABORTDONE
   162  	s.triggerMigration(masterClient)
   163  	worker, err := migrationmaster.New(migrationmaster.Config{
   164  		Facade: masterClient,
   165  		Guard:  newStubGuard(s.stub),
   166  	})
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	workertest.CheckAlive(c, worker)
   169  	workertest.CleanKill(c, worker)
   170  
   171  	// No reliable way to test stub calls in this case unfortunately.
   172  }
   173  
   174  func (s *Suite) TestPreviouslyCompletedMigration(c *gc.C) {
   175  	masterClient := newStubMasterClient(s.stub)
   176  	masterClient.status.Phase = migration.DONE
   177  	s.triggerMigration(masterClient)
   178  	worker, err := migrationmaster.New(migrationmaster.Config{
   179  		Facade: masterClient,
   180  		Guard:  newStubGuard(s.stub),
   181  	})
   182  	c.Assert(err, jc.ErrorIsNil)
   183  
   184  	err = workertest.CheckKilled(c, worker)
   185  	c.Assert(errors.Cause(err), gc.Equals, dependency.ErrUninstall)
   186  
   187  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   188  		{"masterClient.Watch", nil},
   189  		{"masterClient.GetMigrationStatus", nil},
   190  	})
   191  }
   192  
   193  func (s *Suite) TestWatchFailure(c *gc.C) {
   194  	masterClient := newStubMasterClient(s.stub)
   195  	masterClient.watchErr = errors.New("boom")
   196  	worker, err := migrationmaster.New(migrationmaster.Config{
   197  		Facade: masterClient,
   198  		Guard:  newStubGuard(s.stub),
   199  	})
   200  	c.Assert(err, jc.ErrorIsNil)
   201  	err = workertest.CheckKilled(c, worker)
   202  	c.Assert(err, gc.ErrorMatches, "watching for migration: boom")
   203  }
   204  
   205  func (s *Suite) TestStatusError(c *gc.C) {
   206  	masterClient := newStubMasterClient(s.stub)
   207  	masterClient.statusErr = errors.New("splat")
   208  	worker, err := migrationmaster.New(migrationmaster.Config{
   209  		Facade: masterClient,
   210  		Guard:  newStubGuard(s.stub),
   211  	})
   212  	c.Assert(err, jc.ErrorIsNil)
   213  	s.triggerMigration(masterClient)
   214  
   215  	err = workertest.CheckKilled(c, worker)
   216  
   217  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   218  		{"masterClient.Watch", nil},
   219  		{"masterClient.GetMigrationStatus", nil},
   220  	})
   221  }
   222  
   223  func (s *Suite) TestStatusNotFound(c *gc.C) {
   224  	masterClient := newStubMasterClient(s.stub)
   225  	masterClient.statusErr = &params.Error{Code: params.CodeNotFound}
   226  	worker, err := migrationmaster.New(migrationmaster.Config{
   227  		Facade: masterClient,
   228  		Guard:  newStubGuard(s.stub),
   229  	})
   230  	c.Assert(err, jc.ErrorIsNil)
   231  	s.triggerMigration(masterClient)
   232  
   233  	workertest.CheckAlive(c, worker)
   234  	workertest.CleanKill(c, worker)
   235  
   236  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   237  		{"masterClient.Watch", nil},
   238  		{"masterClient.GetMigrationStatus", nil},
   239  		{"guard.Unlock", nil},
   240  	})
   241  }
   242  
   243  func (s *Suite) TestUnlockError(c *gc.C) {
   244  	masterClient := newStubMasterClient(s.stub)
   245  	masterClient.statusErr = &params.Error{Code: params.CodeNotFound}
   246  	guard := newStubGuard(s.stub)
   247  	guard.unlockErr = errors.New("pow")
   248  	worker, err := migrationmaster.New(migrationmaster.Config{
   249  		Facade: masterClient,
   250  		Guard:  guard,
   251  	})
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	s.triggerMigration(masterClient)
   254  
   255  	err = workertest.CheckKilled(c, worker)
   256  	c.Check(err, gc.ErrorMatches, "pow")
   257  
   258  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   259  		{"masterClient.Watch", nil},
   260  		{"masterClient.GetMigrationStatus", nil},
   261  		{"guard.Unlock", nil},
   262  	})
   263  }
   264  
   265  func (s *Suite) TestLockdownError(c *gc.C) {
   266  	masterClient := newStubMasterClient(s.stub)
   267  	guard := newStubGuard(s.stub)
   268  	guard.lockdownErr = errors.New("biff")
   269  	worker, err := migrationmaster.New(migrationmaster.Config{
   270  		Facade: masterClient,
   271  		Guard:  guard,
   272  	})
   273  	c.Assert(err, jc.ErrorIsNil)
   274  	s.triggerMigration(masterClient)
   275  
   276  	err = workertest.CheckKilled(c, worker)
   277  	c.Check(err, gc.ErrorMatches, "biff")
   278  
   279  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   280  		{"masterClient.Watch", nil},
   281  		{"masterClient.GetMigrationStatus", nil},
   282  		{"guard.Lockdown", nil},
   283  	})
   284  }
   285  
   286  func (s *Suite) TestExportFailure(c *gc.C) {
   287  	masterClient := newStubMasterClient(s.stub)
   288  	masterClient.exportErr = errors.New("boom")
   289  	worker, err := migrationmaster.New(migrationmaster.Config{
   290  		Facade: masterClient,
   291  		Guard:  newStubGuard(s.stub),
   292  	})
   293  	c.Assert(err, jc.ErrorIsNil)
   294  	s.triggerMigration(masterClient)
   295  
   296  	err = workertest.CheckKilled(c, worker)
   297  	c.Assert(err, gc.Equals, migrationmaster.ErrDoneForNow)
   298  
   299  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   300  		{"masterClient.Watch", nil},
   301  		{"masterClient.GetMigrationStatus", nil},
   302  		{"guard.Lockdown", nil},
   303  		{"masterClient.SetPhase", []interface{}{migration.READONLY}},
   304  		{"masterClient.SetPhase", []interface{}{migration.PRECHECK}},
   305  		{"masterClient.SetPhase", []interface{}{migration.IMPORT}},
   306  		{"masterClient.Export", nil},
   307  		{"masterClient.SetPhase", []interface{}{migration.ABORT}},
   308  		apiOpenCall,
   309  		abortCall,
   310  		connCloseCall,
   311  		{"masterClient.SetPhase", []interface{}{migration.ABORTDONE}},
   312  	})
   313  }
   314  
   315  func (s *Suite) TestAPIOpenFailure(c *gc.C) {
   316  	masterClient := newStubMasterClient(s.stub)
   317  	worker, err := migrationmaster.New(migrationmaster.Config{
   318  		Facade: masterClient,
   319  		Guard:  newStubGuard(s.stub),
   320  	})
   321  	c.Assert(err, jc.ErrorIsNil)
   322  	s.connectionErr = errors.New("boom")
   323  	s.triggerMigration(masterClient)
   324  
   325  	err = workertest.CheckKilled(c, worker)
   326  	c.Assert(err, gc.Equals, migrationmaster.ErrDoneForNow)
   327  
   328  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   329  		{"masterClient.Watch", nil},
   330  		{"masterClient.GetMigrationStatus", nil},
   331  		{"guard.Lockdown", nil},
   332  		{"masterClient.SetPhase", []interface{}{migration.READONLY}},
   333  		{"masterClient.SetPhase", []interface{}{migration.PRECHECK}},
   334  		{"masterClient.SetPhase", []interface{}{migration.IMPORT}},
   335  		{"masterClient.Export", nil},
   336  		apiOpenCall,
   337  		{"masterClient.SetPhase", []interface{}{migration.ABORT}},
   338  		apiOpenCall,
   339  		{"masterClient.SetPhase", []interface{}{migration.ABORTDONE}},
   340  	})
   341  }
   342  
   343  func (s *Suite) TestImportFailure(c *gc.C) {
   344  	masterClient := newStubMasterClient(s.stub)
   345  	worker, err := migrationmaster.New(migrationmaster.Config{
   346  		Facade: masterClient,
   347  		Guard:  newStubGuard(s.stub),
   348  	})
   349  	c.Assert(err, jc.ErrorIsNil)
   350  	s.connection.importErr = errors.New("boom")
   351  	s.triggerMigration(masterClient)
   352  
   353  	err = workertest.CheckKilled(c, worker)
   354  	c.Assert(err, gc.Equals, migrationmaster.ErrDoneForNow)
   355  
   356  	s.stub.CheckCalls(c, []jujutesting.StubCall{
   357  		{"masterClient.Watch", nil},
   358  		{"masterClient.GetMigrationStatus", nil},
   359  		{"guard.Lockdown", nil},
   360  		{"masterClient.SetPhase", []interface{}{migration.READONLY}},
   361  		{"masterClient.SetPhase", []interface{}{migration.PRECHECK}},
   362  		{"masterClient.SetPhase", []interface{}{migration.IMPORT}},
   363  		{"masterClient.Export", nil},
   364  		apiOpenCall,
   365  		importCall,
   366  		connCloseCall,
   367  		{"masterClient.SetPhase", []interface{}{migration.ABORT}},
   368  		apiOpenCall,
   369  		abortCall,
   370  		connCloseCall,
   371  		{"masterClient.SetPhase", []interface{}{migration.ABORTDONE}},
   372  	})
   373  }
   374  
   375  func newStubGuard(stub *jujutesting.Stub) *stubGuard {
   376  	return &stubGuard{stub: stub}
   377  }
   378  
   379  type stubGuard struct {
   380  	stub        *jujutesting.Stub
   381  	unlockErr   error
   382  	lockdownErr error
   383  }
   384  
   385  func (g *stubGuard) Lockdown(fortress.Abort) error {
   386  	g.stub.AddCall("guard.Lockdown")
   387  	return g.lockdownErr
   388  }
   389  
   390  func (g *stubGuard) Unlock() error {
   391  	g.stub.AddCall("guard.Unlock")
   392  	return g.unlockErr
   393  }
   394  
   395  func newStubMasterClient(stub *jujutesting.Stub) *stubMasterClient {
   396  	return &stubMasterClient{
   397  		stub:           stub,
   398  		watcherChanges: make(chan struct{}, 1),
   399  		status: masterapi.MigrationStatus{
   400  			ModelUUID: "model-uuid",
   401  			Attempt:   2,
   402  			Phase:     migration.QUIESCE,
   403  			TargetInfo: migration.TargetInfo{
   404  				ControllerTag: names.NewModelTag("controller-uuid"),
   405  				Addrs:         []string{"1.2.3.4:5"},
   406  				CACert:        "cert",
   407  				AuthTag:       names.NewUserTag("admin"),
   408  				Password:      "secret",
   409  			},
   410  		},
   411  	}
   412  }
   413  
   414  type stubMasterClient struct {
   415  	masterapi.Client
   416  	stub           *jujutesting.Stub
   417  	watcherChanges chan struct{}
   418  	watchErr       error
   419  	status         masterapi.MigrationStatus
   420  	statusErr      error
   421  	exportErr      error
   422  }
   423  
   424  func (c *stubMasterClient) Watch() (watcher.NotifyWatcher, error) {
   425  	c.stub.AddCall("masterClient.Watch")
   426  	if c.watchErr != nil {
   427  		return nil, c.watchErr
   428  	}
   429  
   430  	return newMockWatcher(c.watcherChanges), nil
   431  }
   432  
   433  func (c *stubMasterClient) GetMigrationStatus() (masterapi.MigrationStatus, error) {
   434  	c.stub.AddCall("masterClient.GetMigrationStatus")
   435  	if c.statusErr != nil {
   436  		return masterapi.MigrationStatus{}, c.statusErr
   437  	}
   438  	return c.status, nil
   439  }
   440  
   441  func (c *stubMasterClient) Export() ([]byte, error) {
   442  	c.stub.AddCall("masterClient.Export")
   443  	if c.exportErr != nil {
   444  		return nil, c.exportErr
   445  	}
   446  	return fakeSerializedModel, nil
   447  }
   448  
   449  func (c *stubMasterClient) SetPhase(phase migration.Phase) error {
   450  	c.stub.AddCall("masterClient.SetPhase", phase)
   451  	return nil
   452  }
   453  
   454  func newMockWatcher(changes chan struct{}) *mockWatcher {
   455  	return &mockWatcher{
   456  		Worker:  workertest.NewErrorWorker(nil),
   457  		changes: changes,
   458  	}
   459  }
   460  
   461  type mockWatcher struct {
   462  	worker.Worker
   463  	changes chan struct{}
   464  }
   465  
   466  func (w *mockWatcher) Changes() watcher.NotifyChannel {
   467  	return w.changes
   468  }
   469  
   470  type stubConnection struct {
   471  	api.Connection
   472  	stub      *jujutesting.Stub
   473  	importErr error
   474  }
   475  
   476  func (c *stubConnection) BestFacadeVersion(string) int {
   477  	return 1
   478  }
   479  
   480  func (c *stubConnection) APICall(objType string, version int, id, request string, params, response interface{}) error {
   481  	c.stub.AddCall("APICall:"+objType+"."+request, params)
   482  
   483  	if objType == "MigrationTarget" {
   484  		switch request {
   485  		case "Import":
   486  			return c.importErr
   487  		case "Activate":
   488  			return nil
   489  		}
   490  	}
   491  	return errors.New("unexpected API call")
   492  }
   493  
   494  func (c *stubConnection) Close() error {
   495  	c.stub.AddCall("Connection.Close")
   496  	return nil
   497  }