github.com/oam-dev/kubevela@v1.9.11/pkg/resourcekeeper/dispatch.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 resourcekeeper 18 19 import ( 20 "context" 21 "fmt" 22 23 velaslices "github.com/kubevela/pkg/util/slices" 24 "github.com/pkg/errors" 25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 utilfeature "k8s.io/apiserver/pkg/util/feature" 27 28 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 29 "github.com/oam-dev/kubevela/pkg/auth" 30 "github.com/oam-dev/kubevela/pkg/features" 31 "github.com/oam-dev/kubevela/pkg/multicluster" 32 "github.com/oam-dev/kubevela/pkg/oam" 33 "github.com/oam-dev/kubevela/pkg/resourcetracker" 34 "github.com/oam-dev/kubevela/pkg/utils/apply" 35 velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors" 36 ) 37 38 // MaxDispatchConcurrent is the max dispatch concurrent number 39 var MaxDispatchConcurrent = 10 40 41 // DispatchOption option for dispatch 42 type DispatchOption interface { 43 ApplyToDispatchConfig(*dispatchConfig) 44 } 45 46 type dispatchConfig struct { 47 rtConfig 48 metaOnly bool 49 creator string 50 } 51 52 func newDispatchConfig(options ...DispatchOption) *dispatchConfig { 53 cfg := &dispatchConfig{} 54 for _, option := range options { 55 option.ApplyToDispatchConfig(cfg) 56 } 57 return cfg 58 } 59 60 // Dispatch dispatch resources 61 func (h *resourceKeeper) Dispatch(ctx context.Context, manifests []*unstructured.Unstructured, applyOpts []apply.ApplyOption, options ...DispatchOption) (err error) { 62 if utilfeature.DefaultMutableFeatureGate.Enabled(features.ApplyOnce) || 63 (h.applyOncePolicy != nil && h.applyOncePolicy.Enable && h.applyOncePolicy.Rules == nil) { 64 options = append(options, MetaOnlyOption{}) 65 } 66 h.ClearNamespaceForClusterScopedResources(manifests) 67 // 0. check admission 68 if err = h.AdmissionCheck(ctx, manifests); err != nil { 69 return err 70 } 71 // 1. pre-dispatch check 72 opts := []apply.ApplyOption{apply.MustBeControlledByApp(h.app), apply.NotUpdateRenderHashEqual()} 73 if len(applyOpts) > 0 { 74 opts = append(opts, applyOpts...) 75 } 76 if utilfeature.DefaultMutableFeatureGate.Enabled(features.PreDispatchDryRun) { 77 if err = h.dispatch(ctx, 78 velaslices.Map(manifests, func(manifest *unstructured.Unstructured) *unstructured.Unstructured { return manifest.DeepCopy() }), 79 append([]apply.ApplyOption{apply.DryRunAll()}, opts...)); err != nil { 80 return fmt.Errorf("pre-dispatch dryrun failed: %w", err) 81 } 82 } 83 // 2. record manifests in resourcetracker 84 if err = h.record(ctx, manifests, options...); err != nil { 85 return err 86 } 87 // 3. apply manifests 88 if err = h.dispatch(ctx, manifests, opts); err != nil { 89 return err 90 } 91 return nil 92 } 93 94 func (h *resourceKeeper) record(ctx context.Context, manifests []*unstructured.Unstructured, options ...DispatchOption) error { 95 h.mu.Lock() 96 defer h.mu.Unlock() 97 var skipGCManifests []*unstructured.Unstructured 98 var rootManifests []*unstructured.Unstructured 99 var versionManifests []*unstructured.Unstructured 100 101 for _, manifest := range manifests { 102 if manifest != nil { 103 _options := options 104 if h.garbageCollectPolicy != nil { 105 if strategy := h.garbageCollectPolicy.FindStrategy(manifest); strategy != nil { 106 _options = append(_options, GarbageCollectStrategyOption(*strategy)) 107 } 108 } 109 cfg := newDispatchConfig(_options...) 110 switch { 111 case cfg.skipGC: 112 skipGCManifests = append(skipGCManifests, manifest) 113 case cfg.useRoot: 114 rootManifests = append(rootManifests, manifest) 115 default: 116 versionManifests = append(versionManifests, manifest) 117 } 118 } 119 } 120 121 cfg := newDispatchConfig(options...) 122 ctx = auth.ContextClearUserInfo(ctx) 123 if len(rootManifests)+len(skipGCManifests) != 0 { 124 rt, err := h.getRootRT(ctx) 125 if err != nil { 126 return errors.Wrapf(err, "failed to get resourcetracker") 127 } 128 if err = resourcetracker.RecordManifestsInResourceTracker(multicluster.ContextInLocalCluster(ctx), h.Client, rt, rootManifests, cfg.metaOnly, false, cfg.creator); err != nil { 129 return errors.Wrapf(err, "failed to record resources in resourcetracker %s", rt.Name) 130 } 131 if err = resourcetracker.RecordManifestsInResourceTracker(multicluster.ContextInLocalCluster(ctx), h.Client, rt, skipGCManifests, cfg.metaOnly, true, cfg.creator); err != nil { 132 return errors.Wrapf(err, "failed to record resources (skip-gc) in resourcetracker %s", rt.Name) 133 } 134 } 135 136 rt, err := h.getCurrentRT(ctx) 137 if err != nil { 138 return errors.Wrapf(err, "failed to get resourcetracker") 139 } 140 if err = resourcetracker.RecordManifestsInResourceTracker(multicluster.ContextInLocalCluster(ctx), h.Client, rt, versionManifests, cfg.metaOnly, false, cfg.creator); err != nil { 141 return errors.Wrapf(err, "failed to record resources in resourcetracker %s", rt.Name) 142 } 143 return nil 144 } 145 146 func (h *resourceKeeper) dispatch(ctx context.Context, manifests []*unstructured.Unstructured, applyOpts []apply.ApplyOption) error { 147 errs := velaslices.ParMap(manifests, func(manifest *unstructured.Unstructured) error { 148 applyCtx := multicluster.ContextWithClusterName(ctx, oam.GetCluster(manifest)) 149 applyCtx = auth.ContextWithUserInfo(applyCtx, h.app) 150 ao := applyOpts 151 if h.isShared(manifest) { 152 ao = append([]apply.ApplyOption{apply.SharedByApp(h.app)}, ao...) 153 } 154 if h.isReadOnly(manifest) { 155 ao = append([]apply.ApplyOption{apply.ReadOnly()}, ao...) 156 } 157 if h.canTakeOver(manifest) { 158 ao = append([]apply.ApplyOption{apply.TakeOver()}, ao...) 159 } 160 if strategy := h.getUpdateStrategy(manifest); strategy != nil { 161 ao = append([]apply.ApplyOption{apply.WithUpdateStrategy(*strategy)}, ao...) 162 } 163 manifest, err := ApplyStrategies(applyCtx, h, manifest, v1alpha1.ApplyOnceStrategyOnAppUpdate) 164 if err != nil { 165 return errors.Wrapf(err, "failed to apply once policy for application %s,%s", h.app.Name, err.Error()) 166 } 167 return h.applicator.Apply(applyCtx, manifest, ao...) 168 }, velaslices.Parallelism(MaxDispatchConcurrent)) 169 return velaerrors.AggregateErrors(errs) 170 }