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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package environupgrader_test
     5  
     6  import (
     7  	"sync"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/names/v5"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/worker/v3/workertest"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/tomb.v2"
    17  
    18  	"github.com/juju/juju/core/status"
    19  	"github.com/juju/juju/core/watcher"
    20  	"github.com/juju/juju/environs"
    21  	"github.com/juju/juju/environs/context"
    22  	"github.com/juju/juju/rpc/params"
    23  	coretesting "github.com/juju/juju/testing"
    24  	"github.com/juju/juju/worker/environupgrader"
    25  )
    26  
    27  type WorkerSuite struct {
    28  	testing.IsolationSuite
    29  }
    30  
    31  var _ = gc.Suite(&WorkerSuite{})
    32  
    33  func (*WorkerSuite) TestNewWorkerValidatesConfig(c *gc.C) {
    34  	_, err := environupgrader.NewWorker(environupgrader.Config{})
    35  	c.Assert(err, gc.ErrorMatches, "nil Facade not valid")
    36  }
    37  
    38  func (*WorkerSuite) TestNewWorker(c *gc.C) {
    39  	mockFacade := mockFacade{current: 123, target: 124}
    40  	mockEnviron := mockEnviron{}
    41  	mockGateUnlocker := mockGateUnlocker{}
    42  	w, err := environupgrader.NewWorker(environupgrader.Config{
    43  		Facade:        &mockFacade,
    44  		Environ:       &mockEnviron,
    45  		GateUnlocker:  &mockGateUnlocker,
    46  		ControllerTag: coretesting.ControllerTag,
    47  		ModelTag:      coretesting.ModelTag,
    48  		CredentialAPI: &credentialAPIForTest{},
    49  		Logger:        loggo.GetLogger("test"),
    50  	})
    51  	c.Assert(err, jc.ErrorIsNil)
    52  	workertest.CheckKill(c, w)
    53  	mockFacade.CheckCalls(c, []testing.StubCall{
    54  		{"ModelTargetEnvironVersion", []interface{}{coretesting.ModelTag}},
    55  		{"ModelEnvironVersion", []interface{}{coretesting.ModelTag}},
    56  		{"SetModelStatus", []interface{}{coretesting.ModelTag, status.Busy, "upgrading environ from version 123 to 124", nilData}},
    57  		{"SetModelStatus", []interface{}{coretesting.ModelTag, status.Available, "", nilData}},
    58  	})
    59  	mockEnviron.CheckCallNames(c, "UpgradeOperations")
    60  	mockGateUnlocker.CheckCallNames(c, "Unlock")
    61  }
    62  
    63  func (*WorkerSuite) TestNewWorkerModelRemovedUninstalls(c *gc.C) {
    64  	mockFacade := mockFacade{current: 123, target: 124}
    65  	mockFacade.SetErrors(&params.Error{Code: params.CodeNotFound})
    66  	mockEnviron := mockEnviron{}
    67  	mockGateUnlocker := mockGateUnlocker{}
    68  	w, err := environupgrader.NewWorker(environupgrader.Config{
    69  		Facade:        &mockFacade,
    70  		Environ:       &mockEnviron,
    71  		GateUnlocker:  &mockGateUnlocker,
    72  		ControllerTag: coretesting.ControllerTag,
    73  		ModelTag:      coretesting.ModelTag,
    74  		CredentialAPI: &credentialAPIForTest{},
    75  		Logger:        loggo.GetLogger("test"),
    76  	})
    77  	c.Assert(errors.Cause(err), gc.ErrorMatches, environupgrader.ErrModelRemoved.Error())
    78  	workertest.CheckNilOrKill(c, w)
    79  	mockFacade.CheckCalls(c, []testing.StubCall{
    80  		{"ModelTargetEnvironVersion", []interface{}{coretesting.ModelTag}},
    81  	})
    82  	mockEnviron.CheckNoCalls(c)
    83  	mockGateUnlocker.CheckNoCalls(c)
    84  }
    85  
    86  func (*WorkerSuite) TestNonUpgradeable(c *gc.C) {
    87  	mockFacade := mockFacade{current: 123, target: 124}
    88  	mockEnviron := struct{ environs.Environ }{} // not an Upgrader
    89  	mockGateUnlocker := mockGateUnlocker{}
    90  	w, err := environupgrader.NewWorker(environupgrader.Config{
    91  		Facade:        &mockFacade,
    92  		Environ:       &mockEnviron,
    93  		GateUnlocker:  &mockGateUnlocker,
    94  		ControllerTag: coretesting.ControllerTag,
    95  		ModelTag:      coretesting.ModelTag,
    96  		CredentialAPI: &credentialAPIForTest{},
    97  		Logger:        loggo.GetLogger("test"),
    98  	})
    99  	c.Assert(err, jc.ErrorIsNil)
   100  	workertest.CheckKill(c, w)
   101  	mockFacade.CheckCalls(c, []testing.StubCall{
   102  		{"ModelTargetEnvironVersion", []interface{}{coretesting.ModelTag}},
   103  		{"ModelEnvironVersion", []interface{}{coretesting.ModelTag}},
   104  		{"SetModelStatus", []interface{}{coretesting.ModelTag, status.Busy, "upgrading environ from version 123 to 124", nilData}},
   105  		{"SetModelStatus", []interface{}{coretesting.ModelTag, status.Available, "", nilData}},
   106  	})
   107  	mockGateUnlocker.CheckCallNames(c, "Unlock")
   108  }
   109  
   110  func (*WorkerSuite) TestRunUpgradeOperations(c *gc.C) {
   111  	var stepsStub testing.Stub
   112  	mockFacade := mockFacade{current: 123, target: 125}
   113  	mockEnviron := mockEnviron{
   114  		ops: []environs.UpgradeOperation{{
   115  			TargetVersion: 123,
   116  			Steps: []environs.UpgradeStep{
   117  				newStep(&stepsStub, "step122"),
   118  			},
   119  		}, {
   120  			TargetVersion: 123,
   121  			Steps: []environs.UpgradeStep{
   122  				newStep(&stepsStub, "step123"),
   123  			},
   124  		}, {
   125  			TargetVersion: 124,
   126  			Steps: []environs.UpgradeStep{
   127  				newStep(&stepsStub, "step124_0"),
   128  				newStep(&stepsStub, "step124_1"),
   129  			},
   130  		}, {
   131  			TargetVersion: 125,
   132  			Steps: []environs.UpgradeStep{
   133  				newStep(&stepsStub, "step125"),
   134  			},
   135  		}, {
   136  			TargetVersion: 126,
   137  			Steps: []environs.UpgradeStep{
   138  				newStep(&stepsStub, "step126"),
   139  			},
   140  		}},
   141  	}
   142  	mockGateUnlocker := mockGateUnlocker{}
   143  	w, err := environupgrader.NewWorker(environupgrader.Config{
   144  		Facade:        &mockFacade,
   145  		Environ:       &mockEnviron,
   146  		GateUnlocker:  &mockGateUnlocker,
   147  		ControllerTag: coretesting.ControllerTag,
   148  		ModelTag:      coretesting.ModelTag,
   149  		CredentialAPI: &credentialAPIForTest{},
   150  		Logger:        loggo.GetLogger("test"),
   151  	})
   152  	c.Assert(err, jc.ErrorIsNil)
   153  	workertest.CheckKill(c, w)
   154  	mockFacade.CheckCalls(c, []testing.StubCall{
   155  		{"ModelTargetEnvironVersion", []interface{}{coretesting.ModelTag}},
   156  		{"ModelEnvironVersion", []interface{}{coretesting.ModelTag}},
   157  		{"SetModelStatus", []interface{}{coretesting.ModelTag, status.Busy, "upgrading environ from version 123 to 125", nilData}},
   158  		{"SetModelEnvironVersion", []interface{}{
   159  			coretesting.ModelTag, 124,
   160  		}},
   161  		{"SetModelEnvironVersion", []interface{}{
   162  			coretesting.ModelTag, 125,
   163  		}},
   164  		{"SetModelStatus", []interface{}{coretesting.ModelTag, status.Available, "", nilData}},
   165  	})
   166  	mockEnviron.CheckCalls(c, []testing.StubCall{
   167  		{"UpgradeOperations", []interface{}{
   168  			mockEnviron.callCtxUsed,
   169  			environs.UpgradeOperationsParams{
   170  				ControllerUUID: coretesting.ControllerTag.Id(),
   171  			}},
   172  		},
   173  	})
   174  	mockGateUnlocker.CheckCallNames(c, "Unlock")
   175  	stepsStub.CheckCallNames(c, "step124_0", "step124_1", "step125")
   176  }
   177  
   178  func (*WorkerSuite) TestRunUpgradeOperationsStepError(c *gc.C) {
   179  	var stepsStub testing.Stub
   180  	stepsStub.SetErrors(errors.New("phooey"))
   181  	mockFacade := mockFacade{current: 123, target: 124}
   182  	mockEnviron := mockEnviron{
   183  		ops: []environs.UpgradeOperation{{
   184  			TargetVersion: 124,
   185  			Steps: []environs.UpgradeStep{
   186  				newStep(&stepsStub, "step124"),
   187  			},
   188  		}},
   189  	}
   190  	mockGateUnlocker := mockGateUnlocker{}
   191  	w, err := environupgrader.NewWorker(environupgrader.Config{
   192  		Facade:        &mockFacade,
   193  		Environ:       &mockEnviron,
   194  		GateUnlocker:  &mockGateUnlocker,
   195  		ControllerTag: coretesting.ControllerTag,
   196  		ModelTag:      coretesting.ModelTag,
   197  		CredentialAPI: &credentialAPIForTest{},
   198  		Logger:        loggo.GetLogger("test"),
   199  	})
   200  	c.Assert(err, jc.ErrorIsNil)
   201  
   202  	err = workertest.CheckKilled(c, w)
   203  	c.Assert(err, gc.ErrorMatches, "upgrading environ: phooey")
   204  
   205  	mockFacade.CheckCalls(c, []testing.StubCall{
   206  		{"ModelTargetEnvironVersion", []interface{}{coretesting.ModelTag}},
   207  		{"ModelEnvironVersion", []interface{}{coretesting.ModelTag}},
   208  		{"SetModelStatus", []interface{}{coretesting.ModelTag, status.Busy, "upgrading environ from version 123 to 124", nilData}},
   209  		{"SetModelStatus", []interface{}{coretesting.ModelTag, status.Error, "failed to upgrade environ: phooey", nilData}},
   210  	})
   211  	mockGateUnlocker.CheckNoCalls(c)
   212  }
   213  
   214  func (*WorkerSuite) TestWaitForUpgrade(c *gc.C) {
   215  	ch := make(chan struct{})
   216  	mockFacade := mockFacade{
   217  		current: 123,
   218  		target:  125,
   219  		watcher: newMockNotifyWatcher(ch),
   220  	}
   221  	mockGateUnlocker := mockGateUnlocker{}
   222  	w, err := environupgrader.NewWorker(environupgrader.Config{
   223  		Facade:        &mockFacade,
   224  		Environ:       nil, // not responsible for running upgrades
   225  		GateUnlocker:  &mockGateUnlocker,
   226  		ControllerTag: coretesting.ControllerTag,
   227  		ModelTag:      coretesting.ModelTag,
   228  		CredentialAPI: &credentialAPIForTest{},
   229  		Logger:        loggo.GetLogger("test"),
   230  	})
   231  	c.Assert(err, jc.ErrorIsNil)
   232  
   233  	// Send the initial change event on the watcher, and
   234  	// wait for the worker to call "ModelEnvironVersion".
   235  	ch <- struct{}{}
   236  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   237  		if len(mockFacade.Calls()) < 3 && a.HasNext() {
   238  			continue
   239  		}
   240  		mockFacade.CheckCalls(c, []testing.StubCall{
   241  			{"ModelTargetEnvironVersion", []interface{}{coretesting.ModelTag}},
   242  			{"WatchModelEnvironVersion", []interface{}{coretesting.ModelTag}},
   243  			{"ModelEnvironVersion", []interface{}{coretesting.ModelTag}},
   244  		})
   245  		mockGateUnlocker.CheckNoCalls(c)
   246  		break
   247  	}
   248  
   249  	// Set the current version >= target. In practice we should
   250  	// only ever see that the current version <= target, as all
   251  	// controller agents should be running the same version at
   252  	// this point. We require that the environ version be strictly
   253  	// increasing, so we can be defensive.
   254  	mockFacade.setCurrent(126)
   255  	ch <- struct{}{}
   256  
   257  	workertest.CheckKill(c, w)
   258  	mockFacade.CheckCalls(c, []testing.StubCall{
   259  		{"ModelTargetEnvironVersion", []interface{}{coretesting.ModelTag}},
   260  		{"WatchModelEnvironVersion", []interface{}{coretesting.ModelTag}},
   261  		{"ModelEnvironVersion", []interface{}{coretesting.ModelTag}},
   262  		{"ModelEnvironVersion", []interface{}{coretesting.ModelTag}},
   263  	})
   264  	mockGateUnlocker.CheckCallNames(c, "Unlock")
   265  }
   266  
   267  func (*WorkerSuite) TestModelNotFoundWhenRunning(c *gc.C) {
   268  	ch := make(chan struct{})
   269  	mockFacade := mockFacade{
   270  		current: 123,
   271  		target:  125,
   272  		watcher: newMockNotifyWatcher(ch),
   273  	}
   274  	w, err := environupgrader.NewWorker(environupgrader.Config{
   275  		Facade:        &mockFacade,
   276  		Environ:       nil, // not responsible for running upgrades
   277  		GateUnlocker:  &mockGateUnlocker{},
   278  		ControllerTag: coretesting.ControllerTag,
   279  		ModelTag:      coretesting.ModelTag,
   280  		CredentialAPI: &credentialAPIForTest{},
   281  		Logger:        loggo.GetLogger("test"),
   282  	})
   283  	c.Assert(err, jc.ErrorIsNil)
   284  
   285  	mockFacade.SetErrors(&params.Error{Code: params.CodeNotFound})
   286  	ch <- struct{}{}
   287  
   288  	err = workertest.CheckKill(c, w)
   289  	// We expect NotFound to be changed to environupgrader.ErrModelRemoved.
   290  	c.Check(err, gc.ErrorMatches, "model has been removed")
   291  }
   292  
   293  func newStep(stub *testing.Stub, name string) environs.UpgradeStep {
   294  	run := func() error {
   295  		stub.AddCall(name)
   296  		return stub.NextErr()
   297  	}
   298  	return mockUpgradeStep{name, run}
   299  }
   300  
   301  type mockUpgradeStep struct {
   302  	description string
   303  	run         func() error
   304  }
   305  
   306  func (s mockUpgradeStep) Description() string {
   307  	return s.description
   308  }
   309  
   310  func (s mockUpgradeStep) Run(ctx context.ProviderCallContext) error {
   311  	return s.run()
   312  }
   313  
   314  type mockFacade struct {
   315  	testing.Stub
   316  	target  int
   317  	watcher *mockNotifyWatcher
   318  
   319  	mu      sync.Mutex
   320  	current int
   321  }
   322  
   323  func (f *mockFacade) setCurrent(v int) {
   324  	f.mu.Lock()
   325  	defer f.mu.Unlock()
   326  	f.current = v
   327  }
   328  
   329  func (f *mockFacade) ModelEnvironVersion(tag names.ModelTag) (int, error) {
   330  	f.mu.Lock()
   331  	defer f.mu.Unlock()
   332  	f.MethodCall(f, "ModelEnvironVersion", tag)
   333  	return f.current, f.NextErr()
   334  }
   335  
   336  func (f *mockFacade) ModelTargetEnvironVersion(tag names.ModelTag) (int, error) {
   337  	f.MethodCall(f, "ModelTargetEnvironVersion", tag)
   338  	return f.target, f.NextErr()
   339  }
   340  
   341  func (f *mockFacade) SetModelEnvironVersion(tag names.ModelTag, v int) error {
   342  	f.MethodCall(f, "SetModelEnvironVersion", tag, v)
   343  	return f.NextErr()
   344  }
   345  
   346  func (f *mockFacade) WatchModelEnvironVersion(tag names.ModelTag) (watcher.NotifyWatcher, error) {
   347  	f.MethodCall(f, "WatchModelEnvironVersion", tag)
   348  	if err := f.NextErr(); err != nil {
   349  		return nil, err
   350  	}
   351  	if f.watcher != nil {
   352  		return f.watcher, nil
   353  	}
   354  	return nil, errors.New("unexpected call to WatchModelEnvironVersion")
   355  }
   356  
   357  var nilData map[string]interface{}
   358  
   359  func (f *mockFacade) SetModelStatus(tag names.ModelTag, status status.Status, info string, data map[string]interface{}) error {
   360  	f.MethodCall(f, "SetModelStatus", tag, status, info, data)
   361  	return f.NextErr()
   362  }
   363  
   364  type mockEnviron struct {
   365  	environs.Environ
   366  	testing.Stub
   367  	ops []environs.UpgradeOperation
   368  
   369  	callCtxUsed context.ProviderCallContext
   370  }
   371  
   372  func (e *mockEnviron) UpgradeOperations(ctx context.ProviderCallContext, args environs.UpgradeOperationsParams) []environs.UpgradeOperation {
   373  	e.MethodCall(e, "UpgradeOperations", ctx, args)
   374  	e.callCtxUsed = ctx
   375  	e.PopNoErr()
   376  	return e.ops
   377  }
   378  
   379  type mockGateUnlocker struct {
   380  	testing.Stub
   381  }
   382  
   383  func (g *mockGateUnlocker) Unlock() {
   384  	g.MethodCall(g, "Unlock")
   385  	g.PopNoErr()
   386  }
   387  
   388  type mockNotifyWatcher struct {
   389  	tomb tomb.Tomb
   390  	ch   chan struct{}
   391  }
   392  
   393  func newMockNotifyWatcher(ch chan struct{}) *mockNotifyWatcher {
   394  	w := &mockNotifyWatcher{ch: ch}
   395  	w.tomb.Go(func() error {
   396  		defer close(ch)
   397  		<-w.tomb.Dying()
   398  		return tomb.ErrDying
   399  	})
   400  	return w
   401  }
   402  
   403  func (w *mockNotifyWatcher) Changes() watcher.NotifyChannel {
   404  	return w.ch
   405  }
   406  
   407  func (w *mockNotifyWatcher) Kill() {
   408  	w.tomb.Kill(nil)
   409  }
   410  
   411  func (w *mockNotifyWatcher) Wait() error {
   412  	return w.tomb.Wait()
   413  }
   414  
   415  type credentialAPIForTest struct{}
   416  
   417  func (*credentialAPIForTest) InvalidateModelCredential(reason string) error {
   418  	return nil
   419  }