github.com/oam-dev/kubevela@v1.9.11/pkg/multicluster/virtual_cluster.go (about)

     1  /*
     2  Copyright 2020-2022 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package multicluster
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"strings"
    24  
    25  	"github.com/kubevela/pkg/util/singleton"
    26  	velaslices "github.com/kubevela/pkg/util/slices"
    27  	"github.com/oam-dev/cluster-gateway/pkg/generated/clientset/versioned"
    28  	"github.com/pkg/errors"
    29  	corev1 "k8s.io/api/core/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/api/meta"
    32  	apilabels "k8s.io/apimachinery/pkg/labels"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/apimachinery/pkg/selection"
    36  	apitypes "k8s.io/apimachinery/pkg/types"
    37  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    38  	"k8s.io/client-go/rest"
    39  	"k8s.io/klog/v2"
    40  	"k8s.io/kubectl/pkg/scheme"
    41  	clusterv1 "open-cluster-management.io/api/cluster/v1"
    42  	"sigs.k8s.io/controller-runtime/pkg/client"
    43  
    44  	"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
    45  	clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
    46  
    47  	"github.com/oam-dev/kubevela/apis/types"
    48  	"github.com/oam-dev/kubevela/pkg/features"
    49  	"github.com/oam-dev/kubevela/pkg/utils/common"
    50  	velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
    51  )
    52  
    53  // InitClusterInfo will initialize control plane cluster info
    54  func InitClusterInfo(cfg *rest.Config) error {
    55  	ctx := context.Background()
    56  	var err error
    57  	types.ControlPlaneClusterVersion, err = GetVersionInfoFromCluster(ctx, ClusterLocalName, cfg)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	if !utilfeature.DefaultMutableFeatureGate.Enabled(features.DisableBootstrapClusterInfo) {
    62  		clusters, err := NewClusterClient(singleton.KubeClient.Get()).List(ctx)
    63  		if err != nil {
    64  			return errors.Wrap(err, "fail to get registered clusters")
    65  		}
    66  
    67  		velaslices.ParFor(clusters.Items, func(cluster v1alpha1.VirtualCluster) {
    68  			if err = SetClusterVersionInfo(ctx, cfg, cluster.Name); err != nil {
    69  				klog.Warningf("set cluster version for %s: %v, skip it...", cluster.Name, err)
    70  			}
    71  		})
    72  	}
    73  	return nil
    74  }
    75  
    76  // VirtualCluster contains base info of cluster, it unifies the difference between different cluster implementations
    77  // like cluster secret or ocm managed cluster
    78  type VirtualCluster struct {
    79  	Name     string
    80  	Alias    string
    81  	Type     v1alpha1.CredentialType
    82  	EndPoint string
    83  	Accepted bool
    84  	Labels   map[string]string
    85  	Metrics  *ClusterMetrics
    86  	Object   client.Object
    87  }
    88  
    89  // FullName the name with alias if available
    90  func (vc *VirtualCluster) FullName() string {
    91  	if vc.Alias != "" {
    92  		return fmt.Sprintf("%s (%s)", vc.Name, vc.Alias)
    93  	}
    94  	return vc.Name
    95  }
    96  
    97  func getClusterAlias(o client.Object) string {
    98  	if annots := o.GetAnnotations(); annots != nil {
    99  		return annots[v1alpha1.AnnotationClusterAlias]
   100  	}
   101  	return ""
   102  }
   103  
   104  func setClusterAlias(o client.Object, alias string) {
   105  	annots := o.GetAnnotations()
   106  	if annots == nil {
   107  		annots = map[string]string{}
   108  	}
   109  	annots[v1alpha1.AnnotationClusterAlias] = alias
   110  	o.SetAnnotations(annots)
   111  }
   112  
   113  // NewVirtualClusterFromLocal return virtual cluster corresponding to local cluster
   114  func NewVirtualClusterFromLocal() *VirtualCluster {
   115  	return &VirtualCluster{
   116  		Name:     ClusterLocalName,
   117  		Type:     types.CredentialTypeInternal,
   118  		EndPoint: types.ClusterBlankEndpoint,
   119  		Accepted: true,
   120  		Labels:   map[string]string{},
   121  		Metrics:  metricsMap[ClusterLocalName],
   122  	}
   123  }
   124  
   125  // NewVirtualClusterFromSecret extract virtual cluster from cluster secret
   126  func NewVirtualClusterFromSecret(secret *corev1.Secret) (*VirtualCluster, error) {
   127  	endpoint := string(secret.Data["endpoint"])
   128  	labels := secret.GetLabels()
   129  	if labels == nil {
   130  		labels = map[string]string{}
   131  	}
   132  	if _endpoint, ok := labels[clustercommon.LabelKeyClusterEndpointType]; ok {
   133  		endpoint = _endpoint
   134  	}
   135  	credType, ok := labels[clustercommon.LabelKeyClusterCredentialType]
   136  	if !ok {
   137  		return nil, errors.Errorf("secret is not a valid cluster secret, no credential type found")
   138  	}
   139  	return &VirtualCluster{
   140  		Name:     secret.Name,
   141  		Alias:    getClusterAlias(secret),
   142  		Type:     v1alpha1.CredentialType(credType),
   143  		EndPoint: endpoint,
   144  		Accepted: true,
   145  		Labels:   labels,
   146  		Metrics:  metricsMap[secret.Name],
   147  		Object:   secret,
   148  	}, nil
   149  }
   150  
   151  // NewVirtualClusterFromManagedCluster extract virtual cluster from ocm managed cluster
   152  func NewVirtualClusterFromManagedCluster(managedCluster *clusterv1.ManagedCluster) (*VirtualCluster, error) {
   153  	if len(managedCluster.Spec.ManagedClusterClientConfigs) == 0 {
   154  		return nil, errors.Errorf("managed cluster has no client config")
   155  	}
   156  	return &VirtualCluster{
   157  		Name:     managedCluster.Name,
   158  		Alias:    getClusterAlias(managedCluster),
   159  		Type:     types.CredentialTypeOCMManagedCluster,
   160  		EndPoint: types.ClusterBlankEndpoint,
   161  		Accepted: managedCluster.Spec.HubAcceptsClient,
   162  		Labels:   managedCluster.GetLabels(),
   163  		Metrics:  metricsMap[managedCluster.Name],
   164  		Object:   managedCluster,
   165  	}, nil
   166  }
   167  
   168  // GetVirtualCluster returns virtual cluster with given clusterName
   169  func GetVirtualCluster(ctx context.Context, c client.Client, clusterName string) (vc *VirtualCluster, err error) {
   170  	if clusterName == ClusterLocalName {
   171  		return NewVirtualClusterFromLocal(), nil
   172  	}
   173  	if ClusterGatewaySecretNamespace == "" {
   174  		ClusterGatewaySecretNamespace = types.DefaultKubeVelaNS
   175  	}
   176  	secret := &corev1.Secret{}
   177  	err = c.Get(ctx, apitypes.NamespacedName{
   178  		Name:      clusterName,
   179  		Namespace: ClusterGatewaySecretNamespace,
   180  	}, secret)
   181  	var secretErr error
   182  	if err == nil {
   183  		vc, secretErr = NewVirtualClusterFromSecret(secret)
   184  		if secretErr == nil {
   185  			return vc, nil
   186  		}
   187  	}
   188  	if err != nil && !apierrors.IsNotFound(err) {
   189  		secretErr = err
   190  	}
   191  
   192  	managedCluster := &clusterv1.ManagedCluster{}
   193  	err = c.Get(ctx, apitypes.NamespacedName{
   194  		Name:      clusterName,
   195  		Namespace: ClusterGatewaySecretNamespace,
   196  	}, managedCluster)
   197  	var managedClusterErr error
   198  	if err == nil {
   199  		vc, managedClusterErr = NewVirtualClusterFromManagedCluster(managedCluster)
   200  		if managedClusterErr == nil {
   201  			return vc, nil
   202  		}
   203  	}
   204  
   205  	if err != nil && !apierrors.IsNotFound(err) && !velaerrors.IsCRDNotExists(err) {
   206  		managedClusterErr = err
   207  	}
   208  
   209  	if secretErr == nil && managedClusterErr == nil {
   210  		return nil, ErrClusterNotExists
   211  	}
   212  
   213  	var errs velaerrors.ErrorList
   214  	if secretErr != nil {
   215  		errs = append(errs, secretErr)
   216  	}
   217  	if managedClusterErr != nil {
   218  		errs = append(errs, managedClusterErr)
   219  	}
   220  	return nil, errs
   221  }
   222  
   223  // MatchVirtualClusterLabels filters the list/delete operation of cluster list
   224  type MatchVirtualClusterLabels map[string]string
   225  
   226  // ApplyToList applies this configuration to the given list options.
   227  func (m MatchVirtualClusterLabels) ApplyToList(opts *client.ListOptions) {
   228  	sel := apilabels.SelectorFromValidatedSet(map[string]string(m))
   229  	r, err := apilabels.NewRequirement(clustercommon.LabelKeyClusterCredentialType, selection.Exists, nil)
   230  	if err == nil {
   231  		sel = sel.Add(*r)
   232  	}
   233  	opts.LabelSelector = sel
   234  	opts.Namespace = ClusterGatewaySecretNamespace
   235  }
   236  
   237  // ApplyToDeleteAllOf applies this configuration to the given a List options.
   238  func (m MatchVirtualClusterLabels) ApplyToDeleteAllOf(opts *client.DeleteAllOfOptions) {
   239  	m.ApplyToList(&opts.ListOptions)
   240  }
   241  
   242  // ListVirtualClusters will get all registered clusters in control plane
   243  func ListVirtualClusters(ctx context.Context, c client.Client) ([]VirtualCluster, error) {
   244  	clusters, err := FindVirtualClustersByLabels(ctx, c, map[string]string{})
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	return append([]VirtualCluster{*NewVirtualClusterFromLocal()}, clusters...), nil
   249  }
   250  
   251  // FindVirtualClustersByLabels will get all virtual clusters with matched labels in control plane
   252  func FindVirtualClustersByLabels(ctx context.Context, c client.Client, labels map[string]string) ([]VirtualCluster, error) {
   253  	var clusters []VirtualCluster
   254  	secrets := corev1.SecretList{}
   255  	if err := c.List(ctx, &secrets, MatchVirtualClusterLabels(labels)); err != nil {
   256  		return nil, errors.Wrapf(err, "failed to get clusterSecret secrets")
   257  	}
   258  	for _, secret := range secrets.Items {
   259  		vc, err := NewVirtualClusterFromSecret(secret.DeepCopy())
   260  		if err == nil {
   261  			clusters = append(clusters, *vc)
   262  		}
   263  	}
   264  
   265  	managedClusters := clusterv1.ManagedClusterList{}
   266  	if err := c.List(context.Background(), &managedClusters, client.MatchingLabels(labels)); err != nil && !velaerrors.IsCRDNotExists(err) {
   267  		return nil, errors.Wrapf(err, "failed to get managed clusters")
   268  	}
   269  	for _, managedCluster := range managedClusters.Items {
   270  		vc, err := NewVirtualClusterFromManagedCluster(managedCluster.DeepCopy())
   271  		if err == nil {
   272  			clusters = append(clusters, *vc)
   273  		}
   274  	}
   275  	return clusters, nil
   276  }
   277  
   278  // ClusterNameMapper mapper for cluster names
   279  type ClusterNameMapper interface {
   280  	GetClusterName(string) string
   281  }
   282  
   283  type clusterAliasMapper map[string]string
   284  
   285  // GetClusterName .
   286  func (cm clusterAliasMapper) GetClusterName(cluster string) string {
   287  	if alias := strings.TrimSpace(cm[cluster]); alias != "" {
   288  		return fmt.Sprintf("%s (%s)", cluster, alias)
   289  	}
   290  	return cluster
   291  }
   292  
   293  // NewClusterNameMapper load all clusters and return the mapper of their names
   294  func NewClusterNameMapper(ctx context.Context, c client.Client) (ClusterNameMapper, error) {
   295  	cm := clusterAliasMapper(make(map[string]string))
   296  	clusters := &v1alpha1.VirtualClusterList{}
   297  	if err := c.List(ctx, clusters); err == nil {
   298  		for _, cluster := range clusters.Items {
   299  			cm[cluster.Name] = cluster.Spec.Alias
   300  		}
   301  		return cm, nil
   302  	} else if err != nil && !meta.IsNoMatchError(err) && !runtime.IsNotRegisteredError(err) {
   303  		return nil, err
   304  	}
   305  	vcs, err := ListVirtualClusters(ctx, c)
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  	for _, cluster := range vcs {
   310  		cm[cluster.Name] = cluster.Alias
   311  	}
   312  	return cm, nil
   313  }
   314  
   315  // SetClusterVersionInfo update cluster version info into virtual cluster object
   316  func SetClusterVersionInfo(ctx context.Context, cfg *rest.Config, clusterName string) error {
   317  	cli, err := client.New(cfg, client.Options{Scheme: common.Scheme})
   318  	if err != nil {
   319  		return err
   320  	}
   321  	if clusterName == ClusterLocalName {
   322  		return nil
   323  	}
   324  	vc, err := GetVirtualCluster(ctx, cli, clusterName)
   325  	if err != nil {
   326  		return errors.Wrap(err, "get virtual cluster")
   327  	}
   328  	_, err = getClusterVersionFromObject(vc.Object)
   329  	if err == nil {
   330  		// info already exist
   331  		return nil
   332  	}
   333  	cv, err := GetVersionInfoFromCluster(ctx, clusterName, cfg)
   334  	if err != nil {
   335  		return err
   336  	}
   337  	setClusterVersion(vc.Object, cv)
   338  	klog.Infof("joining cluster %s with version: %s", clusterName, cv.GitVersion)
   339  	return cli.Update(ctx, vc.Object)
   340  }
   341  
   342  // GetVersionInfoFromObject will get cluster version info from virtual cluster, it will fall back to control plane cluster version if any error occur
   343  func GetVersionInfoFromObject(ctx context.Context, cli client.Client, clusterName string) types.ClusterVersion {
   344  	vc, err := GetVirtualCluster(ctx, cli, clusterName)
   345  	if err != nil {
   346  		klog.Warningf("get virtual cluster for %s err %v, using control plane cluster version", clusterName, err)
   347  		return types.ControlPlaneClusterVersion
   348  	}
   349  	cv, err := getClusterVersionFromObject(vc.Object)
   350  	if err != nil {
   351  		klog.Warningf("get version info for %s err %v, using control plane cluster version", clusterName, err)
   352  		return types.ControlPlaneClusterVersion
   353  	}
   354  	return cv
   355  }
   356  
   357  func setClusterVersion(o client.Object, info types.ClusterVersion) {
   358  	if o == nil {
   359  		return
   360  	}
   361  	ann := o.GetAnnotations()
   362  	if ann == nil {
   363  		ann = map[string]string{}
   364  	}
   365  	content, _ := json.Marshal(info)
   366  	ann[types.AnnotationClusterVersion] = string(content)
   367  	o.SetAnnotations(ann)
   368  }
   369  
   370  func getClusterVersionFromObject(o client.Object) (types.ClusterVersion, error) {
   371  	if o == nil {
   372  		return types.ControlPlaneClusterVersion, nil
   373  	}
   374  	var cv types.ClusterVersion
   375  	ann := o.GetAnnotations()
   376  	if ann == nil {
   377  		return cv, errors.New("no cluster version info")
   378  	}
   379  	versionRaw := ann[types.AnnotationClusterVersion]
   380  	err := json.Unmarshal([]byte(versionRaw), &cv)
   381  	return cv, err
   382  }
   383  
   384  // GetVersionInfoFromCluster will add remote cluster version info into secret annotation
   385  func GetVersionInfoFromCluster(ctx context.Context, clusterName string, cfg *rest.Config) (types.ClusterVersion, error) {
   386  	var cv types.ClusterVersion
   387  	content, err := RequestRawK8sAPIForCluster(ctx, "version", clusterName, cfg)
   388  	if err != nil {
   389  		return cv, err
   390  	}
   391  	if err = json.Unmarshal(content, &cv); err != nil {
   392  		return cv, err
   393  	}
   394  	return cv, nil
   395  }
   396  
   397  // RequestRawK8sAPIForCluster will request multi-cluster K8s API with raw client, such as /healthz, /version, etc
   398  func RequestRawK8sAPIForCluster(ctx context.Context, path, clusterName string, cfg *rest.Config) ([]byte, error) {
   399  	cfg.GroupVersion = &schema.GroupVersion{Group: "", Version: "v1"}
   400  	cfg.NegotiatedSerializer = scheme.Codecs
   401  	defer func() {
   402  		cfg.GroupVersion = nil
   403  		cfg.NegotiatedSerializer = nil
   404  	}()
   405  	if clusterName == ClusterLocalName {
   406  		restClient, err := rest.RESTClientFor(cfg)
   407  		if err != nil {
   408  			return nil, errors.Wrap(err, "fail to get local cluster")
   409  		}
   410  		return restClient.Get().AbsPath(path).DoRaw(ctx)
   411  	}
   412  	return versioned.NewForConfigOrDie(cfg).ClusterV1alpha1().ClusterGateways().RESTClient(clusterName).Get().AbsPath(path).DoRaw(ctx)
   413  }
   414  
   415  // NewClusterClient create virtual cluster client
   416  func NewClusterClient(cli client.Client) v1alpha1.VirtualClusterClient {
   417  	return v1alpha1.NewVirtualClusterClient(cli, ClusterGatewaySecretNamespace, true)
   418  }