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

     1  /*
     2  
     3   Copyright 2021 The KubeVela Authors.
     4  
     5   Licensed under the Apache License, Version 2.0 (the "License");
     6   you may not use this file except in compliance with the License.
     7   You may obtain a copy of the License at
     8  
     9       http://www.apache.org/licenses/LICENSE-2.0
    10  
    11   Unless required by applicable law or agreed to in writing, software
    12   distributed under the License is distributed on an "AS IS" BASIS,
    13   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   See the License for the specific language governing permissions and
    15   limitations under the License.
    16  
    17  */
    18  
    19  package multicluster
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"time"
    25  
    26  	pkgmulticluster "github.com/kubevela/pkg/multicluster"
    27  	"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
    28  	clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
    29  	errors2 "github.com/pkg/errors"
    30  	v1 "k8s.io/api/core/v1"
    31  	"k8s.io/apimachinery/pkg/api/errors"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/client-go/rest"
    35  	"k8s.io/klog/v2"
    36  	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
    37  	"sigs.k8s.io/controller-runtime/pkg/client"
    38  
    39  	"github.com/oam-dev/kubevela/pkg/oam"
    40  	"github.com/oam-dev/kubevela/pkg/utils/common"
    41  	errors3 "github.com/oam-dev/kubevela/pkg/utils/errors"
    42  )
    43  
    44  const (
    45  	// ClusterLocalName specifies the local cluster
    46  	ClusterLocalName = pkgmulticluster.Local
    47  )
    48  
    49  var (
    50  	// ClusterGatewaySecretNamespace the namespace where cluster-gateway secret locates
    51  	ClusterGatewaySecretNamespace = "vela-system"
    52  )
    53  
    54  // ClusterNameInContext extract cluster name from context
    55  func ClusterNameInContext(ctx context.Context) string {
    56  	cluster, _ := pkgmulticluster.ClusterFrom(ctx)
    57  	return cluster
    58  }
    59  
    60  // ContextWithClusterName create context with multi-cluster by cluster name
    61  func ContextWithClusterName(ctx context.Context, clusterName string) context.Context {
    62  	return pkgmulticluster.WithCluster(ctx, clusterName)
    63  }
    64  
    65  // ContextInLocalCluster create context in local cluster
    66  func ContextInLocalCluster(ctx context.Context) context.Context {
    67  	return pkgmulticluster.WithCluster(ctx, ClusterLocalName)
    68  }
    69  
    70  // ResourcesWithClusterName set cluster name for resources
    71  func ResourcesWithClusterName(clusterName string, objs ...*unstructured.Unstructured) []*unstructured.Unstructured {
    72  	var _objs []*unstructured.Unstructured
    73  	for _, obj := range objs {
    74  		if obj != nil {
    75  			oam.SetClusterIfEmpty(obj, clusterName)
    76  			_objs = append(_objs, obj)
    77  		}
    78  	}
    79  	return _objs
    80  }
    81  
    82  // GetClusterGatewayService get cluster gateway backend service reference
    83  // if service is ready, service is returned and no error is returned
    84  // if service exists but is not ready, both service and error are returned
    85  // if service does not exist, only error is returned
    86  func GetClusterGatewayService(ctx context.Context, c client.Client) (*apiregistrationv1.ServiceReference, error) {
    87  	gv := v1alpha1.SchemeGroupVersion
    88  	apiService := &apiregistrationv1.APIService{}
    89  	apiServiceName := gv.Version + "." + gv.Group
    90  	if err := c.Get(ctx, types.NamespacedName{Name: apiServiceName}, apiService); err != nil {
    91  		if errors.IsNotFound(err) {
    92  			return nil, fmt.Errorf("ClusterGateway APIService %s is not found", apiServiceName)
    93  		}
    94  		return nil, errors2.Wrapf(err, "failed to get ClusterGateway APIService %s", apiServiceName)
    95  	}
    96  	if apiService.Spec.Service == nil {
    97  		return nil, fmt.Errorf("ClusterGateway APIService should use the service exposed by dedicated apiserver instead of being handled locally")
    98  	}
    99  	svc := apiService.Spec.Service
   100  	status := apiregistrationv1.ConditionUnknown
   101  	for _, condition := range apiService.Status.Conditions {
   102  		if condition.Type == apiregistrationv1.Available {
   103  			status = condition.Status
   104  		}
   105  	}
   106  	if status == apiregistrationv1.ConditionTrue {
   107  		return svc, nil
   108  	}
   109  	return svc, fmt.Errorf("ClusterGateway APIService (%s/%s:%d) is not ready, current status: %s", svc.Namespace, svc.Name, svc.Port, status)
   110  }
   111  
   112  // WaitUntilClusterGatewayReady wait cluster gateway service to be ready to serve
   113  func WaitUntilClusterGatewayReady(ctx context.Context, c client.Client, maxRetry int, interval time.Duration) (svc *apiregistrationv1.ServiceReference, err error) {
   114  	for i := 0; i < maxRetry; i++ {
   115  		if svc, err = GetClusterGatewayService(ctx, c); err != nil {
   116  			klog.Infof("waiting for cluster gateway service: %v", err)
   117  			time.Sleep(interval)
   118  		} else {
   119  			return
   120  		}
   121  	}
   122  	return nil, errors2.Wrapf(err, "failed to wait cluster gateway service (retry=%d)", maxRetry)
   123  }
   124  
   125  // Initialize prepare multicluster environment by checking cluster gateway service in clusters and hack rest config to use cluster gateway
   126  // if cluster gateway service is not ready, it will wait up to 5 minutes
   127  func Initialize(restConfig *rest.Config, autoUpgrade bool) (client.Client, error) {
   128  	c, err := client.New(restConfig, client.Options{Scheme: common.Scheme})
   129  	if err != nil {
   130  		return nil, errors2.Wrapf(err, "unable to get client to find cluster gateway service")
   131  	}
   132  	svc, err := WaitUntilClusterGatewayReady(context.Background(), c, 60, 5*time.Second)
   133  	if err != nil {
   134  		return nil, ErrDetectClusterGateway
   135  	}
   136  	ClusterGatewaySecretNamespace = svc.Namespace
   137  	if autoUpgrade {
   138  		if err = UpgradeExistingClusterSecret(context.Background(), c); err != nil {
   139  			// this error do not affect the running of current version
   140  			klog.ErrorS(err, "error encountered while grading existing cluster secret to the latest version")
   141  		}
   142  	}
   143  	return c, nil
   144  }
   145  
   146  // UpgradeExistingClusterSecret upgrade outdated cluster secrets in v1.1.1 to latest
   147  func UpgradeExistingClusterSecret(ctx context.Context, c client.Client) error {
   148  	const outdatedClusterCredentialLabelKey = "cluster.core.oam.dev/cluster-credential"
   149  	secrets := &v1.SecretList{}
   150  	if err := c.List(ctx, secrets, client.InNamespace(ClusterGatewaySecretNamespace), client.HasLabels{outdatedClusterCredentialLabelKey}); err != nil {
   151  		if err != nil {
   152  			return errors2.Wrapf(err, "failed to find outdated cluster secrets to do upgrade")
   153  		}
   154  	}
   155  	errs := errors3.ErrorList{}
   156  	for _, item := range secrets.Items {
   157  		credType := item.Labels[clustercommon.LabelKeyClusterCredentialType]
   158  		if credType == "" && item.Type == v1.SecretTypeTLS {
   159  			item.Labels[clustercommon.LabelKeyClusterCredentialType] = string(v1alpha1.CredentialTypeX509Certificate)
   160  			if err := c.Update(ctx, item.DeepCopy()); err != nil {
   161  				errs = append(errs, errors2.Wrapf(err, "failed to update outdated secret %s", item.Name))
   162  			}
   163  		}
   164  	}
   165  	if errs.HasError() {
   166  		return errs
   167  	}
   168  	return nil
   169  }
   170  
   171  // ListExistingClusterSecrets list existing cluster secrets
   172  func ListExistingClusterSecrets(ctx context.Context, c client.Client) ([]v1.Secret, error) {
   173  	secrets := &v1.SecretList{}
   174  	if err := c.List(ctx, secrets, client.InNamespace(ClusterGatewaySecretNamespace), client.HasLabels{clustercommon.LabelKeyClusterCredentialType}); err != nil {
   175  		return nil, errors2.Wrapf(err, "failed to list cluster secrets")
   176  	}
   177  	return secrets.Items, nil
   178  }