1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package v1alpha1
17
18 import (
19 "fmt"
20 "reflect"
21 "sort"
22
23 "github.com/pkg/errors"
24 "k8s.io/apimachinery/pkg/runtime"
25 "k8s.io/apimachinery/pkg/util/validation"
26 "k8s.io/apimachinery/pkg/util/validation/field"
27 logf "sigs.k8s.io/controller-runtime/pkg/log"
28 "sigs.k8s.io/controller-runtime/pkg/webhook"
29 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
30
31 gw "github.com/chaos-mesh/chaos-mesh/api/genericwebhook"
32 )
33
34
35 var workflowlog = logf.Log.WithName("workflow-resource")
36
37 var _ webhook.Validator = &Workflow{}
38
39 func (in *Workflow) ValidateCreate() (admission.Warnings, error) {
40 var allErrs field.ErrorList
41 specPath := field.NewPath("spec")
42 allErrs = append(allErrs, entryMustExists(specPath.Child("entry"), in.Spec.Entry, in.Spec.Templates)...)
43 allErrs = append(allErrs, validateTemplates(specPath.Child("templates"), in.Spec.Templates)...)
44 if len(allErrs) > 0 {
45 return nil, errors.New(allErrs.ToAggregate().Error())
46 }
47 return nil, nil
48 }
49
50 func (in *Workflow) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
51 return in.ValidateCreate()
52 }
53
54 func (in *Workflow) ValidateDelete() (admission.Warnings, error) {
55 return nil, nil
56 }
57
58 func entryMustExists(path *field.Path, entry string, templates []Template) field.ErrorList {
59 var result field.ErrorList
60
61 if len(entry) == 0 {
62 result = append(result, field.Required(path, "the entry of workflow is required"))
63 }
64 founded := false
65 for _, item := range templates {
66 if item.Name == entry {
67 founded = true
68 break
69 }
70 }
71 if !founded {
72 result = append(result, field.Invalid(path, entry, fmt.Sprintf("can not find a template with name %s", entry)))
73 }
74 return result
75 }
76
77 func validateTemplates(path *field.Path, templates []Template) field.ErrorList {
78 var result field.ErrorList
79 if len(templates) == 0 {
80 result = append(result, field.Invalid(path, templates, "templates in workflow could not be empty"))
81 return result
82 }
83 var allNames []string
84 for _, template := range templates {
85 allNames = append(allNames, template.Name)
86 }
87 result = append(result, namesCouldNotBeDuplicated(path, allNames)...)
88
89 for i, item := range templates {
90 itemPath := path.Index(i)
91 result = append(result, validateTemplate(itemPath, item, templates)...)
92 }
93 return result
94 }
95
96 func validateTemplate(path *field.Path, template Template, allTemplates []Template) field.ErrorList {
97 var result field.ErrorList
98
99 if len(template.Name) == 0 {
100 result = append(result, field.Required(path.Child("name"), "name of template is required"))
101 }
102
103
104 errs := validation.IsDNS1123Subdomain(template.Name)
105 if len(errs) > 0 {
106 result = append(result, field.Invalid(path.Child("name"), template.Name, fmt.Sprintf("field name must be DNS-1123 subdomain, %s", errs)))
107 }
108
109
110
111 switch templateType := template.Type; {
112 case templateType == TypeSuspend:
113 if template.Deadline == nil || len(*template.Deadline) == 0 {
114 result = append(result, field.Invalid(path.Child("deadline"), template.Deadline, "deadline in template with type Suspend could not be empty"))
115 }
116 result = append(result, shouldBeNoTask(path, template)...)
117 result = append(result, shouldBeNoChildren(path, template)...)
118 result = append(result, shouldBeNoConditionalBranches(path, template)...)
119 result = append(result, shouldBeNoEmbedChaos(path, template)...)
120 result = append(result, shouldBeNoSchedule(path, template)...)
121 case templateType == TypeSerial, templateType == TypeParallel:
122 for i, item := range template.Children {
123 result = append(result, templateMustExists(item, path.Child("children").Index(i), allTemplates)...)
124 }
125 result = append(result, shouldBeNoTask(path, template)...)
126 result = append(result, shouldBeNoConditionalBranches(path, template)...)
127 result = append(result, shouldBeNoEmbedChaos(path, template)...)
128 result = append(result, shouldBeNoSchedule(path, template)...)
129 case templateType == TypeSchedule:
130 result = append(result, shouldBeNoTask(path, template)...)
131 result = append(result, shouldBeNoChildren(path, template)...)
132 result = append(result, shouldBeNoConditionalBranches(path, template)...)
133 result = append(result, shouldBeNoEmbedChaos(path, template)...)
134 case templateType == TypeTask:
135 result = append(result, shouldBeNoChildren(path, template)...)
136 result = append(result, shouldBeNoEmbedChaos(path, template)...)
137 result = append(result, shouldBeNoSchedule(path, template)...)
138 case IsChaosTemplateType(templateType):
139 result = append(result, shouldNotSetupDurationInTheChaos(path, template)...)
140
141 result = append(result, shouldBeNoTask(path, template)...)
142 result = append(result, shouldBeNoChildren(path, template)...)
143 result = append(result, shouldBeNoConditionalBranches(path, template)...)
144 result = append(result, shouldBeNoSchedule(path, template)...)
145
146 result = append(result, template.EmbedChaos.Validate(path, string(templateType))...)
147 case templateType == TypeStatusCheck:
148 result = append(result, shouldBeNoTask(path, template)...)
149 result = append(result, shouldBeNoChildren(path, template)...)
150 result = append(result, shouldBeNoConditionalBranches(path, template)...)
151 result = append(result, shouldBeNoEmbedChaos(path, template)...)
152 result = append(result, shouldBeNoSchedule(path, template)...)
153 default:
154 result = append(result, field.Invalid(path.Child("templateType"), template.Type, fmt.Sprintf("unrecognized template type: %s", template.Type)))
155 }
156
157 return result
158 }
159
160 func namesCouldNotBeDuplicated(templatesPath *field.Path, names []string) field.ErrorList {
161 nameCounter := make(map[string]int)
162 for _, name := range names {
163 if count, ok := nameCounter[name]; ok {
164 nameCounter[name] = count + 1
165 } else {
166 nameCounter[name] = 1
167 }
168 }
169 var duplicatedNames []string
170 for name, count := range nameCounter {
171 if count > 1 {
172 duplicatedNames = append(duplicatedNames, name)
173 }
174 }
175 sort.Strings(duplicatedNames)
176 if len(duplicatedNames) > 0 {
177 return field.ErrorList{
178 field.Invalid(templatesPath, "", fmt.Sprintf("template name must be unique, duplicated names: %s", duplicatedNames)),
179 }
180 }
181 return nil
182 }
183
184 func templateMustExists(templateName string, path *field.Path, template []Template) field.ErrorList {
185 var result field.ErrorList
186
187 founded := false
188 for _, item := range template {
189 if item.Name == templateName {
190 founded = true
191 break
192 }
193 }
194
195 if !founded {
196 err := field.Invalid(path, templateName, fmt.Sprintf("can not find a template with name %s", templateName))
197 result = append(result, err)
198 }
199 return result
200 }
201
202 func shouldNotSetupDurationInTheChaos(path *field.Path, template Template) field.ErrorList {
203 var result field.ErrorList
204
205 if template.EmbedChaos == nil {
206 result = append(result, field.Invalid(path.Child(string(template.Type)), nil, fmt.Sprintf("the value of chaos %s is required", template.Type)))
207 return result
208 }
209
210 spec := reflect.ValueOf(template.EmbedChaos).Elem().FieldByName(string(template.Type))
211 if !spec.IsValid() || spec.IsNil() {
212 result = append(result, field.Invalid(path.Child(string(template.Type)),
213 nil,
214 fmt.Sprintf("parse workflow field error: missing chaos spec %s", template.Type)))
215 return result
216 }
217 if commonSpec, ok := spec.Interface().(ContainsDuration); !ok {
218 result = append(result, field.Invalid(path, "", fmt.Sprintf("Chaos: %s does not implement CommonSpec", template.Type)))
219 } else {
220 duration, err := commonSpec.GetDuration()
221 if err != nil {
222 result = append(result, field.Invalid(path, "", err.Error()))
223 return result
224 }
225 if duration != nil {
226 result = append(result, field.Invalid(path, duration, "should not define duration in chaos when using Workflow, use Template#Deadline instead."))
227 }
228 }
229 return result
230 }
231
232 func shouldBeNoTask(path *field.Path, template Template) field.ErrorList {
233 if template.Task != nil {
234 return field.ErrorList{
235 field.Invalid(path, template.Task, "this template should not contain Task"),
236 }
237 }
238 return nil
239 }
240
241 func shouldBeNoChildren(path *field.Path, template Template) field.ErrorList {
242 if len(template.Children) > 0 {
243 return field.ErrorList{
244 field.Invalid(path, template.Children, "this template should not contain Children"),
245 }
246 }
247 return nil
248 }
249
250 func shouldBeNoConditionalBranches(path *field.Path, template Template) field.ErrorList {
251 if len(template.ConditionalBranches) > 0 {
252 return field.ErrorList{
253 field.Invalid(path, template.ConditionalBranches, "this template should not contain ConditionalBranches"),
254 }
255 }
256 return nil
257 }
258
259 func shouldBeNoEmbedChaos(path *field.Path, template Template) field.ErrorList {
260
261 if template.EmbedChaos != nil {
262 return field.ErrorList{
263 field.Invalid(path, template.EmbedChaos, "this template should not contain any Chaos"),
264 }
265 }
266 return nil
267 }
268
269 func shouldBeNoSchedule(path *field.Path, template Template) field.ErrorList {
270 if template.Schedule != nil {
271 return field.ErrorList{
272 field.Invalid(path, template.Schedule, "this template should not contain Schedule"),
273 }
274 }
275 return nil
276 }
277
278 func (in *Workflow) Default() {
279 gw.Default(in)
280 }
281