istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/istio/istio.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 istio
    16  
    17  import (
    18  	"net/netip"
    19  	"regexp"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	"google.golang.org/protobuf/types/known/structpb"
    25  	corev1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/types"
    27  
    28  	meshconfig "istio.io/api/mesh/v1alpha1"
    29  	"istio.io/istio/pkg/kube/inject"
    30  	"istio.io/istio/pkg/test"
    31  	"istio.io/istio/pkg/test/framework/components/cluster"
    32  	"istio.io/istio/pkg/test/framework/components/istio/ingress"
    33  	"istio.io/istio/pkg/test/framework/resource"
    34  	"istio.io/istio/pkg/test/framework/resource/config/cleanup"
    35  	"istio.io/istio/pkg/test/scopes"
    36  )
    37  
    38  // OperatorValues is the map of the values from the installed operator yaml.
    39  type OperatorValues map[string]*structpb.Value
    40  
    41  // This regular expression matches list object index selection expression such as
    42  // abc[100], Tba_a[0].
    43  var listObjRex = regexp.MustCompile(`^([a-zA-Z]?[a-z_A-Z\d]*)\[([ ]*[\d]+)[ ]*\]$`)
    44  
    45  func getConfigValue(path []string, val map[string]*structpb.Value) *structpb.Value {
    46  	retVal := structpb.NewNullValue()
    47  	if len(path) > 0 {
    48  		match := listObjRex.FindStringSubmatch(path[0])
    49  		// valid list index
    50  		switch len(match) {
    51  		case 0: // does not match list object selection, should be name of a field, should be struct value
    52  			thisVal := val[path[0]]
    53  			// If it is a struct and looking for more down the path
    54  			if thisVal.GetStructValue() != nil && len(path) > 1 {
    55  				return getConfigValue(path[1:], thisVal.GetStructValue().Fields)
    56  			}
    57  			retVal = thisVal
    58  		case 3: // match something like aaa[100]
    59  			thisVal := val[match[1]]
    60  			// If it is a list and looking for more down the path
    61  			if thisVal.GetListValue() != nil && len(path) > 1 {
    62  				index, _ := strconv.Atoi(match[2])
    63  				return getConfigValue(path[1:], thisVal.GetListValue().Values[index].GetStructValue().Fields)
    64  			}
    65  			retVal = thisVal
    66  		}
    67  	}
    68  	return retVal
    69  }
    70  
    71  // GetConfigValue returns a structpb value from a structpb map by
    72  // using a dotted path such as `pilot.env.LOCAL_CLUSTER_SECRET_WATCHER`.
    73  func (v OperatorValues) GetConfigValue(path string) *structpb.Value {
    74  	return getConfigValue(strings.Split(path, "."), v)
    75  }
    76  
    77  // Instance represents a deployed Istio instance
    78  type Instance interface {
    79  	resource.Resource
    80  
    81  	Settings() Config
    82  	// Ingresses returns all ingresses for "istio-ingressgateway" in each cluster.
    83  	Ingresses() ingress.Instances
    84  	// IngressFor returns an ingress used for reaching workloads in the given cluster.
    85  	// The ingress's service name will be "istio-ingressgateway" and the istio label will be "ingressgateway".
    86  	IngressFor(cluster cluster.Cluster) ingress.Instance
    87  	// EastWestGatewayFor returns an ingress used for east-west traffic and accessing the control plane
    88  	// from outside of the cluster.
    89  	EastWestGatewayFor(cluster cluster.Cluster) ingress.Instance
    90  	// CustomIngressFor returns an ingress with a specific service name and "istio" label used for reaching workloads
    91  	// in the given cluster.
    92  	CustomIngressFor(cluster cluster.Cluster, service types.NamespacedName, istioLabel string) ingress.Instance
    93  
    94  	// RemoteDiscoveryAddressFor returns the external address of the discovery server that controls
    95  	// the given cluster. This allows access to the discovery server from
    96  	// outside its cluster.
    97  	RemoteDiscoveryAddressFor(cluster cluster.Cluster) (netip.AddrPort, error)
    98  	// CreateRemoteSecret on the cluster with the given options.
    99  	CreateRemoteSecret(ctx resource.Context, c cluster.Cluster, opts ...string) (string, error)
   100  	// InternalDiscoveryAddressFor returns an internal (port-forwarded) address for an Istiod instance in the
   101  	// cluster.
   102  	InternalDiscoveryAddressFor(cluster cluster.Cluster) (string, error)
   103  
   104  	// Return POD IPs for the pod with the specified label in the specified namespace
   105  	PodIPsFor(cluster cluster.Cluster, namespace string, label string) ([]corev1.PodIP, error)
   106  
   107  	// Values returns the operator values for the installed control plane.
   108  	Values() (OperatorValues, error)
   109  	ValuesOrFail(test.Failer) OperatorValues
   110  	// MeshConfig used by the Istio installation.
   111  	MeshConfig() (*meshconfig.MeshConfig, error)
   112  	MeshConfigOrFail(test.Failer) *meshconfig.MeshConfig
   113  	// UpdateMeshConfig used by the Istio installation.
   114  	UpdateMeshConfig(resource.Context, func(*meshconfig.MeshConfig) error, cleanup.Strategy) error
   115  	UpdateMeshConfigOrFail(resource.Context, test.Failer, func(*meshconfig.MeshConfig) error, cleanup.Strategy)
   116  	// PatchMeshConfig with the given patch yaml.
   117  	PatchMeshConfig(resource.Context, string) error
   118  	PatchMeshConfigOrFail(resource.Context, test.Failer, string)
   119  	UpdateInjectionConfig(resource.Context, func(*inject.Config) error, cleanup.Strategy) error
   120  	InjectionConfig() (*inject.Config, error)
   121  }
   122  
   123  // SetupConfigFn is a setup function that specifies the overrides of the configuration to deploy Istio.
   124  type SetupConfigFn func(ctx resource.Context, cfg *Config)
   125  
   126  // SetupContextFn is a setup function that uses Context for configuration.
   127  type SetupContextFn func(ctx resource.Context) error
   128  
   129  // Get returns the Istio component from the context. If there is none an error is returned.
   130  func Get(ctx resource.Context) (Instance, error) {
   131  	var i Instance
   132  	if err := ctx.GetResource(&i); err != nil {
   133  		return nil, err
   134  	}
   135  	return i, nil
   136  }
   137  
   138  // GetOrFail returns the Istio component from the context. If there is none the test is failed.
   139  func GetOrFail(t test.Failer, ctx resource.Context) Instance {
   140  	t.Helper()
   141  	i, err := Get(ctx)
   142  	if err != nil {
   143  		t.Fatal(err)
   144  	}
   145  	return i
   146  }
   147  
   148  // DefaultIngress returns the ingress installed in the default cluster. The ingress's service name
   149  // will be "istio-ingressgateway" and the istio label will be "ingressgateway".
   150  func DefaultIngress(ctx resource.Context) (ingress.Instance, error) {
   151  	i, err := Get(ctx)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	return i.IngressFor(ctx.Clusters().Default()), nil
   156  }
   157  
   158  // DefaultIngressOrFail calls DefaultIngress and fails if an error is encountered.
   159  func DefaultIngressOrFail(t test.Failer, ctx resource.Context) ingress.Instance {
   160  	t.Helper()
   161  	i, err := DefaultIngress(ctx)
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  	return i
   166  }
   167  
   168  // Ingresses returns all ingresses for "istio-ingressgateway" in each cluster.
   169  func Ingresses(ctx resource.Context) (ingress.Instances, error) {
   170  	i, err := Get(ctx)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	return i.Ingresses(), nil
   175  }
   176  
   177  // IngressesOrFail calls Ingresses and fails if an error is encountered.
   178  func IngressesOrFail(t test.Failer, ctx resource.Context) ingress.Instances {
   179  	t.Helper()
   180  	i, err := Ingresses(ctx)
   181  	if err != nil {
   182  		t.Fatal(err)
   183  	}
   184  	return i
   185  }
   186  
   187  // Setup is a setup function that will deploy Istio on Kubernetes environment
   188  func Setup(i *Instance, cfn SetupConfigFn, ctxFns ...SetupContextFn) resource.SetupFn {
   189  	return func(ctx resource.Context) error {
   190  		cfg, err := DefaultConfig(ctx)
   191  		if err != nil {
   192  			return err
   193  		}
   194  		if cfn != nil {
   195  			cfn(ctx, &cfg)
   196  		}
   197  		for _, ctxFn := range ctxFns {
   198  			if ctxFn != nil {
   199  				err := ctxFn(ctx)
   200  				if err != nil {
   201  					scopes.Framework.Infof("=== FAILED: context setup function [err=%v] ===", err)
   202  					return err
   203  				}
   204  				scopes.Framework.Info("=== SUCCESS: context setup function ===")
   205  			}
   206  		}
   207  
   208  		t0 := time.Now()
   209  		scopes.Framework.Infof("=== BEGIN: Deploy Istio [Suite=%s] ===", ctx.Settings().TestID)
   210  
   211  		ins, err := newKube(ctx, cfg)
   212  		if err != nil {
   213  			scopes.Framework.Infof("=== FAILED: Deploy Istio in %v [Suite=%s] ===", time.Since(t0), ctx.Settings().TestID)
   214  			return err
   215  		}
   216  
   217  		if i != nil {
   218  			*i = ins
   219  		}
   220  		scopes.Framework.Infof("=== SUCCEEDED: Deploy Istio in %v [Suite=%s]===", time.Since(t0), ctx.Settings().TestID)
   221  		return nil
   222  	}
   223  }