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  }