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(¶ms.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(¶ms.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 }