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 }