github.com/oam-dev/kubevela@v1.9.11/pkg/controller/core.oam.dev/v1beta1/application/dispatcher.go (about)

     1  /*
     2  Copyright 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 application
    18  
    19  import (
    20  	"context"
    21  	"sort"
    22  	"strings"
    23  
    24  	"github.com/pkg/errors"
    25  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  
    28  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    29  	"github.com/oam-dev/kubevela/pkg/cue/definition"
    30  	"github.com/oam-dev/kubevela/pkg/oam"
    31  	oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
    32  
    33  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    34  	"github.com/oam-dev/kubevela/pkg/appfile"
    35  )
    36  
    37  // DispatchOptions is the options for dispatch
    38  type DispatchOptions struct {
    39  	Workload          *unstructured.Unstructured
    40  	Traits            []*unstructured.Unstructured
    41  	OverrideNamespace string
    42  	Stage             StageType
    43  }
    44  
    45  // SortDispatchOptions describe the sorting for options
    46  type SortDispatchOptions []DispatchOptions
    47  
    48  func (s SortDispatchOptions) Len() int {
    49  	return len(s)
    50  }
    51  
    52  func (s SortDispatchOptions) Less(i, j int) bool {
    53  	return s[i].Stage < s[j].Stage
    54  }
    55  
    56  func (s SortDispatchOptions) Swap(i, j int) {
    57  	s[i], s[j] = s[j], s[i]
    58  }
    59  
    60  var _ sort.Interface = new(SortDispatchOptions)
    61  
    62  // StageType is a valid value for TraitDefinitionSpec.Stage
    63  type StageType int
    64  
    65  const (
    66  	// PreDispatch means that pre dispatch for manifests
    67  	PreDispatch StageType = iota
    68  	// DefaultDispatch means that default dispatch for manifests
    69  	DefaultDispatch
    70  	// PostDispatch means that post dispatch for manifests
    71  	PostDispatch
    72  )
    73  
    74  var stages = map[StageType]string{
    75  	PreDispatch:     "PreDispatch",
    76  	DefaultDispatch: "DefaultDispatch",
    77  	PostDispatch:    "PostDispatch",
    78  }
    79  
    80  // ParseStageType parse the StageType from a string
    81  func ParseStageType(s string) (StageType, error) {
    82  	for k, v := range stages {
    83  		if v == s {
    84  			return k, nil
    85  		}
    86  	}
    87  	return -1, errors.New("unknown stage type")
    88  }
    89  
    90  // TraitFilter is used to filter trait object.
    91  type TraitFilter func(trait appfile.Trait) bool
    92  
    93  // ByTraitType returns a filter that does not match the given type and belongs to readyTraits.
    94  func ByTraitType(readyTraits, checkTraits []*unstructured.Unstructured) TraitFilter {
    95  	generateFn := func(manifests []*unstructured.Unstructured) map[string]bool {
    96  		out := map[string]bool{}
    97  		for _, obj := range manifests {
    98  			out[obj.GetLabels()[oam.TraitTypeLabel]] = true
    99  		}
   100  		return out
   101  	}
   102  	readyMap := generateFn(readyTraits)
   103  	checkMap := generateFn(checkTraits)
   104  	return func(trait appfile.Trait) bool {
   105  		return !checkMap[trait.Name] && readyMap[trait.Name]
   106  	}
   107  }
   108  
   109  // manifestDispatcher is a manifest dispatcher
   110  type manifestDispatcher struct {
   111  	run         func(ctx context.Context, c *appfile.Component, appRev *v1beta1.ApplicationRevision, clusterName string) (bool, error)
   112  	healthCheck func(ctx context.Context, c *appfile.Component, appRev *v1beta1.ApplicationRevision) (bool, error)
   113  }
   114  
   115  func (h *AppHandler) generateDispatcher(appRev *v1beta1.ApplicationRevision, readyWorkload *unstructured.Unstructured, readyTraits []*unstructured.Unstructured, overrideNamespace string) ([]*manifestDispatcher, error) {
   116  	dispatcherGenerator := func(options DispatchOptions) *manifestDispatcher {
   117  		assembleManifestFn := func(skipApplyWorkload bool) (bool, []*unstructured.Unstructured) {
   118  			manifests := options.Traits
   119  			skipWorkload := skipApplyWorkload || options.Workload == nil
   120  			if !skipWorkload {
   121  				manifests = append([]*unstructured.Unstructured{options.Workload}, options.Traits...)
   122  			}
   123  			return skipWorkload, manifests
   124  		}
   125  
   126  		dispatcher := new(manifestDispatcher)
   127  		dispatcher.healthCheck = func(ctx context.Context, comp *appfile.Component, appRev *v1beta1.ApplicationRevision) (bool, error) {
   128  			skipWorkload, manifests := assembleManifestFn(comp.SkipApplyWorkload)
   129  			if !h.resourceKeeper.ContainsResources(manifests) {
   130  				return false, nil
   131  			}
   132  			_, _, _, isHealth, err := h.collectHealthStatus(ctx, comp, appRev, options.OverrideNamespace, skipWorkload,
   133  				ByTraitType(readyTraits, options.Traits))
   134  			if err != nil {
   135  				return false, err
   136  			}
   137  			return isHealth, nil
   138  		}
   139  		dispatcher.run = func(ctx context.Context, comp *appfile.Component, appRev *v1beta1.ApplicationRevision, clusterName string) (bool, error) {
   140  			skipWorkload, dispatchManifests := assembleManifestFn(comp.SkipApplyWorkload)
   141  			if isHealth, err := dispatcher.healthCheck(ctx, comp, appRev); !isHealth || err != nil {
   142  				if err := h.Dispatch(ctx, clusterName, common.WorkflowResourceCreator, dispatchManifests...); err != nil {
   143  					return false, errors.WithMessage(err, "Dispatch")
   144  				}
   145  				status, _, _, isHealth, err := h.collectHealthStatus(ctx, comp, appRev, options.OverrideNamespace, skipWorkload,
   146  					ByTraitType(readyTraits, options.Traits))
   147  				if err != nil {
   148  					return false, errors.WithMessage(err, "CollectHealthStatus")
   149  				}
   150  				if options.Stage < DefaultDispatch {
   151  					status.Healthy = false
   152  					if status.Message == "" {
   153  						status.Message = "waiting for previous stage healthy"
   154  					}
   155  					h.addServiceStatus(true, *status)
   156  				}
   157  				if !isHealth {
   158  					return false, nil
   159  				}
   160  			}
   161  			return true, nil
   162  		}
   163  		return dispatcher
   164  	}
   165  
   166  	traitStageMap := make(map[StageType][]*unstructured.Unstructured)
   167  	for _, readyTrait := range readyTraits {
   168  		var (
   169  			traitType = readyTrait.GetLabels()[oam.TraitTypeLabel]
   170  			stageType = DefaultDispatch
   171  			err       error
   172  		)
   173  		switch {
   174  		case traitType == definition.AuxiliaryWorkload:
   175  		case traitType != "":
   176  			if strings.Contains(traitType, "-") {
   177  				splitName := traitType[0:strings.LastIndex(traitType, "-")]
   178  				if _, ok := appRev.Spec.TraitDefinitions[splitName]; ok {
   179  					traitType = splitName
   180  				}
   181  			}
   182  			stageType, err = getTraitDispatchStage(h.Client, traitType, appRev)
   183  			if err != nil {
   184  				return nil, err
   185  			}
   186  		}
   187  		traitStageMap[stageType] = append(traitStageMap[stageType], readyTrait)
   188  	}
   189  	var optionList SortDispatchOptions
   190  	if _, ok := traitStageMap[DefaultDispatch]; !ok {
   191  		traitStageMap[DefaultDispatch] = []*unstructured.Unstructured{}
   192  	}
   193  	for stage, traits := range traitStageMap {
   194  		option := DispatchOptions{
   195  			Stage:             stage,
   196  			Traits:            traits,
   197  			OverrideNamespace: overrideNamespace,
   198  		}
   199  		if stage == DefaultDispatch {
   200  			option.Workload = readyWorkload
   201  		}
   202  		optionList = append(optionList, option)
   203  	}
   204  	sort.Sort(optionList)
   205  
   206  	var manifestDispatchers []*manifestDispatcher
   207  	for _, option := range optionList {
   208  		manifestDispatchers = append(manifestDispatchers, dispatcherGenerator(option))
   209  	}
   210  	return manifestDispatchers, nil
   211  }
   212  
   213  func getTraitDispatchStage(client client.Client, traitType string, appRev *v1beta1.ApplicationRevision) (StageType, error) {
   214  	trait, ok := appRev.Spec.TraitDefinitions[traitType]
   215  	if !ok {
   216  		trait = &v1beta1.TraitDefinition{}
   217  		err := oamutil.GetCapabilityDefinition(context.Background(), client, trait, traitType)
   218  		if err != nil {
   219  			return DefaultDispatch, err
   220  		}
   221  	}
   222  	_stageType := trait.Spec.Stage
   223  	if len(_stageType) == 0 {
   224  		_stageType = v1beta1.DefaultDispatch
   225  	}
   226  	stageType, err := ParseStageType(string(_stageType))
   227  	if err != nil {
   228  		return DefaultDispatch, err
   229  	}
   230  	return stageType, nil
   231  }