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

     1  /*
     2  Copyright 2021 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 resourcetracker
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"github.com/crossplane/crossplane-runtime/pkg/meta"
    24  	"github.com/kubevela/pkg/controller/sharding"
    25  	"github.com/kubevela/pkg/util/compression"
    26  	"github.com/pkg/errors"
    27  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  
    35  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    36  	"github.com/oam-dev/kubevela/pkg/cache"
    37  	"github.com/oam-dev/kubevela/pkg/features"
    38  	"github.com/oam-dev/kubevela/pkg/monitor/metrics"
    39  	"github.com/oam-dev/kubevela/pkg/oam"
    40  	velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
    41  )
    42  
    43  const (
    44  	// Finalizer for resourcetracker to clean up recorded resources
    45  	Finalizer = "resourcetracker.core.oam.dev/finalizer"
    46  )
    47  
    48  var (
    49  	applicationResourceTrackerGroupVersionKind = schema.GroupVersionKind{
    50  		Group:   "prism.oam.dev",
    51  		Version: "v1alpha1",
    52  		Kind:    "ApplicationResourceTracker",
    53  	}
    54  )
    55  
    56  func getPublishVersion(obj client.Object) string {
    57  	if obj.GetAnnotations() != nil {
    58  		return obj.GetAnnotations()[oam.AnnotationPublishVersion]
    59  	}
    60  	return ""
    61  }
    62  
    63  func getRootResourceTrackerName(app *v1beta1.Application) string {
    64  	return fmt.Sprintf("%s-%s", app.Name, app.Namespace)
    65  }
    66  
    67  func getCurrentResourceTrackerName(app *v1beta1.Application) string {
    68  	return fmt.Sprintf("%s-v%d-%s", app.Name, app.GetGeneration(), app.Namespace)
    69  }
    70  
    71  func getComponentRevisionResourceTrackerName(app *v1beta1.Application) string {
    72  	return fmt.Sprintf("%s-comp-rev-%s", app.Name, app.Namespace)
    73  }
    74  
    75  func createResourceTracker(ctx context.Context, cli client.Client, app *v1beta1.Application, rtName string, rtType v1beta1.ResourceTrackerType) (*v1beta1.ResourceTracker, error) {
    76  	rt := &v1beta1.ResourceTracker{}
    77  	rt.SetName(rtName)
    78  	meta.AddFinalizer(rt, Finalizer)
    79  	meta.AddLabels(rt, map[string]string{
    80  		oam.LabelAppName:      app.Name,
    81  		oam.LabelAppNamespace: app.Namespace,
    82  		oam.LabelAppUID:       string(app.UID),
    83  	})
    84  	if app.Status.LatestRevision != nil {
    85  		meta.AddLabels(rt, map[string]string{oam.LabelAppRevision: app.Status.LatestRevision.Name})
    86  	}
    87  	rt.Spec.Type = rtType
    88  	if rtType == v1beta1.ResourceTrackerTypeVersioned {
    89  		rt.Spec.ApplicationGeneration = app.GetGeneration()
    90  		if publishVersion := getPublishVersion(app); publishVersion != "" {
    91  			meta.AddAnnotations(rt, map[string]string{oam.AnnotationPublishVersion: publishVersion})
    92  		}
    93  	} else {
    94  		rt.Spec.ApplicationGeneration = 0
    95  	}
    96  	if utilfeature.DefaultMutableFeatureGate.Enabled(features.GzipResourceTracker) {
    97  		rt.Spec.Compression.Type = compression.Gzip
    98  	}
    99  	// zstd compressor will have higher priority when both gzip and zstd are enabled.
   100  	if utilfeature.DefaultMutableFeatureGate.Enabled(features.ZstdResourceTracker) {
   101  		rt.Spec.Compression.Type = compression.Zstd
   102  	}
   103  	sharding.PropagateScheduledShardIDLabel(app, rt)
   104  	if err := cli.Create(ctx, rt); err != nil {
   105  		return nil, err
   106  	}
   107  	return rt, nil
   108  }
   109  
   110  // CreateRootResourceTracker create root resourcetracker for application
   111  func CreateRootResourceTracker(ctx context.Context, cli client.Client, app *v1beta1.Application) (*v1beta1.ResourceTracker, error) {
   112  	return createResourceTracker(ctx, cli, app, getRootResourceTrackerName(app), v1beta1.ResourceTrackerTypeRoot)
   113  }
   114  
   115  // CreateCurrentResourceTracker create versioned resourcetracker for the latest generation of application
   116  func CreateCurrentResourceTracker(ctx context.Context, cli client.Client, app *v1beta1.Application) (*v1beta1.ResourceTracker, error) {
   117  	if publishVersion := getPublishVersion(app); publishVersion != "" {
   118  		return createResourceTracker(ctx, cli, app, fmt.Sprintf("%s-%s-%s", app.Name, publishVersion, app.Namespace), v1beta1.ResourceTrackerTypeVersioned)
   119  	}
   120  	return createResourceTracker(ctx, cli, app, getCurrentResourceTrackerName(app), v1beta1.ResourceTrackerTypeVersioned)
   121  }
   122  
   123  // CreateComponentRevisionResourceTracker create resourcetracker to record all component revision for application
   124  func CreateComponentRevisionResourceTracker(ctx context.Context, cli client.Client, app *v1beta1.Application) (*v1beta1.ResourceTracker, error) {
   125  	return createResourceTracker(ctx, cli, app, getComponentRevisionResourceTrackerName(app), v1beta1.ResourceTrackerTypeComponentRevision)
   126  }
   127  
   128  func newResourceTrackerFromApplicationResourceTracker(appRt *unstructured.Unstructured) (*v1beta1.ResourceTracker, error) {
   129  	rt := &v1beta1.ResourceTracker{}
   130  	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(appRt.Object, rt); err != nil {
   131  		return nil, err
   132  	}
   133  	namespace := metav1.NamespaceDefault
   134  	if ns := appRt.GetNamespace(); ns != "" {
   135  		namespace = ns
   136  	}
   137  	rt.SetName(appRt.GetName() + "-" + namespace)
   138  	rt.SetNamespace("")
   139  	rt.SetGroupVersionKind(v1beta1.ResourceTrackerKindVersionKind)
   140  	return rt, nil
   141  }
   142  
   143  func listApplicationResourceTrackers(ctx context.Context, cli client.Client, app *v1beta1.Application) ([]v1beta1.ResourceTracker, error) {
   144  	rts := v1beta1.ResourceTrackerList{}
   145  	var err error
   146  	if cache.OptimizeListOp {
   147  		err = cli.List(ctx, &rts, client.MatchingFields{cache.AppIndex: app.Namespace + "/" + app.Name})
   148  	} else {
   149  		err = cli.List(ctx, &rts, client.MatchingLabels{
   150  			oam.LabelAppName:      app.Name,
   151  			oam.LabelAppNamespace: app.Namespace,
   152  		})
   153  	}
   154  	if err == nil {
   155  		return rts.Items, nil
   156  	}
   157  	rtError := err
   158  	if !kerrors.IsForbidden(err) && !kerrors.IsUnauthorized(err) {
   159  		return nil, err
   160  	}
   161  	appRts := &unstructured.UnstructuredList{}
   162  	appRts.SetGroupVersionKind(applicationResourceTrackerGroupVersionKind)
   163  	if err = cli.List(ctx, appRts, client.MatchingLabels{
   164  		oam.LabelAppName: app.Name,
   165  	}, client.InNamespace(app.Namespace)); err != nil {
   166  		if velaerrors.IsCRDNotExists(err) {
   167  			return nil, errors.Wrapf(rtError, "no permission for ResourceTracker and vela-prism is not serving ApplicationResourceTracker")
   168  		}
   169  		return nil, err
   170  	}
   171  	var rtArr []v1beta1.ResourceTracker
   172  	for _, appRt := range appRts.Items {
   173  		rt, err := newResourceTrackerFromApplicationResourceTracker(appRt.DeepCopy())
   174  		if err != nil {
   175  			return nil, err
   176  		}
   177  		rtArr = append(rtArr, *rt)
   178  	}
   179  	return rtArr, nil
   180  }
   181  
   182  // ListApplicationResourceTrackers list resource trackers for application with all historyRTs sorted by version number
   183  // rootRT -> The ResourceTracker that records life-long resources. These resources will only be recycled when application is removed.
   184  // currentRT -> The ResourceTracker that tracks the resources used by the latest version of application.
   185  // historyRTs -> The ResourceTrackers that tracks the resources in outdated versions.
   186  // crRT -> The ResourceTracker that tracks the component revisions created by the application.
   187  func ListApplicationResourceTrackers(ctx context.Context, cli client.Client, app *v1beta1.Application) (rootRT *v1beta1.ResourceTracker, currentRT *v1beta1.ResourceTracker, historyRTs []*v1beta1.ResourceTracker, crRT *v1beta1.ResourceTracker, err error) {
   188  	metrics.ListResourceTrackerCounter.WithLabelValues("application").Inc()
   189  	rts, err := listApplicationResourceTrackers(ctx, cli, app)
   190  	if err != nil {
   191  		return nil, nil, nil, nil, err
   192  	}
   193  	for _, _rt := range rts {
   194  		rt := _rt.DeepCopy()
   195  		if rt.GetLabels() != nil && rt.GetLabels()[oam.LabelAppUID] != "" && rt.GetLabels()[oam.LabelAppUID] != string(app.UID) {
   196  			return nil, nil, nil, nil, fmt.Errorf("resourcetracker %s exists but controlled by another application (uid: %s), this could probably be cased by some mistakes while garbage collecting outdated resource. Please check this resourcetrakcer and delete it manually", rt.Name, rt.GetLabels()[oam.LabelAppUID])
   197  		}
   198  		switch rt.Spec.Type {
   199  		case v1beta1.ResourceTrackerTypeRoot:
   200  			rootRT = rt
   201  		case v1beta1.ResourceTrackerTypeVersioned:
   202  			if publishVersion := getPublishVersion(app); publishVersion != "" {
   203  				if getPublishVersion(rt) == publishVersion {
   204  					currentRT = rt
   205  				} else {
   206  					historyRTs = append(historyRTs, rt)
   207  				}
   208  			} else {
   209  				if rt.Spec.ApplicationGeneration == app.GetGeneration() {
   210  					currentRT = rt
   211  				} else {
   212  					historyRTs = append(historyRTs, rt)
   213  				}
   214  			}
   215  		case v1beta1.ResourceTrackerTypeComponentRevision:
   216  			crRT = rt
   217  		}
   218  	}
   219  	historyRTs = SortResourceTrackersByVersion(historyRTs, false)
   220  	if currentRT != nil && len(historyRTs) > 0 && currentRT.Spec.ApplicationGeneration < historyRTs[len(historyRTs)-1].Spec.ApplicationGeneration {
   221  		return nil, nil, nil, nil, fmt.Errorf("current publish version %s(gen-%d) is in-use and outdated, found newer gen-%d", getPublishVersion(app), currentRT.Spec.ApplicationGeneration, historyRTs[len(historyRTs)-1].Spec.ApplicationGeneration)
   222  	}
   223  	return rootRT, currentRT, historyRTs, crRT, nil
   224  }
   225  
   226  // RecordManifestsInResourceTracker records resources in ResourceTracker
   227  func RecordManifestsInResourceTracker(
   228  	ctx context.Context,
   229  	cli client.Client,
   230  	rt *v1beta1.ResourceTracker,
   231  	manifests []*unstructured.Unstructured,
   232  	metaOnly bool,
   233  	skipGC bool,
   234  	creator string) error {
   235  	if len(manifests) != 0 {
   236  		updated := false
   237  		for _, manifest := range manifests {
   238  			updated = rt.AddManagedResource(manifest, metaOnly, skipGC, creator) || updated
   239  		}
   240  		if updated {
   241  			return cli.Update(ctx, rt)
   242  		}
   243  	}
   244  	return nil
   245  }
   246  
   247  // DeletedManifestInResourceTracker marks resources as deleted in resourcetracker, if remove is true, resources will be removed from resourcetracker
   248  func DeletedManifestInResourceTracker(ctx context.Context, cli client.Client, rt *v1beta1.ResourceTracker, manifest *unstructured.Unstructured, remove bool) error {
   249  	if updated := rt.DeleteManagedResource(manifest, remove); !updated {
   250  		return nil
   251  	}
   252  	return cli.Update(ctx, rt)
   253  }