github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/caasfirewaller/worker_test.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasfirewaller_test 5 6 import ( 7 "time" 8 9 "github.com/juju/charm/v12" 10 "github.com/juju/clock" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/retry" 14 "github.com/juju/testing" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/worker/v3/workertest" 17 gc "gopkg.in/check.v1" 18 19 "github.com/juju/juju/api/common/charms" 20 "github.com/juju/juju/core/config" 21 "github.com/juju/juju/core/life" 22 "github.com/juju/juju/core/watcher/watchertest" 23 coretesting "github.com/juju/juju/testing" 24 "github.com/juju/juju/worker/caasfirewaller" 25 ) 26 27 type WorkerSuite struct { 28 testing.IsolationSuite 29 30 config caasfirewaller.Config 31 applicationGetter mockApplicationGetter 32 serviceExposer mockServiceExposer 33 lifeGetter mockLifeGetter 34 charmGetter mockCharmGetter 35 36 applicationChanges chan []string 37 appExposedChange chan struct{} 38 serviceExposed chan struct{} 39 serviceUnexposed chan struct{} 40 } 41 42 var _ = gc.Suite(&WorkerSuite{}) 43 44 func (s *WorkerSuite) SetUpTest(c *gc.C) { 45 s.IsolationSuite.SetUpTest(c) 46 47 s.applicationChanges = make(chan []string) 48 s.appExposedChange = make(chan struct{}) 49 s.serviceExposed = make(chan struct{}) 50 s.serviceUnexposed = make(chan struct{}) 51 52 s.applicationGetter = mockApplicationGetter{ 53 allWatcher: watchertest.NewMockStringsWatcher(s.applicationChanges), 54 appWatcher: watchertest.NewMockNotifyWatcher(s.appExposedChange), 55 } 56 s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.applicationGetter.allWatcher) }) 57 58 s.lifeGetter = mockLifeGetter{ 59 life: life.Alive, 60 } 61 s.charmGetter = mockCharmGetter{ 62 charmInfo: &charms.CharmInfo{ 63 Manifest: &charm.Manifest{}, 64 Meta: &charm.Meta{}, 65 }, 66 } 67 s.serviceExposer = mockServiceExposer{ 68 exposed: s.serviceExposed, 69 unexposed: s.serviceUnexposed, 70 } 71 72 s.config = caasfirewaller.Config{ 73 ControllerUUID: coretesting.ControllerTag.Id(), 74 ModelUUID: coretesting.ModelTag.Id(), 75 ApplicationGetter: &s.applicationGetter, 76 ServiceExposer: &s.serviceExposer, 77 LifeGetter: &s.lifeGetter, 78 CharmGetter: &s.charmGetter, 79 Logger: loggo.GetLogger("test"), 80 } 81 } 82 83 func (s *WorkerSuite) sendApplicationExposedChange(c *gc.C) { 84 select { 85 case s.appExposedChange <- struct{}{}: 86 case <-time.After(coretesting.LongWait): 87 c.Fatal("timed out sending application exposed change") 88 } 89 } 90 91 func (s *WorkerSuite) TestValidateConfig(c *gc.C) { 92 s.testValidateConfig(c, func(config *caasfirewaller.Config) { 93 config.ControllerUUID = "" 94 }, `missing ControllerUUID not valid`) 95 96 s.testValidateConfig(c, func(config *caasfirewaller.Config) { 97 config.ModelUUID = "" 98 }, `missing ModelUUID not valid`) 99 100 s.testValidateConfig(c, func(config *caasfirewaller.Config) { 101 config.ApplicationGetter = nil 102 }, `missing ApplicationGetter not valid`) 103 104 s.testValidateConfig(c, func(config *caasfirewaller.Config) { 105 config.ServiceExposer = nil 106 }, `missing ServiceExposer not valid`) 107 108 s.testValidateConfig(c, func(config *caasfirewaller.Config) { 109 config.LifeGetter = nil 110 }, `missing LifeGetter not valid`) 111 112 s.testValidateConfig(c, func(config *caasfirewaller.Config) { 113 config.CharmGetter = nil 114 }, `missing CharmGetter not valid`) 115 116 s.testValidateConfig(c, func(config *caasfirewaller.Config) { 117 config.Logger = nil 118 }, `missing Logger not valid`) 119 } 120 121 func (s *WorkerSuite) testValidateConfig(c *gc.C, f func(*caasfirewaller.Config), expect string) { 122 config := s.config 123 f(&config) 124 w, err := caasfirewaller.NewWorker(config) 125 if err == nil { 126 workertest.DirtyKill(c, w) 127 } 128 c.Check(err, gc.ErrorMatches, expect) 129 } 130 131 func (s *WorkerSuite) TestStartStop(c *gc.C) { 132 w, err := caasfirewaller.NewWorker(s.config) 133 c.Assert(err, jc.ErrorIsNil) 134 workertest.CheckAlive(c, w) 135 workertest.CleanKill(c, w) 136 } 137 138 func (s *WorkerSuite) sendApplicationChange(c *gc.C, appName string) { 139 select { 140 case s.applicationChanges <- []string{appName}: 141 case <-time.After(coretesting.LongWait): 142 c.Fatal("timed out sending applications change") 143 } 144 } 145 146 func (s *WorkerSuite) TestExposedChange(c *gc.C) { 147 w, err := caasfirewaller.NewWorker(s.config) 148 c.Assert(err, jc.ErrorIsNil) 149 defer workertest.CleanKill(c, w) 150 151 s.sendApplicationChange(c, "gitlab") 152 153 s.sendApplicationExposedChange(c) 154 // The last known state on start up was unexposed 155 // so we first call Unexpose(). 156 select { 157 case <-s.serviceUnexposed: 158 case <-time.After(coretesting.LongWait): 159 c.Fatal("timed out waiting for service to be unexposed") 160 } 161 select { 162 case <-s.serviceExposed: 163 c.Fatal("service exposed unexpectedly") 164 case <-time.After(coretesting.ShortWait): 165 } 166 167 s.applicationGetter.exposed = true 168 s.sendApplicationExposedChange(c) 169 select { 170 case <-s.serviceExposed: 171 case <-time.After(coretesting.LongWait): 172 c.Fatal("timed out waiting for service to be exposed") 173 } 174 s.serviceExposer.CheckCallNames(c, "UnexposeService", "ExposeService") 175 s.serviceExposer.CheckCall(c, 1, "ExposeService", "gitlab", 176 map[string]string{ 177 "juju-controller-uuid": coretesting.ControllerTag.Id(), 178 "juju-model-uuid": coretesting.ModelTag.Id()}, 179 config.ConfigAttributes{"juju-external-hostname": "exthost"}) 180 } 181 182 func (s *WorkerSuite) TestUnexposedChange(c *gc.C) { 183 w, err := caasfirewaller.NewWorker(s.config) 184 c.Assert(err, jc.ErrorIsNil) 185 defer workertest.CleanKill(c, w) 186 187 s.sendApplicationChange(c, "gitlab") 188 189 s.applicationGetter.exposed = true 190 s.sendApplicationExposedChange(c) 191 // The last known state on start up was exposed 192 // so we first call Expose(). 193 select { 194 case <-s.serviceExposed: 195 case <-time.After(coretesting.LongWait): 196 c.Fatal("timed out waiting for service to be exposed") 197 } 198 select { 199 case <-s.serviceUnexposed: 200 c.Fatal("service unexposed unexpectedly") 201 case <-time.After(coretesting.ShortWait): 202 } 203 204 s.applicationGetter.exposed = false 205 s.sendApplicationExposedChange(c) 206 select { 207 case <-s.serviceUnexposed: 208 case <-time.After(coretesting.LongWait): 209 c.Fatal("timed out waiting for service to be unexposed") 210 } 211 } 212 213 func (s *WorkerSuite) TestWatchApplicationDead(c *gc.C) { 214 w, err := caasfirewaller.NewWorker(s.config) 215 c.Assert(err, jc.ErrorIsNil) 216 defer workertest.CleanKill(c, w) 217 218 s.lifeGetter.life = life.Dead 219 s.sendApplicationChange(c, "gitlab") 220 221 select { 222 case s.appExposedChange <- struct{}{}: 223 c.Fatal("unexpected watch for app exposed") 224 case <-time.After(coretesting.ShortWait): 225 } 226 227 workertest.CleanKill(c, w) 228 } 229 230 func (s *WorkerSuite) TestRemoveApplicationStopsWatchingApplication(c *gc.C) { 231 // Set up the errors before triggering any events to avoid racing 232 // with the worker loop. First time around the loop the 233 // application's alive, then it's gone. 234 s.lifeGetter.SetErrors(nil, errors.NotFoundf("application")) 235 236 w, err := caasfirewaller.NewWorker(s.config) 237 c.Assert(err, jc.ErrorIsNil) 238 defer workertest.CleanKill(c, w) 239 240 s.sendApplicationChange(c, "gitlab") 241 s.sendApplicationChange(c, "gitlab") 242 243 err = workertest.CheckKilled(c, s.applicationGetter.appWatcher) 244 c.Assert(err, jc.ErrorIsNil) 245 } 246 247 func (s *WorkerSuite) TestRemoveApplicationStopsWorker(c *gc.C) { 248 // Set up the errors before triggering any events to avoid racing 249 // with the worker loop. First time around the loop the 250 // application's alive, then it's gone. 251 s.applicationGetter.SetErrors(nil, nil, errors.NotFoundf("application")) 252 253 w, err := caasfirewaller.NewWorker(s.config) 254 c.Assert(err, jc.ErrorIsNil) 255 defer workertest.CleanKill(c, w) 256 257 s.sendApplicationChange(c, "gitlab") 258 259 s.applicationGetter.exposed = true 260 s.sendApplicationExposedChange(c) 261 select { 262 case <-s.serviceExposed: 263 c.Fatal("removed application should not be exposed") 264 case <-time.After(coretesting.ShortWait): 265 } 266 } 267 268 func (s *WorkerSuite) TestWatcherErrorStopsWorker(c *gc.C) { 269 w, err := caasfirewaller.NewWorker(s.config) 270 c.Assert(err, jc.ErrorIsNil) 271 defer workertest.DirtyKill(c, w) 272 273 s.sendApplicationChange(c, "gitlab") 274 275 s.applicationGetter.appWatcher.KillErr(errors.New("splat")) 276 _ = workertest.CheckKilled(c, s.applicationGetter.appWatcher) 277 _ = workertest.CheckKilled(c, s.applicationGetter.allWatcher) 278 err = workertest.CheckKilled(c, w) 279 c.Assert(err, gc.ErrorMatches, "splat") 280 } 281 282 func (s *WorkerSuite) TestV2CharmSkipProcessing(c *gc.C) { 283 s.charmGetter.charmInfo.Manifest = &charm.Manifest{Bases: []charm.Base{{}}} 284 s.charmGetter.charmInfo.Meta = &charm.Meta{} 285 286 w, err := caasfirewaller.NewWorker(s.config) 287 c.Assert(err, jc.ErrorIsNil) 288 289 s.sendApplicationChange(c, "gitlab") 290 s.waitCharmGetterCalls(c, "ApplicationCharmInfo") 291 292 workertest.CleanKill(c, w) 293 294 s.expectNoLifeGetterCalls(c) 295 } 296 297 func (s *WorkerSuite) TestCharmNotFound(c *gc.C) { 298 w, err := caasfirewaller.NewWorker(s.config) 299 c.Assert(err, jc.ErrorIsNil) 300 301 s.charmGetter.charmInfo = nil 302 303 s.sendApplicationChange(c, "gitlab") 304 s.waitCharmGetterCalls(c, "ApplicationCharmInfo") 305 306 workertest.CleanKill(c, w) 307 308 s.expectNoLifeGetterCalls(c) 309 } 310 311 func (s *WorkerSuite) TestCharmChangesToV2(c *gc.C) { 312 w, err := caasfirewaller.NewWorker(s.config) 313 c.Assert(err, jc.ErrorIsNil) 314 defer workertest.CleanKill(c, w) 315 316 s.sendApplicationChange(c, "gitlab") 317 s.waitCharmGetterCalls(c, "ApplicationCharmInfo") 318 s.waitLifeGetterCalls(c, "Life") 319 320 s.charmGetter.charmInfo.Manifest = &charm.Manifest{Bases: []charm.Base{{}}} 321 s.charmGetter.charmInfo.Meta = &charm.Meta{} 322 s.sendApplicationExposedChange(c) 323 s.waitCharmGetterCalls(c, "ApplicationCharmInfo") 324 325 err = workertest.CheckKilled(c, s.applicationGetter.appWatcher) 326 c.Assert(err, jc.ErrorIsNil) 327 } 328 329 func (s *WorkerSuite) waitCharmGetterCalls(c *gc.C, names ...string) { 330 waitStubCalls(c, &s.charmGetter, names...) 331 } 332 333 func (s *WorkerSuite) waitLifeGetterCalls(c *gc.C, names ...string) { 334 waitStubCalls(c, &s.lifeGetter, names...) 335 } 336 337 type waitStub interface { 338 Calls() []testing.StubCall 339 CheckCallNames(c *gc.C, expected ...string) bool 340 ResetCalls() 341 } 342 343 func waitStubCalls(c *gc.C, stub waitStub, names ...string) { 344 retryCallArgs := coretesting.LongRetryStrategy 345 retryCallArgs.Func = func() error { 346 if len(stub.Calls()) >= len(names) { 347 return nil 348 } 349 return errors.Errorf("Not enough calls yet") 350 } 351 err := retry.Call(retryCallArgs) 352 c.Assert(err, jc.ErrorIsNil) 353 354 stub.CheckCallNames(c, names...) 355 stub.ResetCalls() 356 } 357 358 func (s *WorkerSuite) expectNoLifeGetterCalls(c *gc.C) { 359 totalDuration := clock.WallClock.After(coretesting.ShortWait) 360 for { 361 select { 362 case <-clock.WallClock.After(10 * time.Millisecond): 363 if len(s.lifeGetter.Calls()) > 0 { 364 c.Fatalf("unexpected lifegetter call") 365 } 366 case <-totalDuration: 367 return 368 } 369 } 370 }