1
2
3
4
5
6
7
8
9
10
11
12
13
14 package v1alpha1
15
16 import (
17 "fmt"
18 "reflect"
19 "strconv"
20
21 "k8s.io/apimachinery/pkg/runtime"
22 "k8s.io/apimachinery/pkg/util/validation/field"
23 logf "sigs.k8s.io/controller-runtime/pkg/log"
24 "sigs.k8s.io/controller-runtime/pkg/webhook"
25 )
26
27
28 var jvmchaoslog = logf.Log.WithName("jvmchaos-resource")
29
30
31
32 var _ webhook.Defaulter = &JVMChaos{}
33
34
35 func (in *JVMChaos) Default() {
36 jvmchaoslog.Info("default", "name", in.Name)
37
38 in.Spec.Selector.DefaultNamespace(in.GetNamespace())
39 in.Spec.Default()
40 }
41
42 func (in *JVMChaosSpec) Default() {
43
44 }
45
46
47
48 var _ webhook.Validator = &JVMChaos{}
49
50
51 func (in *JVMChaos) ValidateCreate() error {
52 jvmchaoslog.Info("validate create", "name", in.Name)
53
54 return in.Validate()
55 }
56
57
58 func (in *JVMChaos) ValidateUpdate(old runtime.Object) error {
59 jvmchaoslog.Info("validate update", "name", in.Name)
60 if !reflect.DeepEqual(in.Spec, old.(*JVMChaos).Spec) {
61 return ErrCanNotUpdateChaos
62 }
63 return in.Validate()
64 }
65
66
67 func (in *JVMChaos) ValidateDelete() error {
68 jvmchaoslog.Info("validate delete", "name", in.Name)
69
70
71 return nil
72 }
73
74
75 func (in *JVMChaos) Validate() error {
76 allErrs := in.Spec.Validate()
77 if len(allErrs) > 0 {
78 return fmt.Errorf(allErrs.ToAggregate().Error())
79 }
80
81 return nil
82 }
83
84 func (in *JVMChaosSpec) Validate() field.ErrorList {
85 specField := field.NewPath("spec")
86 allErrs := in.validateJvmChaos(specField)
87 allErrs = append(allErrs, validateDuration(in, specField)...)
88 return allErrs
89 }
90
91 func (in *JVMChaosSpec) validateJvmChaos(spec *field.Path) field.ErrorList {
92 allErrs := field.ErrorList{}
93 targetField := spec.Child("target")
94 actionField := spec.Child("action")
95 flagsField := spec.Child("flags")
96 matcherField := spec.Child("matcher")
97 if actions, ok := JvmSpec[in.Target]; ok {
98 if actionPR, actionOK := actions[in.Action]; actionOK {
99 if actionPR.Flags != nil {
100 allErrs = append(allErrs, in.validateParameterRules(in.Flags, actionPR.Flags, flagsField, targetField, actionField)...)
101 }
102
103 if actionPR.Matchers != nil {
104 allErrs = append(allErrs, in.validateParameterRules(in.Matchers, actionPR.Matchers, matcherField, targetField, actionField)...)
105 }
106
107 } else {
108 supportActions := make([]JVMChaosAction, 0)
109 for k := range actions {
110 supportActions = append(supportActions, k)
111 }
112
113 notSupportedError := field.NotSupported(actionField, in.Action, toString(supportActions))
114 errorMsg := fmt.Sprintf("target: %s does not match action: %s, action detail error: %s",
115 in.Target, in.Action, notSupportedError)
116 allErrs = append(allErrs, field.Invalid(targetField, in.Target, errorMsg))
117 }
118 } else {
119 allErrs = append(allErrs, field.Invalid(targetField, in.Target, "unknown JVM chaos target"))
120 }
121
122 return allErrs
123 }
124
125 func toString(actions []JVMChaosAction) []string {
126 ret := make([]string, 0)
127 for _, act := range actions {
128 ret = append(ret, string(act))
129 }
130 return ret
131 }
132
133 func (in *JVMChaosSpec) validateParameterRules(parameters map[string]string, rules []ParameterRules, parent *field.Path, target *field.Path, action *field.Path) field.ErrorList {
134 allErrs := field.ErrorList{}
135 for _, rule := range rules {
136 innerField := parent.Child(rule.Name)
137
138 var value = ""
139 var exist = false
140 if parameters != nil {
141 value, exist = parameters[rule.Name]
142 }
143 if rule.Required && !exist {
144 errorMsg := fmt.Sprintf("with %s: %s, %s: %s", target, in.Target, action, in.Action)
145 allErrs = append(allErrs, field.Required(innerField, errorMsg))
146 }
147
148 if exist && rule.Required && rule.ParameterType == StringType {
149 if len(value) == 0 {
150 errorMsg := fmt.Sprintf("%s:%s cannot be empty", innerField, value)
151 allErrs = append(allErrs, field.Invalid(innerField, value, errorMsg))
152 }
153 }
154
155 if exist && rule.ParameterType == IntType {
156 _, err := strconv.Atoi(value)
157 if err != nil {
158 errorMsg := fmt.Sprintf("%s:%s cannot parse as Int", innerField, value)
159 allErrs = append(allErrs, field.Invalid(innerField, value, errorMsg))
160 }
161 }
162
163 if exist && rule.ParameterType == BoolType {
164 _, err := strconv.ParseBool(value)
165 if err != nil {
166 errorMsg := fmt.Sprintf("%s:%s cannot parse as boolean", innerField, value)
167 allErrs = append(allErrs, field.Invalid(innerField, value, errorMsg))
168 }
169 }
170 }
171 return allErrs
172 }
173
174
175
176
177
178
179 var JvmSpec = map[JVMChaosTarget]map[JVMChaosAction]ActionParameterRules{
180 SERVLET: {
181 JVMDelayAction: ActionParameterRules{
182 Flags: []ParameterRules{
183 {Name: "time", ParameterType: IntType, Required: true},
184 {Name: "offset", ParameterType: IntType},
185 },
186 Matchers: []ParameterRules{
187 {Name: "effect-count", ParameterType: IntType},
188 {Name: "effect-percent", ParameterType: IntType},
189 {Name: "method"},
190 {Name: "querystring"},
191 {Name: "requestpath"},
192 },
193 },
194 JVMExceptionAction: ActionParameterRules{
195 Flags: []ParameterRules{
196 {Name: "exception", Required: true},
197 {Name: "exception-message"},
198 },
199 Matchers: []ParameterRules{
200 {Name: "effect-count", ParameterType: IntType},
201 {Name: "effect-percent", ParameterType: IntType},
202 {Name: "method"},
203 {Name: "querystring"},
204 {Name: "requestpath"},
205 },
206 },
207 },
208 PSQL: {
209 JVMDelayAction: ActionParameterRules{
210 Flags: []ParameterRules{
211 {Name: "time", ParameterType: IntType, Required: true},
212 {Name: "offset", ParameterType: IntType},
213 },
214 Matchers: []ParameterRules{
215 {Name: "effect-count", ParameterType: IntType},
216 {Name: "effect-percent", ParameterType: IntType},
217 {Name: "sqltype"},
218 {Name: "database"},
219 {Name: "port", ParameterType: IntType},
220 {Name: "host"},
221 {Name: "table"},
222 },
223 },
224 JVMExceptionAction: ActionParameterRules{
225 Flags: []ParameterRules{
226 {Name: "exception", Required: true},
227 {Name: "exception-message"},
228 },
229 Matchers: []ParameterRules{
230 {Name: "effect-count", ParameterType: IntType},
231 {Name: "effect-percent", ParameterType: IntType},
232 {Name: "sqltype"},
233 {Name: "database"},
234 {Name: "port", ParameterType: IntType},
235 {Name: "host"},
236 {Name: "table"},
237 },
238 },
239 },
240 MYSQL: {
241 JVMDelayAction: ActionParameterRules{
242 Flags: []ParameterRules{
243 {Name: "time", ParameterType: IntType, Required: true},
244 {Name: "offset", ParameterType: IntType},
245 },
246 Matchers: []ParameterRules{
247 {Name: "effect-count", ParameterType: IntType},
248 {Name: "effect-percent", ParameterType: IntType},
249 {Name: "sqltype"},
250 {Name: "database"},
251 {Name: "port", ParameterType: IntType},
252 {Name: "host"},
253 {Name: "table"},
254 },
255 },
256 JVMExceptionAction: ActionParameterRules{
257 Flags: []ParameterRules{
258 {Name: "exception", Required: true},
259 {Name: "exception-message"},
260 },
261 Matchers: []ParameterRules{
262 {Name: "effect-count", ParameterType: IntType},
263 {Name: "effect-percent", ParameterType: IntType},
264 {Name: "sqltype"},
265 {Name: "database"},
266 {Name: "port", ParameterType: IntType},
267 {Name: "host"},
268 {Name: "table"},
269 },
270 },
271 },
272 JEDIS: {
273 JVMDelayAction: ActionParameterRules{
274 Flags: []ParameterRules{
275 {Name: "time", ParameterType: IntType, Required: true},
276 {Name: "offset", ParameterType: IntType},
277 },
278 Matchers: []ParameterRules{
279 {Name: "effect-count", ParameterType: IntType},
280 {Name: "effect-percent", ParameterType: IntType},
281 {Name: "cmd"},
282 {Name: "key"},
283 },
284 },
285 JVMExceptionAction: ActionParameterRules{
286 Flags: []ParameterRules{
287 {Name: "exception", Required: true},
288 {Name: "exception-message"},
289 },
290 Matchers: []ParameterRules{
291 {Name: "effect-count", ParameterType: IntType},
292 {Name: "effect-percent", ParameterType: IntType},
293 {Name: "cmd"},
294 {Name: "key"},
295 },
296 },
297 },
298 HTTP: {
299 JVMDelayAction: ActionParameterRules{
300 Flags: []ParameterRules{
301 {Name: "time", ParameterType: IntType, Required: true},
302 {Name: "offset", ParameterType: IntType},
303 },
304 Matchers: []ParameterRules{
305 {Name: "effect-count", ParameterType: IntType},
306 {Name: "effect-percent", ParameterType: IntType},
307 {Name: "httpclient4", ParameterType: BoolType},
308 {Name: "rest", ParameterType: BoolType},
309 {Name: "httpclient3", ParameterType: BoolType},
310 {Name: "okhttp3", ParameterType: BoolType},
311 {Name: "uri", Required: true},
312 },
313 },
314 JVMExceptionAction: ActionParameterRules{
315 Flags: []ParameterRules{
316 {Name: "exception", Required: true},
317 {Name: "exception-message"},
318 },
319 Matchers: []ParameterRules{
320 {Name: "effect-count", ParameterType: IntType},
321 {Name: "effect-percent", ParameterType: IntType},
322 {Name: "httpclient4", ParameterType: BoolType},
323 {Name: "rest", ParameterType: BoolType},
324 {Name: "httpclient3", ParameterType: BoolType},
325 {Name: "okhttp3", ParameterType: BoolType},
326 {Name: "uri", Required: true},
327 },
328 },
329 },
330 RABBITMQ: {
331 JVMDelayAction: ActionParameterRules{
332 Flags: []ParameterRules{
333 {Name: "time", ParameterType: IntType, Required: true},
334 {Name: "offset", ParameterType: IntType},
335 },
336 Matchers: []ParameterRules{
337 {Name: "effect-count", ParameterType: IntType},
338 {Name: "effect-percent", ParameterType: IntType},
339 {Name: "routingkey"},
340 {Name: "producer", ParameterType: BoolType},
341 {Name: "topic"},
342 {Name: "exchange"},
343 {Name: "consumer", ParameterType: BoolType},
344 },
345 },
346 JVMExceptionAction: ActionParameterRules{
347 Flags: []ParameterRules{
348 {Name: "exception", Required: true},
349 {Name: "exception-message"},
350 },
351 Matchers: []ParameterRules{
352 {Name: "effect-count", ParameterType: IntType},
353 {Name: "effect-percent", ParameterType: IntType},
354 {Name: "routingkey"},
355 {Name: "producer", ParameterType: BoolType},
356 {Name: "topic"},
357 {Name: "exchange"},
358 {Name: "consumer", ParameterType: BoolType},
359 },
360 },
361 },
362 TARS: {
363 JVMDelayAction: ActionParameterRules{
364 Flags: []ParameterRules{
365 {Name: "time", ParameterType: IntType, Required: true},
366 {Name: "offset", ParameterType: IntType},
367 },
368 Matchers: []ParameterRules{
369 {Name: "effect-count", ParameterType: IntType},
370 {Name: "effect-percent", ParameterType: IntType},
371 {Name: "servant", ParameterType: BoolType},
372 {Name: "functionname"},
373 {Name: "client", ParameterType: BoolType},
374 {Name: "servantname", Required: true},
375 },
376 },
377 JVMExceptionAction: ActionParameterRules{
378 Flags: []ParameterRules{
379 {Name: "exception", Required: true},
380 {Name: "exception-message"},
381 },
382 Matchers: []ParameterRules{
383 {Name: "effect-count", ParameterType: IntType},
384 {Name: "effect-percent", ParameterType: IntType},
385 {Name: "servant", ParameterType: BoolType},
386 {Name: "functionname"},
387 {Name: "client", ParameterType: BoolType},
388 {Name: "servantname", Required: true},
389 },
390 },
391 },
392 DUBBO: {
393 JVMDelayAction: ActionParameterRules{
394 Flags: []ParameterRules{
395 {Name: "time", ParameterType: IntType, Required: true},
396 {Name: "offset", ParameterType: IntType},
397 },
398 Matchers: []ParameterRules{
399 {Name: "effect-count", ParameterType: IntType},
400 {Name: "effect-percent", ParameterType: IntType},
401 {Name: "appname"},
402 {Name: "provider", ParameterType: BoolType},
403 {Name: "service"},
404 {Name: "version"},
405 {Name: "consumer", ParameterType: BoolType},
406 {Name: "methodname"},
407 {Name: "group"},
408 },
409 },
410 JVMExceptionAction: ActionParameterRules{
411 Flags: []ParameterRules{
412 {Name: "exception", Required: true},
413 {Name: "exception-message"},
414 },
415 Matchers: []ParameterRules{
416 {Name: "effect-count", ParameterType: IntType},
417 {Name: "effect-percent", ParameterType: IntType},
418 {Name: "appname"},
419 {Name: "provider", ParameterType: BoolType},
420 {Name: "service"},
421 {Name: "version"},
422 {Name: "consumer", ParameterType: BoolType},
423 {Name: "methodname"},
424 {Name: "group"},
425 },
426 },
427 JVMThreadPoolFullAction: ActionParameterRules{
428 Matchers: []ParameterRules{
429 {Name: "effect-count", ParameterType: IntType},
430 {Name: "effect-percent", ParameterType: IntType},
431 {Name: "provider", ParameterType: BoolType},
432 },
433 },
434 },
435 JVM: {
436 JVMDelayAction: ActionParameterRules{
437 Flags: []ParameterRules{
438 {Name: "time", ParameterType: IntType, Required: true},
439 {Name: "offset", ParameterType: IntType},
440 },
441 Matchers: []ParameterRules{
442 {Name: "effect-count", ParameterType: IntType},
443 {Name: "effect-percent", ParameterType: IntType},
444 {Name: "classname", Required: true},
445 {Name: "after", ParameterType: BoolType},
446 {Name: "methodname", Required: true},
447 },
448 },
449 JVMExceptionAction: ActionParameterRules{
450 Flags: []ParameterRules{
451 {Name: "exception", Required: true},
452 {Name: "exception-message"},
453 },
454 Matchers: []ParameterRules{
455 {Name: "effect-count", ParameterType: IntType},
456 {Name: "effect-percent", ParameterType: IntType},
457 {Name: "classname", Required: true},
458 {Name: "after", ParameterType: BoolType},
459 {Name: "methodname", Required: true},
460 },
461 },
462 JVMCodeCacheFillingAction: ActionParameterRules{},
463 JVMCpuFullloadAction: ActionParameterRules{
464 Flags: []ParameterRules{
465 {Name: "cpu-count", ParameterType: IntType},
466 },
467 },
468 JVMThrowDeclaredExceptionAction: ActionParameterRules{
469 Matchers: []ParameterRules{
470 {Name: "effect-count", ParameterType: IntType},
471 {Name: "effect-percent", ParameterType: IntType},
472 {Name: "classname", Required: true},
473 {Name: "after", ParameterType: BoolType},
474 {Name: "methodname", Required: true},
475 },
476 },
477 JVMReturnAction: ActionParameterRules{
478 Flags: []ParameterRules{
479 {Name: "value", Required: true},
480 },
481 Matchers: []ParameterRules{
482 {Name: "effect-count", ParameterType: IntType},
483 {Name: "effect-percent", ParameterType: IntType},
484 {Name: "classname", Required: true},
485 {Name: "after", ParameterType: BoolType},
486 {Name: "methodname", Required: true},
487 },
488 },
489 JVMScriptAction: ActionParameterRules{
490 Flags: []ParameterRules{
491 {Name: "script-file"},
492 {Name: "script-type"},
493 {Name: "script-content"},
494 {Name: "script-name"},
495 {Name: "external-jar"},
496 {Name: "external-jar-path"},
497 },
498 Matchers: []ParameterRules{
499 {Name: "effect-count", ParameterType: IntType},
500 {Name: "effect-percent", ParameterType: IntType},
501 {Name: "classname", Required: true},
502 {Name: "after", ParameterType: BoolType},
503 {Name: "methodname", Required: true},
504 },
505 },
506 JVMOOMAction: ActionParameterRules{
507 Flags: []ParameterRules{
508 {Name: "area", Required: true},
509 {Name: "wild-mode", ParameterType: BoolType},
510 {Name: "interval", ParameterType: IntType},
511 {Name: "block", ParameterType: IntType},
512 },
513 },
514 },
515 DRUID: {
516 JVMConnectionPoolFullAction: ActionParameterRules{
517 Matchers: []ParameterRules{
518 {Name: "effect-count", ParameterType: IntType},
519 {Name: "effect-percent", ParameterType: IntType},
520 },
521 },
522 },
523 REDISSON: {
524 JVMDelayAction: ActionParameterRules{
525 Flags: []ParameterRules{
526 {Name: "time", ParameterType: IntType, Required: true},
527 {Name: "offset", ParameterType: IntType},
528 },
529 Matchers: []ParameterRules{
530 {Name: "effect-count", ParameterType: IntType},
531 {Name: "effect-percent", ParameterType: IntType},
532 {Name: "cmd"},
533 {Name: "key"},
534 },
535 },
536 JVMExceptionAction: ActionParameterRules{
537 Flags: []ParameterRules{
538 {Name: "exception", Required: true},
539 {Name: "exception-message"},
540 },
541 Matchers: []ParameterRules{
542 {Name: "effect-count", ParameterType: IntType},
543 {Name: "effect-percent", ParameterType: IntType},
544 {Name: "cmd"},
545 {Name: "key"},
546 },
547 },
548 },
549 ROCKETMQ: {
550 JVMDelayAction: ActionParameterRules{
551 Flags: []ParameterRules{
552 {Name: "time", ParameterType: IntType, Required: true},
553 {Name: "offset", ParameterType: IntType},
554 },
555 Matchers: []ParameterRules{
556 {Name: "effect-count", ParameterType: IntType},
557 {Name: "effect-percent", ParameterType: IntType},
558 {Name: "producerGroup"},
559 {Name: "producer"},
560 {Name: "topic", Required: true},
561 {Name: "consumerGroup"},
562 },
563 },
564 JVMExceptionAction: ActionParameterRules{
565 Flags: []ParameterRules{
566 {Name: "exception", Required: true},
567 {Name: "exception-message"},
568 },
569 Matchers: []ParameterRules{
570 {Name: "effect-count", ParameterType: IntType},
571 {Name: "effect-percent", ParameterType: IntType},
572 {Name: "producerGroup"},
573 {Name: "producer"},
574 {Name: "topic", Required: true},
575 {Name: "consumerGroup"},
576 },
577 },
578 },
579 MONGODB: {
580 JVMDelayAction: ActionParameterRules{
581 Flags: []ParameterRules{
582 {Name: "time", ParameterType: IntType, Required: true},
583 {Name: "offset", ParameterType: IntType},
584 },
585 Matchers: []ParameterRules{
586 {Name: "effect-count", ParameterType: IntType},
587 {Name: "effect-percent", ParameterType: IntType},
588 {Name: "sqltype"},
589 {Name: "database"},
590 {Name: "false"},
591 },
592 },
593 JVMExceptionAction: ActionParameterRules{
594 Flags: []ParameterRules{
595 {Name: "exception", Required: true},
596 {Name: "exception-message"},
597 },
598 Matchers: []ParameterRules{
599 {Name: "effect-count", ParameterType: IntType},
600 {Name: "effect-percent", ParameterType: IntType},
601 {Name: "sqltype"},
602 {Name: "database"},
603 {Name: "false"},
604 },
605 },
606 },
607 }
608
609
610 type ActionParameterRules struct {
611
612 Flags []ParameterRules
613
614
615 Matchers []ParameterRules
616 }
617
618
619 type ParameterType string
620
621 const (
622
623 IntType ParameterType = "int"
624
625
626 BoolType ParameterType = "bool"
627
628
629 StringType ParameterType = "string"
630 )
631
632
633 type ParameterRules struct {
634
635 Name string
636
637
638 ParameterType ParameterType
639
640
641 Required bool
642 }
643