istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/vm_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 writ59ing, 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 pilot 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "strings" 25 "testing" 26 "time" 27 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 30 "istio.io/api/annotation" 31 "istio.io/client-go/pkg/apis/networking/v1alpha3" 32 "istio.io/istio/pilot/pkg/features" 33 "istio.io/istio/pkg/test/framework" 34 "istio.io/istio/pkg/test/framework/components/echo" 35 "istio.io/istio/pkg/test/framework/components/echo/check" 36 "istio.io/istio/pkg/test/framework/components/echo/common/ports" 37 "istio.io/istio/pkg/test/framework/components/echo/deployment" 38 "istio.io/istio/pkg/test/framework/components/echo/kube" 39 "istio.io/istio/pkg/test/framework/components/echo/match" 40 kubeenv "istio.io/istio/pkg/test/framework/components/environment/kube" 41 "istio.io/istio/pkg/test/framework/label" 42 "istio.io/istio/pkg/test/scopes" 43 "istio.io/istio/pkg/test/util/retry" 44 "istio.io/istio/tests/integration/pilot/common" 45 ) 46 47 func GetAdditionVMImages(t framework.TestContext) []string { 48 var out []echo.VMDistro 49 imgs := kube.VMImages 50 if t.Environment().(*kubeenv.Environment).Settings().Architecture == kubeenv.ArchARM64 { 51 imgs = kube.ArmVMImages 52 } 53 for distro, image := range imgs { 54 if distro == echo.DefaultVMDistro { 55 continue 56 } 57 out = append(out, image) 58 } 59 return out 60 } 61 62 func TestVmOSPost(t *testing.T) { 63 framework. 64 NewTest(t). 65 Label(label.Postsubmit). 66 Run(func(t framework.TestContext) { 67 if t.Settings().Skip(echo.VM) { 68 t.Skip("VM tests are disabled") 69 } 70 b := deployment.New(t, t.Clusters().Primaries().Default()) 71 images := GetAdditionVMImages(t) 72 for _, image := range images { 73 b = b.WithConfig(echo.Config{ 74 Service: "vm-" + strings.ReplaceAll(image, "_", "-"), 75 Namespace: apps.Namespace, 76 Ports: ports.All(), 77 DeployAsVM: true, 78 VMDistro: image, 79 Subsets: []echo.SubsetConfig{{}}, 80 }) 81 } 82 instances := b.BuildOrFail(t) 83 84 for idx, image := range images { 85 idx, image := idx, image 86 t.NewSubTest(image).RunParallel(func(t framework.TestContext) { 87 tc := common.TrafficContext{ 88 TestContext: t, 89 Istio: i, 90 Apps: apps, 91 } 92 common.VMTestCases(echo.Instances{instances[idx]})(tc) 93 }) 94 } 95 }) 96 } 97 98 func TestVMRegistrationLifecycle(t *testing.T) { 99 t.Skip("https://github.com/istio/istio/issues/33154") 100 // nolint: staticcheck 101 framework. 102 NewTest(t). 103 RequiresSingleCluster(). 104 Run(func(t framework.TestContext) { 105 if t.Settings().Skip(echo.VM) { 106 t.Skip() 107 } 108 scaleDeploymentOrFail(t, "istiod", i.Settings().SystemNamespace, 2) 109 client := match.Cluster(t.Clusters().Default()).FirstOrFail(t, apps.A) 110 // TODO test multi-network (must be shared control plane but on different networks) 111 var autoVM echo.Instance 112 _ = deployment.New(t). 113 With(&autoVM, echo.Config{ 114 Namespace: apps.Namespace, 115 Service: "auto-vm", 116 Ports: ports.All(), 117 DeployAsVM: true, 118 AutoRegisterVM: true, 119 }).BuildOrFail(t) 120 t.NewSubTest("initial registration").Run(func(t framework.TestContext) { 121 retry.UntilSuccessOrFail(t, func() error { 122 result, err := client.Call(echo.CallOptions{ 123 To: autoVM, 124 Count: 1, 125 Port: autoVM.Config().Ports[0], 126 Retry: echo.Retry{ 127 NoRetry: true, 128 }, 129 }) 130 return check.And( 131 check.NoError(), 132 check.OK()).Check(result, err) 133 }, retry.Timeout(15*time.Second)) 134 }) 135 t.NewSubTest("reconnect reuses WorkloadEntry").Run(func(t framework.TestContext) { 136 // ensure we have two pilot instances, other tests can pass before the second one comes up 137 retry.UntilSuccessOrFail(t, func() error { 138 pilotRes, err := t.Clusters().Default().Kube().CoreV1().Pods(i.Settings().SystemNamespace). 139 List(context.TODO(), metav1.ListOptions{LabelSelector: "istio=pilot"}) 140 if err != nil { 141 return err 142 } 143 if len(pilotRes.Items) != 2 { 144 return errors.New("expected 2 pilots") 145 } 146 return nil 147 }, retry.Timeout(10*time.Second)) 148 149 // get the initial workload entry state 150 entries := getWorkloadEntriesOrFail(t, autoVM) 151 if len(entries) != 1 { 152 t.Fatalf("expected exactly 1 WorkloadEntry but got %d", len(entries)) 153 } 154 initialWLE := entries[0] 155 156 // keep force-disconnecting until we observe a reconnect to a different istiod instance 157 initialPilot := initialWLE.Annotations[annotation.IoIstioWorkloadController.Name] 158 disconnectProxy(t, initialPilot, autoVM) 159 retry.UntilSuccessOrFail(t, func() error { 160 entries := getWorkloadEntriesOrFail(t, autoVM) 161 if len(entries) != 1 || entries[0].UID != initialWLE.UID { 162 t.Fatalf("WorkloadEntry was cleaned up unexpectedly") 163 } 164 165 currentPilot := entries[0].Annotations[annotation.IoIstioWorkloadController.Name] 166 if currentPilot == initialPilot || !strings.HasPrefix(currentPilot, "istiod-") { 167 disconnectProxy(t, currentPilot, autoVM) 168 return errors.New("expected WorkloadEntry to be updated by other pilot") 169 } 170 return nil 171 }, retry.Delay(5*time.Second)) 172 }) 173 t.NewSubTest("disconnect deletes WorkloadEntry").Run(func(t framework.TestContext) { 174 d := fmt.Sprintf("%s-%s", autoVM.Config().Service, "v1") 175 scaleDeploymentOrFail(t, d, autoVM.Config().Namespace.Name(), 0) 176 // it should take at most just over GracePeriod to cleanup if all pilots are healthy 177 retry.UntilSuccessOrFail(t, func() error { 178 if len(getWorkloadEntriesOrFail(t, autoVM)) > 0 { 179 return errors.New("expected 0 WorkloadEntries") 180 } 181 return nil 182 }, retry.Timeout(2*features.WorkloadEntryCleanupGracePeriod+(2*time.Second))) 183 }) 184 }) 185 } 186 187 func disconnectProxy(t framework.TestContext, pilot string, instance echo.Instance) { 188 proxyID := strings.Join([]string{instance.WorkloadsOrFail(t)[0].PodName(), instance.Config().Namespace.Name()}, ".") 189 cmd := "pilot-discovery request GET /debug/force_disconnect?proxyID=" + proxyID 190 stdOut, _, err := t.Clusters().Default(). 191 PodExec(pilot, i.Settings().SystemNamespace, "discovery", cmd) 192 if err != nil { 193 scopes.Framework.Warnf("failed to force disconnect %s: %v: %v", proxyID, stdOut, err) 194 } 195 } 196 197 func scaleDeploymentOrFail(t framework.TestContext, name, namespace string, scale int32) { 198 s, err := t.Clusters().Default().Kube().AppsV1().Deployments(namespace). 199 GetScale(context.TODO(), name, metav1.GetOptions{}) 200 if err != nil { 201 t.Fatal(err) 202 } 203 s.Spec.Replicas = scale 204 _, err = t.Clusters().Default().Kube().AppsV1().Deployments(namespace). 205 UpdateScale(context.TODO(), name, s, metav1.UpdateOptions{}) 206 if err != nil { 207 t.Fatal(err) 208 } 209 } 210 211 func getWorkloadEntriesOrFail(t framework.TestContext, vm echo.Instance) []*v1alpha3.WorkloadEntry { 212 res, err := t.Clusters().Default().Istio().NetworkingV1alpha3(). 213 WorkloadEntries(vm.Config().Namespace.Name()). 214 List(context.TODO(), metav1.ListOptions{LabelSelector: "app=" + vm.Config().Service}) 215 if err != nil { 216 t.Fatal(err) 217 } 218 return res.Items 219 }