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 }