github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/caasoperatorprovisioner/worker_test.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasoperatorprovisioner_test 5 6 import ( 7 "fmt" 8 "os" 9 "path/filepath" 10 "reflect" 11 "strconv" 12 "time" 13 14 "github.com/juju/charm/v12" 15 "github.com/juju/clock/testclock" 16 "github.com/juju/errors" 17 "github.com/juju/loggo" 18 "github.com/juju/names/v5" 19 "github.com/juju/retry" 20 jujutesting "github.com/juju/testing" 21 jc "github.com/juju/testing/checkers" 22 "github.com/juju/version/v2" 23 "github.com/juju/worker/v3" 24 "github.com/juju/worker/v3/workertest" 25 gc "gopkg.in/check.v1" 26 27 "github.com/juju/juju/agent" 28 apicaasprovisioner "github.com/juju/juju/api/controller/caasoperatorprovisioner" 29 "github.com/juju/juju/caas" 30 "github.com/juju/juju/core/resources" 31 coretesting "github.com/juju/juju/testing" 32 "github.com/juju/juju/worker/caasoperatorprovisioner" 33 ) 34 35 var _ = gc.Suite(&CAASProvisionerSuite{}) 36 37 type CAASProvisionerSuite struct { 38 coretesting.BaseSuite 39 stub *jujutesting.Stub 40 41 provisionerFacade *mockProvisionerFacade 42 caasClient *mockBroker 43 agentConfig agent.Config 44 clock *testclock.Clock 45 modelTag names.ModelTag 46 } 47 48 func (s *CAASProvisionerSuite) SetUpTest(c *gc.C) { 49 s.BaseSuite.SetUpTest(c) 50 51 s.stub = new(jujutesting.Stub) 52 s.provisionerFacade = newMockProvisionerFacade(s.stub) 53 s.caasClient = &mockBroker{} 54 s.agentConfig = &mockAgentConfig{} 55 s.modelTag = coretesting.ModelTag 56 s.clock = testclock.NewClock(time.Now()) 57 } 58 59 func (s *CAASProvisionerSuite) waitForWorkerStubCalls(c *gc.C, expected []jujutesting.StubCall) { 60 waitForStubCalls(c, s.stub, expected) 61 } 62 63 func waitForStubCalls(c *gc.C, stub *jujutesting.Stub, expected []jujutesting.StubCall) { 64 var calls []jujutesting.StubCall 65 retryCallArgs := coretesting.LongRetryStrategy 66 retryCallArgs.Func = func() error { 67 calls = stub.Calls() 68 if reflect.DeepEqual(calls, expected) { 69 return nil 70 } 71 return errors.NotYetAvailablef("Calls not ready") 72 } 73 err := retry.Call(retryCallArgs) 74 if err != nil { 75 c.Fatalf("failed to see expected calls. saw: %v", calls) 76 } 77 } 78 79 func (s *CAASProvisionerSuite) assertWorker(c *gc.C) worker.Worker { 80 w, err := caasoperatorprovisioner.NewProvisionerWorker(caasoperatorprovisioner.Config{ 81 Facade: s.provisionerFacade, 82 OperatorManager: s.caasClient, 83 ModelTag: s.modelTag, 84 AgentConfig: s.agentConfig, 85 Clock: s.clock, 86 Logger: loggo.GetLogger("test"), 87 }) 88 c.Assert(err, jc.ErrorIsNil) 89 expected := []jujutesting.StubCall{ 90 {"WatchApplications", nil}, 91 } 92 s.waitForWorkerStubCalls(c, expected) 93 s.stub.ResetCalls() 94 return w 95 } 96 97 func (s *CAASProvisionerSuite) TestWorkerStarts(c *gc.C) { 98 w := s.assertWorker(c) 99 workertest.CleanKill(c, w) 100 } 101 102 func (s *CAASProvisionerSuite) assertOperatorCreated(c *gc.C, exists, updateCerts bool) { 103 s.provisionerFacade.life = "alive" 104 s.sendApplicationChanges(c, "myapp") 105 106 expectedCalls := 3 107 retryCallArgs := coretesting.LongRetryStrategy 108 retryCallArgs.Func = func() error { 109 nrCalls := len(s.caasClient.Calls()) 110 if nrCalls >= expectedCalls { 111 return nil 112 } 113 if nrCalls > 0 { 114 s.caasClient.setOperatorExists(false) 115 s.caasClient.setTerminating(false) 116 s.clock.Advance(4 * time.Second) 117 } 118 return errors.Errorf("Not enough calls yet") 119 } 120 err := retry.Call(retryCallArgs) 121 c.Assert(err, jc.ErrorIsNil) 122 123 callNames := []string{"OperatorExists", "Operator", "EnsureOperator"} 124 s.caasClient.CheckCallNames(c, callNames...) 125 c.Assert(s.caasClient.Calls(), gc.HasLen, expectedCalls) 126 127 args := s.caasClient.Calls()[0].Args 128 c.Assert(args, gc.HasLen, 1) 129 c.Assert(args[0], gc.Equals, "myapp") 130 131 ensureIndex := 2 132 args = s.caasClient.Calls()[ensureIndex].Args 133 c.Assert(args, gc.HasLen, 3) 134 c.Assert(args[0], gc.Equals, "myapp") 135 c.Assert(args[1], gc.Equals, "/var/lib/juju") 136 c.Assert(args[2], gc.FitsTypeOf, &caas.OperatorConfig{}) 137 config := args[2].(*caas.OperatorConfig) 138 c.Assert(config.ImageDetails.RegistryPath, gc.Equals, "juju-operator-image") 139 c.Assert(config.Version, gc.Equals, version.MustParse("2.99.0")) 140 c.Assert(config.ResourceTags, jc.DeepEquals, map[string]string{"fred": "mary"}) 141 if s.provisionerFacade.withStorage { 142 c.Assert(config.CharmStorage, jc.DeepEquals, &caas.CharmStorageParams{ 143 Provider: "kubernetes", 144 Size: uint64(1024), 145 ResourceTags: map[string]string{"foo": "bar"}, 146 Attributes: map[string]interface{}{"key": "value"}, 147 }) 148 } else { 149 c.Assert(config.CharmStorage, gc.IsNil) 150 } 151 if updateCerts { 152 c.Assert(config.ConfigMapGeneration, gc.Equals, int64(1)) 153 } else { 154 c.Assert(config.ConfigMapGeneration, gc.Equals, int64(0)) 155 } 156 157 agentFile := filepath.Join(c.MkDir(), "agent.config") 158 err = os.WriteFile(agentFile, config.AgentConf, 0644) 159 c.Assert(err, jc.ErrorIsNil) 160 cfg, err := agent.ReadConfig(agentFile) 161 c.Assert(err, jc.ErrorIsNil) 162 c.Assert(cfg.CACert(), gc.Equals, coretesting.CACert) 163 addr, err := cfg.APIAddresses() 164 c.Assert(err, jc.ErrorIsNil) 165 c.Assert(addr, jc.DeepEquals, []string{"10.0.0.1:17070", "192.18.1.1:17070"}) 166 167 operatorInfo, err := caas.UnmarshalOperatorInfo(config.OperatorInfo) 168 c.Assert(err, jc.ErrorIsNil) 169 c.Assert(operatorInfo.CACert, gc.Equals, coretesting.CACert) 170 c.Assert(operatorInfo.Cert, gc.Equals, coretesting.ServerCert) 171 c.Assert(operatorInfo.PrivateKey, gc.Equals, coretesting.ServerKey) 172 173 retryCallArgs.Func = func() error { 174 if len(s.provisionerFacade.stub.Calls()) > 0 { 175 return nil 176 } 177 return errors.Errorf("Not enough calls yet") 178 } 179 err = retry.Call(retryCallArgs) 180 c.Assert(err, jc.ErrorIsNil) 181 182 if exists { 183 callNames := []string{"ApplicationCharmInfo", "Life", "OperatorProvisioningInfo"} 184 if updateCerts { 185 callNames = append(callNames, "IssueOperatorCertificate") 186 } 187 s.provisionerFacade.stub.CheckCallNames(c, callNames...) 188 c.Assert(s.provisionerFacade.stub.Calls()[0].Args[0], gc.Equals, "myapp") 189 c.Assert(s.provisionerFacade.stub.Calls()[1].Args[0], gc.Equals, "myapp") 190 return 191 } 192 193 s.provisionerFacade.stub.CheckCallNames(c, "ApplicationCharmInfo", "Life", "OperatorProvisioningInfo", "IssueOperatorCertificate", "SetPasswords") 194 c.Assert(s.provisionerFacade.stub.Calls()[0].Args[0], gc.Equals, "myapp") 195 passwords := s.provisionerFacade.stub.Calls()[4].Args[0].([]apicaasprovisioner.ApplicationPassword) 196 197 c.Assert(passwords, gc.HasLen, 1) 198 c.Assert(passwords[0].Name, gc.Equals, "myapp") 199 c.Assert(passwords[0].Password, gc.Not(gc.Equals), "") 200 } 201 202 func (s *CAASProvisionerSuite) TestNewApplicationCreatesNewOperator(c *gc.C) { 203 w := s.assertWorker(c) 204 defer workertest.CleanKill(c, w) 205 206 s.assertOperatorCreated(c, false, false) 207 } 208 209 func (s *CAASProvisionerSuite) TestNewApplicationNoStorage(c *gc.C) { 210 s.provisionerFacade.withStorage = false 211 w := s.assertWorker(c) 212 defer workertest.CleanKill(c, w) 213 214 s.assertOperatorCreated(c, false, false) 215 } 216 217 func (s *CAASProvisionerSuite) TestNewApplicationUpdatesOperator(c *gc.C) { 218 s.caasClient.operatorExists = true 219 s.caasClient.config = &caas.OperatorConfig{ 220 ImageDetails: resources.DockerImageDetails{RegistryPath: "juju-operator-image"}, 221 Version: version.MustParse("2.99.0"), 222 AgentConf: []byte(fmt.Sprintf(` 223 # format 2.0 224 tag: application-myapp 225 upgradedToVersion: 2.99.0 226 controller: controller-deadbeef-1bad-500d-9000-4b1d0d06f00d 227 model: model-deadbeef-0bad-400d-8000-4b1d0d06f00d 228 oldpassword: wow 229 cacert: %s 230 apiaddresses: 231 - 10.0.0.1:17070 232 - 192.18.1.1:17070 233 oldpassword: dxKwhgZPrNzXVTrZSxY1VLHA 234 values: {} 235 `[1:], strconv.Quote(coretesting.CACert))), 236 OperatorInfo: []byte( 237 fmt.Sprintf( 238 "private-key: %s\ncert: %s\nca-cert: %s\n", 239 strconv.Quote(coretesting.ServerKey), 240 strconv.Quote(coretesting.ServerCert), 241 strconv.Quote(coretesting.CACert), 242 ), 243 ), 244 } 245 246 w := s.assertWorker(c) 247 defer workertest.CleanKill(c, w) 248 249 s.assertOperatorCreated(c, true, false) 250 } 251 252 func (s *CAASProvisionerSuite) TestNewApplicationUpdatesOperatorAgentConfAPIAddresses(c *gc.C) { 253 s.caasClient.operatorExists = true 254 s.caasClient.config = &caas.OperatorConfig{ 255 ImageDetails: resources.DockerImageDetails{RegistryPath: "juju-operator-image"}, 256 Version: version.MustParse("2.99.0"), 257 AgentConf: []byte(fmt.Sprintf(` 258 # format 2.0 259 tag: application-myapp 260 upgradedToVersion: 2.99.0 261 controller: controller-deadbeef-1bad-500d-9000-4b1d0d06f00d 262 model: model-deadbeef-0bad-400d-8000-4b1d0d06f00d 263 oldpassword: wow 264 cacert: %s 265 apiaddresses: 266 - 8.8.8.6:17070 # this address will be updated to 10.0.0.1:17070 267 - 192.18.1.1:17070 268 oldpassword: dxKwhgZPrNzXVTrZSxY1VLHA 269 values: {} 270 mongoversion: "0.0" 271 `[1:], strconv.Quote(coretesting.CACert))), 272 OperatorInfo: []byte( 273 fmt.Sprintf( 274 "private-key: %s\ncert: %s\nca-cert: %s\n", 275 strconv.Quote(coretesting.ServerKey), 276 strconv.Quote(coretesting.ServerCert), 277 strconv.Quote(coretesting.CACert), 278 ), 279 ), 280 } 281 282 w := s.assertWorker(c) 283 defer workertest.CleanKill(c, w) 284 s.assertOperatorCreated(c, true, false) 285 } 286 287 func (s *CAASProvisionerSuite) TestNewApplicationUpdatesOperatorAndIssueCerts(c *gc.C) { 288 s.caasClient.operatorExists = true 289 s.caasClient.config = &caas.OperatorConfig{ 290 ImageDetails: resources.DockerImageDetails{RegistryPath: "juju-operator-image"}, 291 Version: version.MustParse("2.99.0"), 292 AgentConf: []byte(fmt.Sprintf(` 293 # format 2.0 294 tag: application-myapp 295 upgradedToVersion: 2.99.0 296 controller: controller-deadbeef-1bad-500d-9000-4b1d0d06f00d 297 model: model-deadbeef-0bad-400d-8000-4b1d0d06f00d 298 oldpassword: wow 299 cacert: %s 300 apiaddresses: 301 - 10.0.0.1:17070 302 - 192.18.1.1:17070 303 oldpassword: dxKwhgZPrNzXVTrZSxY1VLHA 304 values: {} 305 `[1:], strconv.Quote(coretesting.CACert))), 306 ConfigMapGeneration: 1, 307 } 308 309 w := s.assertWorker(c) 310 defer workertest.CleanKill(c, w) 311 312 s.assertOperatorCreated(c, true, true) 313 } 314 315 func (s *CAASProvisionerSuite) TestNewApplicationWaitsOperatorTerminated(c *gc.C) { 316 s.caasClient.operatorExists = true 317 w := s.assertWorker(c) 318 defer workertest.CleanKill(c, w) 319 320 s.caasClient.setTerminating(true) 321 s.provisionerFacade.life = "alive" 322 s.sendApplicationChanges(c, "myapp") 323 324 lastLen := 0 325 gotOperatorCall := false 326 327 retryCallArgs := coretesting.LongRetryStrategy 328 retryCallArgs.Func = func() error { 329 calls := s.caasClient.Calls() 330 newCalls := calls[lastLen:] 331 lastLen = len(calls) 332 for _, call := range newCalls { 333 c.Logf("call to %s", call.FuncName) 334 switch call.FuncName { 335 case "OperatorExists": 336 s.caasClient.setOperatorExists(false) 337 s.caasClient.setTerminating(false) 338 s.clock.Advance(4 * time.Second) 339 case "Operator": 340 gotOperatorCall = true 341 case "EnsureOperator": 342 if !gotOperatorCall { 343 c.Errorf("missing call to Operator") 344 } 345 return nil 346 } 347 } 348 return errors.Errorf("missing expected calls") 349 } 350 err := retry.Call(retryCallArgs) 351 if err != nil { 352 c.Errorf("worker didn't wait for old operator to terminate") 353 } 354 } 355 356 func (s *CAASProvisionerSuite) TestApplicationDeletedRemovesOperator(c *gc.C) { 357 w := s.assertWorker(c) 358 defer workertest.CleanKill(c, w) 359 360 s.assertOperatorCreated(c, false, false) 361 s.caasClient.ResetCalls() 362 s.provisionerFacade.life = "dead" 363 s.sendApplicationChanges(c, "myapp") 364 365 retryCallArgs := coretesting.LongRetryStrategy 366 retryCallArgs.Func = func() error { 367 if len(s.caasClient.Calls()) > 0 { 368 return nil 369 } 370 return errors.Errorf("Not enough calls yet") 371 } 372 err := retry.Call(retryCallArgs) 373 c.Assert(err, jc.ErrorIsNil) 374 s.caasClient.CheckCallNames(c, "DeleteOperator") 375 c.Assert(s.caasClient.Calls()[0].Args[0], gc.Equals, "myapp") 376 } 377 378 func (s *CAASProvisionerSuite) TestV2CharmSkipsProcessing(c *gc.C) { 379 w := s.assertWorker(c) 380 defer workertest.CleanKill(c, w) 381 382 s.provisionerFacade.charmInfo.Manifest.Bases = []charm.Base{{}} 383 s.sendApplicationChanges(c, "app") 384 385 workertest.CheckAlive(c, w) 386 } 387 388 func (s *CAASProvisionerSuite) TestNotFoundCharmSkipsProcessing(c *gc.C) { 389 w := s.assertWorker(c) 390 defer workertest.CleanKill(c, w) 391 392 s.provisionerFacade.charmInfo = nil 393 s.sendApplicationChanges(c, "no-app") 394 395 workertest.CheckAlive(c, w) 396 } 397 398 func (s *CAASProvisionerSuite) sendApplicationChanges(c *gc.C, appNames ...string) { 399 select { 400 case s.provisionerFacade.applicationsWatcher.changes <- appNames: 401 case <-time.After(coretesting.LongWait): 402 c.Fatal("timed out sending applications change") 403 } 404 }