github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/bootstrap_test.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provider_test 5 6 import ( 7 "context" 8 "fmt" 9 "time" 10 11 jujuclock "github.com/juju/clock" 12 "github.com/juju/clock/testclock" 13 "github.com/juju/cmd/v3/cmdtesting" 14 "github.com/juju/errors" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/worker/v3/workertest" 17 "go.uber.org/mock/gomock" 18 gc "gopkg.in/check.v1" 19 apps "k8s.io/api/apps/v1" 20 core "k8s.io/api/core/v1" 21 rbacv1 "k8s.io/api/rbac/v1" 22 k8sstorage "k8s.io/api/storage/v1" 23 k8serrors "k8s.io/apimachinery/pkg/api/errors" 24 "k8s.io/apimachinery/pkg/api/resource" 25 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/util/intstr" 27 "k8s.io/client-go/kubernetes/fake" 28 "k8s.io/client-go/tools/cache" 29 "k8s.io/utils/pointer" 30 31 "github.com/juju/juju/api" 32 "github.com/juju/juju/caas/kubernetes/provider" 33 k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 34 "github.com/juju/juju/caas/kubernetes/provider/mocks" 35 k8swatcher "github.com/juju/juju/caas/kubernetes/provider/watcher" 36 k8swatchertest "github.com/juju/juju/caas/kubernetes/provider/watcher/test" 37 "github.com/juju/juju/cloudconfig/podcfg" 38 "github.com/juju/juju/cmd/modelcmd" 39 "github.com/juju/juju/controller" 40 k8sannotations "github.com/juju/juju/core/annotations" 41 "github.com/juju/juju/core/constraints" 42 "github.com/juju/juju/docker" 43 "github.com/juju/juju/environs/config" 44 envtesting "github.com/juju/juju/environs/testing" 45 "github.com/juju/juju/feature" 46 "github.com/juju/juju/juju/osenv" 47 coretesting "github.com/juju/juju/testing" 48 jujuversion "github.com/juju/juju/version" 49 ) 50 51 type bootstrapSuite struct { 52 fakeClientSuite 53 coretesting.JujuOSEnvSuite 54 55 controllerCfg controller.Config 56 pcfg *podcfg.ControllerPodConfig 57 58 controllerStackerGetter func() provider.ControllerStackerForTest 59 } 60 61 var _ = gc.Suite(&bootstrapSuite{}) 62 63 func (s *bootstrapSuite) SetUpTest(c *gc.C) { 64 s.fakeClientSuite.SetUpTest(c) 65 s.JujuOSEnvSuite.SetUpTest(c) 66 s.SetFeatureFlags(feature.DeveloperMode) 67 s.broker = nil 68 69 controllerName := "controller-1" 70 s.namespace = controllerName 71 72 cfg, err := config.New(config.UseDefaults, coretesting.FakeConfig().Merge(coretesting.Attrs{ 73 config.NameKey: "controller-1", 74 k8sconstants.OperatorStorageKey: "", 75 k8sconstants.WorkloadStorageKey: "", 76 })) 77 c.Assert(err, jc.ErrorIsNil) 78 s.cfg = cfg 79 80 s.controllerCfg = coretesting.FakeControllerConfig() 81 s.controllerCfg["juju-db-snap-channel"] = controller.DefaultJujuDBSnapChannel 82 s.controllerCfg[controller.CAASImageRepo] = ` 83 { 84 "serveraddress": "quay.io", 85 "auth": "xxxxx==", 86 "repository": "test-account" 87 }`[1:] 88 pcfg, err := podcfg.NewBootstrapControllerPodConfig( 89 s.controllerCfg, controllerName, "ubuntu", constraints.MustParse("root-disk=10000M mem=4000M")) 90 c.Assert(err, jc.ErrorIsNil) 91 92 current := jujuversion.Current 93 current.Build = 666 94 pcfg.JujuVersion = current 95 pcfg.APIInfo = &api.Info{ 96 Password: "password", 97 CACert: coretesting.CACert, 98 ModelTag: coretesting.ModelTag, 99 } 100 pcfg.Bootstrap.ControllerModelConfig = s.cfg 101 pcfg.Bootstrap.BootstrapMachineInstanceId = "instance-id" 102 pcfg.Bootstrap.InitialModelConfig = map[string]interface{}{ 103 "name": "my-model", 104 } 105 pcfg.Bootstrap.StateServingInfo = controller.StateServingInfo{ 106 Cert: coretesting.ServerCert, 107 PrivateKey: coretesting.ServerKey, 108 CAPrivateKey: coretesting.CAKey, 109 StatePort: 123, 110 APIPort: 456, 111 } 112 pcfg.Bootstrap.StateServingInfo = controller.StateServingInfo{ 113 Cert: coretesting.ServerCert, 114 PrivateKey: coretesting.ServerKey, 115 CAPrivateKey: coretesting.CAKey, 116 StatePort: 123, 117 APIPort: 456, 118 } 119 pcfg.Bootstrap.ControllerConfig = s.controllerCfg 120 s.pcfg = pcfg 121 s.controllerStackerGetter = func() provider.ControllerStackerForTest { 122 controllerStacker, err := provider.NewcontrollerStackForTest( 123 envtesting.BootstrapContext(context.TODO(), c), "juju-controller-test", "some-storage", s.broker, s.pcfg, 124 ) 125 c.Assert(err, jc.ErrorIsNil) 126 return controllerStacker 127 } 128 } 129 130 func (s *bootstrapSuite) TearDownTest(c *gc.C) { 131 s.pcfg = nil 132 s.controllerCfg = nil 133 s.controllerStackerGetter = nil 134 s.fakeClientSuite.TearDownTest(c) 135 s.JujuOSEnvSuite.TearDownTest(c) 136 } 137 138 func (s *bootstrapSuite) TestFindControllerNamespace(c *gc.C) { 139 tests := []struct { 140 Namespace core.Namespace 141 ModelName string 142 ControllerUUID string 143 }{ 144 { 145 Namespace: core.Namespace{ 146 ObjectMeta: v1.ObjectMeta{ 147 Name: "controller-tlm", 148 Annotations: map[string]string{ 149 "juju.io/controller": "abcd", 150 }, 151 Labels: map[string]string{ 152 "juju-model": "controller", 153 }, 154 }, 155 }, 156 ModelName: "controller", 157 ControllerUUID: "abcd", 158 }, 159 { 160 Namespace: core.Namespace{ 161 ObjectMeta: v1.ObjectMeta{ 162 Name: "controller-tlm", 163 Annotations: map[string]string{ 164 "controller.juju.is/id": "abcd", 165 }, 166 Labels: map[string]string{ 167 "model.juju.is/name": "controller", 168 }, 169 }, 170 }, 171 ModelName: "controller", 172 ControllerUUID: "abcd", 173 }, 174 } 175 176 for _, test := range tests { 177 client := fake.NewSimpleClientset() 178 _, err := client.CoreV1().Namespaces().Create( 179 context.TODO(), 180 &test.Namespace, 181 v1.CreateOptions{}, 182 ) 183 c.Assert(err, jc.ErrorIsNil) 184 ns, err := provider.FindControllerNamespace( 185 client, test.ControllerUUID) 186 c.Assert(err, jc.ErrorIsNil) 187 c.Assert(ns, jc.DeepEquals, &test.Namespace) 188 } 189 } 190 191 type svcSpecTC struct { 192 cloudType string 193 spec *provider.ControllerServiceSpec 194 errStr string 195 cfg *podcfg.BootstrapConfig 196 } 197 198 func (s *bootstrapSuite) TestGetControllerSvcSpec(c *gc.C) { 199 s.namespace = "controller-1" 200 201 getCfg := func(externalName, controllerServiceType string, controllerExternalIPs []string) *podcfg.BootstrapConfig { 202 o := new(podcfg.BootstrapConfig) 203 *o = *s.pcfg.Bootstrap 204 if len(externalName) > 0 { 205 o.ControllerExternalName = externalName 206 } 207 if len(controllerServiceType) > 0 { 208 o.ControllerServiceType = controllerServiceType 209 } 210 if len(controllerExternalIPs) > 0 { 211 o.ControllerExternalIPs = controllerExternalIPs 212 } 213 return o 214 } 215 216 for i, t := range []svcSpecTC{ 217 { 218 cloudType: "azure", 219 spec: &provider.ControllerServiceSpec{ 220 ServiceType: core.ServiceTypeLoadBalancer, 221 }, 222 }, 223 { 224 cloudType: "ec2", 225 spec: &provider.ControllerServiceSpec{ 226 ServiceType: core.ServiceTypeLoadBalancer, 227 Annotations: k8sannotations.New(nil). 228 Add("service.beta.kubernetes.io/aws-load-balancer-backend-protocol", "tcp"), 229 }, 230 }, 231 { 232 cloudType: "gce", 233 spec: &provider.ControllerServiceSpec{ 234 ServiceType: core.ServiceTypeLoadBalancer, 235 }, 236 }, 237 { 238 cloudType: "microk8s", 239 spec: &provider.ControllerServiceSpec{ 240 ServiceType: core.ServiceTypeClusterIP, 241 }, 242 }, 243 { 244 cloudType: "openstack", 245 spec: &provider.ControllerServiceSpec{ 246 ServiceType: core.ServiceTypeLoadBalancer, 247 }, 248 }, 249 { 250 cloudType: "maas", 251 spec: &provider.ControllerServiceSpec{ 252 ServiceType: core.ServiceTypeLoadBalancer, 253 }, 254 }, 255 { 256 cloudType: "lxd", 257 spec: &provider.ControllerServiceSpec{ 258 ServiceType: core.ServiceTypeClusterIP, 259 }, 260 }, 261 { 262 cloudType: "unknown-cloud", 263 spec: &provider.ControllerServiceSpec{ 264 ServiceType: core.ServiceTypeClusterIP, 265 }, 266 }, 267 { 268 cloudType: "microk8s", 269 spec: &provider.ControllerServiceSpec{ 270 ServiceType: core.ServiceTypeLoadBalancer, 271 ExternalIP: "1.1.1.1", 272 ExternalIPs: []string{"1.1.1.1"}, 273 }, 274 cfg: getCfg("", "loadbalancer", []string{"1.1.1.1"}), 275 }, 276 { 277 cloudType: "microk8s", 278 errStr: `external name "example.com" provided but service type was set to "LoadBalancer"`, 279 cfg: getCfg("example.com", "loadbalancer", []string{"1.1.1.1"}), 280 }, 281 { 282 cloudType: "microk8s", 283 spec: &provider.ControllerServiceSpec{ 284 ServiceType: core.ServiceTypeExternalName, 285 ExternalName: "example.com", 286 ExternalIPs: []string{"1.1.1.1"}, 287 }, 288 cfg: getCfg("example.com", "external", []string{"1.1.1.1"}), 289 }, 290 { 291 cloudType: "microk8s", 292 spec: &provider.ControllerServiceSpec{ 293 ServiceType: core.ServiceTypeExternalName, 294 ExternalName: "example.com", 295 }, 296 cfg: getCfg("example.com", "external", nil), 297 }, 298 } { 299 c.Logf("testing %d %q", i, t.cloudType) 300 spec, err := s.controllerStackerGetter().GetControllerSvcSpec(t.cloudType, t.cfg) 301 if len(t.errStr) == 0 { 302 c.Check(err, jc.ErrorIsNil) 303 } else { 304 c.Check(err, gc.ErrorMatches, t.errStr) 305 } 306 c.Check(spec, jc.DeepEquals, t.spec) 307 } 308 } 309 310 func int64Ptr(a int64) *int64 { 311 return &a 312 } 313 314 func (s *bootstrapSuite) TestBootstrap(c *gc.C) { 315 podWatcher, podFirer := k8swatchertest.NewKubernetesTestWatcher() 316 eventWatcher, _ := k8swatchertest.NewKubernetesTestWatcher() 317 <-podWatcher.Changes() 318 <-eventWatcher.Changes() 319 watchers := []k8swatcher.KubernetesNotifyWatcher{podWatcher, eventWatcher} 320 watchCallCount := 0 321 322 s.k8sWatcherFn = func(_ cache.SharedIndexInformer, n string, _ jujuclock.Clock) (k8swatcher.KubernetesNotifyWatcher, error) { 323 if watchCallCount >= len(watchers) { 324 return nil, errors.NotFoundf("no watcher available for index %d", watchCallCount) 325 } 326 w := watchers[watchCallCount] 327 watchCallCount++ 328 return w, nil 329 } 330 331 // Eventually the namespace wil be set to controllerName. 332 // So we have to specify the final namespace(controllerName) for later use. 333 newK8sClientFunc, newK8sRestClientFunc := s.setupK8sRestClient(c, s.pcfg.ControllerName) 334 randomPrefixFunc := func() (string, error) { 335 return "appuuid", nil 336 } 337 _, err := s.mockNamespaces.Get(context.TODO(), s.namespace, v1.GetOptions{}) 338 c.Assert(err, jc.Satisfies, k8serrors.IsNotFound) 339 340 var bootstrapWatchers []k8swatcher.KubernetesNotifyWatcher 341 s.setupBroker(c, newK8sClientFunc, newK8sRestClientFunc, randomPrefixFunc, &bootstrapWatchers) 342 343 // Broker's namespace is "controller" now - controllerModelConfig.Name() 344 c.Assert(s.broker.GetCurrentNamespace(), jc.DeepEquals, s.namespace) 345 c.Assert( 346 s.broker.GetAnnotations().ToMap(), jc.DeepEquals, 347 map[string]string{ 348 "model.juju.is/id": s.cfg.UUID(), 349 "controller.juju.is/id": coretesting.ControllerTag.Id(), 350 }, 351 ) 352 353 // Done in broker.Bootstrap method actually. 354 s.broker.GetAnnotations().Add("controller.juju.is/is-controller", "true") 355 356 s.pcfg.Bootstrap.Timeout = 10 * time.Minute 357 s.pcfg.Bootstrap.ControllerExternalIPs = []string{"10.0.0.1"} 358 s.pcfg.Bootstrap.IgnoreProxy = true 359 360 controllerStacker := s.controllerStackerGetter() 361 362 sharedSecret, sslKey := controllerStacker.GetSharedSecretAndSSLKey(c) 363 364 scName := "some-storage" 365 sc := k8sstorage.StorageClass{ 366 ObjectMeta: v1.ObjectMeta{ 367 Name: scName, 368 }, 369 } 370 371 APIPort := s.controllerCfg.APIPort() 372 ns := &core.Namespace{ 373 ObjectMeta: v1.ObjectMeta{ 374 Name: s.namespace, 375 Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "model.juju.is/name": "controller-1"}, 376 }, 377 } 378 ns.Name = s.namespace 379 s.ensureJujuNamespaceAnnotations(true, ns) 380 svcNotFullyProvisioned := &core.Service{ 381 ObjectMeta: v1.ObjectMeta{ 382 Name: "juju-controller-test-service", 383 Namespace: s.namespace, 384 Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "juju-controller-test"}, 385 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 386 }, 387 Spec: core.ServiceSpec{ 388 Selector: map[string]string{"app.kubernetes.io/name": "juju-controller-test"}, 389 Type: core.ServiceType("ClusterIP"), 390 Ports: []core.ServicePort{ 391 { 392 Name: "api-server", 393 TargetPort: intstr.FromInt(APIPort), 394 Port: int32(APIPort), 395 }, 396 }, 397 ExternalIPs: []string{"10.0.0.1"}, 398 }, 399 } 400 401 svcPublicIP := "1.1.1.1" 402 svcProvisioned := &core.Service{ 403 ObjectMeta: v1.ObjectMeta{ 404 Name: "juju-controller-test-service", 405 Namespace: s.namespace, 406 Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "juju-controller-test"}, 407 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 408 }, 409 Spec: core.ServiceSpec{ 410 Selector: map[string]string{"app.kubernetes.io/name": "juju-controller-test"}, 411 Type: core.ServiceType("ClusterIP"), 412 Ports: []core.ServicePort{ 413 { 414 Name: "api-server", 415 TargetPort: intstr.FromInt(APIPort), 416 Port: int32(APIPort), 417 }, 418 }, 419 ClusterIP: svcPublicIP, 420 ExternalIPs: []string{"10.0.0.1"}, 421 }, 422 } 423 424 secretWithServerPEMAdded := &core.Secret{ 425 ObjectMeta: v1.ObjectMeta{ 426 Name: "juju-controller-test-secret", 427 Namespace: s.namespace, 428 Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "juju-controller-test"}, 429 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 430 }, 431 Type: core.SecretTypeOpaque, 432 Data: map[string][]byte{ 433 "shared-secret": []byte(sharedSecret), 434 "server.pem": []byte(sslKey), 435 }, 436 } 437 438 secretControllerAppConfig := &core.Secret{ 439 ObjectMeta: v1.ObjectMeta{ 440 Name: "juju-controller-test-application-config", 441 Namespace: s.namespace, 442 Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "juju-controller-test"}, 443 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 444 }, 445 Type: core.SecretTypeOpaque, 446 Data: map[string][]byte{ 447 "JUJU_K8S_UNIT_PASSWORD": []byte(controllerStacker.GetControllerUnitAgentPassword()), 448 }, 449 } 450 451 repoDetails, err := docker.NewImageRepoDetails(s.controllerCfg.CAASImageRepo()) 452 c.Assert(err, jc.ErrorIsNil) 453 secretCAASImageRepoData, err := repoDetails.SecretData() 454 c.Assert(err, jc.ErrorIsNil) 455 456 secretCAASImageRepo := &core.Secret{ 457 ObjectMeta: v1.ObjectMeta{ 458 Name: "juju-image-pull-secret", 459 Namespace: s.namespace, 460 Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "juju-controller-test"}, 461 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 462 }, 463 Type: core.SecretTypeDockerConfigJson, 464 Data: map[string][]byte{ 465 core.DockerConfigJsonKey: secretCAASImageRepoData, 466 }, 467 } 468 469 bootstrapParamsContent, err := s.pcfg.Bootstrap.StateInitializationParams.Marshal() 470 c.Assert(err, jc.ErrorIsNil) 471 472 configMapWithAgentConfAdded := &core.ConfigMap{ 473 ObjectMeta: v1.ObjectMeta{ 474 Name: "juju-controller-test-configmap", 475 Namespace: s.namespace, 476 Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "juju-controller-test"}, 477 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 478 }, 479 Data: map[string]string{ 480 "bootstrap-params": string(bootstrapParamsContent), 481 "controller-agent.conf": controllerStacker.GetControllerAgentConfigContent(c), 482 "controller-unit-agent.conf": controllerStacker.GetControllerUnitAgentConfigContent(c), 483 }, 484 } 485 486 numberOfPods := int32(1) 487 statefulSetSpec := &apps.StatefulSet{ 488 ObjectMeta: v1.ObjectMeta{ 489 Name: "juju-controller-test", 490 Namespace: s.namespace, 491 Labels: map[string]string{ 492 "app.kubernetes.io/managed-by": "juju", 493 "app.kubernetes.io/name": "juju-controller-test", 494 "model.juju.is/disable-webhook": "true", 495 }, 496 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 497 }, 498 Spec: apps.StatefulSetSpec{ 499 ServiceName: "juju-controller-test-service", 500 Replicas: &numberOfPods, 501 Selector: &v1.LabelSelector{ 502 MatchLabels: map[string]string{"app.kubernetes.io/name": "juju-controller-test"}, 503 }, 504 VolumeClaimTemplates: []core.PersistentVolumeClaim{ 505 { 506 ObjectMeta: v1.ObjectMeta{ 507 Name: "storage", 508 Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "juju-controller-test"}, 509 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 510 }, 511 Spec: core.PersistentVolumeClaimSpec{ 512 StorageClassName: &scName, 513 AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce}, 514 Resources: core.VolumeResourceRequirements{ 515 Requests: core.ResourceList{ 516 core.ResourceStorage: resource.MustParse("10000Mi"), 517 }, 518 }, 519 }, 520 }, 521 }, 522 Template: core.PodTemplateSpec{ 523 ObjectMeta: v1.ObjectMeta{ 524 Name: "controller-0", 525 Namespace: s.namespace, 526 Labels: map[string]string{ 527 "app.kubernetes.io/name": "juju-controller-test", 528 "model.juju.is/disable-webhook": "true", 529 }, 530 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 531 }, 532 Spec: core.PodSpec{ 533 ServiceAccountName: "controller", 534 AutomountServiceAccountToken: pointer.Bool(true), 535 TerminationGracePeriodSeconds: int64Ptr(30), 536 SecurityContext: &core.PodSecurityContext{ 537 SupplementalGroups: []int64{170}, 538 FSGroup: pointer.Int64(170), 539 }, 540 Volumes: []core.Volume{ 541 { 542 Name: "charm-data", 543 VolumeSource: core.VolumeSource{ 544 EmptyDir: &core.EmptyDirVolumeSource{}, 545 }, 546 }, 547 { 548 Name: "mongo-scratch", 549 VolumeSource: core.VolumeSource{ 550 EmptyDir: &core.EmptyDirVolumeSource{}, 551 }, 552 }, 553 { 554 Name: "apiserver-scratch", 555 VolumeSource: core.VolumeSource{ 556 EmptyDir: &core.EmptyDirVolumeSource{}, 557 }, 558 }, 559 { 560 Name: "juju-controller-test-server-pem", 561 VolumeSource: core.VolumeSource{ 562 Secret: &core.SecretVolumeSource{ 563 SecretName: "juju-controller-test-secret", 564 DefaultMode: pointer.Int32(0400), 565 Items: []core.KeyToPath{ 566 { 567 Key: "server.pem", 568 Path: "template-server.pem", 569 }, 570 }, 571 }, 572 }, 573 }, 574 { 575 Name: "juju-controller-test-shared-secret", 576 VolumeSource: core.VolumeSource{ 577 Secret: &core.SecretVolumeSource{ 578 SecretName: "juju-controller-test-secret", 579 DefaultMode: pointer.Int32(0660), 580 Items: []core.KeyToPath{ 581 { 582 Key: "shared-secret", 583 Path: "shared-secret", 584 }, 585 }, 586 }, 587 }, 588 }, 589 }, 590 }, 591 }, 592 }, 593 } 594 volAgentConf := core.Volume{ 595 Name: "juju-controller-test-agent-conf", 596 VolumeSource: core.VolumeSource{ 597 ConfigMap: &core.ConfigMapVolumeSource{ 598 Items: []core.KeyToPath{ 599 { 600 Key: "controller-agent.conf", 601 Path: "controller-agent.conf", 602 }, { 603 Key: "controller-unit-agent.conf", 604 Path: "controller-unit-agent.conf", 605 }, 606 }, 607 }, 608 }, 609 } 610 volAgentConf.VolumeSource.ConfigMap.Name = "juju-controller-test-configmap" 611 volBootstrapParams := core.Volume{ 612 Name: "juju-controller-test-bootstrap-params", 613 VolumeSource: core.VolumeSource{ 614 ConfigMap: &core.ConfigMapVolumeSource{ 615 Items: []core.KeyToPath{ 616 { 617 Key: "bootstrap-params", 618 Path: "bootstrap-params", 619 }, 620 }, 621 }, 622 }, 623 } 624 volBootstrapParams.VolumeSource.ConfigMap.Name = "juju-controller-test-configmap" 625 statefulSetSpec.Spec.Template.Spec.Volumes = append(statefulSetSpec.Spec.Template.Spec.Volumes, 626 []core.Volume{ 627 volAgentConf, volBootstrapParams, 628 }..., 629 ) 630 631 expectedVersion := jujuversion.Current 632 expectedVersion.Build = 666 633 probCmds := &core.ExecAction{ 634 Command: []string{ 635 "mongo", 636 fmt.Sprintf("--port=%d", s.controllerCfg.StatePort()), 637 "--tls", 638 "--tlsAllowInvalidHostnames", 639 "--tlsAllowInvalidCertificates", 640 "--tlsCertificateKeyFile=/var/lib/juju/server.pem", 641 "--eval", 642 "db.adminCommand('ping')", 643 }, 644 } 645 statefulSetSpec.Spec.Template.Spec.Containers = []core.Container{ 646 { 647 Name: "charm", 648 ImagePullPolicy: core.PullIfNotPresent, 649 Image: "docker.io/jujusolutions/charm-base:ubuntu-22.04", 650 WorkingDir: "/var/lib/juju", 651 Command: []string{"/charm/bin/pebble"}, 652 Args: []string{"run", "--http", ":38812", "--verbose"}, 653 Resources: core.ResourceRequirements{ 654 Requests: core.ResourceList{ 655 core.ResourceMemory: resource.MustParse("4000Mi"), 656 }, 657 Limits: core.ResourceList{ 658 core.ResourceMemory: resource.MustParse("4000Mi"), 659 }, 660 }, 661 Env: []core.EnvVar{ 662 { 663 Name: "JUJU_CONTAINER_NAMES", 664 Value: "api-server", 665 }, 666 { 667 Name: osenv.JujuFeatureFlagEnvKey, 668 Value: "developer-mode", 669 }, 670 }, 671 SecurityContext: &core.SecurityContext{ 672 RunAsUser: int64Ptr(170), 673 RunAsGroup: int64Ptr(170), 674 }, 675 VolumeMounts: []core.VolumeMount{ 676 { 677 Name: "charm-data", 678 ReadOnly: true, 679 MountPath: "/charm/bin", 680 SubPath: "charm/bin", 681 }, 682 { 683 Name: "charm-data", 684 MountPath: "/charm/containers", 685 SubPath: "charm/containers", 686 }, 687 { 688 Name: "charm-data", 689 MountPath: "/var/lib/pebble/default", 690 SubPath: "containeragent/pebble", 691 }, 692 { 693 Name: "charm-data", 694 MountPath: "/var/log/juju", 695 SubPath: "containeragent/var/log/juju", 696 }, 697 { 698 Name: "charm-data", 699 ReadOnly: true, 700 MountPath: "/etc/profile.d/juju-introspection.sh", 701 SubPath: "containeragent/etc/profile.d/juju-introspection.sh", 702 }, 703 { 704 Name: "charm-data", 705 ReadOnly: true, 706 MountPath: "/usr/bin/juju-introspect", 707 SubPath: "charm/bin/containeragent", 708 }, 709 { 710 Name: "charm-data", 711 ReadOnly: true, 712 MountPath: "/usr/bin/juju-exec", 713 SubPath: "charm/bin/containeragent", 714 }, 715 { 716 Name: "juju-controller-test-agent-conf", 717 MountPath: "/var/lib/juju/template-agent.conf", 718 SubPath: "controller-unit-agent.conf", 719 }, 720 { 721 Name: "storage", 722 MountPath: "/var/lib/juju", 723 }, 724 }, 725 }, 726 { 727 Name: "mongodb", 728 ImagePullPolicy: core.PullIfNotPresent, 729 Image: "test-account/juju-db:4.4", 730 Command: []string{ 731 "/bin/sh", 732 }, 733 Args: []string{ 734 "-c", 735 `printf 'args="--dbpath=/var/lib/juju/db --tlsCertificateKeyFile=/var/lib/juju/server.pem --tlsCertificateKeyFilePassword=ignored --tlsMode=requireTLS --port=1234 --journal --replSet=juju --quiet --oplogSize=1024 --auth --keyFile=/var/lib/juju/shared-secret --storageEngine=wiredTiger --bind_ip_all"\nipv6Disabled=$(sysctl net.ipv6.conf.all.disable_ipv6 -n)\nif [ $ipv6Disabled -eq 0 ]; then\n args="${args} --ipv6"\nfi\nSHARED_SECRET_SRC="/var/lib/juju/shared-secret.temp"\nSHARED_SECRET_DST="/var/lib/juju/shared-secret"\nrm "${SHARED_SECRET_DST}" || true\ncp "${SHARED_SECRET_SRC}" "${SHARED_SECRET_DST}"\nchown 170:170 "${SHARED_SECRET_DST}"\nchmod 600 "${SHARED_SECRET_DST}"\nls -lah "${SHARED_SECRET_DST}"\nwhile [ ! -f "/var/lib/juju/server.pem" ]; do\n echo "Waiting for /var/lib/juju/server.pem to be created..."\n sleep 1\ndone\nexec mongod ${args}\n'>/tmp/mongo.sh && chmod a+x /tmp/mongo.sh && exec /tmp/mongo.sh`, 736 }, 737 Ports: []core.ContainerPort{ 738 { 739 Name: "mongodb", 740 ContainerPort: int32(s.controllerCfg.StatePort()), 741 Protocol: "TCP", 742 }, 743 }, 744 StartupProbe: &core.Probe{ 745 ProbeHandler: core.ProbeHandler{ 746 Exec: probCmds, 747 }, 748 FailureThreshold: 60, 749 InitialDelaySeconds: 1, 750 PeriodSeconds: 5, 751 SuccessThreshold: 1, 752 TimeoutSeconds: 1, 753 }, 754 ReadinessProbe: &core.Probe{ 755 ProbeHandler: core.ProbeHandler{ 756 Exec: probCmds, 757 }, 758 FailureThreshold: 3, 759 InitialDelaySeconds: 5, 760 PeriodSeconds: 10, 761 SuccessThreshold: 1, 762 TimeoutSeconds: 1, 763 }, 764 LivenessProbe: &core.Probe{ 765 ProbeHandler: core.ProbeHandler{ 766 Exec: probCmds, 767 }, 768 FailureThreshold: 3, 769 InitialDelaySeconds: 30, 770 PeriodSeconds: 10, 771 SuccessThreshold: 1, 772 TimeoutSeconds: 5, 773 }, 774 VolumeMounts: []core.VolumeMount{ 775 { 776 Name: "mongo-scratch", 777 ReadOnly: false, 778 MountPath: "/var/log", 779 SubPath: "var/log", 780 }, 781 { 782 Name: "mongo-scratch", 783 ReadOnly: false, 784 MountPath: "/tmp", 785 SubPath: "tmp", 786 }, 787 { 788 Name: "storage", 789 ReadOnly: false, 790 MountPath: "/var/lib/juju", 791 SubPath: "", 792 }, 793 { 794 Name: "storage", 795 ReadOnly: false, 796 MountPath: "/var/lib/juju/db", 797 SubPath: "db", 798 }, 799 { 800 Name: "juju-controller-test-server-pem", 801 ReadOnly: true, 802 MountPath: "/var/lib/juju/template-server.pem", 803 SubPath: "template-server.pem", 804 }, 805 { 806 Name: "juju-controller-test-shared-secret", 807 ReadOnly: true, 808 MountPath: "/var/lib/juju/shared-secret.temp", 809 SubPath: "shared-secret", 810 }, 811 }, 812 SecurityContext: &core.SecurityContext{ 813 RunAsUser: int64Ptr(170), 814 RunAsGroup: int64Ptr(170), 815 ReadOnlyRootFilesystem: pointer.Bool(true), 816 }, 817 }, 818 { 819 Name: "api-server", 820 ImagePullPolicy: core.PullIfNotPresent, 821 Image: "test-account/jujud-operator:" + expectedVersion.String(), 822 Env: []core.EnvVar{{ 823 Name: osenv.JujuFeatureFlagEnvKey, 824 Value: "developer-mode", 825 }}, 826 Command: []string{ 827 "/bin/sh", 828 }, 829 Args: []string{ 830 "-c", 831 ` 832 export JUJU_DATA_DIR=/var/lib/juju 833 export JUJU_TOOLS_DIR=$JUJU_DATA_DIR/tools 834 835 mkdir -p $JUJU_TOOLS_DIR 836 cp /opt/jujud $JUJU_TOOLS_DIR/jujud 837 838 test -e $JUJU_DATA_DIR/agents/controller-0/agent.conf || JUJU_DEV_FEATURE_FLAGS=developer-mode $JUJU_TOOLS_DIR/jujud bootstrap-state $JUJU_DATA_DIR/bootstrap-params --data-dir $JUJU_DATA_DIR --debug --timeout 10m0s 839 840 mkdir -p /var/lib/pebble/default/layers 841 cat > /var/lib/pebble/default/layers/001-jujud.yaml <<EOF 842 summary: jujud service 843 services: 844 jujud: 845 summary: Juju controller agent 846 startup: enabled 847 override: replace 848 command: $JUJU_TOOLS_DIR/jujud machine --data-dir $JUJU_DATA_DIR --controller-id 0 --log-to-stderr --debug 849 environment: 850 JUJU_DEV_FEATURE_FLAGS: developer-mode 851 852 EOF 853 854 exec /opt/pebble run --http :38811 --verbose 855 `[1:], 856 }, 857 WorkingDir: "/var/lib/juju", 858 EnvFrom: []core.EnvFromSource{{ 859 SecretRef: &core.SecretEnvSource{ 860 LocalObjectReference: core.LocalObjectReference{ 861 Name: "juju-controller-test-application-config", 862 }, 863 }, 864 }}, 865 VolumeMounts: []core.VolumeMount{ 866 { 867 Name: "apiserver-scratch", 868 MountPath: "/tmp", 869 SubPath: "tmp", 870 }, 871 { 872 Name: "apiserver-scratch", 873 MountPath: "/var/lib/pebble", 874 SubPath: "var/lib/pebble", 875 }, 876 { 877 Name: "apiserver-scratch", 878 MountPath: "/var/log/juju", 879 SubPath: "var/log/juju", 880 }, 881 { 882 Name: "storage", 883 MountPath: "/var/lib/juju", 884 }, 885 { 886 Name: "storage", 887 MountPath: "/var/lib/juju/agents/controller-0", 888 SubPath: "agents/controller-0", 889 }, 890 { 891 Name: "juju-controller-test-agent-conf", 892 ReadOnly: true, 893 MountPath: "/var/lib/juju/agents/controller-0/template-agent.conf", 894 SubPath: "controller-agent.conf", 895 }, 896 { 897 Name: "juju-controller-test-server-pem", 898 ReadOnly: true, 899 MountPath: "/var/lib/juju/template-server.pem", 900 SubPath: "template-server.pem", 901 }, 902 { 903 Name: "juju-controller-test-shared-secret", 904 ReadOnly: true, 905 MountPath: "/var/lib/juju/shared-secret", 906 SubPath: "shared-secret", 907 }, 908 { 909 Name: "juju-controller-test-bootstrap-params", 910 ReadOnly: true, 911 MountPath: "/var/lib/juju/bootstrap-params", 912 SubPath: "bootstrap-params", 913 }, 914 { 915 Name: "charm-data", 916 MountPath: "/charm/container", 917 SubPath: "charm/containers/api-server", 918 }, 919 { 920 Name: "charm-data", 921 ReadOnly: true, 922 MountPath: "/etc/profile.d/juju-introspection.sh", 923 SubPath: "containeragent/etc/profile.d/juju-introspection.sh", 924 }, 925 { 926 Name: "charm-data", 927 ReadOnly: true, 928 MountPath: "/usr/bin/juju-introspect", 929 SubPath: "charm/bin/containeragent", 930 }, 931 { 932 Name: "charm-data", 933 ReadOnly: true, 934 MountPath: "/usr/bin/juju-exec", 935 SubPath: "charm/bin/containeragent", 936 }, 937 { 938 Name: "charm-data", 939 ReadOnly: true, 940 MountPath: "/usr/bin/juju-dumplogs", 941 SubPath: "charm/bin/containeragent", 942 }, 943 }, 944 StartupProbe: &core.Probe{ 945 ProbeHandler: core.ProbeHandler{ 946 HTTPGet: &core.HTTPGetAction{ 947 Path: "/v1/health?level=alive", 948 Port: intstr.Parse("38811"), 949 }, 950 }, 951 InitialDelaySeconds: 3, 952 TimeoutSeconds: 3, 953 PeriodSeconds: 3, 954 SuccessThreshold: 1, 955 FailureThreshold: 100, 956 }, 957 LivenessProbe: &core.Probe{ 958 ProbeHandler: core.ProbeHandler{ 959 HTTPGet: &core.HTTPGetAction{ 960 Path: "/v1/health?level=alive", 961 Port: intstr.Parse("38811"), 962 }, 963 }, 964 InitialDelaySeconds: 1, 965 TimeoutSeconds: 3, 966 PeriodSeconds: 5, 967 SuccessThreshold: 1, 968 FailureThreshold: 2, 969 }, 970 ReadinessProbe: &core.Probe{ 971 ProbeHandler: core.ProbeHandler{ 972 HTTPGet: &core.HTTPGetAction{ 973 Path: "/v1/health?level=ready", 974 Port: intstr.Parse("38811"), 975 }, 976 }, 977 InitialDelaySeconds: 1, 978 TimeoutSeconds: 3, 979 PeriodSeconds: 5, 980 SuccessThreshold: 1, 981 FailureThreshold: 2, 982 }, 983 SecurityContext: &core.SecurityContext{ 984 RunAsUser: pointer.Int64(170), 985 RunAsGroup: pointer.Int64(170), 986 ReadOnlyRootFilesystem: pointer.Bool(true), 987 }, 988 }, 989 } 990 statefulSetSpec.Spec.Template.Spec.InitContainers = []core.Container{{ 991 Name: "charm-init", 992 ImagePullPolicy: core.PullIfNotPresent, 993 Image: "test-account/jujud-operator:" + expectedVersion.String(), 994 WorkingDir: "/var/lib/juju", 995 Command: []string{"/opt/containeragent"}, 996 Args: []string{"init", "--containeragent-pebble-dir", "/containeragent/pebble", "--charm-modified-version", "0", "--data-dir", "/var/lib/juju", "--bin-dir", "/charm/bin", "--profile-dir", "/containeragent/etc/profile.d", "--controller"}, 997 Env: []core.EnvVar{ 998 { 999 Name: "JUJU_CONTAINER_NAMES", 1000 Value: "api-server", 1001 }, 1002 { 1003 Name: "JUJU_K8S_POD_NAME", 1004 ValueFrom: &core.EnvVarSource{ 1005 FieldRef: &core.ObjectFieldSelector{ 1006 FieldPath: "metadata.name", 1007 }, 1008 }, 1009 }, 1010 { 1011 Name: "JUJU_K8S_POD_UUID", 1012 ValueFrom: &core.EnvVarSource{ 1013 FieldRef: &core.ObjectFieldSelector{ 1014 FieldPath: "metadata.uid", 1015 }, 1016 }, 1017 }, 1018 }, 1019 EnvFrom: []core.EnvFromSource{ 1020 { 1021 SecretRef: &core.SecretEnvSource{ 1022 LocalObjectReference: core.LocalObjectReference{ 1023 Name: "controller-application-config", 1024 }, 1025 }, 1026 }, 1027 }, 1028 VolumeMounts: []core.VolumeMount{ 1029 { 1030 Name: "charm-data", 1031 MountPath: "/var/lib/juju", 1032 SubPath: "var/lib/juju", 1033 }, { 1034 Name: "charm-data", 1035 MountPath: "/charm/bin", 1036 SubPath: "charm/bin", 1037 }, { 1038 Name: "charm-data", 1039 MountPath: "/charm/containers", 1040 SubPath: "charm/containers", 1041 }, { 1042 Name: "charm-data", 1043 MountPath: "/containeragent/pebble", 1044 SubPath: "containeragent/pebble", 1045 }, { 1046 Name: "charm-data", 1047 MountPath: "/containeragent/etc/profile.d", 1048 SubPath: "containeragent/etc/profile.d", 1049 }, { 1050 Name: "juju-controller-test-agent-conf", 1051 MountPath: "/var/lib/juju/template-agent.conf", 1052 SubPath: "controller-unit-agent.conf", 1053 }, 1054 }, 1055 SecurityContext: &core.SecurityContext{ 1056 RunAsUser: int64Ptr(170), 1057 RunAsGroup: int64Ptr(170), 1058 ReadOnlyRootFilesystem: pointer.Bool(true), 1059 }, 1060 }} 1061 1062 controllerServiceAccount := &core.ServiceAccount{ 1063 ObjectMeta: v1.ObjectMeta{ 1064 Name: "controller", 1065 Namespace: "controller-1", 1066 Labels: map[string]string{ 1067 "app.kubernetes.io/managed-by": "juju", 1068 "app.kubernetes.io/name": "juju-controller-test", 1069 "model.juju.is/disable-webhook": "true", 1070 }, 1071 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 1072 }, 1073 AutomountServiceAccountToken: pointer.BoolPtr(true), 1074 } 1075 1076 controllerServiceAccountPatchedWithSecretCAASImageRepo := &core.ServiceAccount{} 1077 *controllerServiceAccountPatchedWithSecretCAASImageRepo = *controllerServiceAccount 1078 controllerServiceAccountPatchedWithSecretCAASImageRepo.ImagePullSecrets = append( 1079 controllerServiceAccountPatchedWithSecretCAASImageRepo.ImagePullSecrets, 1080 core.LocalObjectReference{Name: secretCAASImageRepo.Name}, 1081 ) 1082 1083 controllerServiceCRB := &rbacv1.ClusterRoleBinding{ 1084 ObjectMeta: v1.ObjectMeta{ 1085 Name: "controller-1", 1086 Labels: map[string]string{ 1087 "model.juju.is/name": "controller", 1088 }, 1089 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 1090 }, 1091 Subjects: []rbacv1.Subject{ 1092 { 1093 Kind: "ServiceAccount", 1094 Name: "controller", 1095 Namespace: "controller-1", 1096 }, 1097 }, 1098 RoleRef: rbacv1.RoleRef{ 1099 APIGroup: "rbac.authorization.k8s.io", 1100 Kind: "ClusterRole", 1101 Name: "cluster-admin", 1102 }, 1103 } 1104 1105 errChan := make(chan error) 1106 done := make(chan struct{}) 1107 s.AddCleanup(func(c *gc.C) { 1108 close(done) 1109 }) 1110 1111 // Ensure storage class is inplace. 1112 _, err = s.mockStorageClass.Create(context.Background(), &sc, v1.CreateOptions{}) 1113 c.Assert(err, jc.ErrorIsNil) 1114 1115 serviceWatcher, err := s.mockServices.Watch(context.Background(), v1.ListOptions{LabelSelector: "app.kubernetes.io/name=juju-controller-test"}) 1116 c.Assert(err, jc.ErrorIsNil) 1117 defer serviceWatcher.Stop() 1118 serviceChanges := serviceWatcher.ResultChan() 1119 1120 statefulsetWatcher, err := s.mockStatefulSets.Watch(context.Background(), v1.ListOptions{LabelSelector: "app.kubernetes.io/name=juju-controller-test"}) 1121 c.Assert(err, jc.ErrorIsNil) 1122 defer statefulsetWatcher.Stop() 1123 statefulsetChanges := statefulsetWatcher.ResultChan() 1124 1125 go func() { 1126 errChan <- controllerStacker.Deploy() 1127 }() 1128 go func(clk *testclock.Clock) { 1129 for { 1130 select { 1131 case <-done: 1132 return 1133 case <-serviceChanges: 1134 // Ensure service address is available. 1135 svc, err := s.mockServices.Get(context.Background(), "juju-controller-test-service", v1.GetOptions{}) 1136 c.Assert(err, jc.ErrorIsNil) 1137 c.Assert(svc, gc.DeepEquals, svcNotFullyProvisioned) 1138 1139 svc.Spec.ClusterIP = svcPublicIP 1140 svc, err = s.mockServices.Update(context.Background(), svc, v1.UpdateOptions{}) 1141 c.Assert(err, jc.ErrorIsNil) 1142 c.Assert(svc, gc.DeepEquals, svcProvisioned) 1143 err = clk.WaitAdvance(3*time.Second, coretesting.ShortWait, 1) 1144 c.Assert(err, jc.ErrorIsNil) 1145 serviceChanges = nil 1146 case <-statefulsetChanges: 1147 // Ensure pod created - the fake client does not automatically create pods for the statefulset. 1148 podName := s.pcfg.GetPodName() 1149 ss, err := s.mockStatefulSets.Get(context.Background(), `juju-controller-test`, v1.GetOptions{}) 1150 c.Assert(err, jc.ErrorIsNil) 1151 c.Assert(ss, gc.DeepEquals, statefulSetSpec) 1152 p := &core.Pod{ 1153 ObjectMeta: v1.ObjectMeta{ 1154 Name: podName, 1155 Labels: map[string]string{ 1156 "app.kubernetes.io/name": "juju-controller-test", 1157 "model.juju.is/disable-webhook": "true", 1158 }, 1159 Annotations: map[string]string{"controller.juju.is/id": coretesting.ControllerTag.Id()}, 1160 }, 1161 } 1162 p.Spec = ss.Spec.Template.Spec 1163 1164 pp, err := s.mockPods.Create(context.Background(), p, v1.CreateOptions{}) 1165 c.Assert(err, jc.ErrorIsNil) 1166 1167 _, err = s.broker.GetPod(podName) 1168 c.Assert(err, jc.ErrorIsNil) 1169 podFirer() 1170 pp.Status.Phase = core.PodRunning 1171 _, err = s.mockPods.Update(context.Background(), pp, v1.UpdateOptions{}) 1172 c.Assert(err, jc.ErrorIsNil) 1173 podFirer() 1174 statefulsetChanges = nil 1175 } 1176 } 1177 }(s.clock) 1178 1179 select { 1180 case err := <-errChan: 1181 c.Assert(err, jc.ErrorIsNil) 1182 1183 ss, err := s.mockStatefulSets.Get(context.Background(), `juju-controller-test`, v1.GetOptions{}) 1184 c.Assert(err, jc.ErrorIsNil) 1185 c.Assert(ss, gc.DeepEquals, statefulSetSpec) 1186 1187 svc, err := s.mockServices.Get(context.Background(), `juju-controller-test-service`, v1.GetOptions{}) 1188 c.Assert(err, jc.ErrorIsNil) 1189 c.Assert(svc, gc.DeepEquals, svcProvisioned) 1190 1191 secret, err := s.mockSecrets.Get(context.Background(), "juju-controller-test-secret", v1.GetOptions{}) 1192 c.Assert(err, jc.ErrorIsNil) 1193 c.Assert(secret, gc.DeepEquals, secretWithServerPEMAdded) 1194 1195 secret, err = s.mockSecrets.Get(context.Background(), "juju-image-pull-secret", v1.GetOptions{}) 1196 c.Assert(err, jc.ErrorIsNil) 1197 c.Assert(secret, gc.DeepEquals, secretCAASImageRepo) 1198 1199 secret, err = s.mockSecrets.Get(context.Background(), "juju-controller-test-application-config", v1.GetOptions{}) 1200 c.Assert(err, jc.ErrorIsNil) 1201 c.Assert(secret, gc.DeepEquals, secretControllerAppConfig) 1202 1203 configmap, err := s.mockConfigMaps.Get(context.Background(), "juju-controller-test-configmap", v1.GetOptions{}) 1204 c.Assert(err, jc.ErrorIsNil) 1205 c.Assert(configmap, gc.DeepEquals, configMapWithAgentConfAdded) 1206 1207 crb, err := s.mockClusterRoleBindings.Get(context.Background(), `controller-1`, v1.GetOptions{}) 1208 c.Assert(err, jc.ErrorIsNil) 1209 c.Assert(crb, gc.DeepEquals, controllerServiceCRB) 1210 1211 c.Assert(bootstrapWatchers, gc.HasLen, 2) 1212 c.Assert(workertest.CheckKilled(c, bootstrapWatchers[0]), jc.ErrorIsNil) 1213 c.Assert(workertest.CheckKilled(c, bootstrapWatchers[1]), jc.ErrorIsNil) 1214 case <-time.After(coretesting.LongWait): 1215 c.Fatalf("timed out waiting for deploy return") 1216 } 1217 } 1218 1219 func (s *bootstrapSuite) TestBootstrapFailedTimeout(c *gc.C) { 1220 ctrl := gomock.NewController(c) 1221 defer ctrl.Finish() 1222 1223 // Eventually the namespace wil be set to controllerName. 1224 // So we have to specify the final namespace(controllerName) for later use. 1225 newK8sClientFunc, newK8sRestClientFunc := s.setupK8sRestClient(c, s.pcfg.ControllerName) 1226 randomPrefixFunc := func() (string, error) { 1227 return "appuuid", nil 1228 } 1229 _, err := s.mockNamespaces.Get(context.TODO(), s.namespace, v1.GetOptions{}) 1230 c.Assert(err, jc.Satisfies, k8serrors.IsNotFound) 1231 1232 var watchers []k8swatcher.KubernetesNotifyWatcher 1233 s.setupBroker(c, newK8sClientFunc, newK8sRestClientFunc, randomPrefixFunc, &watchers) 1234 1235 // Broker's namespace is "controller" now - controllerModelConfig.Name() 1236 c.Assert(s.broker.GetCurrentNamespace(), jc.DeepEquals, s.namespace) 1237 c.Assert( 1238 s.broker.GetAnnotations().ToMap(), jc.DeepEquals, 1239 map[string]string{ 1240 "model.juju.is/id": s.cfg.UUID(), 1241 "controller.juju.is/id": coretesting.ControllerTag.Id(), 1242 }, 1243 ) 1244 1245 // Done in broker.Bootstrap method actually. 1246 s.broker.GetAnnotations().Add("controller.juju.is/is-controller", "true") 1247 1248 s.pcfg.Bootstrap.Timeout = 10 * time.Minute 1249 s.pcfg.Bootstrap.ControllerExternalIPs = []string{"10.0.0.1"} 1250 s.pcfg.Bootstrap.IgnoreProxy = true 1251 1252 controllerStacker := s.controllerStackerGetter() 1253 mockStdCtx := mocks.NewMockContext(ctrl) 1254 ctx := modelcmd.BootstrapContext(mockStdCtx, cmdtesting.Context(c)) 1255 controllerStacker.SetContext(ctx) 1256 1257 ns := &core.Namespace{ 1258 ObjectMeta: v1.ObjectMeta{ 1259 Name: s.namespace, 1260 Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "model.juju.is/name": "controller-1"}, 1261 }, 1262 } 1263 ns.Name = s.namespace 1264 s.ensureJujuNamespaceAnnotations(true, ns) 1265 1266 ctxDoneChan := make(chan struct{}, 1) 1267 1268 gomock.InOrder( 1269 mockStdCtx.EXPECT().Err().Return(nil), 1270 mockStdCtx.EXPECT().Done().DoAndReturn(func() <-chan struct{} { 1271 ctxDoneChan <- struct{}{} 1272 return ctxDoneChan 1273 }), 1274 mockStdCtx.EXPECT().Err().Return(context.DeadlineExceeded).MinTimes(1), 1275 ) 1276 1277 errChan := make(chan error) 1278 go func() { 1279 errChan <- controllerStacker.Deploy() 1280 }() 1281 1282 select { 1283 case err := <-errChan: 1284 c.Assert(err, gc.ErrorMatches, `creating service for controller: waiting for controller service address fully provisioned timeout`) 1285 c.Assert(watchers, gc.HasLen, 0) 1286 case <-time.After(coretesting.LongWait): 1287 c.Fatalf("timed out waiting for deploy return") 1288 } 1289 }