1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package v1alpha1
17
18 import (
19 "encoding/json"
20 "fmt"
21 "reflect"
22 "strconv"
23 "strings"
24
25 "github.com/alecthomas/units"
26 "github.com/google/uuid"
27 "github.com/pkg/errors"
28 "k8s.io/apimachinery/pkg/util/validation/field"
29 )
30
31 func (in *PhysicalMachineChaosSpec) Default(root interface{}, field *reflect.StructField) {
32 if in == nil {
33 return
34 }
35
36 if len(in.UID) == 0 {
37 in.UID = uuid.New().String()
38 }
39
40 for i := range in.Address {
41
42 if !strings.HasPrefix(in.Address[i], "http") {
43 in.Address[i] = fmt.Sprintf("http://%s", in.Address[i])
44 }
45 }
46 }
47
48 func (in *PhysicalMachineChaosSpec) Validate(root interface{}, path *field.Path) field.ErrorList {
49 allErrs := field.ErrorList{}
50
51
52 var inInterface map[string]interface{}
53 inrec, err := json.Marshal(in)
54 if err != nil {
55 allErrs = append(allErrs,
56 field.Invalid(path.Child("spec"), in, err.Error()))
57 }
58
59 err = json.Unmarshal(inrec, &inInterface)
60 if err != nil {
61 allErrs = append(allErrs,
62 field.Invalid(path.Child("spec"), in, err.Error()))
63 }
64
65 skipConfigCheck := false
66 if _, ok := inInterface[string(in.Action)]; !ok {
67 skipConfigCheck = true
68 allErrs = append(allErrs,
69 field.Invalid(path.Child("spec"), in,
70 "the configuration corresponding to action is required"))
71 }
72
73 if len(in.Address) == 0 && in.Selector.Empty() {
74 allErrs = append(allErrs,
75 field.Invalid(path.Child("address"), in.Address, "one of address or selector should be specified"))
76 }
77 if len(in.Address) != 0 && !in.Selector.Empty() {
78 allErrs = append(allErrs,
79 field.Invalid(path.Child("address"), in.Address, "only one of address or selector could be specified"))
80 }
81
82 for _, address := range in.Address {
83 if len(address) == 0 {
84 allErrs = append(allErrs,
85 field.Invalid(path.Child("address"), in.Address, "the address is required"))
86 }
87 }
88
89 if skipConfigCheck {
90 return allErrs
91 }
92
93 var validateConfigErr error
94 switch in.Action {
95 case PMStressCPUAction:
96 validateConfigErr = validateStressCPUAction(in.StressCPU)
97 case PMStressMemAction:
98 validateConfigErr = validateStressMemAction(in.StressMemory)
99 case PMDiskWritePayloadAction:
100 validateConfigErr = validateDiskPayloadAction(in.DiskWritePayload)
101 case PMDiskReadPayloadAction:
102 validateConfigErr = validateDiskPayloadAction(in.DiskReadPayload)
103 case PMDiskFillAction:
104 validateConfigErr = validateDiskFillAction(in.DiskFill)
105 case PMNetworkCorruptAction:
106 validateConfigErr = validateNetworkCorruptAction(in.NetworkCorrupt)
107 case PMNetworkDuplicateAction:
108 validateConfigErr = validateNetworkDuplicateAction(in.NetworkDuplicate)
109 case PMNetworkLossAction:
110 validateConfigErr = validateNetworkLossAction(in.NetworkLoss)
111 case PMNetworkDelayAction:
112 validateConfigErr = validateNetworkDelayAction(in.NetworkDelay)
113 case PMNetworkPartitionAction:
114 validateConfigErr = validateNetworkPartitionAction(in.NetworkPartition)
115 case PMNetworkBandwidthAction:
116 validateConfigErr = validateNetworkBandwidthAction(in.NetworkBandwidth)
117 case PMNetworkDNSAction:
118 validateConfigErr = validateNetworkDNSAction(in.NetworkDNS)
119 case PMNetworkFloodAction:
120 validateConfigErr = validateNetworkFlood(in.NetworkFlood)
121 case PMNetworkDownAction:
122 validateConfigErr = validateNetworkDownAction(in.NetworkDown)
123 case PMProcessAction:
124 validateConfigErr = validateProcessAction(in.Process)
125 case PMJVMExceptionAction:
126 validateConfigErr = validateJVMExceptionAction(in.JVMException)
127 case PMJVMGCAction:
128 validateConfigErr = validateJVMGCAction(in.JVMGC)
129 case PMJVMLatencyAction:
130 validateConfigErr = validateJVMLatencyAction(in.JVMLatency)
131 case PMJVMReturnAction:
132 validateConfigErr = validateJVMReturnAction(in.JVMReturn)
133 case PMJVMStressAction:
134 validateConfigErr = validateJVMStressAction(in.JVMStress)
135 case PMJVMRuleDataAction:
136 validateConfigErr = validateJVMRuleDataAction(in.JVMRuleData)
137 case PMJVMMySQLAction:
138 validateConfigErr = validateJVMMySQLAction(in.JVMMySQL)
139 case PMClockAction:
140 validateConfigErr = validateClockAction(in.Clock)
141 case PMRedisExpirationAction:
142 validateConfigErr = validateRedisExpirationAction(in.RedisExpiration)
143 case PMRedisCacheLimitAction:
144 validateConfigErr = validateRedisCacheLimitAction(in.RedisCacheLimit)
145 case PMRedisPenetrationAction:
146 validateConfigErr = validateRedisPenetrationAction(in.RedisPenetration)
147 case PMRedisSentinelStopAction:
148 validateConfigErr = validateRedisSentinelStopAction(in.RedisSentinelStop)
149 case PMRedisSentinelRestartAction:
150 validateConfigErr = validateRedisSentinelRestartAction(in.RedisSentinelRestart)
151 case PMKafkaFillAction:
152 validateConfigErr = validateKafkaFillAction(in.KafkaFill)
153 case PMKafkaFloodAction:
154 validateConfigErr = validateKafkaFloodAction(in.KafkaFlood)
155 case PMKafkaIOAction:
156 validateConfigErr = validateKafkaIOAction(in.KafkaIO)
157 case PMFileCreateAction:
158 validateConfigErr = validateFileCreateAction(in.FileCreate)
159 case PMFileModifyPrivilegeAction:
160 validateConfigErr = validateFileModifyPrivilegeAction(in.FileModifyPrivilege)
161 case PMFileDeleteAction:
162 validateConfigErr = validateFileDeleteAction(in.FileDelete)
163 case PMFileRenameAction:
164 validateConfigErr = validateFileRenameAction(in.FileRename)
165 case PMFileAppendAction:
166 validateConfigErr = validateFileAppendAction(in.FileAppend)
167 case PMFileReplaceAction:
168 validateConfigErr = validateFileReplaceAction(in.FileReplace)
169 case PMUserDefinedAction:
170 validateConfigErr = validateUserDefinedAction(in.UserDefined)
171 default:
172 }
173
174 if validateConfigErr != nil {
175 allErrs = append(allErrs,
176 field.Invalid(path.Child("spec"), in,
177 validateConfigErr.Error()))
178 }
179
180 return allErrs
181 }
182
183 func validateStressCPUAction(spec *StressCPUSpec) error {
184 if spec.Load == 0 {
185 return errors.New("load can't be 0")
186 }
187
188 if spec.Workers == 0 {
189 return errors.New("workers can't be 0")
190 }
191
192 return nil
193 }
194
195 func validateStressMemAction(spec *StressMemorySpec) error {
196 if len(spec.Size) == 0 {
197 return errors.New("size is required")
198 }
199
200 if _, err := ParseUnit(spec.Size); err != nil {
201 return err
202 }
203
204 return nil
205 }
206
207 func validateDiskPayloadAction(spec *DiskPayloadSpec) error {
208 if spec.PayloadProcessNum == 0 {
209 return errors.New("payload-process-num can't be 0")
210 }
211
212 if len(spec.Size) == 0 {
213 return errors.New("size is required")
214 }
215
216 if _, err := ParseUnit(spec.Size); err != nil {
217 return err
218 }
219
220 return nil
221 }
222
223 func validateDiskFillAction(spec *DiskFillSpec) error {
224 if len(spec.Size) == 0 {
225 return errors.New("size is required")
226 }
227
228 if _, err := ParseUnit(spec.Size); err != nil {
229 return err
230 }
231
232 return nil
233 }
234
235 func validateNetworkCommon(spec *NetworkCommonSpec) error {
236 if !CheckPercent(spec.Correlation, true) {
237 return errors.Errorf("correlation %s is invalid", spec.Correlation)
238 }
239
240 if len(spec.Device) == 0 {
241 return errors.New("device is required")
242 }
243
244 return nil
245 }
246
247 func validateNetworkCorruptAction(spec *NetworkCorruptSpec) error {
248 if err := validateNetworkCommon(&spec.NetworkCommonSpec); err != nil {
249 return err
250 }
251
252 if !CheckPercent(spec.Percent, false) {
253 return errors.New("percent is invalid")
254 }
255
256 return nil
257 }
258
259 func validateNetworkDuplicateAction(spec *NetworkDuplicateSpec) error {
260 if err := validateNetworkCommon(&spec.NetworkCommonSpec); err != nil {
261 return err
262 }
263
264 if !CheckPercent(spec.Percent, false) {
265 return errors.New("percent is invalid")
266 }
267
268 return nil
269 }
270
271 func validateNetworkLossAction(spec *NetworkLossSpec) error {
272 if err := validateNetworkCommon(&spec.NetworkCommonSpec); err != nil {
273 return err
274 }
275
276 if !CheckPercent(spec.Percent, false) {
277 return errors.New("percent is invalid")
278 }
279
280 return nil
281 }
282
283 func validateNetworkDelayAction(spec *NetworkDelaySpec) error {
284 if err := validateNetworkCommon(&spec.NetworkCommonSpec); err != nil {
285 return err
286 }
287
288 if len(spec.Latency) == 0 {
289 return errors.New("latency is invalid")
290 }
291
292 if len(spec.AcceptTCPFlags) > 0 && spec.IPProtocol != "tcp" {
293 return errors.New("protocol should be 'tcp' when set accept-tcp-flags")
294 }
295
296 return nil
297 }
298
299 func validateNetworkPartitionAction(spec *NetworkPartitionSpec) error {
300 if len(spec.Device) == 0 {
301 return errors.New("device is required")
302 }
303
304 if len(spec.IPAddress) == 0 && len(spec.Hostname) == 0 {
305 return errors.New("one of ip-address and hostname is required")
306 }
307
308 if spec.Direction != "to" && spec.Direction != "from" {
309 return errors.New("direction should be one of 'to' and 'from'")
310 }
311
312 if len(spec.AcceptTCPFlags) > 0 && spec.IPProtocol != "tcp" {
313 return errors.New("protocol should be 'tcp' when set accept-tcp-flags")
314 }
315
316 return nil
317 }
318
319 func validateNetworkBandwidthAction(spec *NetworkBandwidthSpec) error {
320 if len(spec.Device) == 0 {
321 return errors.New("device is required")
322 }
323
324 if len(spec.Rate) == 0 || spec.Limit == 0 || spec.Buffer == 0 {
325 return errors.Errorf("rate, limit and buffer both are required when action is bandwidth")
326 }
327
328 return nil
329 }
330
331 func validateNetworkDNSAction(spec *NetworkDNSSpec) error {
332 if (len(spec.DNSDomainName) != 0 && len(spec.DNSIp) == 0) || (len(spec.DNSDomainName) == 0 && len(spec.DNSIp) != 0) {
333 return errors.Errorf("DNS host %s must match a DNS ip %s", spec.DNSDomainName, spec.DNSIp)
334 }
335
336 return nil
337 }
338
339 func validateNetworkFlood(spec *NetworkFloodSpec) error {
340 if len(spec.IPAddress) == 0 {
341 return errors.New("ip-address is required")
342 }
343
344 if len(spec.Port) == 0 {
345 return errors.New("port is required")
346 }
347
348 if len(spec.Rate) == 0 {
349 return errors.New("rate is required")
350 }
351
352 if len(spec.Duration) == 0 {
353 return errors.New("duration is required")
354 }
355
356 return nil
357 }
358
359 func validateNetworkDownAction(spec *NetworkDownSpec) error {
360 if len(spec.Device) == 0 {
361 return errors.New("device is required")
362 }
363 if len(spec.Duration) == 0 {
364 return errors.New("duration is required")
365 }
366
367 return nil
368 }
369
370 func validateProcessAction(spec *ProcessSpec) error {
371 if len(spec.Process) == 0 {
372 return errors.New("process is required")
373 }
374
375 if spec.Signal == 0 {
376 return errors.New("signal is required")
377 }
378
379 return nil
380 }
381
382 func validateJVMClassMethod(spec *JVMClassMethodSpec) error {
383 if len(spec.Class) == 0 {
384 return errors.New("class is required")
385 }
386
387 if len(spec.Method) == 0 {
388 return errors.New("method is required")
389 }
390
391 return nil
392 }
393
394 func validateJVMExceptionAction(spec *JVMExceptionSpec) error {
395 if err := CheckPid(spec.Pid); err != nil {
396 return err
397 }
398
399 if err := validateJVMClassMethod(&spec.JVMClassMethodSpec); err != nil {
400 return err
401 }
402
403 if len(spec.ThrowException) == 0 {
404 return errors.New("exception is required")
405 }
406
407 return nil
408 }
409
410 func validateJVMGCAction(spec *JVMGCSpec) error {
411 return CheckPid(spec.Pid)
412 }
413
414 func validateJVMLatencyAction(spec *JVMLatencySpec) error {
415 if err := CheckPid(spec.Pid); err != nil {
416 return err
417 }
418
419 if err := validateJVMClassMethod(&spec.JVMClassMethodSpec); err != nil {
420 return err
421 }
422
423 if spec.LatencyDuration == 0 {
424 return errors.New("latency is required")
425 }
426
427 return nil
428 }
429
430 func validateJVMReturnAction(spec *JVMReturnSpec) error {
431 if err := CheckPid(spec.Pid); err != nil {
432 return err
433 }
434
435 if err := validateJVMClassMethod(&spec.JVMClassMethodSpec); err != nil {
436 return err
437 }
438
439 if len(spec.ReturnValue) == 0 {
440 return errors.New("value is required")
441 }
442
443 return nil
444 }
445
446 func validateJVMStressAction(spec *JVMStressSpec) error {
447 if err := CheckPid(spec.Pid); err != nil {
448 return err
449 }
450
451 if spec.CPUCount == 0 && len(spec.MemoryType) == 0 {
452 return errors.New("one of cpu-count and mem-type is required")
453 }
454
455 if spec.CPUCount > 0 && len(spec.MemoryType) > 0 {
456 return errors.New("inject stress on both CPU and memory is not support")
457 }
458
459 return nil
460 }
461
462 func validateJVMRuleDataAction(spec *JVMRuleDataSpec) error {
463 if err := CheckPid(spec.Pid); err != nil {
464 return err
465 }
466
467 if len(spec.RuleData) == 0 {
468 return errors.New("rule-data is required")
469 }
470
471 return nil
472 }
473
474 func validateJVMMySQLAction(spec *PMJVMMySQLSpec) error {
475 if err := CheckPid(spec.Pid); err != nil {
476 return err
477 }
478
479 if len(spec.MySQLConnectorVersion) == 0 {
480 return errors.New("MySQL connector version not provided")
481 }
482
483 if len(spec.ThrowException) == 0 && spec.LatencyDuration == 0 {
484 return errors.New("must set one of exception or latency")
485 }
486
487 return nil
488 }
489 func validateClockAction(spec *ClockSpec) error {
490 if err := CheckPid(spec.Pid); err != nil {
491 return err
492 }
493
494 if len(spec.TimeOffset) == 0 {
495 return errors.New("time-offset is required")
496 }
497
498 return nil
499 }
500
501 func CheckPid(pid int) error {
502 if pid == 0 {
503 return errors.New("pid is required")
504 }
505
506 if pid < 0 {
507 return errors.New("pid is invalid")
508 }
509
510 return nil
511 }
512
513 func CheckPercent(p string, allowZero bool) bool {
514 if len(p) == 0 {
515 return allowZero
516 }
517
518 v, err := strconv.ParseFloat(p, 32)
519 if err != nil {
520 return false
521 }
522
523 if v == 0 && !allowZero {
524 return false
525 }
526
527 if v < 0 || v > 100 {
528 return false
529 }
530
531 return true
532 }
533
534 var (
535
536 shortBinaryUnitMap = units.MakeUnitMap("", "c", 1024)
537 binaryUnitMap = units.MakeUnitMap("iB", "c", 1024)
538 decimalUnitMap = units.MakeUnitMap("B", "c", 1000)
539 )
540
541
542
543
544 func ParseUnit(s string) (uint64, error) {
545 if _, err := strconv.Atoi(s); err == nil {
546 s += "B"
547 }
548 if n, err := units.ParseUnit(s, shortBinaryUnitMap); err == nil {
549 return uint64(n), nil
550 }
551
552 if n, err := units.ParseUnit(s, binaryUnitMap); err == nil {
553 return uint64(n), nil
554 }
555
556 if n, err := units.ParseUnit(s, decimalUnitMap); err == nil {
557 return uint64(n), nil
558 }
559 return 0, errors.Wrapf(errInvalidValue, "unknown unit %s", s)
560 }
561
562 func (in *NetworkBandwidthSpec) Validate(root interface{}, path *field.Path) field.ErrorList {
563 allErrs := field.ErrorList{}
564
565 if len(in.Rate) == 0 {
566 allErrs = append(allErrs,
567 field.Invalid(path.Child("rate"), in.Rate, "rate is required"))
568 }
569
570 return allErrs
571 }
572
573 var ValidOptions = map[string]bool{"XX": true, "NX": true, "GT": true, "LT": true}
574
575 func validateRedisCommonAction(spec *RedisCommonSpec) error {
576 if len(spec.Addr) == 0 {
577 return errors.New("addr of redis server is required")
578 }
579
580 return nil
581 }
582
583 func validateRedisExpirationAction(spec *RedisExpirationSpec) error {
584 if err := validateRedisCommonAction(&spec.RedisCommonSpec); err != nil {
585 return err
586 }
587
588 if _, ok := ValidOptions[spec.Option]; ok {
589 return errors.New("option invalid")
590 }
591
592 return nil
593 }
594
595 func validateRedisCacheLimitAction(spec *RedisCacheLimitSpec) error {
596 if err := validateRedisCommonAction(&spec.RedisCommonSpec); err != nil {
597 return err
598 }
599
600 if spec.Size != "0" && spec.Percent != "" {
601 return errors.New("only one of size and percent can be set")
602 }
603
604 return nil
605 }
606
607 func validateRedisPenetrationAction(spec *RedisPenetrationSpec) error {
608 if err := validateRedisCommonAction(&spec.RedisCommonSpec); err != nil {
609 return err
610 }
611
612 if spec.RequestNum == 0 {
613 return errors.New("requestNum is required")
614 }
615
616 return nil
617 }
618
619 func validateRedisSentinelStopAction(spec *RedisSentinelStopSpec) error {
620 return validateRedisCommonAction(&spec.RedisCommonSpec)
621 }
622
623 func validateRedisSentinelRestartAction(spec *RedisSentinelRestartSpec) error {
624 if err := validateRedisCommonAction(&spec.RedisCommonSpec); err != nil {
625 return err
626 }
627
628 if len(spec.Conf) == 0 {
629 return errors.New("conf is required to restart the sentinel")
630 }
631
632 return nil
633 }
634
635 func validateKafkaCommonAction(spec *KafkaCommonSpec) error {
636 if spec.Host == "" {
637 return errors.New("host is required")
638 }
639
640 if spec.Port == 0 {
641 return errors.New("port is required")
642 }
643
644 return nil
645 }
646
647 func validateKafkaFillAction(spec *KafkaFillSpec) error {
648 if err := validateKafkaCommonAction(&spec.KafkaCommonSpec); err != nil {
649 return err
650 }
651
652 if spec.MaxBytes == 0 {
653 return errors.New("max bytes is required")
654 }
655
656 if spec.ReloadCommand == "" {
657 return errors.New("reload command is required")
658 }
659
660 return nil
661 }
662
663 func validateKafkaFloodAction(spec *KafkaFloodSpec) error {
664 if err := validateKafkaCommonAction(&spec.KafkaCommonSpec); err != nil {
665 return err
666 }
667
668 if spec.Threads == 0 {
669 return errors.New("threads is required")
670 }
671
672 return nil
673 }
674
675 func validateKafkaIOAction(spec *KafkaIOSpec) error {
676 if !spec.NonReadable && !spec.NonWritable {
677 return errors.New("at least one of non-readable or non-writable is required")
678 }
679
680 return nil
681 }
682
683 func validateFileCreateAction(spec *FileCreateSpec) error {
684 if len(spec.FileName) == 0 && len(spec.DirName) == 0 {
685 return errors.New("one of file-name and dir-name is required")
686 }
687
688 return nil
689 }
690
691 func validateFileModifyPrivilegeAction(spec *FileModifyPrivilegeSpec) error {
692 if len(spec.FileName) == 0 {
693 return errors.New("file name is required")
694 }
695
696 if spec.Privilege == 0 {
697 return errors.New("file privilege is required")
698 }
699
700 return nil
701 }
702
703 func validateFileDeleteAction(spec *FileDeleteSpec) error {
704 if len(spec.FileName) == 0 && len(spec.DirName) == 0 {
705 return errors.New("one of file-name and dir-name is required")
706 }
707
708 return nil
709 }
710
711 func validateFileRenameAction(spec *FileRenameSpec) error {
712 if len(spec.SourceFile) == 0 || len(spec.DestFile) == 0 {
713 return errors.New("both source file and destination file are required")
714 }
715
716 return nil
717 }
718
719 func validateFileAppendAction(spec *FileAppendSpec) error {
720 if len(spec.FileName) == 0 {
721 return errors.New("file-name is required")
722 }
723
724 if len(spec.Data) == 0 {
725 return errors.New("append data is required")
726 }
727
728 return nil
729 }
730
731 func validateFileReplaceAction(spec *FileReplaceSpec) error {
732 if len(spec.FileName) == 0 {
733 return errors.New("file-name is required")
734 }
735
736 if len(spec.OriginStr) == 0 || len(spec.DestStr) == 0 {
737 return errors.New("both origin and destination string are required")
738 }
739
740 return nil
741 }
742
743 func validateUserDefinedAction(spec *UserDefinedSpec) error {
744 if len(spec.AttackCmd) == 0 {
745 return errors.New("attack command not provided")
746 }
747
748 if len(spec.RecoverCmd) == 0 {
749 return errors.New("recover command not provided")
750 }
751
752 return nil
753 }
754