github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/controller/caasunitprovisioner/provisioner_test.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasunitprovisioner_test 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/charm/v12" 11 "github.com/juju/clock" 12 "github.com/juju/clock/testclock" 13 "github.com/juju/names/v5" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/worker/v3/workertest" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/apiserver/common" 19 "github.com/juju/juju/apiserver/facades/controller/caasunitprovisioner" 20 apiservertesting "github.com/juju/juju/apiserver/testing" 21 k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 22 "github.com/juju/juju/core/constraints" 23 "github.com/juju/juju/core/life" 24 "github.com/juju/juju/core/network" 25 "github.com/juju/juju/core/status" 26 "github.com/juju/juju/rpc/params" 27 "github.com/juju/juju/state" 28 statetesting "github.com/juju/juju/state/testing" 29 storageprovider "github.com/juju/juju/storage/provider" 30 coretesting "github.com/juju/juju/testing" 31 jujuversion "github.com/juju/juju/version" 32 ) 33 34 var _ = gc.Suite(&CAASProvisionerSuite{}) 35 36 type CAASProvisionerSuite struct { 37 coretesting.BaseSuite 38 39 clock clock.Clock 40 st *mockState 41 storage *mockStorage 42 storagePoolManager *mockStoragePoolManager 43 registry *mockStorageRegistry 44 devices *mockDeviceBackend 45 applicationsChanges chan []string 46 podSpecChanges chan struct{} 47 scaleChanges chan struct{} 48 settingsChanges chan []string 49 50 resources *common.Resources 51 authorizer *apiservertesting.FakeAuthorizer 52 facade *caasunitprovisioner.Facade 53 54 isRawK8sSpec *bool 55 } 56 57 func boolptr(i bool) *bool { 58 return &i 59 } 60 61 func (s *CAASProvisionerSuite) SetUpTest(c *gc.C) { 62 s.BaseSuite.SetUpTest(c) 63 64 s.applicationsChanges = make(chan []string, 1) 65 s.podSpecChanges = make(chan struct{}, 1) 66 s.scaleChanges = make(chan struct{}, 1) 67 s.settingsChanges = make(chan []string, 1) 68 s.isRawK8sSpec = boolptr(false) 69 s.st = &mockState{ 70 application: mockApplication{ 71 tag: names.NewApplicationTag("gitlab"), 72 life: state.Alive, 73 scaleWatcher: statetesting.NewMockNotifyWatcher(s.scaleChanges), 74 settingsWatcher: statetesting.NewMockStringsWatcher(s.settingsChanges), 75 scale: 5, 76 }, 77 applicationsWatcher: statetesting.NewMockStringsWatcher(s.applicationsChanges), 78 model: mockModel{ 79 podSpecWatcher: statetesting.NewMockNotifyWatcher(s.podSpecChanges), 80 isRawK8sSpec: s.isRawK8sSpec, 81 }, 82 unit: mockUnit{ 83 life: state.Dying, 84 }, 85 } 86 s.storage = &mockStorage{ 87 storageFilesystems: make(map[names.StorageTag]names.FilesystemTag), 88 storageVolumes: make(map[names.StorageTag]names.VolumeTag), 89 storageAttachments: make(map[names.UnitTag]names.StorageTag), 90 backingVolume: names.NewVolumeTag("66"), 91 } 92 s.storagePoolManager = &mockStoragePoolManager{} 93 s.devices = &mockDeviceBackend{} 94 s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.applicationsWatcher) }) 95 s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.application.scaleWatcher) }) 96 s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.application.settingsWatcher) }) 97 s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.model.podSpecWatcher) }) 98 99 s.resources = common.NewResources() 100 s.authorizer = &apiservertesting.FakeAuthorizer{ 101 Tag: names.NewMachineTag("0"), 102 Controller: true, 103 } 104 s.clock = testclock.NewClock(time.Now()) 105 s.PatchValue(&jujuversion.OfficialBuild, 0) 106 107 facade, err := caasunitprovisioner.NewFacade( 108 s.resources, s.authorizer, s.st, s.storage, s.devices, 109 s.storagePoolManager, s.registry, nil, nil, s.clock) 110 c.Assert(err, jc.ErrorIsNil) 111 s.facade = facade 112 } 113 114 func (s *CAASProvisionerSuite) TestPermission(c *gc.C) { 115 s.authorizer = &apiservertesting.FakeAuthorizer{ 116 Tag: names.NewMachineTag("0"), 117 } 118 _, err := caasunitprovisioner.NewFacade( 119 s.resources, s.authorizer, s.st, s.storage, s.devices, 120 s.storagePoolManager, s.registry, nil, nil, s.clock) 121 c.Assert(err, gc.ErrorMatches, "permission denied") 122 } 123 124 func (s *CAASProvisionerSuite) TestWatchApplications(c *gc.C) { 125 applicationNames := []string{"db2", "hadoop"} 126 s.applicationsChanges <- applicationNames 127 result, err := s.facade.WatchApplications() 128 c.Assert(err, jc.ErrorIsNil) 129 c.Assert(result.Error, gc.IsNil) 130 c.Assert(result.StringsWatcherId, gc.Equals, "1") 131 c.Assert(result.Changes, jc.DeepEquals, applicationNames) 132 133 resource := s.resources.Get("1") 134 c.Assert(resource, gc.Equals, s.st.applicationsWatcher) 135 } 136 137 func (s *CAASProvisionerSuite) TestWatchPodSpec(c *gc.C) { 138 s.podSpecChanges <- struct{}{} 139 140 results, err := s.facade.WatchPodSpec(params.Entities{ 141 Entities: []params.Entity{ 142 {Tag: "application-gitlab"}, 143 {Tag: "unit-gitlab-0"}, 144 }, 145 }) 146 c.Assert(err, jc.ErrorIsNil) 147 c.Assert(results.Results, gc.HasLen, 2) 148 c.Assert(results.Results[0].Error, gc.IsNil) 149 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 150 Message: `"unit-gitlab-0" is not a valid application tag`, 151 }) 152 153 c.Assert(results.Results[0].NotifyWatcherId, gc.Equals, "1") 154 resource := s.resources.Get("1") 155 c.Assert(resource, gc.Equals, s.st.model.podSpecWatcher) 156 } 157 158 func (s *CAASProvisionerSuite) TestWatchApplicationsScale(c *gc.C) { 159 s.scaleChanges <- struct{}{} 160 161 results, err := s.facade.WatchApplicationsScale(params.Entities{ 162 Entities: []params.Entity{ 163 {Tag: "application-gitlab"}, 164 {Tag: "unit-gitlab-0"}, 165 }, 166 }) 167 c.Assert(err, jc.ErrorIsNil) 168 c.Assert(results.Results, gc.HasLen, 2) 169 c.Assert(results.Results[0].Error, gc.IsNil) 170 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 171 Message: `"unit-gitlab-0" is not a valid application tag`, 172 }) 173 174 c.Assert(results.Results[0].NotifyWatcherId, gc.Equals, "1") 175 resource := s.resources.Get("1") 176 c.Assert(resource, gc.Equals, s.st.application.scaleWatcher) 177 } 178 179 func (s *CAASProvisionerSuite) TestWatchApplicationsConfigSetingsHash(c *gc.C) { 180 s.settingsChanges <- []string{"hash"} 181 182 results, err := s.facade.WatchApplicationsTrustHash(params.Entities{ 183 Entities: []params.Entity{ 184 {Tag: "application-gitlab"}, 185 {Tag: "unit-gitlab-0"}, 186 }, 187 }) 188 c.Assert(err, jc.ErrorIsNil) 189 c.Assert(results.Results, gc.HasLen, 2) 190 c.Assert(results.Results[0].Error, gc.IsNil) 191 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 192 Message: `"unit-gitlab-0" is not a valid application tag`, 193 }) 194 195 c.Assert(results.Results[0].StringsWatcherId, gc.Equals, "1") 196 resource := s.resources.Get("1") 197 c.Assert(resource, gc.Equals, s.st.application.settingsWatcher) 198 } 199 200 func (s *CAASProvisionerSuite) assertProvisioningInfo(c *gc.C, isRawK8sSpec bool) { 201 s.st.application.units = []caasunitprovisioner.Unit{ 202 &mockUnit{name: "gitlab/0", life: state.Dying}, 203 &mockUnit{name: "gitlab/1", life: state.Alive}, 204 } 205 s.st.application.charm = &mockCharm{ 206 meta: charm.Meta{ 207 Storage: map[string]charm.Storage{ 208 "data": { 209 Name: "data", 210 Type: charm.StorageFilesystem, 211 ReadOnly: true, 212 }, 213 "logs": { 214 Name: "logs", 215 Type: charm.StorageFilesystem, 216 }, 217 }, 218 Deployment: &charm.Deployment{ 219 DeploymentType: charm.DeploymentStateful, 220 ServiceType: charm.ServiceLoadBalancer, 221 }, 222 }, 223 } 224 *s.isRawK8sSpec = isRawK8sSpec 225 results, err := s.facade.ProvisioningInfo(params.Entities{ 226 Entities: []params.Entity{ 227 {Tag: "application-gitlab"}, 228 {Tag: "unit-gitlab-0"}, 229 }, 230 }) 231 c.Assert(err, jc.ErrorIsNil) 232 expectedVersion := jujuversion.Current 233 expectedVersion.Build = 666 234 // Maps are harder to check... 235 // http://ci.jujucharms.com/job/make-check-juju/4853/testReport/junit/github/com_juju_juju_apiserver_facades_controller_caasunitprovisioner/TestAll/ 236 expectedResult := ¶ms.KubernetesProvisioningInfo{ 237 DeploymentInfo: ¶ms.KubernetesDeploymentInfo{ 238 DeploymentType: "stateful", 239 ServiceType: "loadbalancer", 240 }, 241 ImageRepo: params.DockerImageInfo{ 242 RegistryPath: fmt.Sprintf("docker.io/jujusolutions/jujud-operator:%s", expectedVersion.String()), 243 }, 244 Devices: []params.KubernetesDeviceParams{ 245 { 246 Type: "nvidia.com/gpu", 247 Count: 3, 248 Attributes: map[string]string{"gpu": "nvidia-tesla-p100"}, 249 }, 250 }, 251 Constraints: constraints.MustParse("mem=64G"), 252 Tags: map[string]string{ 253 "juju-model-uuid": coretesting.ModelTag.Id(), 254 "juju-controller-uuid": coretesting.ControllerTag.Id()}, 255 } 256 if isRawK8sSpec { 257 expectedResult.RawK8sSpec = "raw spec(gitlab)" 258 } else { 259 expectedResult.PodSpec = "spec(gitlab)" 260 } 261 expectedFileSystems := map[string]params.KubernetesFilesystemParams{ 262 "data": { 263 StorageName: "data", 264 Provider: string(k8sconstants.StorageProviderType), 265 Size: 100, 266 Attributes: map[string]interface{}{ 267 "storage-class": "k8s-storage", 268 "foo": "bar", 269 }, 270 Tags: map[string]string{ 271 "juju-storage-owner": "gitlab", 272 "juju-model-uuid": coretesting.ModelTag.Id(), 273 "juju-controller-uuid": coretesting.ControllerTag.Id()}, 274 Attachment: ¶ms.KubernetesFilesystemAttachmentParams{ 275 Provider: string(k8sconstants.StorageProviderType), 276 MountPoint: "/var/lib/juju/storage/data/0", 277 ReadOnly: true, 278 }, 279 }, 280 "logs": { 281 StorageName: "logs", 282 Provider: string(storageprovider.RootfsProviderType), 283 Size: 200, 284 Attributes: map[string]interface{}{}, 285 Tags: map[string]string{ 286 "juju-storage-owner": "gitlab", 287 "juju-model-uuid": coretesting.ModelTag.Id(), 288 "juju-controller-uuid": coretesting.ControllerTag.Id()}, 289 Attachment: ¶ms.KubernetesFilesystemAttachmentParams{ 290 Provider: string(storageprovider.RootfsProviderType), 291 MountPoint: "/var/lib/juju/storage/logs/0", 292 }, 293 }} 294 c.Assert(results.Results[0].Error, gc.IsNil) 295 obtained := results.Results[0].Result 296 c.Assert(obtained, gc.NotNil) 297 c.Assert(obtained.PodSpec, jc.DeepEquals, expectedResult.PodSpec) 298 c.Assert(obtained.RawK8sSpec, jc.DeepEquals, expectedResult.RawK8sSpec) 299 c.Assert(obtained.DeploymentInfo, jc.DeepEquals, expectedResult.DeploymentInfo) 300 c.Assert(obtained.ImageRepo.RegistryPath, gc.Equals, expectedResult.ImageRepo.RegistryPath) 301 c.Assert(obtained.CharmModifiedVersion, jc.DeepEquals, 888) 302 c.Assert(len(obtained.Filesystems), gc.Equals, len(expectedFileSystems)) 303 for _, fs := range obtained.Filesystems { 304 c.Assert(fs, gc.DeepEquals, expectedFileSystems[fs.StorageName]) 305 } 306 c.Assert(obtained.Devices, jc.DeepEquals, expectedResult.Devices) 307 c.Assert(obtained.Constraints, jc.DeepEquals, expectedResult.Constraints) 308 c.Assert(obtained.Tags, jc.DeepEquals, expectedResult.Tags) 309 c.Assert(results.Results[1], jc.DeepEquals, params.KubernetesProvisioningInfoResult{ 310 Error: ¶ms.Error{ 311 Message: `"unit-gitlab-0" is not a valid application tag`, 312 }, 313 }) 314 s.st.CheckCallNames(c, "Model", "Application", "ControllerConfig", "ResolveConstraints") 315 s.st.CheckCall(c, 3, "ResolveConstraints", constraints.MustParse("mem=64G")) 316 s.storagePoolManager.CheckCallNames(c, "Get", "Get") 317 } 318 319 func (s *CAASProvisionerSuite) TestProvisioningInfoK8sSpec(c *gc.C) { 320 s.assertProvisioningInfo(c, false) 321 } 322 func (s *CAASProvisionerSuite) TestProvisioningInfoRawK8sSpec(c *gc.C) { 323 s.assertProvisioningInfo(c, true) 324 } 325 326 func (s *CAASProvisionerSuite) TestApplicationScale(c *gc.C) { 327 results, err := s.facade.ApplicationsScale(params.Entities{ 328 Entities: []params.Entity{ 329 {Tag: "application-gitlab"}, 330 {Tag: "unit-gitlab-0"}, 331 }, 332 }) 333 c.Assert(err, jc.ErrorIsNil) 334 c.Assert(results, jc.DeepEquals, params.IntResults{ 335 Results: []params.IntResult{{ 336 Result: 5, 337 }, { 338 Error: ¶ms.Error{ 339 Message: `"unit-gitlab-0" is not a valid application tag`, 340 }, 341 }}, 342 }) 343 s.st.CheckCallNames(c, "Application") 344 } 345 346 func (s *CAASProvisionerSuite) TestDeploymentMode(c *gc.C) { 347 s.st.application.charm = &mockCharm{ 348 meta: charm.Meta{ 349 Deployment: &charm.Deployment{ 350 DeploymentMode: charm.ModeWorkload, 351 }, 352 }, 353 } 354 results, err := s.facade.DeploymentMode(params.Entities{ 355 Entities: []params.Entity{ 356 {Tag: "application-gitlab"}, 357 {Tag: "unit-gitlab-0"}, 358 }, 359 }) 360 c.Assert(err, jc.ErrorIsNil) 361 c.Assert(results, jc.DeepEquals, params.StringResults{ 362 Results: []params.StringResult{{ 363 Result: "workload", 364 }, { 365 Error: ¶ms.Error{ 366 Message: `"unit-gitlab-0" is not a valid application tag`, 367 }, 368 }}, 369 }) 370 s.st.CheckCallNames(c, "Application") 371 } 372 373 func (s *CAASProvisionerSuite) TestLife(c *gc.C) { 374 results, err := s.facade.Life(params.Entities{ 375 Entities: []params.Entity{ 376 {Tag: "unit-gitlab-0"}, 377 {Tag: "application-gitlab"}, 378 {Tag: "machine-0"}, 379 }, 380 }) 381 c.Assert(err, jc.ErrorIsNil) 382 c.Assert(results, jc.DeepEquals, params.LifeResults{ 383 Results: []params.LifeResult{{ 384 Life: life.Dying, 385 }, { 386 Life: life.Alive, 387 }, { 388 Error: ¶ms.Error{ 389 Code: "unauthorized access", 390 Message: "permission denied", 391 }, 392 }}, 393 }) 394 } 395 396 func (s *CAASProvisionerSuite) TestApplicationConfig(c *gc.C) { 397 results, err := s.facade.ApplicationsConfig(params.Entities{ 398 Entities: []params.Entity{ 399 {Tag: "application-gitlab"}, 400 {Tag: "unit-gitlab-0"}, 401 }, 402 }) 403 c.Assert(err, jc.ErrorIsNil) 404 c.Assert(results.Results, gc.HasLen, 2) 405 c.Assert(results.Results[0].Error, gc.IsNil) 406 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 407 Message: `"unit-gitlab-0" is not a valid application tag`, 408 }) 409 c.Assert(results.Results[0].Config, jc.DeepEquals, map[string]interface{}{"foo": "bar"}) 410 } 411 412 func (s *CAASProvisionerSuite) TestClearApplicationsResources(c *gc.C) { 413 results, err := s.facade.ClearApplicationsResources(params.Entities{ 414 Entities: []params.Entity{ 415 {Tag: "application-gitlab"}, 416 {Tag: "unit-gitlab-0"}, 417 }, 418 }) 419 c.Assert(err, jc.ErrorIsNil) 420 c.Assert(results, jc.DeepEquals, params.ErrorResults{ 421 Results: []params.ErrorResult{ 422 {}, 423 { 424 Error: ¶ms.Error{ 425 Message: `"unit-gitlab-0" is not a valid application tag`, 426 }, 427 }}, 428 }) 429 s.st.CheckCallNames(c, "Application") 430 s.st.application.CheckCallNames(c, "ClearResources") 431 } 432 433 func strPtr(s string) *string { 434 return &s 435 } 436 437 func intPtr(i int) *int { 438 return &i 439 } 440 func int64Ptr(i int64) *int64 { 441 return &i 442 } 443 444 func (s *CAASProvisionerSuite) TestUpdateApplicationsStatelessUnits(c *gc.C) { 445 s.assertUpdateApplicationsStatelessUnits(c, true) 446 } 447 448 func (s *CAASProvisionerSuite) TestUpdateApplicationsStatelessUnitsWithoutGeneration(c *gc.C) { 449 s.assertUpdateApplicationsStatelessUnits(c, false) 450 } 451 452 func (s *CAASProvisionerSuite) assertUpdateApplicationsStatelessUnits(c *gc.C, withGeneration bool) { 453 s.st.application.units = []caasunitprovisioner.Unit{ 454 &mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive}, 455 &mockUnit{name: "gitlab/1", life: state.Alive}, 456 &mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "uuid2"}, life: state.Alive}, 457 &mockUnit{name: "gitlab/3", life: state.Alive}, 458 } 459 s.st.application.scale = 4 460 461 units := []params.ApplicationUnitParams{ 462 {ProviderId: "uuid", Address: "address", Ports: []string{"port"}, 463 Status: "allocating", Info: ""}, 464 {ProviderId: "another-uuid", Address: "another-address", Ports: []string{"another-port"}, 465 Status: "allocating", Info: "another message"}, 466 {ProviderId: "new-uuid", Address: "new-address", Ports: []string{"new-port"}, 467 Status: "running", Info: "new message"}, 468 {ProviderId: "really-new-uuid", Address: "really-new-address", Ports: []string{"really-new-port"}, 469 Status: "running", Info: "really new message"}, 470 } 471 472 args := []params.UpdateApplicationUnits{ 473 {ApplicationTag: "application-another", Units: []params.ApplicationUnitParams{}}, 474 } 475 gitlab := params.UpdateApplicationUnits{ApplicationTag: "application-gitlab", Units: units, Scale: intPtr(4)} 476 if withGeneration { 477 gitlab.Generation = int64Ptr(1) 478 } 479 args = append(args, gitlab) 480 481 results, err := s.facade.UpdateApplicationsUnits(params.UpdateApplicationUnitArgs{Args: args}) 482 c.Assert(err, jc.ErrorIsNil) 483 c.Assert(results, gc.DeepEquals, params.UpdateApplicationUnitResults{ 484 Results: []params.UpdateApplicationUnitResult{ 485 {Error: ¶ms.Error{Message: "application another not found", Code: "not found"}}, 486 {Error: nil}, 487 }, 488 }) 489 s.st.application.CheckCallNames(c, "Life", "AddOperation", "Name", "GetScale") 490 s.st.application.CheckCall(c, 1, "AddOperation", state.UnitUpdateProperties{ 491 ProviderId: strPtr("really-new-uuid"), 492 Address: strPtr("really-new-address"), Ports: &[]string{"really-new-port"}, 493 CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "really new message"}, 494 AgentStatus: &status.StatusInfo{Status: status.Idle}, 495 }) 496 s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 497 // CloudContainer message is not overwritten based on agent status 498 s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 499 ProviderId: strPtr("uuid"), 500 Address: strPtr("address"), Ports: &[]string{"port"}, 501 CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: ""}, 502 AgentStatus: &status.StatusInfo{Status: status.Allocating}, 503 }) 504 s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 505 // CloudContainer message is not overwritten based on agent status 506 s.st.application.units[1].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 507 ProviderId: strPtr("another-uuid"), 508 Address: strPtr("another-address"), Ports: &[]string{"another-port"}, 509 CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: "another message"}, 510 AgentStatus: &status.StatusInfo{Status: status.Allocating, Message: "another message"}, 511 }) 512 s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life", "DestroyOperation", "UpdateOperation") 513 s.st.application.units[2].(*mockUnit).CheckCall(c, 2, "UpdateOperation", state.UnitUpdateProperties{ 514 AgentStatus: &status.StatusInfo{Status: status.Idle}, 515 CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"}, 516 }) 517 s.st.application.units[3].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 518 s.st.application.units[3].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 519 ProviderId: strPtr("new-uuid"), 520 Address: strPtr("new-address"), Ports: &[]string{"new-port"}, 521 CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "new message"}, 522 AgentStatus: &status.StatusInfo{Status: status.Idle}, 523 }) 524 } 525 526 func (s *CAASProvisionerSuite) TestUpdateApplicationsScaleChange(c *gc.C) { 527 s.st.application.units = []caasunitprovisioner.Unit{ 528 &mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive}, 529 &mockUnit{name: "gitlab/1", life: state.Alive}, 530 &mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "uuid2"}, life: state.Alive}, 531 } 532 s.st.application.scale = 3 533 534 units := []params.ApplicationUnitParams{ 535 {ProviderId: "uuid", Address: "address", Ports: []string{"port"}, 536 Status: "allocating", Info: ""}, 537 {ProviderId: "another-uuid", Address: "another-address", Ports: []string{"another-port"}, 538 Status: "allocating", Info: "another message"}, 539 } 540 args := params.UpdateApplicationUnitArgs{ 541 Args: []params.UpdateApplicationUnits{ 542 { 543 ApplicationTag: "application-gitlab", 544 Units: units, 545 Scale: intPtr(2), 546 Generation: int64Ptr(1), 547 Status: params.EntityStatus{Status: status.Active, Info: "working"}}, 548 }, 549 } 550 results, err := s.facade.UpdateApplicationsUnits(args) 551 c.Assert(err, jc.ErrorIsNil) 552 c.Assert(results, gc.DeepEquals, params.UpdateApplicationUnitResults{ 553 Results: []params.UpdateApplicationUnitResult{ 554 {}, 555 }, 556 }) 557 s.st.application.CheckCallNames(c, "SetOperatorStatus", "Life", "Name", "GetScale", "SetScale") 558 now := s.clock.Now() 559 s.st.application.CheckCall(c, 0, "SetOperatorStatus", 560 status.StatusInfo{Status: status.Active, Message: "working", Since: &now}) 561 s.st.application.CheckCall(c, 4, "SetScale", 2) 562 563 s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 564 // CloudContainer message is not overwritten based on agent status 565 s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 566 ProviderId: strPtr("uuid"), 567 Address: strPtr("address"), Ports: &[]string{"port"}, 568 CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: ""}, 569 AgentStatus: &status.StatusInfo{Status: status.Allocating}, 570 }) 571 s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 572 // CloudContainer message is not overwritten based on agent status 573 s.st.application.units[1].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 574 ProviderId: strPtr("another-uuid"), 575 Address: strPtr("another-address"), Ports: &[]string{"another-port"}, 576 CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: "another message"}, 577 AgentStatus: &status.StatusInfo{Status: status.Allocating, Message: "another message"}, 578 }) 579 s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life", "DestroyOperation", "UpdateOperation") 580 s.st.application.units[2].(*mockUnit).CheckCall(c, 2, "UpdateOperation", state.UnitUpdateProperties{ 581 AgentStatus: &status.StatusInfo{Status: status.Idle}, 582 CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"}, 583 }) 584 } 585 586 func (s *CAASProvisionerSuite) TestUpdateApplicationsUnknownScale(c *gc.C) { 587 s.st.application.units = []caasunitprovisioner.Unit{ 588 &mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive}, 589 &mockUnit{name: "gitlab/1", life: state.Alive}, 590 &mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "uuid2"}, life: state.Alive}, 591 } 592 s.st.application.scale = 3 593 594 units := []params.ApplicationUnitParams{ 595 {ProviderId: "uuid", Address: "address", Ports: []string{"port"}, 596 Status: "allocating", Info: ""}, 597 {ProviderId: "another-uuid", Address: "another-address", Ports: []string{"another-port"}, 598 Status: "allocating", Info: "another message"}, 599 } 600 args := params.UpdateApplicationUnitArgs{ 601 Args: []params.UpdateApplicationUnits{ 602 {ApplicationTag: "application-gitlab", Units: units, Scale: nil}, 603 }, 604 } 605 results, err := s.facade.UpdateApplicationsUnits(args) 606 c.Assert(err, jc.ErrorIsNil) 607 c.Assert(results, gc.DeepEquals, params.UpdateApplicationUnitResults{ 608 Results: []params.UpdateApplicationUnitResult{ 609 {nil, nil}, 610 }, 611 }) 612 s.st.application.CheckCallNames(c, "Life", "Name") 613 614 s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 615 // CloudContainer message is not overwritten based on agent status 616 s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 617 ProviderId: strPtr("uuid"), 618 Address: strPtr("address"), Ports: &[]string{"port"}, 619 CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: ""}, 620 AgentStatus: &status.StatusInfo{Status: status.Allocating}, 621 }) 622 s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 623 // CloudContainer message is not overwritten based on agent status 624 s.st.application.units[1].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 625 ProviderId: strPtr("another-uuid"), 626 Address: strPtr("another-address"), Ports: &[]string{"another-port"}, 627 CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: "another message"}, 628 AgentStatus: &status.StatusInfo{Status: status.Allocating, Message: "another message"}, 629 }) 630 s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 631 s.st.application.units[2].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 632 AgentStatus: &status.StatusInfo{Status: status.Idle}, 633 CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"}, 634 }) 635 } 636 637 func (s *CAASProvisionerSuite) TestUpdateApplicationsUnitsNotAlive(c *gc.C) { 638 s.st.application.units = []caasunitprovisioner.Unit{ 639 &mockUnit{name: "gitlab/0", life: state.Alive}, 640 &mockUnit{name: "gitlab/1", life: state.Alive}, 641 &mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "uuid2"}, life: state.Alive}, 642 } 643 s.st.application.scale = 3 644 s.st.application.life = state.Dying 645 646 units := []params.ApplicationUnitParams{ 647 {ProviderId: "uuid", UnitTag: "unit-gitlab-0", Address: "address", Ports: []string{"port"}, 648 Status: "running", Info: "message"}, 649 {ProviderId: "another-uuid", UnitTag: "unit-gitlab-1", Address: "another-address", Ports: []string{"another-port"}, 650 Status: "error", Info: "another message"}, 651 } 652 args := params.UpdateApplicationUnitArgs{ 653 Args: []params.UpdateApplicationUnits{ 654 {ApplicationTag: "application-gitlab", Units: units, Scale: intPtr(3), Generation: int64Ptr(1)}, 655 }, 656 } 657 results, err := s.facade.UpdateApplicationsUnits(args) 658 c.Assert(err, jc.ErrorIsNil) 659 c.Assert(results, gc.DeepEquals, params.UpdateApplicationUnitResults{ 660 Results: []params.UpdateApplicationUnitResult{ 661 {nil, nil}, 662 }, 663 }) 664 s.st.application.CheckCallNames(c, "Life", "Name", "GetScale") 665 s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life") 666 s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life") 667 s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life") 668 } 669 670 func (s *CAASProvisionerSuite) TestUpdateApplicationsUnitsWithStorage(c *gc.C) { 671 s.st.application.units = []caasunitprovisioner.Unit{ 672 &mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive}, 673 &mockUnit{name: "gitlab/1", life: state.Alive}, 674 &mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "gone-uuid"}, life: state.Alive}, 675 &mockUnit{name: "gitlab/3", containerInfo: &mockContainerInfo{providerId: "gone-uuid2"}, life: state.Alive}, 676 } 677 s.st.model.containers = []state.CloudContainer{ 678 &mockContainerInfo{unitName: "gitlab/0", providerId: "uuid"}, 679 &mockContainerInfo{unitName: "gitlab/1", providerId: "another-uuid"}, 680 } 681 s.storage.storageFilesystems[names.NewStorageTag("data/0")] = names.NewFilesystemTag("gitlab/0/0") 682 s.storage.storageFilesystems[names.NewStorageTag("data/1")] = names.NewFilesystemTag("gitlab/1/0") 683 s.storage.storageFilesystems[names.NewStorageTag("data/2")] = names.NewFilesystemTag("gitlab/2/0") 684 s.storage.storageVolumes[names.NewStorageTag("data/0")] = names.NewVolumeTag("0") 685 s.storage.storageVolumes[names.NewStorageTag("data/1")] = names.NewVolumeTag("1") 686 s.storage.storageAttachments[names.NewUnitTag("gitlab/0")] = names.NewStorageTag("data/0") 687 s.storage.storageAttachments[names.NewUnitTag("gitlab/1")] = names.NewStorageTag("data/1") 688 s.storage.storageAttachments[names.NewUnitTag("gitlab/2")] = names.NewStorageTag("data/2") 689 690 units := []params.ApplicationUnitParams{ 691 {ProviderId: "uuid", Address: "address", Ports: []string{"port"}, 692 Status: "running", Info: "message", Stateful: true, 693 FilesystemInfo: []params.KubernetesFilesystemInfo{ 694 {StorageName: "data", FilesystemId: "fs-id", Size: 100, MountPoint: "/path/to/here", ReadOnly: true, 695 Status: "pending", Info: "not ready", 696 Volume: params.KubernetesVolumeInfo{ 697 VolumeId: "vol-id", Size: 100, Persistent: true, 698 Status: "pending", Info: "vol not ready", 699 }}, 700 }, 701 }, 702 {ProviderId: "another-uuid", Address: "another-address", Ports: []string{"another-port"}, 703 Status: "running", Info: "another message", Stateful: true, 704 FilesystemInfo: []params.KubernetesFilesystemInfo{ 705 {StorageName: "data", FilesystemId: "fs-id2", Size: 200, MountPoint: "/path/to/there", ReadOnly: true, 706 Status: "attached", Info: "ready", 707 Volume: params.KubernetesVolumeInfo{ 708 VolumeId: "vol-id2", Size: 200, Persistent: true, 709 Status: "attached", Info: "vol ready", 710 }}, 711 }, 712 }, 713 } 714 s.st.application.scale = 3 715 args := params.UpdateApplicationUnitArgs{ 716 Args: []params.UpdateApplicationUnits{ 717 {ApplicationTag: "application-gitlab", Units: units, Scale: intPtr(3), Generation: int64Ptr(1)}, 718 }, 719 } 720 results, err := s.facade.UpdateApplicationsUnits(args) 721 c.Assert(err, jc.ErrorIsNil) 722 c.Assert(results.Results[0], gc.DeepEquals, params.UpdateApplicationUnitResult{ 723 Info: ¶ms.UpdateApplicationUnitsInfo{ 724 Units: []params.ApplicationUnitInfo{ 725 {ProviderId: "uuid", UnitTag: "unit-gitlab-0"}, 726 {ProviderId: "another-uuid", UnitTag: "unit-gitlab-1"}, 727 }, 728 }, 729 }) 730 s.st.application.CheckCallNames(c, "Life", "Name", "GetScale") 731 s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 732 s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 733 ProviderId: strPtr("uuid"), 734 Address: strPtr("address"), Ports: &[]string{"port"}, 735 CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "message"}, 736 AgentStatus: &status.StatusInfo{Status: status.Idle}, 737 }) 738 s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 739 s.st.application.units[1].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 740 ProviderId: strPtr("another-uuid"), 741 Address: strPtr("another-address"), Ports: &[]string{"another-port"}, 742 CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "another message"}, 743 AgentStatus: &status.StatusInfo{Status: status.Idle}, 744 }) 745 // Units with state that disappear from the cluster are deleted 746 // if they cause the application scale to be exceeded. 747 s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life", "DestroyOperation", "UpdateOperation") 748 s.st.application.units[2].(*mockUnit).CheckCall(c, 2, "UpdateOperation", state.UnitUpdateProperties{ 749 CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"}, 750 AgentStatus: &status.StatusInfo{Status: status.Idle}, 751 }) 752 // Units with state that disappear from the cluster are not deleted if 753 // the application scale is maintained. 754 s.st.application.units[3].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 755 s.st.application.units[3].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 756 CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"}, 757 AgentStatus: &status.StatusInfo{Status: status.Idle}, 758 }) 759 760 s.storage.CheckCallNames(c, 761 "UnitStorageAttachments", "UnitStorageAttachments", "UnitStorageAttachments", 762 "StorageInstance", "UnitStorageAttachments", "StorageInstance", "AllFilesystems", 763 "Volume", "SetVolumeInfo", "SetVolumeAttachmentInfo", "Volume", "SetStatus", "Volume", "SetStatus", 764 "Filesystem", "SetFilesystemInfo", "SetFilesystemAttachmentInfo", 765 "Filesystem", "SetStatus", "Filesystem", "SetStatus", "Filesystem", "SetStatus", "Filesystem", "SetStatus") 766 s.storage.CheckCall(c, 0, "UnitStorageAttachments", names.NewUnitTag("gitlab/2")) 767 s.storage.CheckCall(c, 1, "UnitStorageAttachments", names.NewUnitTag("gitlab/3")) 768 s.storage.CheckCall(c, 2, "UnitStorageAttachments", names.NewUnitTag("gitlab/0")) 769 s.storage.CheckCall(c, 3, "StorageInstance", names.NewStorageTag("data/0")) 770 s.storage.CheckCall(c, 4, "UnitStorageAttachments", names.NewUnitTag("gitlab/1")) 771 s.storage.CheckCall(c, 5, "StorageInstance", names.NewStorageTag("data/1")) 772 773 now := s.clock.Now() 774 s.storage.CheckCall(c, 8, "SetVolumeInfo", 775 names.NewVolumeTag("1"), 776 state.VolumeInfo{ 777 Size: 200, 778 VolumeId: "vol-id2", 779 Persistent: true, 780 }) 781 s.storage.CheckCall(c, 9, "SetVolumeAttachmentInfo", 782 names.NewUnitTag("gitlab/1"), names.NewVolumeTag("1"), 783 state.VolumeAttachmentInfo{ 784 ReadOnly: true, 785 }) 786 s.storage.CheckCall(c, 11, "SetStatus", 787 status.StatusInfo{ 788 Status: status.Pending, 789 Message: "vol not ready", 790 Since: &now, 791 }) 792 s.storage.CheckCall(c, 13, "SetStatus", 793 status.StatusInfo{ 794 Status: status.Attached, 795 Message: "vol ready", 796 Since: &now, 797 }) 798 799 s.storage.CheckCall(c, 15, "SetFilesystemInfo", 800 names.NewFilesystemTag("gitlab/1/0"), 801 state.FilesystemInfo{ 802 Size: 200, 803 FilesystemId: "fs-id2", 804 }) 805 s.storage.CheckCall(c, 16, "SetFilesystemAttachmentInfo", 806 names.NewUnitTag("gitlab/1"), names.NewFilesystemTag("gitlab/1/0"), 807 state.FilesystemAttachmentInfo{ 808 MountPoint: "/path/to/there", 809 ReadOnly: true, 810 }) 811 s.storage.CheckCall(c, 20, "SetStatus", 812 status.StatusInfo{ 813 Status: status.Pending, 814 Message: "not ready", 815 Since: &now, 816 }) 817 s.storage.CheckCall(c, 22, "SetStatus", 818 status.StatusInfo{ 819 Status: status.Attached, 820 Message: "ready", 821 Since: &now, 822 }) 823 s.storage.CheckCall(c, 24, "SetStatus", 824 status.StatusInfo{ 825 Status: status.Detached, 826 Since: &now, 827 }) 828 829 s.st.model.CheckCall(c, 0, "Containers", []string{"another-uuid"}) 830 s.st.model.CheckCall(c, 1, "Containers", []string{"uuid", "another-uuid"}) 831 } 832 833 func (s *CAASProvisionerSuite) TestUpdateApplicationsUnitsWithStorageNoBackingVolume(c *gc.C) { 834 s.st.application.units = []caasunitprovisioner.Unit{ 835 &mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive}, 836 } 837 s.storage.backingVolume = names.VolumeTag{} 838 s.storage.storageFilesystems[names.NewStorageTag("data/0")] = names.NewFilesystemTag("gitlab/0/0") 839 s.storage.storageAttachments[names.NewUnitTag("gitlab/0")] = names.NewStorageTag("data/0") 840 841 units := []params.ApplicationUnitParams{ 842 {ProviderId: "uuid", Address: "address", Ports: []string{"port"}, 843 Status: "running", Info: "message", Stateful: true, 844 FilesystemInfo: []params.KubernetesFilesystemInfo{ 845 {StorageName: "data", FilesystemId: "fs-id", Size: 100, MountPoint: "/path/to/here", ReadOnly: true, 846 Status: "attached", 847 }, 848 }, 849 }, 850 } 851 s.st.application.scale = 1 852 args := params.UpdateApplicationUnitArgs{ 853 Args: []params.UpdateApplicationUnits{ 854 {ApplicationTag: "application-gitlab", Units: units, Scale: intPtr(1), Generation: int64Ptr(1)}, 855 }, 856 } 857 results, err := s.facade.UpdateApplicationsUnits(args) 858 c.Assert(err, jc.ErrorIsNil) 859 c.Assert(results, gc.DeepEquals, params.UpdateApplicationUnitResults{ 860 Results: []params.UpdateApplicationUnitResult{ 861 {nil, nil}, 862 }, 863 }) 864 s.st.application.CheckCallNames(c, "Life", "Name", "GetScale") 865 s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation") 866 s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{ 867 ProviderId: strPtr("uuid"), 868 Address: strPtr("address"), Ports: &[]string{"port"}, 869 CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "message"}, 870 AgentStatus: &status.StatusInfo{Status: status.Idle}, 871 }) 872 873 s.storage.CheckCallNames(c, 874 "UnitStorageAttachments", "StorageInstance", "AllFilesystems", "Filesystem", 875 "SetFilesystemInfo", "SetFilesystemAttachmentInfo", "Filesystem", "SetStatus") 876 s.storage.CheckCall(c, 0, "UnitStorageAttachments", names.NewUnitTag("gitlab/0")) 877 s.storage.CheckCall(c, 1, "StorageInstance", names.NewStorageTag("data/0")) 878 879 now := s.clock.Now() 880 s.storage.CheckCall(c, 4, "SetFilesystemInfo", 881 names.NewFilesystemTag("gitlab/0/0"), 882 state.FilesystemInfo{ 883 Size: 100, 884 FilesystemId: "fs-id", 885 }) 886 s.storage.CheckCall(c, 5, "SetFilesystemAttachmentInfo", 887 names.NewUnitTag("gitlab/0"), names.NewFilesystemTag("gitlab/0/0"), 888 state.FilesystemAttachmentInfo{ 889 MountPoint: "/path/to/here", 890 ReadOnly: true, 891 }) 892 s.storage.CheckCall(c, 7, "SetStatus", 893 status.StatusInfo{ 894 Status: status.Attached, 895 Since: &now, 896 }) 897 } 898 899 func (s *CAASProvisionerSuite) TestUpdateApplicationsService(c *gc.C) { 900 addr := network.NewSpaceAddress("10.0.0.1") 901 results, err := s.facade.UpdateApplicationsService(params.UpdateApplicationServiceArgs{ 902 Args: []params.UpdateApplicationServiceArg{ 903 { 904 ApplicationTag: "application-gitlab", 905 ProviderId: "id", 906 Addresses: params.FromMachineAddresses(addr.MachineAddress), 907 }, { 908 ApplicationTag: "unit-gitlab-0", 909 }, 910 }, 911 }) 912 c.Assert(err, jc.ErrorIsNil) 913 c.Assert(results.Results, gc.HasLen, 2) 914 c.Assert(results.Results[0].Error, gc.IsNil) 915 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 916 Message: `"unit-gitlab-0" is not a valid application tag`, 917 }) 918 c.Assert(s.st.application.providerId, gc.Equals, "id") 919 c.Assert(s.st.application.addresses, jc.DeepEquals, []network.SpaceAddress{addr}) 920 } 921 922 func (s *CAASProvisionerSuite) TestSetOperatorStatus(c *gc.C) { 923 results, err := s.facade.SetOperatorStatus(params.SetStatus{ 924 Entities: []params.EntityStatusArgs{ 925 {Tag: "application-gitlab", Status: "error", Info: "broken", Data: map[string]interface{}{"foo": "bar"}}, 926 {Tag: "unit-gitlab-0"}, 927 }, 928 }) 929 c.Assert(err, jc.ErrorIsNil) 930 c.Assert(results.Results, gc.HasLen, 2) 931 c.Assert(results.Results[0].Error, gc.IsNil) 932 c.Assert(results.Results[1].Error, jc.DeepEquals, ¶ms.Error{ 933 Message: `"unit-gitlab-0" is not a valid application tag`, 934 }) 935 now := s.clock.Now() 936 s.st.application.CheckCall(c, 0, "SetOperatorStatus", status.StatusInfo{ 937 Status: status.Error, 938 Message: "broken", 939 Data: map[string]interface{}{"foo": "bar"}, 940 Since: &now, 941 }) 942 }