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 }