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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package migrationminion_test
     5  
     6  import (
     7  	"reflect"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/juju/clock/testclock"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/names/v5"
    15  	jujutesting "github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/worker/v3"
    18  	"github.com/juju/worker/v3/workertest"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/agent"
    22  	"github.com/juju/juju/api"
    23  	"github.com/juju/juju/api/base"
    24  	apiservererrors "github.com/juju/juju/apiserver/errors"
    25  	"github.com/juju/juju/core/migration"
    26  	"github.com/juju/juju/core/network"
    27  	"github.com/juju/juju/core/watcher"
    28  	coretesting "github.com/juju/juju/testing"
    29  	"github.com/juju/juju/worker/fortress"
    30  	"github.com/juju/juju/worker/migrationminion"
    31  )
    32  
    33  var (
    34  	modelTag      = names.NewModelTag("model-uuid")
    35  	addrs         = []string{"1.1.1.1:1111", "2.2.2.2:2222"}
    36  	agentTag      = names.NewMachineTag("42")
    37  	agentPassword = "sekret"
    38  	caCert        = "cert"
    39  )
    40  
    41  type Suite struct {
    42  	coretesting.BaseSuite
    43  	config migrationminion.Config
    44  	stub   *jujutesting.Stub
    45  	client *stubMinionClient
    46  	guard  *stubGuard
    47  	agent  *stubAgent
    48  	clock  *testclock.Clock
    49  }
    50  
    51  var _ = gc.Suite(&Suite{})
    52  
    53  func (s *Suite) SetUpTest(c *gc.C) {
    54  	s.BaseSuite.SetUpTest(c)
    55  	s.stub = new(jujutesting.Stub)
    56  	s.client = newStubMinionClient(s.stub)
    57  	s.guard = newStubGuard(s.stub)
    58  	s.agent = newStubAgent()
    59  	s.clock = testclock.NewClock(time.Now())
    60  	s.config = migrationminion.Config{
    61  		Facade:  s.client,
    62  		Guard:   s.guard,
    63  		Agent:   s.agent,
    64  		Clock:   s.clock,
    65  		APIOpen: s.apiOpen,
    66  		ValidateMigration: func(base.APICaller) error {
    67  			s.stub.AddCall("ValidateMigration")
    68  			return nil
    69  		},
    70  		Logger: loggo.GetLogger("test"),
    71  	}
    72  }
    73  
    74  func (s *Suite) apiOpen(info *api.Info, _ api.DialOpts) (api.Connection, error) {
    75  	s.stub.AddCall("API open", info)
    76  	return &stubConnection{stub: s.stub}, nil
    77  }
    78  
    79  func (s *Suite) TestStartAndStop(c *gc.C) {
    80  	w, err := migrationminion.New(s.config)
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	workertest.CleanKill(c, w)
    83  	s.stub.CheckCallNames(c, "Watch")
    84  }
    85  
    86  func (s *Suite) TestWatchFailure(c *gc.C) {
    87  	s.client.watchErr = errors.New("boom")
    88  	w, err := migrationminion.New(s.config)
    89  	c.Assert(err, jc.ErrorIsNil)
    90  	err = workertest.CheckKilled(c, w)
    91  	c.Check(err, gc.ErrorMatches, "setting up watcher: boom")
    92  }
    93  
    94  func (s *Suite) TestClosedWatcherChannel(c *gc.C) {
    95  	close(s.client.watcher.changes)
    96  	w, err := migrationminion.New(s.config)
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	err = workertest.CheckKilled(c, w)
    99  	c.Check(err, gc.ErrorMatches, "watcher channel closed")
   100  }
   101  
   102  func (s *Suite) TestUnlockError(c *gc.C) {
   103  	s.client.watcher.changes <- watcher.MigrationStatus{
   104  		Phase: migration.NONE,
   105  	}
   106  	s.guard.unlockErr = errors.New("squish")
   107  	w, err := migrationminion.New(s.config)
   108  	c.Assert(err, jc.ErrorIsNil)
   109  
   110  	err = workertest.CheckKilled(c, w)
   111  	c.Check(err, gc.ErrorMatches, "squish")
   112  	s.stub.CheckCallNames(c, "Watch", "Unlock")
   113  }
   114  
   115  func (s *Suite) TestLockdownError(c *gc.C) {
   116  	s.client.watcher.changes <- watcher.MigrationStatus{
   117  		Phase: migration.QUIESCE,
   118  	}
   119  	s.guard.lockdownErr = errors.New("squash")
   120  	w, err := migrationminion.New(s.config)
   121  	c.Assert(err, jc.ErrorIsNil)
   122  
   123  	err = workertest.CheckKilled(c, w)
   124  	c.Check(err, gc.ErrorMatches, "squash")
   125  	s.stub.CheckCallNames(c, "Watch", "Lockdown")
   126  }
   127  
   128  func (s *Suite) TestNonRunningPhases(c *gc.C) {
   129  	phases := []migration.Phase{
   130  		migration.UNKNOWN,
   131  		migration.NONE,
   132  		migration.LOGTRANSFER,
   133  		migration.REAP,
   134  		migration.REAPFAILED,
   135  		migration.DONE,
   136  		migration.ABORT,
   137  		migration.ABORTDONE,
   138  	}
   139  	for _, phase := range phases {
   140  		s.checkNonRunningPhase(c, phase)
   141  	}
   142  }
   143  
   144  func (s *Suite) checkNonRunningPhase(c *gc.C, phase migration.Phase) {
   145  	c.Logf("checking %s", phase)
   146  	s.stub.ResetCalls()
   147  	s.client.watcher.changes <- watcher.MigrationStatus{Phase: phase}
   148  	w, err := migrationminion.New(s.config)
   149  	c.Assert(err, jc.ErrorIsNil)
   150  	workertest.CheckAlive(c, w)
   151  	workertest.CleanKill(c, w)
   152  	s.stub.CheckCallNames(c, "Watch", "Unlock")
   153  }
   154  
   155  func (s *Suite) TestQUIESCE(c *gc.C) {
   156  	s.client.watcher.changes <- watcher.MigrationStatus{
   157  		MigrationId: "id",
   158  		Phase:       migration.QUIESCE,
   159  	}
   160  	w, err := migrationminion.New(s.config)
   161  	c.Assert(err, jc.ErrorIsNil)
   162  	defer workertest.CleanKill(c, w)
   163  
   164  	s.waitForStubCalls(c, []string{
   165  		"Watch",
   166  		"Lockdown",
   167  		"Report",
   168  	})
   169  	s.stub.CheckCall(c, 2, "Report", "id", migration.QUIESCE, true)
   170  }
   171  
   172  func (s *Suite) TestVALIDATION(c *gc.C) {
   173  	s.client.watcher.changes <- watcher.MigrationStatus{
   174  		MigrationId:    "id",
   175  		Phase:          migration.VALIDATION,
   176  		TargetAPIAddrs: addrs,
   177  		TargetCACert:   caCert,
   178  	}
   179  	w, err := migrationminion.New(s.config)
   180  	c.Assert(err, jc.ErrorIsNil)
   181  	defer workertest.CleanKill(c, w)
   182  
   183  	s.waitForStubCalls(c, []string{
   184  		"Watch",
   185  		"Lockdown",
   186  		"API open",
   187  		"ValidateMigration",
   188  		"API close",
   189  		"Report",
   190  	})
   191  	s.stub.CheckCall(c, 2, "API open", &api.Info{
   192  		ModelTag: modelTag,
   193  		Tag:      agentTag,
   194  		Password: agentPassword,
   195  		Addrs:    addrs,
   196  		CACert:   caCert,
   197  	})
   198  	s.stub.CheckCall(c, 5, "Report", "id", migration.VALIDATION, true)
   199  }
   200  
   201  func (s *Suite) TestVALIDATIONCantConnect(c *gc.C) {
   202  	s.client.watcher.changes <- watcher.MigrationStatus{
   203  		MigrationId: "id",
   204  		Phase:       migration.VALIDATION,
   205  	}
   206  	s.config.APIOpen = func(*api.Info, api.DialOpts) (api.Connection, error) {
   207  		s.stub.AddCall("API open")
   208  		return nil, errors.New("boom")
   209  	}
   210  	w, err := migrationminion.New(s.config)
   211  	c.Assert(err, jc.ErrorIsNil)
   212  	defer workertest.CleanKill(c, w)
   213  
   214  	// Advance time enough for all of the retries to be exhausted.
   215  	sleepTime := 100 * time.Millisecond
   216  	for i := 0; i < 9; i++ {
   217  		err := s.clock.WaitAdvance(sleepTime, coretesting.ShortWait, 1)
   218  		c.Assert(err, jc.ErrorIsNil)
   219  		sleepTime = (16 * sleepTime) / 10
   220  	}
   221  
   222  	s.waitForStubCalls(c, []string{
   223  		"Watch",
   224  		"Lockdown",
   225  		"API open",
   226  		"API open",
   227  		"API open",
   228  		"API open",
   229  		"API open",
   230  		"API open",
   231  		"API open",
   232  		"API open",
   233  		"API open",
   234  		"API open",
   235  		"Report",
   236  	})
   237  	s.stub.CheckCall(c, 12, "Report", "id", migration.VALIDATION, false)
   238  }
   239  
   240  func (s *Suite) TestVALIDATIONCantConnectNotReportForTryAgainError(c *gc.C) {
   241  	s.client.watcher.changes <- watcher.MigrationStatus{
   242  		MigrationId: "id",
   243  		Phase:       migration.VALIDATION,
   244  	}
   245  	s.config.APIOpen = func(*api.Info, api.DialOpts) (api.Connection, error) {
   246  		s.stub.AddCall("API open")
   247  		return nil, apiservererrors.ErrTryAgain
   248  	}
   249  	w, err := migrationminion.New(s.config)
   250  	c.Assert(err, jc.ErrorIsNil)
   251  	defer workertest.CleanKill(c, w)
   252  
   253  	// Advance time enough for all of the retries to be exhausted.
   254  	sleepTime := 100 * time.Millisecond
   255  	for i := 0; i < 9; i++ {
   256  		err := s.clock.WaitAdvance(sleepTime, coretesting.ShortWait, 1)
   257  		c.Assert(err, jc.ErrorIsNil)
   258  		sleepTime = (16 * sleepTime) / 10
   259  	}
   260  
   261  	s.waitForStubCalls(c, []string{
   262  		"Watch",
   263  		"Lockdown",
   264  		"API open",
   265  		"API open",
   266  		"API open",
   267  		"API open",
   268  		"API open",
   269  		"API open",
   270  		"API open",
   271  		"API open",
   272  		"API open",
   273  		"API open",
   274  	})
   275  }
   276  
   277  func (s *Suite) TestVALIDATIONFail(c *gc.C) {
   278  	s.client.watcher.changes <- watcher.MigrationStatus{
   279  		MigrationId: "id",
   280  		Phase:       migration.VALIDATION,
   281  	}
   282  	s.config.ValidateMigration = func(base.APICaller) error {
   283  		s.stub.AddCall("ValidateMigration")
   284  		return errors.New("boom")
   285  	}
   286  	w, err := migrationminion.New(s.config)
   287  	c.Assert(err, jc.ErrorIsNil)
   288  	defer workertest.CleanKill(c, w)
   289  
   290  	// Advance time enough for all of the retries to be exhausted.
   291  	sleepTime := 100 * time.Millisecond
   292  	for i := 0; i < 9; i++ {
   293  		err := s.clock.WaitAdvance(sleepTime, coretesting.ShortWait, 1)
   294  		c.Assert(err, jc.ErrorIsNil)
   295  		sleepTime = (16 * sleepTime) / 10
   296  	}
   297  
   298  	expectedCalls := []string{"Watch", "Lockdown"}
   299  	for i := 0; i < 10; i++ {
   300  		expectedCalls = append(expectedCalls, "API open", "ValidateMigration", "API close")
   301  	}
   302  	expectedCalls = append(expectedCalls, "Report")
   303  	s.waitForStubCalls(c, expectedCalls)
   304  	s.stub.CheckCall(c, 32, "Report", "id", migration.VALIDATION, false)
   305  }
   306  
   307  func (s *Suite) TestVALIDATIONRetrySucceed(c *gc.C) {
   308  	s.client.watcher.changes <- watcher.MigrationStatus{
   309  		MigrationId: "id",
   310  		Phase:       migration.VALIDATION,
   311  	}
   312  	var stub jujutesting.Stub
   313  	stub.SetErrors(errors.New("nope"), errors.New("not yet"), nil)
   314  	s.config.ValidateMigration = func(base.APICaller) error {
   315  		stub.AddCall("ValidateMigration")
   316  		return stub.NextErr()
   317  	}
   318  
   319  	w, err := migrationminion.New(s.config)
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	defer workertest.CleanKill(c, w)
   322  
   323  	waitForStubCalls(c, &stub, "ValidateMigration")
   324  
   325  	err = s.clock.WaitAdvance(100*time.Millisecond, coretesting.LongWait, 1)
   326  	c.Assert(err, jc.ErrorIsNil)
   327  
   328  	waitForStubCalls(c, &stub, "ValidateMigration", "ValidateMigration")
   329  
   330  	err = s.clock.WaitAdvance(160*time.Millisecond, coretesting.LongWait, 1)
   331  	c.Assert(err, jc.ErrorIsNil)
   332  
   333  	s.waitForStubCalls(c, []string{
   334  		"Watch",
   335  		"Lockdown",
   336  		"API open",
   337  		"API close",
   338  		"API open",
   339  		"API close",
   340  		"API open",
   341  		"API close",
   342  		"Report",
   343  	})
   344  	s.stub.CheckCall(c, 8, "Report", "id", migration.VALIDATION, true)
   345  }
   346  
   347  func (s *Suite) TestSUCCESS(c *gc.C) {
   348  	s.client.watcher.changes <- watcher.MigrationStatus{
   349  		MigrationId:    "id",
   350  		Phase:          migration.SUCCESS,
   351  		TargetAPIAddrs: addrs,
   352  		TargetCACert:   caCert,
   353  	}
   354  	w, err := migrationminion.New(s.config)
   355  	c.Assert(err, jc.ErrorIsNil)
   356  
   357  	select {
   358  	case <-s.agent.configChanged:
   359  	case <-time.After(coretesting.LongWait):
   360  		c.Fatal("timed out")
   361  	}
   362  	workertest.CleanKill(c, w)
   363  	c.Assert(s.agent.conf.addrs, gc.DeepEquals, addrs)
   364  	c.Assert(s.agent.conf.caCert, gc.DeepEquals, caCert)
   365  	s.stub.CheckCallNames(c, "Watch", "Lockdown", "Report")
   366  	s.stub.CheckCall(c, 2, "Report", "id", migration.SUCCESS, true)
   367  }
   368  
   369  func (s *Suite) waitForStubCalls(c *gc.C, expectedCallNames []string) {
   370  	waitForStubCalls(c, s.stub, expectedCallNames...)
   371  }
   372  
   373  func waitForStubCalls(c *gc.C, stub *jujutesting.Stub, expectedCallNames ...string) {
   374  	var callNames []string
   375  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   376  		callNames = stubCallNames(stub)
   377  		if reflect.DeepEqual(callNames, expectedCallNames) {
   378  			return
   379  		}
   380  	}
   381  	c.Fatalf("failed to see expected calls. saw: %v", callNames)
   382  }
   383  
   384  // Make this a feature of stub
   385  func stubCallNames(stub *jujutesting.Stub) []string {
   386  	var out []string
   387  	for _, call := range stub.Calls() {
   388  		out = append(out, call.FuncName)
   389  	}
   390  	return out
   391  }
   392  
   393  func newStubGuard(stub *jujutesting.Stub) *stubGuard {
   394  	return &stubGuard{stub: stub}
   395  }
   396  
   397  type stubGuard struct {
   398  	stub        *jujutesting.Stub
   399  	unlockErr   error
   400  	lockdownErr error
   401  }
   402  
   403  func (g *stubGuard) Lockdown(fortress.Abort) error {
   404  	g.stub.AddCall("Lockdown")
   405  	return g.lockdownErr
   406  }
   407  
   408  func (g *stubGuard) Unlock() error {
   409  	g.stub.AddCall("Unlock")
   410  	return g.unlockErr
   411  }
   412  
   413  func newStubMinionClient(stub *jujutesting.Stub) *stubMinionClient {
   414  	return &stubMinionClient{
   415  		stub:    stub,
   416  		watcher: newStubWatcher(),
   417  	}
   418  }
   419  
   420  type stubMinionClient struct {
   421  	stub     *jujutesting.Stub
   422  	watcher  *stubWatcher
   423  	watchErr error
   424  }
   425  
   426  func (c *stubMinionClient) Watch() (watcher.MigrationStatusWatcher, error) {
   427  	c.stub.MethodCall(c, "Watch")
   428  	if c.watchErr != nil {
   429  		return nil, c.watchErr
   430  	}
   431  	return c.watcher, nil
   432  }
   433  
   434  func (c *stubMinionClient) Report(id string, phase migration.Phase, success bool) error {
   435  	c.stub.MethodCall(c, "Report", id, phase, success)
   436  	return nil
   437  }
   438  
   439  func newStubWatcher() *stubWatcher {
   440  	return &stubWatcher{
   441  		Worker:  workertest.NewErrorWorker(nil),
   442  		changes: make(chan watcher.MigrationStatus, 1),
   443  	}
   444  }
   445  
   446  type stubWatcher struct {
   447  	worker.Worker
   448  	changes chan watcher.MigrationStatus
   449  }
   450  
   451  func (w *stubWatcher) Changes() <-chan watcher.MigrationStatus {
   452  	return w.changes
   453  }
   454  
   455  func newStubAgent() *stubAgent {
   456  	return &stubAgent{
   457  		configChanged: make(chan bool),
   458  	}
   459  }
   460  
   461  type stubAgent struct {
   462  	agent.Agent
   463  	configChanged chan bool
   464  	conf          stubAgentConfig
   465  }
   466  
   467  func (ma *stubAgent) CurrentConfig() agent.Config {
   468  	return &ma.conf
   469  }
   470  
   471  func (ma *stubAgent) ChangeConfig(f agent.ConfigMutator) error {
   472  	defer close(ma.configChanged)
   473  	return f(&ma.conf)
   474  }
   475  
   476  type stubAgentConfig struct {
   477  	agent.ConfigSetter
   478  
   479  	mu     sync.Mutex
   480  	addrs  []string
   481  	caCert string
   482  }
   483  
   484  func (mc *stubAgentConfig) SetAPIHostPorts(servers []network.HostPorts) error {
   485  	mc.mu.Lock()
   486  	defer mc.mu.Unlock()
   487  	mc.addrs = nil
   488  	for _, hps := range servers {
   489  		for _, hp := range hps {
   490  			mc.addrs = append(mc.addrs, network.DialAddress(hp))
   491  		}
   492  	}
   493  	return nil
   494  }
   495  
   496  func (mc *stubAgentConfig) SetCACert(cert string) {
   497  	mc.mu.Lock()
   498  	defer mc.mu.Unlock()
   499  	mc.caCert = cert
   500  }
   501  
   502  func (mc *stubAgentConfig) APIInfo() (*api.Info, bool) {
   503  	mc.mu.Lock()
   504  	defer mc.mu.Unlock()
   505  	return &api.Info{
   506  		Addrs:    mc.addrs,
   507  		CACert:   mc.caCert,
   508  		ModelTag: modelTag,
   509  		Tag:      agentTag,
   510  		Password: agentPassword,
   511  	}, true
   512  }
   513  
   514  type stubConnection struct {
   515  	api.Connection
   516  	stub *jujutesting.Stub
   517  }
   518  
   519  func (c *stubConnection) Close() error {
   520  	c.stub.AddCall("API close")
   521  	return nil
   522  }