istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/echo/kube/deployment.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kube 16 17 import ( 18 "context" 19 "fmt" 20 "net/netip" 21 "os" 22 "path" 23 "path/filepath" 24 "strings" 25 "text/template" 26 "time" 27 28 "github.com/hashicorp/go-multierror" 29 appsv1 "k8s.io/api/apps/v1" 30 corev1 "k8s.io/api/core/v1" 31 kerrors "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/types" 34 "k8s.io/client-go/kubernetes" 35 36 "istio.io/api/label" 37 meshconfig "istio.io/api/mesh/v1alpha1" 38 istioctlcmd "istio.io/istio/istioctl/pkg/workload" 39 "istio.io/istio/pkg/config/constants" 40 "istio.io/istio/pkg/config/protocol" 41 "istio.io/istio/pkg/log" 42 echoCommon "istio.io/istio/pkg/test/echo/common" 43 "istio.io/istio/pkg/test/env" 44 "istio.io/istio/pkg/test/framework/components/echo" 45 "istio.io/istio/pkg/test/framework/components/environment/kube" 46 "istio.io/istio/pkg/test/framework/components/istio" 47 "istio.io/istio/pkg/test/framework/components/istioctl" 48 "istio.io/istio/pkg/test/framework/components/namespace" 49 "istio.io/istio/pkg/test/framework/resource" 50 "istio.io/istio/pkg/test/framework/resource/config/apply" 51 "istio.io/istio/pkg/test/scopes" 52 "istio.io/istio/pkg/test/util/file" 53 "istio.io/istio/pkg/test/util/retry" 54 "istio.io/istio/pkg/test/util/tmpl" 55 "istio.io/istio/pkg/util/protomarshal" 56 ) 57 58 const ( 59 // for proxyless we add a special gRPC server that doesn't get configured with xDS for test-runner use 60 grpcMagicPort = 17171 61 // for non-Go implementations of gRPC echo, this is the port used to forward non-gRPC requests to the Go server 62 grpcFallbackPort = 17777 63 ) 64 65 var echoKubeTemplatesDir = path.Join(env.IstioSrc, "pkg/test/framework/components/echo/kube/templates") 66 67 func getTemplate(tmplFilePath string) *template.Template { 68 yamlPath := path.Join(echoKubeTemplatesDir, tmplFilePath) 69 if filepath.IsAbs(tmplFilePath) { 70 yamlPath = tmplFilePath 71 } 72 return tmpl.MustParse(file.MustAsString(yamlPath)) 73 } 74 75 var _ workloadHandler = &deployment{} 76 77 type deployment struct { 78 ctx resource.Context 79 cfg echo.Config 80 shouldCreateWLE bool 81 } 82 83 func newDeployment(ctx resource.Context, cfg echo.Config) (*deployment, error) { 84 if !cfg.Cluster.IsConfig() && cfg.DeployAsVM { 85 return nil, fmt.Errorf("cannot deploy %s/%s as VM on non-config %s", 86 cfg.Namespace.Name(), 87 cfg.Service, 88 cfg.Cluster.Name()) 89 } 90 91 if cfg.DeployAsVM { 92 if err := createVMConfig(ctx, cfg); err != nil { 93 return nil, fmt.Errorf("failed creating vm config for %s/%s: %v", 94 cfg.Namespace.Name(), 95 cfg.Service, 96 err) 97 } 98 } 99 100 deploymentYAML, err := GenerateDeployment(ctx, cfg, ctx.Settings()) 101 if err != nil { 102 return nil, fmt.Errorf("failed generating echo deployment YAML for %s/%s: %v", 103 cfg.Namespace.Name(), 104 cfg.Service, err) 105 } 106 107 // Apply the deployment to the configured cluster. 108 if err = ctx.ConfigKube(cfg.Cluster). 109 YAML(cfg.Namespace.Name(), deploymentYAML). 110 Apply(apply.NoCleanup); err != nil { 111 return nil, fmt.Errorf("failed deploying echo %s to cluster %s: %v", 112 cfg.ClusterLocalFQDN(), cfg.Cluster.Name(), err) 113 } 114 115 return &deployment{ 116 ctx: ctx, 117 cfg: cfg, 118 shouldCreateWLE: cfg.DeployAsVM && !cfg.AutoRegisterVM, 119 }, nil 120 } 121 122 // Restart performs restarts of all the pod of the deployment. 123 // This is analogous to `kubectl rollout restart` on the echo deployment and waits for 124 // `kubectl rollout status` to complete before returning, but uses direct API calls. 125 func (d *deployment) Restart() error { 126 var errs error 127 var deploymentNames []string 128 for _, s := range d.cfg.Subsets { 129 // TODO(Monkeyanator) move to common place so doesn't fall out of sync with templates 130 deploymentNames = append(deploymentNames, fmt.Sprintf("%s-%s", d.cfg.Service, s.Version)) 131 } 132 curTimestamp := time.Now().Format(time.RFC3339) 133 for _, deploymentName := range deploymentNames { 134 patchOpts := metav1.PatchOptions{} 135 patchData := fmt.Sprintf(`{ 136 "spec": { 137 "template": { 138 "metadata": { 139 "annotations": { 140 "kubectl.kubernetes.io/restartedAt": %q 141 } 142 } 143 } 144 } 145 }`, curTimestamp) // e.g., “2006-01-02T15:04:05Z07:00” 146 var err error 147 appsv1Client := d.cfg.Cluster.Kube().AppsV1() 148 149 if d.cfg.IsStatefulSet() { 150 _, err = appsv1Client.StatefulSets(d.cfg.Namespace.Name()).Patch(context.TODO(), deploymentName, 151 types.StrategicMergePatchType, []byte(patchData), patchOpts) 152 } else { 153 _, err = appsv1Client.Deployments(d.cfg.Namespace.Name()).Patch(context.TODO(), deploymentName, 154 types.StrategicMergePatchType, []byte(patchData), patchOpts) 155 } 156 if err != nil { 157 errs = multierror.Append(errs, fmt.Errorf("failed to rollout restart %v/%v: %v (timestamp:%q)", d.cfg.Namespace.Name(), deploymentName, err, curTimestamp)) 158 continue 159 } 160 161 if err := retry.UntilSuccess(func() error { 162 if d.cfg.IsStatefulSet() { 163 sts, err := appsv1Client.StatefulSets(d.cfg.Namespace.Name()).Get(context.TODO(), deploymentName, metav1.GetOptions{}) 164 if err != nil { 165 return err 166 } 167 if sts.Spec.Replicas == nil || !statefulsetComplete(sts) { 168 return fmt.Errorf("rollout is not yet done (updated replicas:%v)", sts.Status.UpdatedReplicas) 169 } 170 } else { 171 dep, err := appsv1Client.Deployments(d.cfg.Namespace.Name()).Get(context.TODO(), deploymentName, metav1.GetOptions{}) 172 if err != nil { 173 return err 174 } 175 if dep.Spec.Replicas == nil || !deploymentComplete(dep) { 176 return fmt.Errorf("rollout is not yet done (updated replicas: %v)", dep.Status.UpdatedReplicas) 177 } 178 } 179 return nil 180 }, retry.Timeout(60*time.Second), retry.Delay(2*time.Second)); err != nil { 181 errs = multierror.Append(errs, fmt.Errorf("failed to wait rollout status for %v/%v: %v", 182 d.cfg.Namespace.Name(), deploymentName, err)) 183 } 184 } 185 return errs 186 } 187 188 func (d *deployment) WorkloadReady(w *workload) { 189 if !d.shouldCreateWLE { 190 return 191 } 192 193 // Deploy the workload entry to the primary cluster. We will read WorkloadEntry across clusters. 194 wle := d.workloadEntryYAML(w) 195 if err := d.ctx.ConfigKube(d.cfg.Cluster.Primary()). 196 YAML(d.cfg.Namespace.Name(), wle). 197 Apply(apply.NoCleanup); err != nil { 198 log.Warnf("failed deploying echo WLE for %s/%s to primary cluster: %v", 199 d.cfg.Namespace.Name(), 200 d.cfg.Service, 201 err) 202 } 203 } 204 205 func (d *deployment) WorkloadNotReady(w *workload) { 206 if !d.shouldCreateWLE { 207 return 208 } 209 210 wle := d.workloadEntryYAML(w) 211 if err := d.ctx.ConfigKube(d.cfg.Cluster.Primary()).YAML(d.cfg.Namespace.Name(), wle).Delete(); err != nil { 212 log.Warnf("failed deleting echo WLE for %s/%s from primary cluster: %v", 213 d.cfg.Namespace.Name(), 214 d.cfg.Service, 215 err) 216 } 217 } 218 219 func (d *deployment) workloadEntryYAML(w *workload) string { 220 name := w.pod.Name 221 podIP := w.pod.Status.PodIP 222 sa := serviceAccount(d.cfg) 223 network := d.cfg.Cluster.NetworkName() 224 service := d.cfg.Service 225 version := w.pod.Labels[constants.TestVMVersionLabel] 226 227 return fmt.Sprintf(` 228 apiVersion: networking.istio.io/v1alpha3 229 kind: WorkloadEntry 230 metadata: 231 name: %s 232 spec: 233 address: %s 234 serviceAccount: %s 235 network: %q 236 labels: 237 app: %s 238 version: %s 239 `, name, podIP, sa, network, service, version) 240 } 241 242 func GenerateDeployment(ctx resource.Context, cfg echo.Config, settings *resource.Settings) (string, error) { 243 if settings == nil { 244 var err error 245 settings, err = resource.SettingsFromCommandLine("template") 246 if err != nil { 247 return "", err 248 } 249 } 250 251 params, err := deploymentParams(ctx, cfg, settings) 252 if err != nil { 253 return "", err 254 } 255 256 deploy := getTemplate(deploymentTemplateFile) 257 if cfg.DeployAsVM { 258 deploy = getTemplate(vmDeploymentTemplateFile) 259 } 260 261 return tmpl.Execute(deploy, params) 262 } 263 264 func GenerateService(cfg echo.Config) (string, error) { 265 params := serviceParams(cfg) 266 return tmpl.Execute(getTemplate(serviceTemplateFile), params) 267 } 268 269 var VMImages = map[echo.VMDistro]string{ 270 echo.UbuntuBionic: "app_sidecar_ubuntu_bionic", 271 echo.UbuntuNoble: "app_sidecar_ubuntu_noble", 272 echo.Debian12: "app_sidecar_debian_12", 273 echo.Rockylinux9: "app_sidecar_rockylinux_9", 274 } 275 276 // ArmVMImages is the subset of images that work on arm64. These fail because Istio's arm64 build has a higher GLIBC requirement 277 var ArmVMImages = map[echo.VMDistro]string{ 278 echo.UbuntuNoble: "app_sidecar_ubuntu_noble", 279 echo.Debian12: "app_sidecar_debian_12", 280 echo.Rockylinux9: "app_sidecar_rockylinux_9", 281 } 282 283 var RevVMImages = func() map[string]echo.VMDistro { 284 r := map[string]echo.VMDistro{} 285 for k, v := range VMImages { 286 r[v] = k 287 } 288 return r 289 }() 290 291 // getVMOverrideForIstiodDNS returns the DNS alias to use for istiod on VMs. VMs always access 292 // istiod via the east-west gateway, even though they are installed on the same cluster as istiod. 293 func getVMOverrideForIstiodDNS(ctx resource.Context, cfg echo.Config) (istioHost string, istioIP string) { 294 if ctx == nil { 295 return 296 } 297 298 ist, err := istio.Get(ctx) 299 if err != nil { 300 log.Warnf("VM config failed to get Istio component for %s: %v", cfg.Cluster.Name(), err) 301 return 302 } 303 304 // Generate the istiod host the same way as istioctl. 305 istioNS := ist.Settings().SystemNamespace 306 istioRevision := getIstioRevision(cfg.Namespace) 307 istioHost = istioctlcmd.IstiodHost(istioNS, istioRevision) 308 309 istioIPAddr := ist.EastWestGatewayFor(cfg.Cluster).DiscoveryAddresses()[0].Addr() 310 if !istioIPAddr.IsValid() { 311 log.Warnf("VM config failed to get east-west gateway IP for %s", cfg.Cluster.Name()) 312 istioHost, istioIP = "", "" 313 } else { 314 istioIP = istioIPAddr.String() 315 } 316 return 317 } 318 319 func deploymentParams(ctx resource.Context, cfg echo.Config, settings *resource.Settings) (map[string]any, error) { 320 supportStartupProbe := cfg.Cluster.MinKubeVersion(0) 321 imagePullSecretName, err := settings.Image.PullSecretName() 322 if err != nil { 323 return nil, err 324 } 325 326 containerPorts := getContainerPorts(cfg) 327 appContainers := []map[string]any{{ 328 "Name": appContainerName, 329 "ImageFullPath": settings.EchoImage, // This overrides image hub/tag if it's not empty. 330 "ContainerPorts": containerPorts, 331 }} 332 333 // Only use the custom image for proxyless gRPC instances. This will bind the gRPC ports on one container 334 // and all other ports on another. Additionally, we bind one port for communication between the custom image 335 // container, and the regular Go server. 336 if cfg.IsProxylessGRPC() && settings.CustomGRPCEchoImage != "" { 337 var grpcPorts, otherPorts echoCommon.PortList 338 for _, port := range containerPorts { 339 if port.Protocol == protocol.GRPC { 340 grpcPorts = append(grpcPorts, port) 341 } else { 342 otherPorts = append(otherPorts, port) 343 } 344 } 345 otherPorts = append(otherPorts, &echoCommon.Port{ 346 Name: "grpc-fallback", 347 Protocol: protocol.GRPC, 348 Port: grpcFallbackPort, 349 }) 350 appContainers[0]["ContainerPorts"] = otherPorts 351 appContainers = append(appContainers, map[string]any{ 352 "Name": "custom-grpc-" + appContainerName, 353 "ImageFullPath": settings.CustomGRPCEchoImage, // This overrides image hub/tag if it's not empty. 354 "ContainerPorts": grpcPorts, 355 "FallbackPort": grpcFallbackPort, 356 }) 357 } 358 359 if cfg.WorkloadWaypointProxy != "" { 360 for _, subset := range cfg.Subsets { 361 if subset.Labels == nil { 362 subset.Labels = make(map[string]string) 363 } 364 subset.Labels[constants.AmbientUseWaypointLabel] = cfg.WorkloadWaypointProxy 365 } 366 } 367 368 params := map[string]any{ 369 "ImageHub": settings.Image.Hub, 370 "ImageTag": settings.Image.Tag, 371 "ImagePullPolicy": settings.Image.PullPolicy, 372 "ImagePullSecretName": imagePullSecretName, 373 "Service": cfg.Service, 374 "StatefulSet": cfg.StatefulSet, 375 "ProxylessGRPC": cfg.IsProxylessGRPC(), 376 "GRPCMagicPort": grpcMagicPort, 377 "Locality": cfg.Locality, 378 "ServiceAccount": cfg.ServiceAccount, 379 "DisableAutomountSAToken": cfg.DisableAutomountSAToken, 380 "AppContainers": appContainers, 381 "ContainerPorts": containerPorts, 382 "Subsets": cfg.Subsets, 383 "TLSSettings": cfg.TLSSettings, 384 "Cluster": cfg.Cluster.Name(), 385 "ReadinessTCPPort": cfg.ReadinessTCPPort, 386 "ReadinessGRPCPort": cfg.ReadinessGRPCPort, 387 "StartupProbe": supportStartupProbe, 388 "IncludeExtAuthz": cfg.IncludeExtAuthz, 389 "Revisions": settings.Revisions.TemplateMap(), 390 "Compatibility": settings.Compatibility, 391 "WorkloadClass": cfg.WorkloadClass(), 392 "OverlayIstioProxy": canCreateIstioProxy(settings.Revisions.Minimum()) && !settings.Ambient, 393 "Ambient": settings.Ambient, 394 } 395 396 vmIstioHost, vmIstioIP := "", "" 397 if cfg.IsVM() { 398 vmImage := VMImages[cfg.VMDistro] 399 _, knownImage := RevVMImages[cfg.VMDistro] 400 if vmImage == "" { 401 if knownImage { 402 vmImage = cfg.VMDistro 403 } else { 404 vmImage = VMImages[echo.DefaultVMDistro] 405 } 406 log.Debugf("no image for distro %s, defaulting to %s", cfg.VMDistro, echo.DefaultVMDistro) 407 } 408 409 vmIstioHost, vmIstioIP = getVMOverrideForIstiodDNS(ctx, cfg) 410 411 params["VM"] = map[string]any{ 412 "Image": vmImage, 413 "IstioHost": vmIstioHost, 414 "IstioIP": vmIstioIP, 415 } 416 } 417 418 return params, nil 419 } 420 421 func serviceParams(cfg echo.Config) map[string]any { 422 if cfg.ServiceWaypointProxy != "" { 423 if cfg.ServiceLabels == nil { 424 cfg.ServiceLabels = make(map[string]string) 425 } 426 cfg.ServiceLabels[constants.AmbientUseWaypointLabel] = cfg.ServiceWaypointProxy 427 } 428 return map[string]any{ 429 "Service": cfg.Service, 430 "Headless": cfg.Headless, 431 "ServiceAccount": cfg.ServiceAccount, 432 "ServicePorts": cfg.Ports.GetServicePorts(), 433 "ServiceLabels": cfg.ServiceLabels, 434 "IPFamilies": cfg.IPFamilies, 435 "IPFamilyPolicy": cfg.IPFamilyPolicy, 436 } 437 } 438 439 // createVMConfig sets up a Service account, 440 func createVMConfig(ctx resource.Context, cfg echo.Config) error { 441 istioCtl, err := istioctl.New(ctx, istioctl.Config{Cluster: cfg.Cluster}) 442 if err != nil { 443 return err 444 } 445 // generate config files for VM bootstrap 446 dirname := fmt.Sprintf("%s-vm-config-", cfg.Service) 447 dir, err := ctx.CreateDirectory(dirname) 448 if err != nil { 449 return err 450 } 451 452 wg := tmpl.MustEvaluate(` 453 apiVersion: networking.istio.io/v1alpha3 454 kind: WorkloadGroup 455 metadata: 456 name: {{.name}} 457 namespace: {{.namespace}} 458 spec: 459 metadata: 460 labels: 461 app: {{.name}} 462 test.istio.io/class: {{ .workloadClass }} 463 template: 464 serviceAccount: {{.serviceAccount}} 465 network: "{{.network}}" 466 probe: 467 failureThreshold: 5 468 httpGet: 469 path: / 470 port: 8080 471 periodSeconds: 2 472 successThreshold: 1 473 timeoutSeconds: 2 474 475 `, map[string]string{ 476 "name": cfg.Service, 477 "namespace": cfg.Namespace.Name(), 478 "serviceAccount": serviceAccount(cfg), 479 "network": cfg.Cluster.NetworkName(), 480 "workloadClass": cfg.WorkloadClass(), 481 }) 482 483 // Push the WorkloadGroup for auto-registration 484 if cfg.AutoRegisterVM { 485 if err := ctx.ConfigKube(cfg.Cluster). 486 YAML(cfg.Namespace.Name(), wg). 487 Apply(apply.NoCleanup); err != nil { 488 return err 489 } 490 } 491 492 if cfg.ServiceAccount { 493 // create service account, the next workload command will use it to generate a token 494 err = createServiceAccount(cfg.Cluster.Kube(), cfg.Namespace.Name(), serviceAccount(cfg)) 495 if err != nil && !kerrors.IsAlreadyExists(err) { 496 return err 497 } 498 } 499 500 if err := os.WriteFile(path.Join(dir, "workloadgroup.yaml"), []byte(wg), 0o600); err != nil { 501 return err 502 } 503 504 ist, err := istio.Get(ctx) 505 if err != nil { 506 return err 507 } 508 // this will wait until the eastwest gateway has an IP before running the next command 509 istiodAddr, err := ist.RemoteDiscoveryAddressFor(cfg.Cluster) 510 if err != nil { 511 return err 512 } 513 514 var subsetDir string 515 for _, subset := range cfg.Subsets { 516 subsetDir, err = os.MkdirTemp(dir, subset.Version+"-") 517 if err != nil { 518 return err 519 } 520 cmd := []string{ 521 "x", "workload", "entry", "configure", 522 "-f", path.Join(dir, "workloadgroup.yaml"), 523 "-o", subsetDir, 524 } 525 if ctx.Clusters().IsMulticluster() { 526 // When VMs talk about "cluster", they refer to the cluster they connect to for discovery 527 cmd = append(cmd, "--clusterID", cfg.Cluster.Name()) 528 } 529 if cfg.AutoRegisterVM { 530 cmd = append(cmd, "--autoregister") 531 } 532 if !ctx.Environment().(*kube.Environment).Settings().LoadBalancerSupported { 533 // LoadBalancer may not be supported and the command doesn't have NodePort fallback logic that the tests do 534 cmd = append(cmd, "--ingressIP", istiodAddr.Addr().String()) 535 } 536 if rev := getIstioRevision(cfg.Namespace); len(rev) > 0 { 537 cmd = append(cmd, "--revision", rev) 538 } 539 // make sure namespace controller has time to create root-cert ConfigMap 540 if err := retry.UntilSuccess(func() error { 541 stdout, stderr, err := istioCtl.Invoke(cmd) 542 if err != nil { 543 return fmt.Errorf("%v:\nstdout: %s\nstderr: %s", err, stdout, stderr) 544 } 545 return nil 546 }, retry.Timeout(20*time.Second)); err != nil { 547 return err 548 } 549 550 // support proxyConfig customizations on VMs via annotation in the echo API. 551 for k, v := range subset.Annotations { 552 if k == "proxy.istio.io/config" { 553 if err := patchProxyConfigFile(path.Join(subsetDir, "mesh.yaml"), v); err != nil { 554 return fmt.Errorf("failed patching proxyconfig: %v", err) 555 } 556 } 557 } 558 559 if err := customizeVMEnvironment(ctx, cfg, path.Join(subsetDir, "cluster.env"), istiodAddr); err != nil { 560 return fmt.Errorf("failed customizing cluster.env: %v", err) 561 } 562 563 // push bootstrap config as a ConfigMap so we can mount it on our "vm" pods 564 cmData := map[string][]byte{} 565 generatedFiles, err := os.ReadDir(subsetDir) 566 if err != nil { 567 return err 568 } 569 for _, file := range generatedFiles { 570 if file.IsDir() { 571 continue 572 } 573 cmData[file.Name()], err = os.ReadFile(path.Join(subsetDir, file.Name())) 574 if err != nil { 575 return err 576 } 577 } 578 cmName := fmt.Sprintf("%s-%s-vm-bootstrap", cfg.Service, subset.Version) 579 cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: cmName}, BinaryData: cmData} 580 _, err = cfg.Cluster.Kube().CoreV1().ConfigMaps(cfg.Namespace.Name()).Create(context.TODO(), cm, metav1.CreateOptions{}) 581 if err != nil && !kerrors.IsAlreadyExists(err) { 582 return fmt.Errorf("failed creating configmap %s: %v", cm.Name, err) 583 } 584 } 585 586 // push the generated token as a Secret (only need one, they should be identical) 587 token, err := os.ReadFile(path.Join(subsetDir, "istio-token")) 588 if err != nil { 589 return err 590 } 591 secret := &corev1.Secret{ 592 ObjectMeta: metav1.ObjectMeta{ 593 Name: cfg.Service + "-istio-token", 594 Namespace: cfg.Namespace.Name(), 595 }, 596 Data: map[string][]byte{ 597 "istio-token": token, 598 }, 599 } 600 if _, err := cfg.Cluster.Kube().CoreV1().Secrets(cfg.Namespace.Name()).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil { 601 if kerrors.IsAlreadyExists(err) { 602 if _, err := cfg.Cluster.Kube().CoreV1().Secrets(cfg.Namespace.Name()).Update(context.TODO(), secret, metav1.UpdateOptions{}); err != nil { 603 return fmt.Errorf("failed updating secret %s: %v", secret.Name, err) 604 } 605 } else { 606 return fmt.Errorf("failed creating secret %s: %v", secret.Name, err) 607 } 608 } 609 610 return nil 611 } 612 613 func patchProxyConfigFile(file string, overrides string) error { 614 config, err := readMeshConfig(file) 615 if err != nil { 616 return err 617 } 618 overrideYAML := "defaultConfig:\n" 619 overrideYAML += istio.Indent(overrides, " ") 620 if err := protomarshal.ApplyYAML(overrideYAML, config.DefaultConfig); err != nil { 621 return err 622 } 623 outYAML, err := protomarshal.ToYAML(config) 624 if err != nil { 625 return err 626 } 627 return os.WriteFile(file, []byte(outYAML), 0o744) 628 } 629 630 func readMeshConfig(file string) (*meshconfig.MeshConfig, error) { 631 baseYAML, err := os.ReadFile(file) 632 if err != nil { 633 return nil, err 634 } 635 config := &meshconfig.MeshConfig{} 636 if err := protomarshal.ApplyYAML(string(baseYAML), config); err != nil { 637 return nil, err 638 } 639 return config, nil 640 } 641 642 func createServiceAccount(client kubernetes.Interface, ns string, serviceAccount string) error { 643 scopes.Framework.Debugf("Creating service account for: %s/%s", ns, serviceAccount) 644 _, err := client.CoreV1().ServiceAccounts(ns).Create(context.TODO(), &corev1.ServiceAccount{ 645 ObjectMeta: metav1.ObjectMeta{Name: serviceAccount}, 646 }, metav1.CreateOptions{}) 647 return err 648 } 649 650 // getContainerPorts converts the ports to a port list of container ports. 651 // Adds ports for health/readiness if necessary. 652 func getContainerPorts(cfg echo.Config) echoCommon.PortList { 653 ports := cfg.Ports 654 containerPorts := make(echoCommon.PortList, 0, len(ports)) 655 var healthPort *echoCommon.Port 656 var readyPort *echoCommon.Port 657 for _, p := range ports { 658 // Add the port to the set of application ports. 659 cport := &echoCommon.Port{ 660 Name: p.Name, 661 Protocol: p.Protocol, 662 Port: p.WorkloadPort, 663 TLS: p.TLS, 664 ServerFirst: p.ServerFirst, 665 InstanceIP: p.InstanceIP, 666 LocalhostIP: p.LocalhostIP, 667 } 668 containerPorts = append(containerPorts, cport) 669 670 switch p.Protocol { 671 case protocol.GRPC: 672 if cfg.IsProxylessGRPC() { 673 cport.XDSServer = true 674 } 675 continue 676 case protocol.HTTP: 677 if p.WorkloadPort == httpReadinessPort { 678 readyPort = cport 679 } 680 default: 681 if p.WorkloadPort == tcpHealthPort { 682 healthPort = cport 683 } 684 } 685 } 686 687 // If we haven't added the readiness/health ports, do so now. 688 if readyPort == nil { 689 containerPorts = append(containerPorts, &echoCommon.Port{ 690 Name: "http-readiness-port", 691 Protocol: protocol.HTTP, 692 Port: httpReadinessPort, 693 }) 694 } 695 if healthPort == nil { 696 containerPorts = append(containerPorts, &echoCommon.Port{ 697 Name: "tcp-health-port", 698 Protocol: protocol.HTTP, 699 Port: tcpHealthPort, 700 }) 701 } 702 703 // gives something the test runner to connect to without being in the mesh 704 if cfg.IsProxylessGRPC() { 705 containerPorts = append(containerPorts, &echoCommon.Port{ 706 Name: "grpc-magic-port", 707 Protocol: protocol.GRPC, 708 Port: grpcMagicPort, 709 LocalhostIP: true, 710 }) 711 } 712 return containerPorts 713 } 714 715 func customizeVMEnvironment(ctx resource.Context, cfg echo.Config, clusterEnv string, istiodAddr netip.AddrPort) error { 716 f, err := os.OpenFile(clusterEnv, os.O_APPEND|os.O_WRONLY, os.ModeAppend) 717 if err != nil { 718 return fmt.Errorf("failed opening %s: %v", clusterEnv, err) 719 } 720 defer f.Close() 721 722 if cfg.VMEnvironment != nil { 723 for k, v := range cfg.VMEnvironment { 724 addition := fmt.Sprintf("%s=%s\n", k, v) 725 _, err = f.WriteString(addition) 726 if err != nil { 727 return fmt.Errorf("failed writing %q to %s: %v", addition, clusterEnv, err) 728 } 729 } 730 } 731 if !ctx.Environment().(*kube.Environment).Settings().LoadBalancerSupported { 732 // customize cluster.env with NodePort mapping 733 _, err = f.WriteString(fmt.Sprintf("ISTIO_PILOT_PORT=%d\n", istiodAddr.Port())) 734 if err != nil { 735 return err 736 } 737 } 738 return err 739 } 740 741 func canCreateIstioProxy(version resource.IstioVersion) bool { 742 // if no revision specified create the istio-proxy 743 if string(version) == "" { 744 return true 745 } 746 if minor := strings.Split(string(version), ".")[1]; minor > "8" || len(minor) > 1 { 747 return true 748 } 749 return false 750 } 751 752 func getIstioRevision(n namespace.Instance) string { 753 nsLabels, err := n.Labels() 754 if err != nil { 755 log.Warnf("failed fetching labels for %s; assuming no-revision (can cause failures): %v", n.Name(), err) 756 return "" 757 } 758 return nsLabels[label.IoIstioRev.Name] 759 } 760 761 func statefulsetComplete(statefulset *appsv1.StatefulSet) bool { 762 return statefulset.Status.UpdatedReplicas == *(statefulset.Spec.Replicas) && 763 statefulset.Status.Replicas == *(statefulset.Spec.Replicas) && 764 statefulset.Status.AvailableReplicas == *(statefulset.Spec.Replicas) && 765 statefulset.Status.ReadyReplicas == *(statefulset.Spec.Replicas) && 766 statefulset.Status.ObservedGeneration >= statefulset.Generation 767 } 768 769 func deploymentComplete(deployment *appsv1.Deployment) bool { 770 return deployment.Status.UpdatedReplicas == *(deployment.Spec.Replicas) && 771 deployment.Status.Replicas == *(deployment.Spec.Replicas) && 772 deployment.Status.AvailableReplicas == *(deployment.Spec.Replicas) && 773 deployment.Status.ReadyReplicas == *(deployment.Spec.Replicas) && 774 deployment.Status.ObservedGeneration >= deployment.Generation 775 }