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 }