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  }