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 }