github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/caasapplicationprovisioner/application_test.go (about) 1 // Copyright 2020 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasapplicationprovisioner_test 5 6 import ( 7 "time" 8 9 "github.com/juju/clock" 10 "github.com/juju/clock/testclock" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names/v5" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/worker/v3" 16 "github.com/juju/worker/v3/workertest" 17 "go.uber.org/mock/gomock" 18 gc "gopkg.in/check.v1" 19 20 "github.com/juju/juju/caas" 21 caasmocks "github.com/juju/juju/caas/mocks" 22 "github.com/juju/juju/core/life" 23 "github.com/juju/juju/core/status" 24 "github.com/juju/juju/core/watcher" 25 "github.com/juju/juju/core/watcher/watchertest" 26 "github.com/juju/juju/rpc/params" 27 coretesting "github.com/juju/juju/testing" 28 "github.com/juju/juju/worker/caasapplicationprovisioner" 29 "github.com/juju/juju/worker/caasapplicationprovisioner/mocks" 30 ) 31 32 var _ = gc.Suite(&ApplicationWorkerSuite{}) 33 34 type ApplicationWorkerSuite struct { 35 coretesting.BaseSuite 36 37 modelTag names.ModelTag 38 logger loggo.Logger 39 } 40 41 func (s *ApplicationWorkerSuite) SetUpTest(c *gc.C) { 42 s.BaseSuite.SetUpTest(c) 43 44 s.modelTag = names.NewModelTag("ffffffff-ffff-ffff-ffff-ffffffffffff") 45 s.logger = loggo.GetLogger("test") 46 } 47 48 func (s *ApplicationWorkerSuite) waitDone(c *gc.C, done chan struct{}) { 49 select { 50 case <-done: 51 case <-time.After(coretesting.LongWait): 52 c.Errorf("timed out waiting for worker") 53 } 54 } 55 56 func (s *ApplicationWorkerSuite) startAppWorker( 57 c *gc.C, 58 clk clock.Clock, 59 facade caasapplicationprovisioner.CAASProvisionerFacade, 60 broker caasapplicationprovisioner.CAASBroker, 61 unitFacade caasapplicationprovisioner.CAASUnitProvisionerFacade, 62 ops caasapplicationprovisioner.ApplicationOps, 63 statusOnly bool, 64 ) worker.Worker { 65 config := caasapplicationprovisioner.AppWorkerConfig{ 66 Name: "test", 67 Facade: facade, 68 Broker: broker, 69 ModelTag: s.modelTag, 70 Clock: clk, 71 Logger: s.logger, 72 UnitFacade: unitFacade, 73 Ops: ops, 74 StatusOnly: statusOnly, 75 } 76 startFunc := caasapplicationprovisioner.NewAppWorker(config) 77 c.Assert(startFunc, gc.NotNil) 78 appWorker, err := startFunc() 79 c.Assert(err, jc.ErrorIsNil) 80 c.Assert(appWorker, gc.NotNil) 81 return appWorker 82 } 83 84 func (s *ApplicationWorkerSuite) TestLifeNotFound(c *gc.C) { 85 ctrl := gomock.NewController(c) 86 defer ctrl.Finish() 87 88 broker := mocks.NewMockCAASBroker(ctrl) 89 brokerApp := caasmocks.NewMockApplication(ctrl) 90 facade := mocks.NewMockCAASProvisionerFacade(ctrl) 91 ops := mocks.NewMockApplicationOps(ctrl) 92 done := make(chan struct{}) 93 94 gomock.InOrder( 95 broker.EXPECT().Application("test", caas.DeploymentStateful).Return(brokerApp), 96 facade.EXPECT().Life("test").DoAndReturn(func(appName string) (life.Value, error) { 97 close(done) 98 return "", errors.NotFoundf("test charm") 99 }), 100 ) 101 appWorker := s.startAppWorker(c, nil, facade, broker, nil, ops, false) 102 103 s.waitDone(c, done) 104 workertest.CleanKill(c, appWorker) 105 } 106 107 func (s *ApplicationWorkerSuite) TestLifeDead(c *gc.C) { 108 ctrl := gomock.NewController(c) 109 defer ctrl.Finish() 110 111 broker := mocks.NewMockCAASBroker(ctrl) 112 app := caasmocks.NewMockApplication(ctrl) 113 facade := mocks.NewMockCAASProvisionerFacade(ctrl) 114 unitFacade := mocks.NewMockCAASUnitProvisionerFacade(ctrl) 115 ops := mocks.NewMockApplicationOps(ctrl) 116 clk := testclock.NewDilatedWallClock(time.Millisecond) 117 118 done := make(chan struct{}) 119 120 gomock.InOrder( 121 broker.EXPECT().Application("test", caas.DeploymentStateful).Return(app), 122 facade.EXPECT().Life("test").Return(life.Dead, nil), 123 ops.EXPECT().AppDying("test", app, life.Dead, facade, unitFacade, s.logger).Return(nil), 124 ops.EXPECT().AppDead("test", app, broker, facade, unitFacade, clk, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) error { 125 close(done) 126 return nil 127 }), 128 ) 129 appWorker := s.startAppWorker(c, clk, facade, broker, unitFacade, ops, false) 130 131 s.waitDone(c, done) 132 workertest.CleanKill(c, appWorker) 133 } 134 135 func (s *ApplicationWorkerSuite) TestUpgradePodSpec(c *gc.C) { 136 ctrl := gomock.NewController(c) 137 defer ctrl.Finish() 138 139 broker := mocks.NewMockCAASBroker(ctrl) 140 brokerApp := caasmocks.NewMockApplication(ctrl) 141 facade := mocks.NewMockCAASProvisionerFacade(ctrl) 142 ops := mocks.NewMockApplicationOps(ctrl) 143 done := make(chan struct{}) 144 145 clk := testclock.NewClock(time.Time{}) 146 gomock.InOrder( 147 broker.EXPECT().Application("test", caas.DeploymentStateful).Return(brokerApp), 148 facade.EXPECT().Life("test").Return(life.Alive, nil), 149 150 // Verify charm is v2 151 ops.EXPECT().VerifyCharmUpgraded("test", gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil), 152 153 // Operator delete loop (with a retry) 154 ops.EXPECT().UpgradePodSpec("test", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil), 155 156 // Make SetPassword return an error to exit early (we've tested what 157 // we want to above). 158 facade.EXPECT().SetPassword("test", gomock.Any()).DoAndReturn(func(appName, password string) error { 159 close(done) 160 return errors.New("exit early error") 161 }), 162 ) 163 164 appWorker := s.startAppWorker(c, clk, facade, broker, nil, ops, false) 165 166 s.waitDone(c, done) 167 workertest.DirtyKill(c, appWorker) 168 } 169 170 func (s *ApplicationWorkerSuite) TestWorker(c *gc.C) { 171 ctrl := gomock.NewController(c) 172 defer ctrl.Finish() 173 174 broker := mocks.NewMockCAASBroker(ctrl) 175 app := caasmocks.NewMockApplication(ctrl) 176 facade := mocks.NewMockCAASProvisionerFacade(ctrl) 177 unitFacade := mocks.NewMockCAASUnitProvisionerFacade(ctrl) 178 ops := mocks.NewMockApplicationOps(ctrl) 179 done := make(chan struct{}) 180 181 clk := testclock.NewDilatedWallClock(time.Millisecond) 182 183 scaleChan := make(chan struct{}, 1) 184 trustChan := make(chan []string, 1) 185 provisioningInfoChan := make(chan struct{}, 1) 186 appUnitsChan := make(chan []string, 1) 187 appChan := make(chan struct{}, 1) 188 appReplicasChan := make(chan struct{}, 1) 189 190 ops.EXPECT().RefreshApplicationStatus("test", app, gomock.Any(), facade, s.logger).Return(nil).AnyTimes() 191 192 gomock.InOrder( 193 broker.EXPECT().Application("test", caas.DeploymentStateful).Return(app), 194 facade.EXPECT().Life("test").Return(life.Alive, nil), 195 196 ops.EXPECT().VerifyCharmUpgraded("test", gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil), 197 ops.EXPECT().UpgradePodSpec("test", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil), 198 199 facade.EXPECT().SetPassword("test", gomock.Any()).Return(nil), 200 201 unitFacade.EXPECT().WatchApplicationScale("test").Return(watchertest.NewMockNotifyWatcher(scaleChan), nil), 202 unitFacade.EXPECT().WatchApplicationTrustHash("test").Return(watchertest.NewMockStringsWatcher(trustChan), nil), 203 facade.EXPECT().WatchUnits("test").Return(watchertest.NewMockStringsWatcher(appUnitsChan), nil), 204 205 // handleChange 206 facade.EXPECT().Life("test").Return(life.Alive, nil), 207 facade.EXPECT().ProvisioningState("test").Return(nil, nil), 208 facade.EXPECT().WatchProvisioningInfo("test").Return(watchertest.NewMockNotifyWatcher(provisioningInfoChan), nil), 209 ops.EXPECT().AppAlive("test", app, gomock.Any(), gomock.Any(), facade, clk, s.logger).Return(nil), 210 app.EXPECT().Watch().Return(watchertest.NewMockNotifyWatcher(appChan), nil), 211 app.EXPECT().WatchReplicas().DoAndReturn(func() (watcher.NotifyWatcher, error) { 212 scaleChan <- struct{}{} 213 return watchertest.NewMockNotifyWatcher(appReplicasChan), nil 214 }), 215 216 // scaleChan fired 217 ops.EXPECT().EnsureScale("test", app, life.Alive, facade, unitFacade, s.logger).Return(errors.NotFound), 218 ops.EXPECT().EnsureScale("test", app, life.Alive, facade, unitFacade, s.logger).Return(errors.ConstError("try again")), 219 ops.EXPECT().EnsureScale("test", app, life.Alive, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _ any) error { 220 trustChan <- nil 221 return nil 222 }), 223 224 // trustChan fired 225 ops.EXPECT().EnsureTrust("test", app, unitFacade, s.logger).Return(errors.NotFound), 226 ops.EXPECT().EnsureTrust("test", app, unitFacade, s.logger).DoAndReturn(func(_, _, _, _ any) error { 227 appUnitsChan <- nil 228 return nil 229 }), 230 231 // appUnitsChan fired 232 ops.EXPECT().ReconcileDeadUnitScale("test", app, facade, s.logger).Return(errors.NotFound), 233 ops.EXPECT().ReconcileDeadUnitScale("test", app, facade, s.logger).Return(errors.ConstError("try again")), 234 ops.EXPECT().ReconcileDeadUnitScale("test", app, facade, s.logger).DoAndReturn(func(_, _, _, _ any) error { 235 appChan <- struct{}{} 236 return nil 237 }), 238 239 // appChan fired 240 ops.EXPECT().UpdateState("test", app, gomock.Any(), broker, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) (map[string]status.StatusInfo, error) { 241 appReplicasChan <- struct{}{} 242 return nil, nil 243 }), 244 // appReplicasChan fired 245 ops.EXPECT().UpdateState("test", app, gomock.Any(), broker, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) (map[string]status.StatusInfo, error) { 246 provisioningInfoChan <- struct{}{} 247 return nil, nil 248 }), 249 250 // provisioningInfoChan fired 251 facade.EXPECT().Life("test").Return(life.Alive, nil), 252 ops.EXPECT().AppAlive("test", app, gomock.Any(), gomock.Any(), facade, clk, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) error { 253 provisioningInfoChan <- struct{}{} 254 return nil 255 }), 256 facade.EXPECT().Life("test").Return(life.Dying, nil), 257 ops.EXPECT().AppDying("test", app, life.Dying, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _ any) error { 258 provisioningInfoChan <- struct{}{} 259 return nil 260 }), 261 facade.EXPECT().Life("test").Return(life.Dead, nil), 262 ops.EXPECT().AppDying("test", app, life.Dead, facade, unitFacade, s.logger).Return(nil), 263 ops.EXPECT().AppDead("test", app, broker, facade, unitFacade, clk, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) error { 264 close(done) 265 return nil 266 }), 267 ) 268 269 appWorker := s.startAppWorker(c, clk, facade, broker, unitFacade, ops, false) 270 s.waitDone(c, done) 271 workertest.CheckKill(c, appWorker) 272 } 273 274 func (s *ApplicationWorkerSuite) TestWorkerStatusOnly(c *gc.C) { 275 ctrl := gomock.NewController(c) 276 defer ctrl.Finish() 277 278 broker := mocks.NewMockCAASBroker(ctrl) 279 app := caasmocks.NewMockApplication(ctrl) 280 facade := mocks.NewMockCAASProvisionerFacade(ctrl) 281 unitFacade := mocks.NewMockCAASUnitProvisionerFacade(ctrl) 282 ops := mocks.NewMockApplicationOps(ctrl) 283 done := make(chan struct{}) 284 285 clk := testclock.NewDilatedWallClock(time.Millisecond) 286 287 scaleChan := make(chan struct{}, 1) 288 trustChan := make(chan []string, 1) 289 provisioningInfoChan := make(chan struct{}, 1) 290 appUnitsChan := make(chan []string, 1) 291 appChan := make(chan struct{}, 1) 292 appReplicasChan := make(chan struct{}, 1) 293 294 ops.EXPECT().RefreshApplicationStatus("test", app, gomock.Any(), facade, s.logger).Return(nil).AnyTimes() 295 296 gomock.InOrder( 297 broker.EXPECT().Application("test", caas.DeploymentStateful).Return(app), 298 facade.EXPECT().Life("test").Return(life.Alive, nil), 299 300 unitFacade.EXPECT().WatchApplicationScale("test").Return(watchertest.NewMockNotifyWatcher(scaleChan), nil), 301 unitFacade.EXPECT().WatchApplicationTrustHash("test").Return(watchertest.NewMockStringsWatcher(trustChan), nil), 302 facade.EXPECT().WatchUnits("test").Return(watchertest.NewMockStringsWatcher(appUnitsChan), nil), 303 304 // handleChange 305 facade.EXPECT().Life("test").Return(life.Alive, nil), 306 facade.EXPECT().ProvisioningState("test").Return(¶ms.CAASApplicationProvisioningState{Scaling: true, ScaleTarget: 1}, nil), 307 facade.EXPECT().SetProvisioningState("test", params.CAASApplicationProvisioningState{}).Return(nil), 308 facade.EXPECT().WatchProvisioningInfo("test").Return(watchertest.NewMockNotifyWatcher(provisioningInfoChan), nil), 309 app.EXPECT().Watch().Return(watchertest.NewMockNotifyWatcher(appChan), nil), 310 app.EXPECT().WatchReplicas().DoAndReturn(func() (watcher.NotifyWatcher, error) { 311 appChan <- struct{}{} 312 return watchertest.NewMockNotifyWatcher(appReplicasChan), nil 313 }), 314 315 // appChan fired 316 ops.EXPECT().UpdateState("test", app, gomock.Any(), broker, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) (map[string]status.StatusInfo, error) { 317 appReplicasChan <- struct{}{} 318 return nil, nil 319 }), 320 // appReplicasChan fired 321 ops.EXPECT().UpdateState("test", app, gomock.Any(), broker, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) (map[string]status.StatusInfo, error) { 322 provisioningInfoChan <- struct{}{} 323 return nil, nil 324 }), 325 326 // provisioningInfoChan fired 327 facade.EXPECT().Life("test").DoAndReturn(func(_ string) (life.Value, error) { 328 provisioningInfoChan <- struct{}{} 329 return life.Alive, nil 330 }), 331 facade.EXPECT().Life("test").DoAndReturn(func(_ string) (life.Value, error) { 332 provisioningInfoChan <- struct{}{} 333 return life.Dying, nil 334 }), 335 facade.EXPECT().Life("test").DoAndReturn(func(_ string) (life.Value, error) { 336 provisioningInfoChan <- struct{}{} 337 close(done) 338 return life.Dead, nil 339 }), 340 ) 341 342 appWorker := s.startAppWorker(c, clk, facade, broker, unitFacade, ops, true) 343 s.waitDone(c, done) 344 workertest.CheckKill(c, appWorker) 345 }