blob: 27c164af94a5e8dfc9afb8fb14e21060c00386dd [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package kube
import (
"fmt"
)
import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
import (
istioKube "github.com/apache/dubbo-go-pixiu/pkg/kube"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/cluster"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/config"
"github.com/apache/dubbo-go-pixiu/pkg/test/scopes"
)
// clusterIndex is the index of a cluster within the KubeConfig or topology file entries
type clusterIndex int
// clusterTopology defines the associations between multiple clusters in a topology.
type clusterTopology = map[clusterIndex]clusterIndex
// ClientFactoryFunc is a transformation function that creates k8s clients
// from the provided k8s config files.
type ClientFactoryFunc func(kubeConfigs []string) ([]istioKube.ExtendedClient, error)
// Settings provide kube-specific Settings from flags.
type Settings struct {
// An array of paths to kube config files. Required if the environment is kubernetes.
KubeConfig []string
// Indicates that the LoadBalancer services can obtain a public IP. If not, NodePort be used as a workaround
// for ingress gateway. KinD will not support LoadBalancer out of the box and requires a workaround such as
// MetalLB.
LoadBalancerSupported bool
// MCSControllerEnabled indicates that the Kubernetes environment has a Multi-Cluster Services (MCS)
// controller up and running.
MCSControllerEnabled bool
// MCSAPIGroup the group to use for the MCS API
MCSAPIGroup string
// MCSAPIVersion the version to use for the MCS API
MCSAPIVersion string
// controlPlaneTopology maps each cluster to the cluster that runs its control plane. For replicated control
// plane cases (where each cluster has its own control plane), the cluster will map to itself (e.g. 0->0).
controlPlaneTopology clusterTopology
// networkTopology is used for the initial assignment of networks to each cluster.
// The source of truth clusters' networks is the Cluster instances themselves, rather than this field.
networkTopology map[clusterIndex]string
// configTopology maps each cluster to the cluster that runs it's config.
// If the cluster runs its own config, the cluster will map to itself (e.g. 0->0)
// By default, we use the controlPlaneTopology as the config topology.
configTopology clusterTopology
}
func (s *Settings) clone() *Settings {
c := *s
return &c
}
func (s *Settings) clusterConfigs() ([]cluster.Config, error) {
if len(clusterConfigs) == 0 {
// not loaded from file file, build directly from provided kubeconfigs and topology flag maps
return s.clusterConfigsFromFlags()
}
return s.clusterConfigsFromFile()
}
func (s *Settings) clusterConfigsFromFlags() ([]cluster.Config, error) {
if len(s.KubeConfig) == 0 {
// flag-based, but no kubeconfigs, get kubeconfigs from environment
scopes.Framework.Info("Flags istio.test.kube.config and istio.test.kube.topology not specified.")
var err error
s.KubeConfig, err = getKubeConfigsFromEnvironment()
if err != nil {
return nil, fmt.Errorf("error parsing KubeConfigs from environment: %v", err)
}
}
scopes.Framework.Infof("Using KubeConfigs: %v.", s.KubeConfig)
if err := s.validateTopologyFlags(len(s.KubeConfig)); err != nil {
return nil, err
}
var configs []cluster.Config
for i, kc := range s.KubeConfig {
ci := clusterIndex(i)
cfg := cluster.Config{
Name: fmt.Sprintf("cluster-%d", i),
Kind: cluster.Kubernetes,
Network: s.networkTopology[ci],
Meta: config.Map{"kubeconfig": kc},
}
if idx, ok := s.controlPlaneTopology[ci]; ok {
cfg.PrimaryClusterName = fmt.Sprintf("cluster-%d", idx)
}
if idx, ok := s.configTopology[ci]; ok {
cfg.ConfigClusterName = fmt.Sprintf("cluster-%d", idx)
}
configs = append(configs, cfg)
}
return configs, nil
}
func (s *Settings) clusterConfigsFromFile() ([]cluster.Config, error) {
// Allow kubeconfig flag to override file
var err error
clusterConfigs, err = replaceKubeconfigs(clusterConfigs, s.KubeConfig)
if err != nil {
return nil, err
}
if err := s.validateTopologyFlags(len(clusterConfigs)); err != nil {
return nil, err
}
// Apply clusterConfigs overrides from flags, if specified.
if s.controlPlaneTopology != nil && len(s.controlPlaneTopology) > 0 {
if len(s.controlPlaneTopology) != len(clusterConfigs) {
return nil, fmt.Errorf("istio.test.kube.controlPlaneTopology has %d entries but there are %d clusters", len(controlPlaneTopology), len(clusterConfigs))
}
for src, dst := range s.controlPlaneTopology {
clusterConfigs[src].PrimaryClusterName = clusterConfigs[dst].Name
}
}
if s.configTopology != nil {
if len(s.configTopology) != len(clusterConfigs) {
return nil, fmt.Errorf("istio.test.kube.configTopology has %d entries but there are %d clusters", len(controlPlaneTopology), len(clusterConfigs))
}
for src, dst := range s.controlPlaneTopology {
clusterConfigs[src].ConfigClusterName = clusterConfigs[dst].Name
}
}
if s.networkTopology != nil {
if len(s.networkTopology) != len(clusterConfigs) {
return nil, fmt.Errorf("istio.test.kube.networkTopology has %d entries but there are %d clusters", len(controlPlaneTopology), len(clusterConfigs))
}
for src, network := range s.networkTopology {
clusterConfigs[src].ConfigClusterName = network
}
}
return clusterConfigs, nil
}
func (s *Settings) validateTopologyFlags(nClusters int) error {
makeErr := func(idx clusterIndex, flag string) error {
return fmt.Errorf("invalid cluster index %d in %s exceeds %d, the number of configured clusters", idx, flag, nClusters)
}
for flag, m := range map[string]clusterTopology{
"istio.test.kube.controlPlaneTopology": s.controlPlaneTopology,
"istio.test.kube.configTopology": s.configTopology,
} {
for src, dst := range m {
if int(src) >= nClusters {
return makeErr(src, flag)
}
if int(dst) >= nClusters {
return makeErr(dst, flag)
}
}
}
for idx := range s.networkTopology {
if int(idx) >= nClusters {
return makeErr(idx, "istio.test.kube.networkTopology")
}
}
return nil
}
// replaceKubeconfigs allows using flags to specify the kubeconfigs for each cluster instead of the topology flags.
// This capability is needed for backwards compatibility and will likely be removed.
func replaceKubeconfigs(configs []cluster.Config, kubeconfigs []string) ([]cluster.Config, error) {
if len(kubeconfigs) == 0 {
return configs, nil
}
kube := 0
out := []cluster.Config{}
for _, cfg := range configs {
if cfg.Kind == cluster.Kubernetes {
if kube >= len(kubeconfigs) {
// not enough to cover all clusters in file
return nil, fmt.Errorf("istio.test.kube.cfg should have a kubeconfig for each kube cluster")
}
if cfg.Meta == nil {
cfg.Meta = config.Map{}
}
cfg.Meta["kubeconfig"] = kubeconfigs[kube]
}
kube++
out = append(out, cfg)
}
if kube < len(kubeconfigs) {
return nil, fmt.Errorf("%d kubeconfigs were provided but topolgy has %d kube clusters", len(kubeconfigs), kube)
}
return out, nil
}
func (s *Settings) MCSAPIGroupVersion() schema.GroupVersion {
return schema.GroupVersion{
Group: s.MCSAPIGroup,
Version: s.MCSAPIVersion,
}
}
func (s *Settings) ServiceExportGVR() schema.GroupVersionResource {
return s.MCSAPIGroupVersion().WithResource("serviceexports")
}
func (s *Settings) ServiceImportGVR() schema.GroupVersionResource {
return s.MCSAPIGroupVersion().WithResource("serviceimports")
}
// String implements fmt.Stringer
func (s *Settings) String() string {
result := ""
result += fmt.Sprintf("Kubeconfigs: %s\n", s.KubeConfig)
result += fmt.Sprintf("LoadBalancerSupported: %v\n", s.LoadBalancerSupported)
result += fmt.Sprintf("MCSControllerEnabled: %v\n", s.MCSControllerEnabled)
result += fmt.Sprintf("ControlPlaneTopology: %v\n", s.controlPlaneTopology)
result += fmt.Sprintf("NetworkTopology: %v\n", s.networkTopology)
result += fmt.Sprintf("ConfigTopology: %v\n", s.configTopology)
return result
}