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 }