istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/helm/util.go (about) 1 //go:build integ 2 // +build integ 3 4 // Copyright Istio Authors 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package helm 19 20 import ( 21 "context" 22 "fmt" 23 "os" 24 "path/filepath" 25 "strings" 26 "time" 27 28 v1 "k8s.io/api/core/v1" 29 kerrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 33 networking "istio.io/api/networking/v1alpha3" 34 "istio.io/client-go/pkg/apis/networking/v1alpha3" 35 "istio.io/istio/pkg/config/constants" 36 "istio.io/istio/pkg/log" 37 "istio.io/istio/pkg/maps" 38 "istio.io/istio/pkg/test" 39 "istio.io/istio/pkg/test/env" 40 "istio.io/istio/pkg/test/framework" 41 "istio.io/istio/pkg/test/framework/components/cluster" 42 "istio.io/istio/pkg/test/framework/components/cluster/kube" 43 "istio.io/istio/pkg/test/helm" 44 kubetest "istio.io/istio/pkg/test/kube" 45 "istio.io/istio/pkg/test/scopes" 46 "istio.io/istio/pkg/test/util/retry" 47 ) 48 49 const ( 50 IstioNamespace = "istio-system" 51 ReleasePrefix = "istio-" 52 BaseChart = "base" 53 CRDsFolder = "crds" 54 DiscoveryChartsDir = "istio-discovery" 55 BaseReleaseName = ReleasePrefix + BaseChart 56 RepoBaseChartPath = BaseChart 57 RepoDiscoveryChartPath = "istiod" 58 RepoGatewayChartPath = "gateway" 59 IstiodReleaseName = "istiod" 60 IngressReleaseName = "istio-ingress" 61 ControlChartsDir = "istio-control" 62 GatewayChartsDir = "gateway" 63 CniChartsDir = "istio-cni" 64 ZtunnelChartsDir = "ztunnel" 65 RepoCniChartPath = "cni" 66 CniReleaseName = ReleasePrefix + "cni" 67 RepoZtunnelChartPath = "ztunnel" 68 ZtunnelReleaseName = "ztunnel" 69 70 RetryDelay = 2 * time.Second 71 RetryTimeOut = 5 * time.Minute 72 Timeout = 2 * time.Minute 73 74 defaultValues = ` 75 global: 76 hub: %s 77 %s 78 variant: %q 79 revision: "%s" 80 ` 81 ambientProfileOverride = ` 82 global: 83 hub: %s 84 %s 85 variant: %q 86 profile: ambient 87 ` 88 sampleEnvoyFilter = ` 89 apiVersion: networking.istio.io/v1alpha3 90 kind: EnvoyFilter 91 metadata: 92 name: sample 93 spec: 94 workloadSelector: 95 labels: 96 istio: ingressgateway 97 configPatches: 98 - applyTo: NETWORK_FILTER # http connection manager is a filter in Envoy 99 match: 100 context: GATEWAY 101 listener: 102 filterChain: 103 sni: app.example.com 104 filter: 105 name: "envoy.filters.network.http_connection_manager" 106 patch: 107 operation: MERGE 108 value: 109 typed_config: 110 "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 111 xff_num_trusted_hops: 5 112 common_http_protocol_options: 113 idle_timeout: 30s 114 ` 115 116 revisionedSampleEnvoyFilter = ` 117 apiVersion: networking.istio.io/v1alpha3 118 kind: EnvoyFilter 119 metadata: 120 name: sample 121 labels: 122 istio.io/rev: %s 123 spec: 124 workloadSelector: 125 labels: 126 istio: ingressgateway 127 configPatches: 128 - applyTo: NETWORK_FILTER # http connection manager is a filter in Envoy 129 match: 130 context: GATEWAY 131 listener: 132 filterChain: 133 sni: app.example.com 134 filter: 135 name: "envoy.filters.network.http_connection_manager" 136 patch: 137 operation: MERGE 138 value: 139 typed_config: 140 "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" 141 xff_num_trusted_hops: 5 142 common_http_protocol_options: 143 idle_timeout: 30s 144 ` 145 146 extendedTelemetry = ` 147 apiVersion: telemetry.istio.io/v1 148 kind: Telemetry 149 metadata: 150 name: sample 151 spec: 152 metrics: 153 - providers: 154 - name: prometheus 155 reportingInterval: 10s 156 ` 157 158 revisionedExtendedTelemetry = ` 159 apiVersion: telemetry.istio.io/v1 160 kind: Telemetry 161 metadata: 162 name: sample 163 labels: 164 istio.io/rev: %s 165 spec: 166 metrics: 167 - providers: 168 - name: prometheus 169 reportingInterval: 10s 170 ` 171 ) 172 173 // ManifestsChartPath is path of local Helm charts used for testing. 174 var ManifestsChartPath = filepath.Join(env.IstioSrc, "manifests/charts") 175 176 // adjustValuesForOpenShift adds the "openshift" or "openshift-ambient" profile to the 177 // values if tests are running in OpenShift, and returns the modified values 178 func adjustValuesForOpenShift(ctx framework.TestContext, values string) string { 179 if !ctx.Settings().OpenShift { 180 return values 181 } 182 183 if !strings.Contains(values, "profile: ") { 184 values += "\nprofile: openshift\n" 185 } else if strings.Contains(values, "profile: ambient") { 186 values = strings.ReplaceAll(values, "profile: ambient", "profile: openshift-ambient") 187 } 188 189 return values 190 } 191 192 // getValuesOverrides returns the values file created to pass into Helm override default values 193 // for the hub and tag. 194 // 195 // Tag can be the empty string, which means the values file will not have a 196 // tag. In other words, the tag will come from the chart. This is useful in the upgrade 197 // tests, where we deploy an old Istio version using the chart, and we want to use 198 // the tag that comes with the chart. 199 func GetValuesOverrides(ctx framework.TestContext, hub, tag, variant, revision string, isAmbient bool) string { 200 workDir := ctx.CreateTmpDirectoryOrFail("helm") 201 202 // Only use a tag value if not empty. Not having a tag in values means: Use the tag directly from the chart 203 if tag != "" { 204 tag = "tag: " + tag 205 } 206 207 overrideValues := fmt.Sprintf(defaultValues, hub, tag, variant, revision) 208 if isAmbient { 209 overrideValues = fmt.Sprintf(ambientProfileOverride, hub, tag, variant) 210 } 211 overrideValues = adjustValuesForOpenShift(ctx, overrideValues) 212 213 overrideValuesFile := filepath.Join(workDir, "values.yaml") 214 if err := os.WriteFile(overrideValuesFile, []byte(overrideValues), os.ModePerm); err != nil { 215 ctx.Fatalf("failed to write iop cr file: %v", err) 216 } 217 218 return overrideValuesFile 219 } 220 221 var DefaultNamespaceConfig = NewNamespaceConfig() 222 223 func NewNamespaceConfig(config ...types.NamespacedName) NamespaceConfig { 224 result := make(nsConfig, len(config)) 225 for _, c := range config { 226 result[c.Name] = c.Namespace 227 } 228 return result 229 } 230 231 type nsConfig map[string]string 232 233 func (n nsConfig) Get(name string) string { 234 if ns, ok := n[name]; ok { 235 return ns 236 } 237 return IstioNamespace 238 } 239 240 func (n nsConfig) Set(name, ns string) { 241 n[name] = ns 242 } 243 244 func (n nsConfig) AllNamespaces() []string { 245 return maps.Values(n) 246 } 247 248 type NamespaceConfig interface { 249 Get(name string) string 250 Set(name, ns string) 251 AllNamespaces() []string 252 } 253 254 // InstallIstio install Istio using Helm charts with the provided 255 // override values file and fails the tests on any failures. 256 func InstallIstio(t framework.TestContext, cs cluster.Cluster, h *helm.Helm, overrideValuesFile, 257 version string, installGateway bool, ambientProfile bool, nsConfig NamespaceConfig, 258 ) { 259 for _, ns := range nsConfig.AllNamespaces() { 260 CreateNamespace(t, cs, ns) 261 } 262 CreateNamespace(t, cs, IstioNamespace) 263 264 versionArgs := "" 265 266 baseChartPath := RepoBaseChartPath 267 discoveryChartPath := RepoDiscoveryChartPath 268 gatewayChartPath := RepoGatewayChartPath 269 cniChartPath := RepoCniChartPath 270 ztunnelChartPath := RepoZtunnelChartPath 271 272 gatewayOverrideValuesFile := overrideValuesFile 273 274 if version != "" { 275 // Prepend ~ to the version, so that we can refer to the latest patch version of a minor version 276 versionArgs = fmt.Sprintf("--repo %s --version ~%s", t.Settings().HelmRepo, version) 277 // Currently the ambient in-place upgrade tests try an upgrade from previous release which is 1.20, 278 // and many of the profile override values seem to be unrecognized by the gateway installation. 279 // So, this is a workaround until we move to 1.21 where we can use --set profile=ambient for the install/upgrade. 280 // TODO: Remove this once the previous release version for the test becomes 1.21 281 // refer: https://github.com/istio/istio/issues/49242 282 if ambientProfile { 283 gatewayOverrideValuesFile = GetValuesOverrides(t, t.Settings().Image.Hub, version, t.Settings().Image.Variant, "", false) 284 } 285 } else { 286 baseChartPath = filepath.Join(ManifestsChartPath, BaseChart) 287 discoveryChartPath = filepath.Join(ManifestsChartPath, ControlChartsDir, DiscoveryChartsDir) 288 gatewayChartPath = filepath.Join(ManifestsChartPath, version, GatewayChartsDir) 289 cniChartPath = filepath.Join(ManifestsChartPath, version, CniChartsDir) 290 ztunnelChartPath = filepath.Join(ManifestsChartPath, version, ZtunnelChartsDir) 291 292 } 293 294 // Install base chart 295 err := h.InstallChart(BaseReleaseName, baseChartPath, nsConfig.Get(BaseReleaseName), overrideValuesFile, Timeout, versionArgs) 296 if err != nil { 297 t.Fatalf("failed to install istio %s chart: %v", BaseChart, err) 298 } 299 300 // Install discovery chart 301 err = h.InstallChart(IstiodReleaseName, discoveryChartPath, nsConfig.Get(IstiodReleaseName), overrideValuesFile, Timeout, versionArgs) 302 if err != nil { 303 t.Fatalf("failed to install istio %s chart: %v", DiscoveryChartsDir, err) 304 } 305 306 if installGateway { 307 err = h.InstallChart(IngressReleaseName, gatewayChartPath, nsConfig.Get(IngressReleaseName), gatewayOverrideValuesFile, Timeout, versionArgs) 308 if err != nil { 309 t.Fatalf("failed to install istio %s chart: %v", GatewayChartsDir, err) 310 } 311 } 312 313 if ambientProfile || t.Settings().OpenShift { 314 // Install cni chart 315 err = h.InstallChart(CniReleaseName, cniChartPath, nsConfig.Get(CniReleaseName), overrideValuesFile, Timeout, versionArgs) 316 if err != nil { 317 t.Fatalf("failed to install istio %s chart: %v", CniChartsDir, err) 318 } 319 } 320 321 if ambientProfile { 322 323 // Install ztunnel chart 324 err = h.InstallChart(ZtunnelReleaseName, ztunnelChartPath, nsConfig.Get(ZtunnelReleaseName), overrideValuesFile, Timeout, versionArgs) 325 if err != nil { 326 t.Fatalf("failed to install istio %s chart: %v", ZtunnelChartsDir, err) 327 } 328 } 329 } 330 331 // InstallIstioWithRevision install Istio using Helm charts with the provided 332 // override values file and fails the tests on any failures. 333 func InstallIstioWithRevision(t framework.TestContext, cs cluster.Cluster, 334 h *helm.Helm, version, revision, overrideValuesFile string, upgradeBaseChart, useTestData bool, 335 ) { 336 CreateNamespace(t, cs, IstioNamespace) 337 versionArgs := "" 338 baseChartPath := RepoBaseChartPath 339 discoveryChartPath := RepoDiscoveryChartPath 340 if version != "" { 341 // Prepend ~ to the version, so that we can refer to the latest patch version of a minor version 342 versionArgs = fmt.Sprintf("--repo %s --version ~%s", t.Settings().HelmRepo, version) 343 } else { 344 baseChartPath = filepath.Join(ManifestsChartPath, BaseChart) 345 discoveryChartPath = filepath.Join(ManifestsChartPath, ControlChartsDir, DiscoveryChartsDir) 346 } 347 // base chart may already be installed if the Istio was previously already installed 348 if upgradeBaseChart { 349 // upgrade is always done with ../manifests/charts/base 350 err := h.UpgradeChart(BaseReleaseName, filepath.Join(ManifestsChartPath, BaseChart), 351 IstioNamespace, overrideValuesFile, Timeout) 352 if err != nil { 353 t.Fatalf("failed to upgrade istio %s chart", BaseChart) 354 } 355 } else { 356 err := h.InstallChart(BaseReleaseName, baseChartPath, IstioNamespace, overrideValuesFile, Timeout, versionArgs) 357 if err != nil { 358 t.Fatalf("failed to upgrade istio %s chart", BaseChart) 359 } 360 } 361 362 // install discovery chart with --set revision=NAME 363 if useTestData { 364 err := h.InstallChart(IstiodReleaseName+"-"+revision, discoveryChartPath, IstioNamespace, overrideValuesFile, Timeout, versionArgs) 365 if err != nil { 366 t.Fatalf("failed to install istio %s chart", DiscoveryChartsDir) 367 } 368 } else { 369 err := h.InstallChart(IstiodReleaseName+"-"+revision, filepath.Join(ManifestsChartPath, ControlChartsDir, DiscoveryChartsDir), 370 IstioNamespace, overrideValuesFile, Timeout) 371 if err != nil { 372 t.Fatalf("failed to install istio %s chart", DiscoveryChartsDir) 373 } 374 375 } 376 } 377 378 func CreateNamespace(t test.Failer, cs cluster.Cluster, namespace string) { 379 if _, err := cs.Kube().CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ 380 ObjectMeta: metav1.ObjectMeta{ 381 Name: namespace, 382 }, 383 }, metav1.CreateOptions{}); err != nil { 384 if kerrors.IsAlreadyExists(err) { 385 log.Debugf("%v namespace already exist", IstioNamespace) 386 } else { 387 t.Fatalf("failed to create %v namespace: %v", IstioNamespace, err) 388 } 389 } 390 } 391 392 // DeleteIstio deletes installed Istio Helm charts and resources 393 func DeleteIstio(t framework.TestContext, h *helm.Helm, cs *kube.Cluster, config NamespaceConfig, isAmbient bool) { 394 scopes.Framework.Infof("cleaning up resources") 395 if err := h.DeleteChart(IngressReleaseName, config.Get(IngressReleaseName)); err != nil { 396 t.Errorf("failed to delete %s release: %v", IngressReleaseName, err) 397 } 398 if err := h.DeleteChart(IstiodReleaseName, config.Get(IstiodReleaseName)); err != nil { 399 t.Errorf("failed to delete %s release: %v", IstiodReleaseName, err) 400 } 401 if isAmbient { 402 if err := h.DeleteChart(ZtunnelReleaseName, config.Get(ZtunnelReleaseName)); err != nil { 403 t.Errorf("failed to delete %s release: %v", ZtunnelReleaseName, err) 404 } 405 } 406 if isAmbient || t.Settings().OpenShift { 407 if err := h.DeleteChart(CniReleaseName, config.Get(CniReleaseName)); err != nil { 408 t.Errorf("failed to delete %s release: %v", CniReleaseName, err) 409 } 410 } 411 if err := h.DeleteChart(BaseReleaseName, config.Get(BaseReleaseName)); err != nil { 412 t.Errorf("failed to delete %s release: %v", BaseReleaseName, err) 413 } 414 for _, ns := range config.AllNamespaces() { 415 if ns == constants.KubeSystemNamespace { 416 continue 417 } 418 if err := cs.Kube().CoreV1().Namespaces().Delete(context.TODO(), ns, metav1.DeleteOptions{}); err != nil { 419 t.Errorf("failed to delete %s namespace: %v", ns, err) 420 } 421 if err := kubetest.WaitForNamespaceDeletion(cs.Kube(), ns, retry.Timeout(RetryTimeOut)); err != nil { 422 t.Errorf("waiting for %s namespace to be deleted: %v", ns, err) 423 } 424 } 425 } 426 427 // VerifyInstallation verify that the Helm installation is successful 428 func VerifyPodReady(ctx framework.TestContext, cs cluster.Cluster, ns, label string) { 429 retry.UntilSuccessOrFail(ctx, func() error { 430 if _, err := kubetest.CheckPodsAreReady(kubetest.NewPodFetch(cs, ns, label)); err != nil { 431 return fmt.Errorf("%s pod is not ready: %v", label, err) 432 } 433 return nil 434 }, retry.Timeout(RetryTimeOut), retry.Delay(RetryDelay)) 435 } 436 437 // VerifyInstallation verify that the Helm installation is successful 438 func VerifyInstallation(ctx framework.TestContext, cs cluster.Cluster, nsConfig NamespaceConfig, verifyGateway bool, verifyAmbient bool, revision string) { 439 scopes.Framework.Infof("=== verifying istio installation === ") 440 441 validatingwebhookName := "istiod-default-validator" 442 if revision != "" { 443 validatingwebhookName = fmt.Sprintf("istio-validator-%s-istio-system", revision) 444 } 445 VerifyValidatingWebhookConfigurations(ctx, cs, []string{ 446 validatingwebhookName, 447 }) 448 449 VerifyPodReady(ctx, cs, nsConfig.Get(IstiodReleaseName), "app=istiod") 450 if verifyAmbient || ctx.Settings().OpenShift { 451 VerifyPodReady(ctx, cs, nsConfig.Get(CniReleaseName), "k8s-app=istio-cni-node") 452 } 453 if verifyAmbient { 454 VerifyPodReady(ctx, cs, nsConfig.Get(ZtunnelReleaseName), "app=ztunnel") 455 } 456 if verifyGateway { 457 VerifyPodReady(ctx, cs, nsConfig.Get(IngressReleaseName), "app=istio-ingress") 458 } 459 scopes.Framework.Infof("=== succeeded ===") 460 } 461 462 func SetRevisionTagWithVersion(ctx framework.TestContext, h *helm.Helm, revision, revisionTag, version string) { 463 scopes.Framework.Infof("=== setting revision tag with version === ") 464 // Prepend ~ to the version, so that we can refer to the latest patch version of a minor version 465 template, err := h.Template(IstiodReleaseName+"-"+revision, RepoDiscoveryChartPath, 466 IstioNamespace, "templates/revision-tags.yaml", Timeout, "--version", "~"+version, "--repo", ctx.Settings().HelmRepo, "--set", 467 fmt.Sprintf("revision=%s", revision), "--set", fmt.Sprintf("revisionTags={%s}", revisionTag)) 468 if err != nil { 469 ctx.Fatalf("failed to install istio %s chart", DiscoveryChartsDir) 470 } 471 472 err = ctx.ConfigIstio().YAML(IstioNamespace, template).Apply() 473 if err != nil { 474 ctx.Fatalf("failed to apply templated revision tags yaml: %v", err) 475 } 476 477 scopes.Framework.Infof("=== succeeded === ") 478 } 479 480 func SetRevisionTag(ctx framework.TestContext, h *helm.Helm, fileSuffix, revision, revisionTag, relPath, version string) { 481 scopes.Framework.Infof("=== setting revision tag === ") 482 template, err := h.Template(IstiodReleaseName+"-"+revision, filepath.Join(relPath, version, ControlChartsDir, DiscoveryChartsDir)+fileSuffix, 483 IstioNamespace, "templates/revision-tags.yaml", Timeout, "--set", 484 fmt.Sprintf("revision=%s", revision), "--set", fmt.Sprintf("revisionTags={%s}", revisionTag)) 485 if err != nil { 486 ctx.Fatalf("failed to install istio %s chart", DiscoveryChartsDir) 487 } 488 489 err = ctx.ConfigIstio().YAML(IstioNamespace, template).Apply() 490 if err != nil { 491 ctx.Fatalf("failed to apply templated revision tags yaml: %v", err) 492 } 493 494 scopes.Framework.Infof("=== succeeded === ") 495 } 496 497 // VerifyMutatingWebhookConfigurations verifies that the proper number of mutating webhooks are running, used with 498 // revisions and revision tags 499 func VerifyMutatingWebhookConfigurations(ctx framework.TestContext, cs cluster.Cluster, names []string) { 500 scopes.Framework.Infof("=== verifying mutating webhook configurations === ") 501 if ok := kubetest.MutatingWebhookConfigurationsExists(cs.Kube(), names); !ok { 502 ctx.Fatalf("Not all mutating webhook configurations were installed. Expected [%v]", names) 503 } 504 scopes.Framework.Infof("=== succeeded ===") 505 } 506 507 // VerifyValidatingWebhookConfigurations verifies that the proper number of validating webhooks are running, used with 508 // revisions and revision tags 509 func VerifyValidatingWebhookConfigurations(ctx framework.TestContext, cs cluster.Cluster, names []string) { 510 scopes.Framework.Infof("=== verifying validating webhook configurations === ") 511 if ok := kubetest.ValidatingWebhookConfigurationsExists(cs.Kube(), names); !ok { 512 ctx.Fatalf("Not all validating webhook configurations were installed. Expected [%v]", names) 513 } 514 scopes.Framework.Infof("=== succeeded ===") 515 } 516 517 // verifyValidation verifies that Istio resource validation is active on the cluster. 518 func verifyValidation(ctx framework.TestContext, revision string) { 519 ctx.Helper() 520 invalidGateway := &v1alpha3.Gateway{ 521 ObjectMeta: metav1.ObjectMeta{ 522 Name: "invalid-istio-gateway", 523 Namespace: IstioNamespace, 524 }, 525 Spec: networking.Gateway{}, 526 } 527 528 if revision != "" { 529 invalidGateway.Labels = map[string]string{ 530 "istio.io/rev": revision, 531 } 532 } 533 createOptions := metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}} 534 istioClient := ctx.Clusters().Default().Istio().NetworkingV1alpha3() 535 retry.UntilOrFail(ctx, func() bool { 536 _, err := istioClient.Gateways(IstioNamespace).Create(context.TODO(), invalidGateway, createOptions) 537 rejected := err != nil 538 return rejected 539 }) 540 }