github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/modelupgrader/worker_test.go (about)

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