istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/ambient/waypoint.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ambient
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"io"
    21  	"time"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  
    26  	"istio.io/istio/pkg/config/constants"
    27  	istioKube "istio.io/istio/pkg/kube"
    28  	"istio.io/istio/pkg/maps"
    29  	"istio.io/istio/pkg/test/framework"
    30  	"istio.io/istio/pkg/test/framework/components/crd"
    31  	"istio.io/istio/pkg/test/framework/components/istioctl"
    32  	"istio.io/istio/pkg/test/framework/components/namespace"
    33  	"istio.io/istio/pkg/test/framework/resource"
    34  	testKube "istio.io/istio/pkg/test/kube"
    35  	"istio.io/istio/pkg/test/scopes"
    36  	"istio.io/istio/pkg/test/util/retry"
    37  )
    38  
    39  var _ io.Closer = &kubeComponent{}
    40  
    41  type kubeComponent struct {
    42  	id resource.ID
    43  
    44  	ns       namespace.Instance
    45  	inbound  istioKube.PortForwarder
    46  	outbound istioKube.PortForwarder
    47  	pod      v1.Pod
    48  }
    49  
    50  func (k kubeComponent) Namespace() namespace.Instance {
    51  	return k.ns
    52  }
    53  
    54  func (k kubeComponent) PodIP() string {
    55  	return k.pod.Status.PodIP
    56  }
    57  
    58  func (k kubeComponent) Inbound() string {
    59  	return k.inbound.Address()
    60  }
    61  
    62  func (k kubeComponent) Outbound() string {
    63  	return k.outbound.Address()
    64  }
    65  
    66  func (k kubeComponent) ID() resource.ID {
    67  	return k.id
    68  }
    69  
    70  func (k kubeComponent) Close() error {
    71  	if k.inbound != nil {
    72  		k.inbound.Close()
    73  	}
    74  	if k.outbound != nil {
    75  		k.outbound.Close()
    76  	}
    77  	return nil
    78  }
    79  
    80  // WaypointProxy describes a waypoint proxy deployment
    81  type WaypointProxy interface {
    82  	Namespace() namespace.Instance
    83  	Inbound() string
    84  	Outbound() string
    85  	PodIP() string
    86  }
    87  
    88  // NewWaypointProxy creates a new WaypointProxy.
    89  func NewWaypointProxy(ctx resource.Context, ns namespace.Instance, name string) (WaypointProxy, error) {
    90  	server := &kubeComponent{
    91  		ns: ns,
    92  	}
    93  	server.id = ctx.TrackResource(server)
    94  	if err := crd.DeployGatewayAPI(ctx); err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	// TODO support multicluster
    99  	ik, err := istioctl.New(ctx, istioctl.Config{})
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	// TODO: detect from UseWaypointProxy in echo.Config
   104  	_, _, err = ik.Invoke([]string{
   105  		"x",
   106  		"waypoint",
   107  		"apply",
   108  		"--namespace",
   109  		ns.Name(),
   110  		"--name",
   111  		name,
   112  		"--for",
   113  		constants.AllTraffic,
   114  	})
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	cls := ctx.Clusters().Kube().Default()
   120  	// Find the Waypoint pod and service, and start forwarding a local port.
   121  	fetchFn := testKube.NewSinglePodFetch(cls, ns.Name(), fmt.Sprintf("%s=%s", constants.GatewayNameLabel, name))
   122  	pods, err := testKube.WaitUntilPodsAreReady(fetchFn)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	pod := pods[0]
   127  	inbound, err := cls.NewPortForwarder(pod.Name, pod.Namespace, "", 0, 15008)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	if err := inbound.Start(); err != nil {
   133  		return nil, err
   134  	}
   135  	outbound, err := cls.NewPortForwarder(pod.Name, pod.Namespace, "", 0, 15001)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	if err := outbound.Start(); err != nil {
   141  		return nil, err
   142  	}
   143  	server.inbound = inbound
   144  	server.outbound = outbound
   145  	server.pod = pod
   146  	return server, nil
   147  }
   148  
   149  // NewWaypointProxyOrFail calls NewWaypointProxy and fails if an error occurs.
   150  func NewWaypointProxyOrFail(t framework.TestContext, ns namespace.Instance, name string) WaypointProxy {
   151  	t.Helper()
   152  	s, err := NewWaypointProxy(t, ns, name)
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	return s
   157  }
   158  
   159  func SetWaypointForService(t framework.TestContext, ns namespace.Instance, service, waypoint string) {
   160  	if service == "" {
   161  		return
   162  	}
   163  
   164  	cs := t.AllClusters().Kube()
   165  	for _, c := range cs {
   166  		oldSvc, err := c.Kube().CoreV1().Services(ns.Name()).Get(t.Context(), service, metav1.GetOptions{})
   167  		if err != nil {
   168  			t.Fatalf("error getting svc %s, err %v", service, err)
   169  		}
   170  		oldLabels := oldSvc.ObjectMeta.GetLabels()
   171  		if oldLabels == nil {
   172  			oldLabels = make(map[string]string, 1)
   173  		}
   174  		newLabels := maps.Clone(oldLabels)
   175  		if waypoint != "" {
   176  			newLabels[constants.AmbientUseWaypointLabel] = waypoint
   177  		} else {
   178  			delete(newLabels, constants.AmbientUseWaypointLabel)
   179  		}
   180  
   181  		doLabel := func(labels map[string]string) error {
   182  			// update needs the latest version
   183  			svc, err := c.Kube().CoreV1().Services(ns.Name()).Get(t.Context(), service, metav1.GetOptions{})
   184  			if err != nil {
   185  				return err
   186  			}
   187  			svc.ObjectMeta.SetLabels(labels)
   188  			_, err = c.Kube().CoreV1().Services(ns.Name()).Update(t.Context(), svc, metav1.UpdateOptions{})
   189  			return err
   190  		}
   191  
   192  		if err = doLabel(newLabels); err != nil {
   193  			t.Fatalf("error updating svc %s, err %v", service, err)
   194  		}
   195  		t.Cleanup(func() {
   196  			if err := doLabel(oldLabels); err != nil {
   197  				scopes.Framework.Errorf("failed resetting waypoint for %s/%s; this will likely break other tests", ns.Name(), service)
   198  			}
   199  		})
   200  
   201  	}
   202  }
   203  
   204  func DeleteWaypoint(t framework.TestContext, ns namespace.Instance, waypoint string) {
   205  	istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
   206  		"x",
   207  		"waypoint",
   208  		"delete",
   209  		"--namespace",
   210  		ns.Name(),
   211  		waypoint,
   212  	})
   213  	waypointError := retry.UntilSuccess(func() error {
   214  		fetch := testKube.NewPodFetch(t.AllClusters()[0], ns.Name(), constants.GatewayNameLabel+"="+waypoint)
   215  		pods, err := testKube.CheckPodsAreReady(fetch)
   216  		if err != nil && !errors.Is(err, testKube.ErrNoPodsFetched) {
   217  			return fmt.Errorf("cannot fetch pod: %v", err)
   218  		} else if len(pods) != 0 {
   219  			return fmt.Errorf("waypoint pod is not deleted")
   220  		}
   221  		return nil
   222  	}, retry.Timeout(time.Minute), retry.BackoffDelay(time.Millisecond*100))
   223  	if waypointError != nil {
   224  		t.Fatal(waypointError)
   225  	}
   226  }
   227  
   228  func RemoveWaypointFromService(t framework.TestContext, ns namespace.Instance, service, waypoint string) {
   229  	if service != "" {
   230  		cs := t.AllClusters().Configs()
   231  		for _, c := range cs {
   232  			oldSvc, err := c.Kube().CoreV1().Services(ns.Name()).Get(t.Context(), service, metav1.GetOptions{})
   233  			if err != nil {
   234  				t.Fatalf("error getting svc %s, err %v", service, err)
   235  			}
   236  			labels := oldSvc.ObjectMeta.GetLabels()
   237  			if labels != nil {
   238  				delete(labels, constants.AmbientUseWaypointLabel)
   239  				oldSvc.ObjectMeta.SetLabels(labels)
   240  			}
   241  			_, err = c.Kube().CoreV1().Services(ns.Name()).Update(t.Context(), oldSvc, metav1.UpdateOptions{})
   242  			if err != nil {
   243  				t.Fatalf("error updating svc %s, err %v", service, err)
   244  			}
   245  		}
   246  	}
   247  }