istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/revisions/uninstall_test.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 revisions
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"os"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  
    32  	"istio.io/istio/operator/pkg/helmreconciler"
    33  	"istio.io/istio/operator/pkg/name"
    34  	"istio.io/istio/pkg/config/schema/gvr"
    35  	"istio.io/istio/pkg/test"
    36  	"istio.io/istio/pkg/test/framework"
    37  	"istio.io/istio/pkg/test/framework/components/cluster"
    38  	"istio.io/istio/pkg/test/framework/components/istioctl"
    39  	"istio.io/istio/pkg/test/scopes"
    40  	"istio.io/istio/pkg/test/util/retry"
    41  )
    42  
    43  const (
    44  	stableRevision       = "stable"
    45  	canaryRevision       = "canary"
    46  	notFoundRevision     = "not-found"
    47  	checkResourceTimeout = time.Second * 120
    48  	checkResourceDelay   = time.Millisecond * 100
    49  
    50  	revisionNotFound = "could not find target revision"
    51  )
    52  
    53  var allGVKs = append(helmreconciler.NamespacedResources(), helmreconciler.ClusterCPResources...)
    54  
    55  func TestUninstallByRevision(t *testing.T) {
    56  	framework.
    57  		NewTest(t).
    58  		Run(func(t framework.TestContext) {
    59  			t.NewSubTest("uninstall_revision").Run(func(t framework.TestContext) {
    60  				istioCtl := istioctl.NewOrFail(t, t, istioctl.Config{})
    61  				uninstallCmd := []string{
    62  					"uninstall",
    63  					"--revision=" + stableRevision, "--skip-confirmation",
    64  				}
    65  				out, _, err := istioCtl.Invoke(uninstallCmd)
    66  				if err != nil {
    67  					scopes.Framework.Errorf("failed to uninstall: %v, output: %v", err, out)
    68  				}
    69  				cs := t.Clusters().Default()
    70  				ls := fmt.Sprintf("istio.io/rev=%s", stableRevision)
    71  				checkCPResourcesUninstalled(t, cs, allGVKs, ls, false)
    72  			})
    73  		})
    74  }
    75  
    76  func TestUninstallByNotFoundRevision(t *testing.T) {
    77  	framework.
    78  		NewTest(t).
    79  		Run(func(t framework.TestContext) {
    80  			t.NewSubTest("uninstall_revision_notfound").Run(func(t framework.TestContext) {
    81  				istioCtl := istioctl.NewOrFail(t, t, istioctl.Config{})
    82  				uninstallCmd := []string{
    83  					"uninstall",
    84  					"--revision=" + notFoundRevision, "--dry-run",
    85  				}
    86  				_, actualError, _ := istioCtl.Invoke(uninstallCmd)
    87  				if !strings.Contains(actualError, revisionNotFound) {
    88  					scopes.Framework.Errorf("istioctl uninstall command expects to fail with error message: %s, but got: %s", revisionNotFound, actualError)
    89  				}
    90  			})
    91  		})
    92  }
    93  
    94  func TestUninstallWithSetFlag(t *testing.T) {
    95  	framework.
    96  		NewTest(t).
    97  		Run(func(t framework.TestContext) {
    98  			t.NewSubTest("uninstall_revision").Run(func(t framework.TestContext) {
    99  				istioCtl := istioctl.NewOrFail(t, t, istioctl.Config{})
   100  				uninstallCmd := []string{
   101  					"uninstall", "--set",
   102  					"revision=" + stableRevision, "--skip-confirmation",
   103  				}
   104  				out, _, err := istioCtl.Invoke(uninstallCmd)
   105  				if err != nil {
   106  					scopes.Framework.Errorf("failed to uninstall: %v, output: %v", err, out)
   107  				}
   108  				cs := t.Clusters().Default()
   109  				ls := fmt.Sprintf("istio.io/rev=%s", stableRevision)
   110  				checkCPResourcesUninstalled(t, cs, allGVKs, ls, false)
   111  			})
   112  		})
   113  }
   114  
   115  func TestUninstallCustomFile(t *testing.T) {
   116  	framework.NewTest(t).
   117  		Run(func(t framework.TestContext) {
   118  			istioCtl := istioctl.NewOrFail(t, t, istioctl.Config{})
   119  
   120  			createIstioOperatorTempFile := func(name, revision string) (fileName string) {
   121  				tempFile, err := os.CreateTemp("", name)
   122  				if err != nil {
   123  					t.Fatalf("failed to create temp file: %v", err)
   124  				}
   125  				defer tempFile.Close()
   126  				contents := fmt.Sprintf(`apiVersion: install.istio.io/v1alpha1
   127  kind: IstioOperator
   128  metadata:
   129    name: %s
   130    namespace: istio-system
   131  spec:
   132    profile: remote
   133    revision: %s
   134  `, name, revision)
   135  				if _, err = tempFile.WriteString(contents); err != nil {
   136  					t.Fatalf("failed to write to temp file: %v", err)
   137  				}
   138  				return tempFile.Name()
   139  			}
   140  
   141  			// Creating custom installation and empty uninstallation files
   142  			customFileName := createIstioOperatorTempFile("custom-install", canaryRevision)
   143  			randomFileName := createIstioOperatorTempFile("random-uninstall", canaryRevision)
   144  
   145  			// Install webhook with custom file
   146  			istioCtl.InvokeOrFail(t, []string{"install", "-f", customFileName, "--skip-confirmation"})
   147  
   148  			// Check if custom webhook is installed
   149  			validateWebhookExistence := func() {
   150  				ls := fmt.Sprintf("%s=%s", helmreconciler.IstioComponentLabelStr, name.IstiodRemoteComponentName)
   151  				cs := t.Clusters().Default()
   152  				objs, _ := getRemainingResourcesCluster(cs, gvr.MutatingWebhookConfiguration, ls)
   153  				if len(objs) == 0 {
   154  					t.Fatalf("expected custom webhook to exist")
   155  				}
   156  			}
   157  
   158  			validateWebhookExistence()
   159  
   160  			// Uninstall with a different file (should have no effect)
   161  			istioCtl.InvokeOrFail(t, []string{"uninstall", "-f", randomFileName, "-r" + canaryRevision, "--skip-confirmation"})
   162  
   163  			// Check the webhook still exists
   164  			validateWebhookExistence()
   165  
   166  			// Uninstall with the correct file
   167  			istioCtl.InvokeOrFail(t, []string{"uninstall", "-f=" + customFileName, "-r=" + canaryRevision, "--skip-confirmation"})
   168  
   169  			// Check no resources from the custom file exist
   170  			checkCPResourcesUninstalled(t, t.Clusters().Default(), allGVKs,
   171  				fmt.Sprintf("%s=%s", helmreconciler.IstioComponentLabelStr, name.IstiodRemoteComponentName), true)
   172  		})
   173  }
   174  
   175  func TestUninstallPurge(t *testing.T) {
   176  	framework.
   177  		NewTest(t).
   178  		Run(func(t framework.TestContext) {
   179  			istioCtl := istioctl.NewOrFail(t, t, istioctl.Config{})
   180  			uninstallCmd := []string{
   181  				"uninstall",
   182  				"--purge", "--skip-confirmation",
   183  			}
   184  			istioCtl.InvokeOrFail(t, uninstallCmd)
   185  			cs := t.Clusters().Default()
   186  			checkCPResourcesUninstalled(t, cs, allGVKs, helmreconciler.IstioComponentLabelStr, true)
   187  		})
   188  }
   189  
   190  // checkCPResourcesUninstalled is a helper function to check list of gvk resources matched with label are uninstalled
   191  // If purge is set to true, we expect all resources are removed.
   192  // Otherwise we expect only selected resources from control plane are removed, resources from base and the legacy addon installation would not be touched.
   193  func checkCPResourcesUninstalled(t test.Failer, cs cluster.Cluster, gvkResources []schema.GroupVersionKind, label string, purge bool) {
   194  	retry.UntilSuccessOrFail(t, func() error {
   195  		var reStrlist []string
   196  		var reItemList []unstructured.Unstructured
   197  		for _, gvk := range gvkResources {
   198  			resources := strings.ToLower(gvk.Kind) + "s"
   199  			gvr := schema.GroupVersionResource{Group: gvk.Group, Version: gvk.Version, Resource: resources}
   200  			reList, reStr := getRemainingResourcesCluster(cs, gvr, label)
   201  			reItemList = append(reItemList, reList...)
   202  			reStrlist = append(reStrlist, reStr...)
   203  		}
   204  		return inspectRemainingResources(reItemList, reStrlist, purge)
   205  	}, retry.Delay(checkResourceDelay), retry.Timeout(checkResourceTimeout))
   206  }
   207  
   208  // getRemainingResourcesCluster get specific resources from the cluster
   209  func getRemainingResourcesCluster(cs cluster.Cluster, gvr schema.GroupVersionResource, ls string) ([]unstructured.Unstructured, []string) {
   210  	usList, _ := cs.Dynamic().Resource(gvr).List(context.TODO(), metav1.ListOptions{LabelSelector: ls})
   211  	var remainingResources []unstructured.Unstructured
   212  	var staleList []string
   213  	if usList != nil && len(usList.Items) != 0 {
   214  		for _, item := range usList.Items {
   215  			// ignore IstioOperator CRD because the operator CR is not in the pruning list
   216  			if item.GetName() == "istiooperators.install.istio.io" {
   217  				continue
   218  			}
   219  			remainingResources = append(remainingResources, item)
   220  			staleList = append(staleList, item.GroupVersionKind().String()+"/"+item.GetName())
   221  		}
   222  	}
   223  	return remainingResources, staleList
   224  }
   225  
   226  func inspectRemainingResources(reItemList []unstructured.Unstructured, reStrList []string, purge bool) error {
   227  	// for purge case we expect all resources removed
   228  	if purge {
   229  		if len(reStrList) != 0 {
   230  			msg := fmt.Sprintf("resources expected to be pruned but still exist in the cluster: %s",
   231  				strings.Join(reStrList, " "))
   232  			scopes.Framework.Warnf(msg)
   233  			return fmt.Errorf(msg)
   234  		}
   235  		return nil
   236  	}
   237  	// for other cases, we expect base component resources to be kept.
   238  	if len(reStrList) != 0 {
   239  		for _, remaining := range reItemList {
   240  			labels := remaining.GetLabels()
   241  			cn, ok := labels["operator.istio.io/component"]
   242  			// we don't need to check the legacy addons here because we would not install that in test anymore.
   243  			if ok && cn != string(name.IstioBaseComponentName) {
   244  				return fmt.Errorf("expect only base component resources still exist")
   245  			}
   246  		}
   247  	} else {
   248  		return fmt.Errorf("expect base component resources to exist but they were removed")
   249  	}
   250  	return nil
   251  }