...

Source file src/github.com/chaos-mesh/chaos-mesh/api/v1alpha1/physical_machine_chaos_webhook.go

Documentation: github.com/chaos-mesh/chaos-mesh/api/v1alpha1

     1  // Copyright 2021 Chaos Mesh Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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  		// add http prefix for address
    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  	// make sure the configuration corresponding to action is not empty
    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  	// make sure address is not empty
    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  	// See https://en.wikipedia.org/wiki/Binary_prefix
   536  	shortBinaryUnitMap = units.MakeUnitMap("", "c", 1024)
   537  	binaryUnitMap      = units.MakeUnitMap("iB", "c", 1024)
   538  	decimalUnitMap     = units.MakeUnitMap("B", "c", 1000)
   539  )
   540  
   541  // ParseUnit parse a digit with unit such as "K" , "KiB", "KB", "c", "MiB", "MB", "M".
   542  // If input string is a digit without unit ,
   543  // it will be regarded as a digit with unit M(1024*1024 bytes).
   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