istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/environment/kube/settings.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 kube
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"k8s.io/apimachinery/pkg/runtime/schema"
    21  
    22  	istioKube "istio.io/istio/pkg/kube"
    23  	"istio.io/istio/pkg/test/framework/components/cluster"
    24  	"istio.io/istio/pkg/test/framework/config"
    25  	"istio.io/istio/pkg/test/scopes"
    26  )
    27  
    28  // clusterIndex is the index of a cluster within the KubeConfig or topology file entries
    29  type clusterIndex int
    30  
    31  // clusterTopology defines the associations between multiple clusters in a topology.
    32  type clusterTopology = map[clusterIndex]clusterIndex
    33  
    34  // ClientFactoryFunc is a transformation function that creates k8s clients
    35  // from the provided k8s config files.
    36  type ClientFactoryFunc func(kubeConfigs []string) ([]istioKube.CLIClient, error)
    37  
    38  // Settings provide kube-specific Settings from flags.
    39  type Settings struct {
    40  	// An array of paths to kube config files. Required if the environment is kubernetes.
    41  	KubeConfig []string
    42  
    43  	// Indicates that the LoadBalancer services can obtain a public IP. If not, NodePort be used as a workaround
    44  	// for ingress gateway. KinD will not support LoadBalancer out of the box and requires a workaround such as
    45  	// MetalLB.
    46  	LoadBalancerSupported bool
    47  
    48  	// Architecture indicates the architecture of the cluster under test
    49  	Architecture string
    50  
    51  	// MCSControllerEnabled indicates that the Kubernetes environment has a Multi-Cluster Services (MCS)
    52  	// controller up and running.
    53  	MCSControllerEnabled bool
    54  
    55  	// MCSAPIGroup the group to use for the MCS API
    56  	MCSAPIGroup string
    57  
    58  	// MCSAPIVersion the version to use for the MCS API
    59  	MCSAPIVersion string
    60  
    61  	// controlPlaneTopology maps each cluster to the cluster that runs its control plane. For replicated control
    62  	// plane cases (where each cluster has its own control plane), the cluster will map to itself (e.g. 0->0).
    63  	controlPlaneTopology clusterTopology
    64  
    65  	// networkTopology is used for the initial assignment of networks to each cluster.
    66  	// The source of truth clusters' networks is the Cluster instances themselves, rather than this field.
    67  	networkTopology map[clusterIndex]string
    68  
    69  	// configTopology maps each cluster to the cluster that runs it's config.
    70  	// If the cluster runs its own config, the cluster will map to itself (e.g. 0->0)
    71  	// By default, we use the controlPlaneTopology as the config topology.
    72  	configTopology clusterTopology
    73  }
    74  
    75  func (s *Settings) clone() *Settings {
    76  	c := *s
    77  	return &c
    78  }
    79  
    80  func (s *Settings) clusterConfigs() ([]cluster.Config, error) {
    81  	if len(clusterConfigs) == 0 {
    82  		// not loaded from file file, build directly from provided kubeconfigs and topology flag maps
    83  		return s.clusterConfigsFromFlags()
    84  	}
    85  
    86  	return s.clusterConfigsFromFile()
    87  }
    88  
    89  func (s *Settings) clusterConfigsFromFlags() ([]cluster.Config, error) {
    90  	if len(s.KubeConfig) == 0 {
    91  		// flag-based, but no kubeconfigs, get kubeconfigs from environment
    92  		scopes.Framework.Info("Flags istio.test.kube.config and istio.test.kube.topology not specified.")
    93  		var err error
    94  		s.KubeConfig, err = getKubeConfigsFromEnvironment()
    95  		if err != nil {
    96  			return nil, fmt.Errorf("error parsing KubeConfigs from environment: %v", err)
    97  		}
    98  	}
    99  	scopes.Framework.Infof("Using KubeConfigs: %v.", s.KubeConfig)
   100  	if err := s.validateTopologyFlags(len(s.KubeConfig)); err != nil {
   101  		return nil, err
   102  	}
   103  	var configs []cluster.Config
   104  	for i, kc := range s.KubeConfig {
   105  		ci := clusterIndex(i)
   106  		cfg := cluster.Config{
   107  			Name:    fmt.Sprintf("cluster-%d", i),
   108  			Kind:    cluster.Kubernetes,
   109  			Network: s.networkTopology[ci],
   110  			Meta:    config.Map{"kubeconfig": kc},
   111  		}
   112  		if idx, ok := s.controlPlaneTopology[ci]; ok {
   113  			cfg.PrimaryClusterName = fmt.Sprintf("cluster-%d", idx)
   114  		}
   115  		if idx, ok := s.configTopology[ci]; ok {
   116  			cfg.ConfigClusterName = fmt.Sprintf("cluster-%d", idx)
   117  		}
   118  		configs = append(configs, cfg)
   119  	}
   120  	return configs, nil
   121  }
   122  
   123  func (s *Settings) clusterConfigsFromFile() ([]cluster.Config, error) {
   124  	// Allow kubeconfig flag to override file
   125  	var err error
   126  	clusterConfigs, err = replaceKubeconfigs(clusterConfigs, s.KubeConfig)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	if err := s.validateTopologyFlags(len(clusterConfigs)); err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	// Apply clusterConfigs overrides from flags, if specified.
   136  	if s.controlPlaneTopology != nil && len(s.controlPlaneTopology) > 0 {
   137  		if len(s.controlPlaneTopology) != len(clusterConfigs) {
   138  			return nil, fmt.Errorf("istio.test.kube.controlPlaneTopology has %d entries but there are %d clusters", len(controlPlaneTopology), len(clusterConfigs))
   139  		}
   140  		for src, dst := range s.controlPlaneTopology {
   141  			clusterConfigs[src].PrimaryClusterName = clusterConfigs[dst].Name
   142  		}
   143  	}
   144  	if s.configTopology != nil {
   145  		if len(s.configTopology) != len(clusterConfigs) {
   146  			return nil, fmt.Errorf("istio.test.kube.configTopology has %d entries but there are %d clusters", len(controlPlaneTopology), len(clusterConfigs))
   147  		}
   148  		for src, dst := range s.controlPlaneTopology {
   149  			clusterConfigs[src].ConfigClusterName = clusterConfigs[dst].Name
   150  		}
   151  	}
   152  	if s.networkTopology != nil {
   153  		if len(s.networkTopology) != len(clusterConfigs) {
   154  			return nil, fmt.Errorf("istio.test.kube.networkTopology has %d entries but there are %d clusters", len(controlPlaneTopology), len(clusterConfigs))
   155  		}
   156  		for src, network := range s.networkTopology {
   157  			clusterConfigs[src].ConfigClusterName = network
   158  		}
   159  	}
   160  
   161  	return clusterConfigs, nil
   162  }
   163  
   164  func (s *Settings) validateTopologyFlags(nClusters int) error {
   165  	makeErr := func(idx clusterIndex, flag string) error {
   166  		return fmt.Errorf("invalid cluster index %d in %s exceeds %d, the number of configured clusters", idx, flag, nClusters)
   167  	}
   168  	for flag, m := range map[string]clusterTopology{
   169  		"istio.test.kube.controlPlaneTopology": s.controlPlaneTopology,
   170  		"istio.test.kube.configTopology":       s.configTopology,
   171  	} {
   172  		for src, dst := range m {
   173  			if int(src) >= nClusters {
   174  				return makeErr(src, flag)
   175  			}
   176  			if int(dst) >= nClusters {
   177  				return makeErr(dst, flag)
   178  			}
   179  		}
   180  	}
   181  	for idx := range s.networkTopology {
   182  		if int(idx) >= nClusters {
   183  			return makeErr(idx, "istio.test.kube.networkTopology")
   184  		}
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  // replaceKubeconfigs allows using flags to specify the kubeconfigs for each cluster instead of the topology flags.
   191  // This capability is needed for backwards compatibility and will likely be removed.
   192  func replaceKubeconfigs(configs []cluster.Config, kubeconfigs []string) ([]cluster.Config, error) {
   193  	if len(kubeconfigs) == 0 {
   194  		return configs, nil
   195  	}
   196  	kube := 0
   197  	out := []cluster.Config{}
   198  	for _, cfg := range configs {
   199  		if cfg.Kind == cluster.Kubernetes {
   200  			if kube >= len(kubeconfigs) {
   201  				// not enough to cover all clusters in file
   202  				return nil, fmt.Errorf("istio.test.kube.cfg should have a kubeconfig for each kube cluster")
   203  			}
   204  			if cfg.Meta == nil {
   205  				cfg.Meta = config.Map{}
   206  			}
   207  			cfg.Meta["kubeconfig"] = kubeconfigs[kube]
   208  		}
   209  		kube++
   210  		out = append(out, cfg)
   211  	}
   212  	if kube < len(kubeconfigs) {
   213  		return nil, fmt.Errorf("%d kubeconfigs were provided but topolgy has %d kube clusters", len(kubeconfigs), kube)
   214  	}
   215  
   216  	return out, nil
   217  }
   218  
   219  func (s *Settings) MCSAPIGroupVersion() schema.GroupVersion {
   220  	return schema.GroupVersion{
   221  		Group:   s.MCSAPIGroup,
   222  		Version: s.MCSAPIVersion,
   223  	}
   224  }
   225  
   226  func (s *Settings) ServiceExportGVR() schema.GroupVersionResource {
   227  	return s.MCSAPIGroupVersion().WithResource("serviceexports")
   228  }
   229  
   230  func (s *Settings) ServiceImportGVR() schema.GroupVersionResource {
   231  	return s.MCSAPIGroupVersion().WithResource("serviceimports")
   232  }
   233  
   234  // String implements fmt.Stringer
   235  func (s *Settings) String() string {
   236  	result := ""
   237  
   238  	result += fmt.Sprintf("Kubeconfigs:           %s\n", s.KubeConfig)
   239  	result += fmt.Sprintf("LoadBalancerSupported: %v\n", s.LoadBalancerSupported)
   240  	result += fmt.Sprintf("MCSControllerEnabled:  %v\n", s.MCSControllerEnabled)
   241  	result += fmt.Sprintf("ControlPlaneTopology:  %v\n", s.controlPlaneTopology)
   242  	result += fmt.Sprintf("NetworkTopology:       %v\n", s.networkTopology)
   243  	result += fmt.Sprintf("ConfigTopology:        %v\n", s.configTopology)
   244  	return result
   245  }