mirror of https://github.com/chaos-mesh/chaosd.git
				
				
				
			factor disk attack&add schedule support for disk read&write (#77)
* fix bugs in SplitBytesByProcessNum & add overwrite control Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * delete overwrite control Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * add recover for disk&&delete fill destory Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * patch Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * factor disk attack Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * add schedule support for disk read&write Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * remove some test & add new unit test & fix some bug Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * fix some mistakes in log Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * fix unsupportted unit `c` in fallocate. Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * roll back scheduler Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * add comment Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * save some indentation Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * add a comment Signed-off-by: andrewmatilde <davis6813585853062@outlook.com>
This commit is contained in:
		
							parent
							
								
									c2fddc2942
								
							
						
					
					
						commit
						352ba5a47a
					
				| 
						 | 
				
			
			@ -13,7 +13,11 @@
 | 
			
		|||
 | 
			
		||||
package attack
 | 
			
		||||
 | 
			
		||||
import "github.com/spf13/cobra"
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/chaos-mesh/chaosd/pkg/core"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewAttackCommand() *cobra.Command {
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
| 
						 | 
				
			
			@ -35,3 +39,8 @@ func NewAttackCommand() *cobra.Command {
 | 
			
		|||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SetScheduleFlags(cmd *cobra.Command, conf *core.SchedulerConfig) {
 | 
			
		||||
	cmd.Flags().StringVar(&conf.Duration, "duration", "",
 | 
			
		||||
		`Work duration of attacks.A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,6 +80,7 @@ func NewDiskWritePayloadCommand(dep fx.Option, options *core.DiskOption) *cobra.
 | 
			
		|||
			"If path not provided, payload will write into a temp file, temp file will be deleted after writing")
 | 
			
		||||
	cmd.Flags().Uint8VarP(&options.PayloadProcessNum, "process-num", "n", 1,
 | 
			
		||||
		"'process-num' specifies the number of process work on writing , default 1, only 1-255 is valid value")
 | 
			
		||||
	SetScheduleFlags(cmd, &options.SchedulerConfig)
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -103,6 +104,7 @@ func NewDiskReadPayloadCommand(dep fx.Option, options *core.DiskOption) *cobra.C
 | 
			
		|||
			"If path not provided, payload will read from disk mount on \"/\"")
 | 
			
		||||
	cmd.Flags().Uint8VarP(&options.PayloadProcessNum, "process-num", "n", 1,
 | 
			
		||||
		"'process-num' specifies the number of process work on reading , default 1, only 1-255 is valid value")
 | 
			
		||||
	SetScheduleFlags(cmd, &options.SchedulerConfig)
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -126,15 +128,17 @@ func NewDiskFillCommand(dep fx.Option, options *core.DiskOption) *cobra.Command
 | 
			
		|||
			"If path not provided, a temp file will be generated and deleted immediately after data filled in or allocated")
 | 
			
		||||
	cmd.Flags().StringVarP(&options.Percent, "percent", "c", "",
 | 
			
		||||
		"'percent' how many percent data of disk will fill in the file path")
 | 
			
		||||
	cmd.Flags().BoolVarP(&options.FillByFallocate, "fallocate", "f", true, "fill disk by fallocate instead of dd")
 | 
			
		||||
	cmd.Flags().BoolVarP(&options.FillByFAllocate, "fallocate", "f", true, "fill disk by fallocate instead of dd")
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func processDiskAttack(options *core.DiskOption, chaos *chaosd.Server) {
 | 
			
		||||
	if err := options.Validate(); err != nil {
 | 
			
		||||
	attackConfig, err := options.PreProcess()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.ExitWithError(utils.ExitBadArgs, err)
 | 
			
		||||
	}
 | 
			
		||||
	uid, err := chaos.ExecuteAttack(chaosd.DiskAttack, options, core.CommandMode)
 | 
			
		||||
 | 
			
		||||
	uid, err := chaos.ExecuteAttack(chaosd.DiskAttack, attackConfig, core.CommandMode)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.ExitWithError(utils.ExitError, err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,323 +0,0 @@
 | 
			
		|||
// Copyright 2021 Chaos Mesh Authors.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package attack
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"go.uber.org/fx"
 | 
			
		||||
	"go.uber.org/fx/fxtest"
 | 
			
		||||
 | 
			
		||||
	"github.com/chaos-mesh/chaosd/cmd/server"
 | 
			
		||||
	"github.com/chaos-mesh/chaosd/pkg/core"
 | 
			
		||||
	"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
 | 
			
		||||
	"github.com/chaos-mesh/chaosd/pkg/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type diskTest struct {
 | 
			
		||||
	name    string
 | 
			
		||||
	option  *core.DiskOption
 | 
			
		||||
	wantErr bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServer_DiskFill(t *testing.T) {
 | 
			
		||||
	fxtest.New(
 | 
			
		||||
		t,
 | 
			
		||||
		server.Module,
 | 
			
		||||
		fx.Provide(func() []diskTest {
 | 
			
		||||
			return []diskTest{
 | 
			
		||||
				{
 | 
			
		||||
					name: "0",
 | 
			
		||||
					option: &core.DiskOption{
 | 
			
		||||
						CommonAttackConfig: core.CommonAttackConfig{
 | 
			
		||||
							Action: core.DiskFillAction,
 | 
			
		||||
							Kind:   core.DiskAttack,
 | 
			
		||||
						},
 | 
			
		||||
						Size:              "1024M",
 | 
			
		||||
						Path:              "./temp",
 | 
			
		||||
						FillByFallocate:   true,
 | 
			
		||||
						PayloadProcessNum: 1,
 | 
			
		||||
					},
 | 
			
		||||
					wantErr: false,
 | 
			
		||||
				}, {
 | 
			
		||||
					name: "1",
 | 
			
		||||
					option: &core.DiskOption{
 | 
			
		||||
						CommonAttackConfig: core.CommonAttackConfig{
 | 
			
		||||
							Action: core.DiskFillAction,
 | 
			
		||||
							Kind:   core.DiskAttack,
 | 
			
		||||
						},
 | 
			
		||||
						Size:              "24MB",
 | 
			
		||||
						Path:              "./temp",
 | 
			
		||||
						FillByFallocate:   false,
 | 
			
		||||
						PayloadProcessNum: 1,
 | 
			
		||||
					},
 | 
			
		||||
					wantErr: false,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *chaosd.Server, tests []diskTest) {
 | 
			
		||||
			for _, tt := range tests {
 | 
			
		||||
				t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
					_, err := s.ExecuteAttack(chaosd.DiskAttack, tt.option, core.CommandMode)
 | 
			
		||||
					if (err != nil) != tt.wantErr {
 | 
			
		||||
						t.Errorf("DiskFill() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					stat, err := os.Stat(tt.option.Path)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						t.Errorf("unexpected err %v when stat temp file", err)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					size, _ := utils.ParseUnit(tt.option.Size)
 | 
			
		||||
					if stat.Size() != int64(size) {
 | 
			
		||||
						t.Errorf("DiskFill() size %v, expect %d", stat.Size(), size)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					os.Remove(tt.option.Path)
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServer_DiskPayload(t *testing.T) {
 | 
			
		||||
	fxtest.New(
 | 
			
		||||
		t,
 | 
			
		||||
		server.Module,
 | 
			
		||||
		fx.Provide(func() []diskTest {
 | 
			
		||||
			return []diskTest{
 | 
			
		||||
				{
 | 
			
		||||
					name: "0",
 | 
			
		||||
					option: &core.DiskOption{
 | 
			
		||||
						CommonAttackConfig: core.CommonAttackConfig{
 | 
			
		||||
							Action: core.DiskWritePayloadAction,
 | 
			
		||||
							Kind:   core.DiskAttack,
 | 
			
		||||
						},
 | 
			
		||||
						Size:              "24M",
 | 
			
		||||
						Path:              "./temp",
 | 
			
		||||
						PayloadProcessNum: 1,
 | 
			
		||||
					},
 | 
			
		||||
					wantErr: false,
 | 
			
		||||
				}, {
 | 
			
		||||
					name: "1",
 | 
			
		||||
					option: &core.DiskOption{
 | 
			
		||||
						CommonAttackConfig: core.CommonAttackConfig{
 | 
			
		||||
							Action: core.DiskReadPayloadAction,
 | 
			
		||||
							Kind:   core.DiskAttack,
 | 
			
		||||
						},
 | 
			
		||||
						Size:              "24M",
 | 
			
		||||
						Path:              "./temp",
 | 
			
		||||
						PayloadProcessNum: 1,
 | 
			
		||||
					},
 | 
			
		||||
					wantErr: false,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *chaosd.Server, tests []diskTest) {
 | 
			
		||||
			for _, tt := range tests {
 | 
			
		||||
				t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
					_, err := s.ExecuteAttack(chaosd.DiskAttack, &core.DiskOption{
 | 
			
		||||
						CommonAttackConfig: core.CommonAttackConfig{
 | 
			
		||||
							Action: core.DiskFillAction,
 | 
			
		||||
							Kind:   core.DiskAttack,
 | 
			
		||||
						},
 | 
			
		||||
						PayloadProcessNum: 1,
 | 
			
		||||
						Size:              tt.option.Size,
 | 
			
		||||
						Path:              "./temp",
 | 
			
		||||
						FillByFallocate:   true,
 | 
			
		||||
					}, core.CommandMode)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						t.Error(err)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					_, err = s.ExecuteAttack(chaosd.DiskAttack, tt.option, core.CommandMode)
 | 
			
		||||
					if (err != nil) != tt.wantErr {
 | 
			
		||||
						t.Errorf("DiskPayload() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					os.Remove(tt.option.Path)
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type writeArgs struct {
 | 
			
		||||
	Size              string
 | 
			
		||||
	Path              string
 | 
			
		||||
	PayloadProcessNum uint8
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeArgsToDiskOption(args writeArgs) core.DiskOption {
 | 
			
		||||
	return core.DiskOption{
 | 
			
		||||
		CommonAttackConfig: core.CommonAttackConfig{
 | 
			
		||||
			SchedulerConfig: core.SchedulerConfig{},
 | 
			
		||||
			Action:          core.DiskWritePayloadAction,
 | 
			
		||||
			Kind:            "",
 | 
			
		||||
		},
 | 
			
		||||
		Size:              args.Size,
 | 
			
		||||
		Path:              args.Path,
 | 
			
		||||
		Percent:           "",
 | 
			
		||||
		FillByFallocate:   false,
 | 
			
		||||
		PayloadProcessNum: args.PayloadProcessNum,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeArgsAttack(args writeArgs) error {
 | 
			
		||||
	opt := writeArgsToDiskOption(args)
 | 
			
		||||
	return chaosd.DiskAttack.Attack(&opt, chaosd.Environment{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewDiskWritePayloadCommand(t *testing.T) {
 | 
			
		||||
	var opt core.DiskOption
 | 
			
		||||
	var err error
 | 
			
		||||
	opt = writeArgsToDiskOption(writeArgs{
 | 
			
		||||
		Size:              "",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 0,
 | 
			
		||||
	})
 | 
			
		||||
	err = opt.Validate()
 | 
			
		||||
	assert.EqualError(t, err, "one of percent and size must not be empty, DiskOption : write-payload")
 | 
			
		||||
 | 
			
		||||
	opt = writeArgsToDiskOption(writeArgs{
 | 
			
		||||
		Size:              "1Ms",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 0,
 | 
			
		||||
	})
 | 
			
		||||
	err = opt.Validate()
 | 
			
		||||
	assert.EqualError(t, err, "unknown units of size : 1Ms, DiskOption : write-payload")
 | 
			
		||||
 | 
			
		||||
	opt = writeArgsToDiskOption(writeArgs{
 | 
			
		||||
		Size:              "0",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 0,
 | 
			
		||||
	})
 | 
			
		||||
	err = opt.Validate()
 | 
			
		||||
	assert.EqualError(t, err, "unsupport process num : 0, DiskOption : write-payload")
 | 
			
		||||
 | 
			
		||||
	opt = writeArgsToDiskOption(writeArgs{
 | 
			
		||||
		Size:              "0",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 1,
 | 
			
		||||
	})
 | 
			
		||||
	err = opt.Validate()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, writeArgsAttack(writeArgs{
 | 
			
		||||
		Size:              "0",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 1,
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, writeArgsAttack(writeArgs{
 | 
			
		||||
		Size:              "0",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 255,
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, writeArgsAttack(writeArgs{
 | 
			
		||||
		Size:              "1",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 2,
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	assert.Error(t, writeArgsAttack(writeArgs{
 | 
			
		||||
		Size:              "1",
 | 
			
		||||
		Path:              "&^%$#@#$%^&*(",
 | 
			
		||||
		PayloadProcessNum: 5,
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type readArgs struct {
 | 
			
		||||
	Size              string
 | 
			
		||||
	Path              string
 | 
			
		||||
	PayloadProcessNum uint8
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func readArgsToDiskOption(args readArgs) core.DiskOption {
 | 
			
		||||
	return core.DiskOption{
 | 
			
		||||
		CommonAttackConfig: core.CommonAttackConfig{
 | 
			
		||||
			SchedulerConfig: core.SchedulerConfig{},
 | 
			
		||||
			Action:          core.DiskReadPayloadAction,
 | 
			
		||||
			Kind:            "",
 | 
			
		||||
		},
 | 
			
		||||
		Size:              args.Size,
 | 
			
		||||
		Path:              args.Path,
 | 
			
		||||
		Percent:           "",
 | 
			
		||||
		FillByFallocate:   false,
 | 
			
		||||
		PayloadProcessNum: args.PayloadProcessNum,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func readArgsAttack(args readArgs) error {
 | 
			
		||||
	opt := readArgsToDiskOption(args)
 | 
			
		||||
	return chaosd.DiskAttack.Attack(&opt, chaosd.Environment{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewDiskReadPayloadCommand(t *testing.T) {
 | 
			
		||||
	var opt core.DiskOption
 | 
			
		||||
	var err error
 | 
			
		||||
	opt = readArgsToDiskOption(readArgs{
 | 
			
		||||
		Size:              "",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 0,
 | 
			
		||||
	})
 | 
			
		||||
	err = opt.Validate()
 | 
			
		||||
	assert.EqualError(t, err, "one of percent and size must not be empty, DiskOption : read-payload")
 | 
			
		||||
 | 
			
		||||
	opt = readArgsToDiskOption(readArgs{
 | 
			
		||||
		Size:              "1Ms",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 0,
 | 
			
		||||
	})
 | 
			
		||||
	err = opt.Validate()
 | 
			
		||||
	assert.EqualError(t, err, "unknown units of size : 1Ms, DiskOption : read-payload")
 | 
			
		||||
 | 
			
		||||
	opt = readArgsToDiskOption(readArgs{
 | 
			
		||||
		Size:              "0",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 0,
 | 
			
		||||
	})
 | 
			
		||||
	err = opt.Validate()
 | 
			
		||||
	assert.EqualError(t, err, "unsupport process num : 0, DiskOption : read-payload")
 | 
			
		||||
 | 
			
		||||
	opt = readArgsToDiskOption(readArgs{
 | 
			
		||||
		Size:              "0",
 | 
			
		||||
		Path:              "",
 | 
			
		||||
		PayloadProcessNum: 1,
 | 
			
		||||
	})
 | 
			
		||||
	err = opt.Validate()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, readArgsAttack(readArgs{
 | 
			
		||||
		Size:              "0",
 | 
			
		||||
		Path:              "/dev/zero",
 | 
			
		||||
		PayloadProcessNum: 1,
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, readArgsAttack(readArgs{
 | 
			
		||||
		Size:              "1",
 | 
			
		||||
		Path:              "/dev/zero",
 | 
			
		||||
		PayloadProcessNum: 2,
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, readArgsAttack(readArgs{
 | 
			
		||||
		Size:              "100GB",
 | 
			
		||||
		Path:              "/dev/zero",
 | 
			
		||||
		PayloadProcessNum: 1,
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										289
									
								
								pkg/core/disk.go
								
								
								
								
							
							
						
						
									
										289
									
								
								pkg/core/disk.go
								
								
								
								
							| 
						 | 
				
			
			@ -18,7 +18,12 @@ import (
 | 
			
		|||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/pingcap/log"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
 | 
			
		||||
	"github.com/chaos-mesh/chaosd/pkg/utils"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +34,41 @@ const (
 | 
			
		|||
	DiskReadPayloadAction  = "read-payload"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var _ AttackConfig = &DiskAttackConfig{}
 | 
			
		||||
 | 
			
		||||
type DiskAttackConfig struct {
 | 
			
		||||
	CommonAttackConfig
 | 
			
		||||
	DdOptions       *[]DdOption
 | 
			
		||||
	FAllocateOption *FAllocateOption
 | 
			
		||||
	Path            string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d DiskAttackConfig) RecoverData() string {
 | 
			
		||||
	data, _ := json.Marshal(d)
 | 
			
		||||
 | 
			
		||||
	return string(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var DdCommand = utils.Command{Name: "dd"}
 | 
			
		||||
 | 
			
		||||
type DdOption struct {
 | 
			
		||||
	ReadPath  string `dd:"if"`
 | 
			
		||||
	WritePath string `dd:"of"`
 | 
			
		||||
	BlockSize string `dd:"bs"`
 | 
			
		||||
	Count     string `dd:"count"`
 | 
			
		||||
	Iflag     string `dd:"iflag"`
 | 
			
		||||
	Oflag     string `dd:"oflag"`
 | 
			
		||||
	Conv      string `dd:"conv"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var FAllocateCommand = utils.Command{Name: "fallocate"}
 | 
			
		||||
 | 
			
		||||
type FAllocateOption struct {
 | 
			
		||||
	LengthOpt string `fallocate:"-"`
 | 
			
		||||
	Length    string `fallocate:"-"`
 | 
			
		||||
	FileName  string `fallocate:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DiskOption struct {
 | 
			
		||||
	CommonAttackConfig
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,71 +77,7 @@ type DiskOption struct {
 | 
			
		|||
	Percent           string `json:"percent"`
 | 
			
		||||
	PayloadProcessNum uint8  `json:"payload_process_num"`
 | 
			
		||||
 | 
			
		||||
	FillByFallocate bool `json:"fill_by_fallocate"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ AttackConfig = &DiskOption{}
 | 
			
		||||
 | 
			
		||||
func (d *DiskOption) Validate() error {
 | 
			
		||||
	if err := d.CommonAttackConfig.Validate(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	var byteSize uint64
 | 
			
		||||
	var err error
 | 
			
		||||
	if d.Size == "" {
 | 
			
		||||
		if d.Percent == "" {
 | 
			
		||||
			return fmt.Errorf("one of percent and size must not be empty, DiskOption : %v", d)
 | 
			
		||||
		}
 | 
			
		||||
		if byteSize, err = strconv.ParseUint(d.Percent, 10, 0); err != nil {
 | 
			
		||||
			return fmt.Errorf("unsupport percent : %s, DiskOption : %v", d.Percent, d)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if byteSize, err = utils.ParseUnit(d.Size); err != nil {
 | 
			
		||||
			return fmt.Errorf("unknown units of size : %s, DiskOption : %v", d.Size, d)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if d.Action == DiskFillAction || d.Action == DiskWritePayloadAction {
 | 
			
		||||
		if d.Action == DiskFillAction && d.FillByFallocate && byteSize == 0 {
 | 
			
		||||
			return fmt.Errorf("fallocate not suppurt 0 size or 0 percent data, "+
 | 
			
		||||
				"if you want allocate a 0 size file please set fallocate=false, DiskOption : %v", d)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_, err := os.Stat(d.Path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if os.IsNotExist(err) {
 | 
			
		||||
				// check if Path of file is valid when Path is not empty
 | 
			
		||||
				if d.Path != "" {
 | 
			
		||||
					var b []byte
 | 
			
		||||
					if err := ioutil.WriteFile(d.Path, b, 0644); err != nil {
 | 
			
		||||
						return err
 | 
			
		||||
					}
 | 
			
		||||
					if err := os.Remove(d.Path); err != nil {
 | 
			
		||||
						return err
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if d.Action == DiskFillAction {
 | 
			
		||||
				return fmt.Errorf("fill into an existing file")
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("write into an existing file")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if d.PayloadProcessNum == 0 {
 | 
			
		||||
		return fmt.Errorf("unsupport process num : %d, DiskOption : %v", d.PayloadProcessNum, d.Action)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d DiskOption) RecoverData() string {
 | 
			
		||||
	data, _ := json.Marshal(d)
 | 
			
		||||
 | 
			
		||||
	return string(data)
 | 
			
		||||
	FillByFAllocate bool `json:"fill_by_fallocate"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewDiskOption() *DiskOption {
 | 
			
		||||
| 
						 | 
				
			
			@ -111,3 +87,186 @@ func NewDiskOption() *DiskOption {
 | 
			
		|||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opt *DiskOption) PreProcess() (*DiskAttackConfig, error) {
 | 
			
		||||
	if err := opt.CommonAttackConfig.Validate(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path, err := initPath(opt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	byteSize, err := initSize(opt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opt.Action == DiskFillAction && opt.FillByFAllocate && byteSize != 0 {
 | 
			
		||||
		return &DiskAttackConfig{
 | 
			
		||||
			CommonAttackConfig: opt.CommonAttackConfig,
 | 
			
		||||
			DdOptions:          nil,
 | 
			
		||||
			FAllocateOption: &FAllocateOption{
 | 
			
		||||
				LengthOpt: "-l",
 | 
			
		||||
				Length:    strconv.FormatUint(byteSize, 10),
 | 
			
		||||
				FileName:  path,
 | 
			
		||||
			},
 | 
			
		||||
			Path: path,
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ddOptions, err := initDdOptions(opt, path, byteSize)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &DiskAttackConfig{
 | 
			
		||||
		CommonAttackConfig: opt.CommonAttackConfig,
 | 
			
		||||
		DdOptions:          &ddOptions,
 | 
			
		||||
		FAllocateOption:    nil,
 | 
			
		||||
		Path:               path,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initDdOptions(opt *DiskOption, path string, byteSize uint64) ([]DdOption, error) {
 | 
			
		||||
	ddBlocks, err := utils.SplitBytesByProcessNum(byteSize, opt.PayloadProcessNum)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("fail to split disk size", zap.Error(err))
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var ddOpts []DdOption
 | 
			
		||||
	switch opt.Action {
 | 
			
		||||
	case DiskFillAction:
 | 
			
		||||
		for _, block := range ddBlocks {
 | 
			
		||||
			ddOpts = append(ddOpts, DdOption{
 | 
			
		||||
				ReadPath:  "/dev/zero",
 | 
			
		||||
				WritePath: path,
 | 
			
		||||
				BlockSize: block.BlockSize,
 | 
			
		||||
				Count:     block.Count,
 | 
			
		||||
				Iflag:     "fullblock", // fullblock : accumulate full blocks of input.
 | 
			
		||||
				Oflag:     "append",
 | 
			
		||||
				Conv:      "notrunc", // notrunc : do not truncate the output file.
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	case DiskWritePayloadAction:
 | 
			
		||||
		for _, block := range ddBlocks {
 | 
			
		||||
			ddOpts = append(ddOpts, DdOption{
 | 
			
		||||
				ReadPath:  "/dev/zero",
 | 
			
		||||
				WritePath: path,
 | 
			
		||||
				BlockSize: block.BlockSize,
 | 
			
		||||
				Count:     block.Count,
 | 
			
		||||
				Oflag:     "dsync", // dsync : use synchronized I/O for data.
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	case DiskReadPayloadAction:
 | 
			
		||||
		for _, block := range ddBlocks {
 | 
			
		||||
			ddOpts = append(ddOpts, DdOption{
 | 
			
		||||
				ReadPath:  path,
 | 
			
		||||
				WritePath: "/dev/null",
 | 
			
		||||
				BlockSize: block.BlockSize,
 | 
			
		||||
				Count:     block.Count,
 | 
			
		||||
				Iflag:     "dsync,fullblock,nocache", // nocache : Request to drop cache.
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ddOpts, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initPath(opt *DiskOption) (string, error) {
 | 
			
		||||
	switch opt.Action {
 | 
			
		||||
	case DiskFillAction, DiskWritePayloadAction:
 | 
			
		||||
		if opt.Path == "" {
 | 
			
		||||
			var err error
 | 
			
		||||
			// Check if the path is valid.
 | 
			
		||||
			opt.Path, err = utils.CreateTempFile()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error(fmt.Sprintf("unexpected err when CreateTempFile in action: %s", opt.Action))
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if err := os.Remove(opt.Path); err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			_, err := os.Stat(opt.Path)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				// check if Path of file is valid when Path is not empty
 | 
			
		||||
				if os.IsNotExist(err) {
 | 
			
		||||
					var b []byte
 | 
			
		||||
					if err := ioutil.WriteFile(opt.Path, b, 0600); err != nil {
 | 
			
		||||
						return "", err
 | 
			
		||||
					}
 | 
			
		||||
					if err := os.Remove(opt.Path); err != nil {
 | 
			
		||||
						return "", err
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				return "", fmt.Errorf("fill into an existing file")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return opt.Path, nil
 | 
			
		||||
	case DiskReadPayloadAction:
 | 
			
		||||
		if opt.Path == "" {
 | 
			
		||||
			path, err := utils.GetRootDevice()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("err when GetRootDevice in reading payload", zap.Error(err))
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if path == "" {
 | 
			
		||||
				err = fmt.Errorf("can not get root device path")
 | 
			
		||||
				log.Error(fmt.Sprintf("payload action: %s", opt.Action), zap.Error(err))
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			return path, nil
 | 
			
		||||
		}
 | 
			
		||||
		var fi os.FileInfo
 | 
			
		||||
		var err error
 | 
			
		||||
		if fi, err = os.Stat(opt.Path); err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		if fi.IsDir() {
 | 
			
		||||
			return "", fmt.Errorf("path is a dictory, path : %s", opt.Path)
 | 
			
		||||
		}
 | 
			
		||||
		f, err := os.Open(opt.Path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		err = f.Close()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", nil
 | 
			
		||||
		}
 | 
			
		||||
		return opt.Path, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return "", fmt.Errorf("unsupported action %s", opt.Action)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initSize(opt *DiskOption) (uint64, error) {
 | 
			
		||||
	if opt.Size != "" {
 | 
			
		||||
		byteSize, err := utils.ParseUnit(opt.Size)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error(fmt.Sprintf("fail to get parse size per units , %s", opt.Size), zap.Error(err))
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		return byteSize, nil
 | 
			
		||||
	} else if opt.Percent != "" {
 | 
			
		||||
		opt.Percent = strings.Trim(opt.Percent, " %")
 | 
			
		||||
		percent, err := strconv.ParseUint(opt.Percent, 10, 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error(fmt.Sprintf("unexcepted err when parsing disk percent '%s'", opt.Percent), zap.Error(err))
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		dir := filepath.Dir(opt.Path)
 | 
			
		||||
		totalSize, err := utils.GetDiskTotalSize(dir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("fail to get disk total size", zap.Error(err))
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		return totalSize * percent / 100, nil
 | 
			
		||||
	}
 | 
			
		||||
	if opt.Action == DiskFillAction {
 | 
			
		||||
		return 0, fmt.Errorf("one of percent and size must not be empty, DiskOption : %v", opt)
 | 
			
		||||
	}
 | 
			
		||||
	return 0, fmt.Errorf("size must not be empty, DiskOption : %v", opt)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
// Copyright 2021 Chaos Mesh Authors.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Test_initSize(t *testing.T) {
 | 
			
		||||
	opt := DiskOption{
 | 
			
		||||
		CommonAttackConfig: CommonAttackConfig{
 | 
			
		||||
			Action: DiskFillAction,
 | 
			
		||||
		},
 | 
			
		||||
		Size: "1024M",
 | 
			
		||||
	}
 | 
			
		||||
	byteSize, err := initSize(&opt)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, 1024<<20, byteSize)
 | 
			
		||||
 | 
			
		||||
	opt.Percent = "99%"
 | 
			
		||||
	opt.Size = ""
 | 
			
		||||
	byteSize, err = initSize(&opt)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	t.Logf("percent %s with bytesize %sGB\n", opt.Percent, strconv.Itoa(int(byteSize>>30)))
 | 
			
		||||
 | 
			
		||||
	opt.Percent = ""
 | 
			
		||||
	opt.Size = ""
 | 
			
		||||
	_, err = initSize(&opt)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_initPath(t *testing.T) {
 | 
			
		||||
	opt := DiskOption{
 | 
			
		||||
		CommonAttackConfig: CommonAttackConfig{
 | 
			
		||||
			Action: DiskFillAction,
 | 
			
		||||
		},
 | 
			
		||||
		Path: "/1/12/1/2/1/21",
 | 
			
		||||
	}
 | 
			
		||||
	_, err := initPath(&opt)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
 | 
			
		||||
	opt = DiskOption{
 | 
			
		||||
		CommonAttackConfig: CommonAttackConfig{
 | 
			
		||||
			Action: DiskReadPayloadAction,
 | 
			
		||||
		},
 | 
			
		||||
		Path: "/",
 | 
			
		||||
	}
 | 
			
		||||
	_, err = initPath(&opt)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +101,7 @@ func GetAttackByKind(kind string) *AttackConfig {
 | 
			
		|||
	case StressAttack:
 | 
			
		||||
		attackConfig = &StressCommand{}
 | 
			
		||||
	case DiskAttack:
 | 
			
		||||
		attackConfig = &DiskOption{}
 | 
			
		||||
		attackConfig = &DiskAttackConfig{}
 | 
			
		||||
	case JVMAttack:
 | 
			
		||||
		attackConfig = &JVMCommand{}
 | 
			
		||||
	default:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,11 +46,6 @@ func (s *Server) newEnvironment(uid string) Environment {
 | 
			
		|||
// If options.Schedule isn't provided, then the attack is executed immediately.
 | 
			
		||||
// Otherwise the attack is scheduled based on the provided schedule spec and duration.
 | 
			
		||||
func (s *Server) ExecuteAttack(attackType AttackType, options core.AttackConfig, launchMode string) (uid string, err error) {
 | 
			
		||||
	if err = options.Validate(); err != nil {
 | 
			
		||||
		err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uid = options.GetUID()
 | 
			
		||||
	if len(uid) == 0 {
 | 
			
		||||
		uid = uuid.New().String()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,128 +17,90 @@ import (
 | 
			
		|||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/hashicorp/go-multierror"
 | 
			
		||||
	"github.com/pingcap/errors"
 | 
			
		||||
	"github.com/pingcap/log"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
 | 
			
		||||
	"github.com/chaos-mesh/chaosd/pkg/server/utils"
 | 
			
		||||
 | 
			
		||||
	"github.com/chaos-mesh/chaosd/pkg/core"
 | 
			
		||||
	"github.com/chaos-mesh/chaosd/pkg/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type diskAttack struct{}
 | 
			
		||||
 | 
			
		||||
var DiskAttack AttackType = diskAttack{}
 | 
			
		||||
 | 
			
		||||
const DDWritePayloadCommand = "dd if=/dev/zero of=%s bs=%s count=%s oflag=dsync"
 | 
			
		||||
const DDReadPayloadCommand = "dd if=%s of=/dev/null bs=%s count=%s iflag=dsync,fullblock,nocache"
 | 
			
		||||
 | 
			
		||||
func (disk diskAttack) Attack(options core.AttackConfig, env Environment) (err error) {
 | 
			
		||||
	attack := options.(*core.DiskOption)
 | 
			
		||||
 | 
			
		||||
	if options.String() == core.DiskFillAction {
 | 
			
		||||
		return disk.diskFill(attack)
 | 
			
		||||
func (disk diskAttack) Attack(options core.AttackConfig, env Environment) error {
 | 
			
		||||
	var attackConf *core.DiskAttackConfig
 | 
			
		||||
	var ok bool
 | 
			
		||||
	if attackConf, ok = options.(*core.DiskAttackConfig); !ok {
 | 
			
		||||
		return fmt.Errorf("AttackConfig -> *DiskAttackConfig meet error")
 | 
			
		||||
	}
 | 
			
		||||
	return disk.diskPayload(attack)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initWritePayloadPath(payload *core.DiskOption) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	payload.Path, err = utils.CreateTempFile()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error(fmt.Sprintf("unexpected err when CreateTempFile in action: %s", payload.Action))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initReadPayloadPath(payload *core.DiskOption) error {
 | 
			
		||||
	path, err := utils.GetRootDevice()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("err when GetRootDevice in reading payload", zap.Error(err))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if path == "" {
 | 
			
		||||
		err = errors.Errorf("can not get root device path")
 | 
			
		||||
		log.Error(fmt.Sprintf("payload action: %s", payload.Action), zap.Error(err))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	payload.Path = path
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// diskPayload will execute a dd command (DDWritePayloadCommand or DDReadPayloadCommand)
 | 
			
		||||
// to add a write or read payload.
 | 
			
		||||
func (diskAttack) diskPayload(payload *core.DiskOption) error {
 | 
			
		||||
	var cmdFormat string
 | 
			
		||||
	switch payload.Action {
 | 
			
		||||
	case core.DiskWritePayloadAction:
 | 
			
		||||
		cmdFormat = DDWritePayloadCommand
 | 
			
		||||
		if payload.Path == "" {
 | 
			
		||||
			err := initWritePayloadPath(payload)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case core.DiskReadPayloadAction:
 | 
			
		||||
		cmdFormat = DDReadPayloadCommand
 | 
			
		||||
		if payload.Path == "" {
 | 
			
		||||
			err := initReadPayloadPath(payload)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		err := errors.Errorf("invalid payload action")
 | 
			
		||||
		log.Error(fmt.Sprintf("payload action: %s", payload.Action), zap.Error(err))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	byteSize, err := utils.ParseUnit(payload.Size)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error(fmt.Sprintf("fail to get parse size per units , %s", payload.Size), zap.Error(err))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	ddBlocks, err := utils.SplitBytesByProcessNum(byteSize, payload.PayloadProcessNum)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error(fmt.Sprintf("split size, process num %d", payload.PayloadProcessNum), zap.Error(err))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if len(ddBlocks) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	rest := ddBlocks[len(ddBlocks)-1]
 | 
			
		||||
	ddBlocks = ddBlocks[:len(ddBlocks)-1]
 | 
			
		||||
	cmd := exec.Command("bash", "-c", fmt.Sprintf(cmdFormat, payload.Path, rest.BlockSize, rest.Count))
 | 
			
		||||
	if attackConf.Action == core.DiskFillAction {
 | 
			
		||||
		if attackConf.FAllocateOption != nil {
 | 
			
		||||
			cmd := core.FAllocateCommand.Unmarshal(*attackConf.FAllocateOption)
 | 
			
		||||
			output, err := cmd.CombinedOutput()
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
		log.Error(cmd.String()+string(output), zap.Error(err))
 | 
			
		||||
				log.Error(string(output), zap.Error(err))
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			log.Info(string(output))
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, DdOption := range *attackConf.DdOptions {
 | 
			
		||||
			cmd := core.DdCommand.Unmarshal(DdOption)
 | 
			
		||||
			output, err := cmd.CombinedOutput()
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error(string(output), zap.Error(err))
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			log.Info(string(output))
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if attackConf.DdOptions != nil {
 | 
			
		||||
		duration, _ := options.ScheduleDuration()
 | 
			
		||||
		var deadline <-chan time.Time
 | 
			
		||||
		if duration != nil {
 | 
			
		||||
			deadline = time.After(*duration)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(*attackConf.DdOptions) == 0 {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		rest := (*attackConf.DdOptions)[len(*attackConf.DdOptions)-1]
 | 
			
		||||
		*attackConf.DdOptions = (*attackConf.DdOptions)[:len(*attackConf.DdOptions)-1]
 | 
			
		||||
 | 
			
		||||
		cmd := core.DdCommand.Unmarshal(rest)
 | 
			
		||||
		err := utils.ExecWithDeadline(deadline, cmd)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var wg sync.WaitGroup
 | 
			
		||||
		var mu sync.Mutex
 | 
			
		||||
		var errs error
 | 
			
		||||
	wg.Add(len(ddBlocks))
 | 
			
		||||
	for _, sizeBlock := range ddBlocks {
 | 
			
		||||
		cmd := exec.Command("bash", "-c", fmt.Sprintf(cmdFormat, payload.Path, sizeBlock.BlockSize, sizeBlock.Count))
 | 
			
		||||
		wg.Add(len(*attackConf.DdOptions))
 | 
			
		||||
		for _, ddOpt := range *attackConf.DdOptions {
 | 
			
		||||
			cmd := core.DdCommand.Unmarshal(ddOpt)
 | 
			
		||||
 | 
			
		||||
			go func(cmd *exec.Cmd) {
 | 
			
		||||
				defer wg.Done()
 | 
			
		||||
			output, err := cmd.CombinedOutput()
 | 
			
		||||
				err := utils.ExecWithDeadline(deadline, cmd)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
				log.Error(cmd.String()+string(output), zap.Error(err))
 | 
			
		||||
					log.Error(cmd.String(), zap.Error(err))
 | 
			
		||||
					mu.Lock()
 | 
			
		||||
					defer mu.Unlock()
 | 
			
		||||
					errs = multierror.Append(errs, err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			log.Info(string(output))
 | 
			
		||||
			}(cmd)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -147,90 +109,22 @@ func (diskAttack) diskPayload(payload *core.DiskOption) error {
 | 
			
		|||
		if errs != nil {
 | 
			
		||||
			return errs
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// dd command with 'oflag=append conv=notrunc' will append new data in the file.
 | 
			
		||||
const DDFillCommand = "dd if=/dev/zero of=%s bs=%s count=%s iflag=fullblock oflag=append conv=notrunc"
 | 
			
		||||
const FallocateCommand = "fallocate -l %s %s"
 | 
			
		||||
 | 
			
		||||
// diskFill will execute a dd command (DDFillCommand or FallocateCommand)
 | 
			
		||||
// to fill the disk.
 | 
			
		||||
func (diskAttack) diskFill(fill *core.DiskOption) error {
 | 
			
		||||
	if fill.Path == "" {
 | 
			
		||||
		var err error
 | 
			
		||||
		fill.Path, err = utils.CreateTempFile()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error(fmt.Sprintf("unexpected err when CreateTempFile in action: %s", fill.Action))
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fill.Size != "" {
 | 
			
		||||
		fill.Size = strings.Trim(fill.Size, " ")
 | 
			
		||||
	} else if fill.Percent != "" {
 | 
			
		||||
		fill.Percent = strings.Trim(fill.Percent, " ")
 | 
			
		||||
		percent, err := strconv.ParseUint(fill.Percent, 10, 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error(fmt.Sprintf(" unexcepted err when parsing disk percent '%s'", fill.Percent), zap.Error(err))
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		dir := filepath.Dir(fill.Path)
 | 
			
		||||
		totalSize, err := utils.GetDiskTotalSize(dir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("fail to get disk total size", zap.Error(err))
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fill.Size = strconv.FormatUint(totalSize*percent/100, 10) + "c"
 | 
			
		||||
	}
 | 
			
		||||
	var cmd *exec.Cmd
 | 
			
		||||
	if fill.FillByFallocate {
 | 
			
		||||
		cmd = exec.Command("bash", "-c", fmt.Sprintf(FallocateCommand, fill.Size, fill.Path))
 | 
			
		||||
		output, err := cmd.CombinedOutput()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error(string(output), zap.Error(err))
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Info(string(output))
 | 
			
		||||
	} else {
 | 
			
		||||
		byteSize, err := utils.ParseUnit(fill.Size)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("fail to parse disk size", zap.Error(err))
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ddBlocks, err := utils.SplitBytesByProcessNum(byteSize, 1)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("fail to split disk size", zap.Error(err))
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, block := range ddBlocks {
 | 
			
		||||
			cmd = exec.Command("bash", "-c", fmt.Sprintf(DDFillCommand, fill.Path, block.BlockSize, block.Count))
 | 
			
		||||
			output, err := cmd.CombinedOutput()
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error(string(output), zap.Error(err))
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			log.Info(string(output))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (diskAttack) Recover(exp core.Experiment, _ Environment) error {
 | 
			
		||||
	config, err := exp.GetRequestCommand()
 | 
			
		||||
	attackConfig, err := exp.GetRequestCommand()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	option := *config.(*core.DiskOption)
 | 
			
		||||
	switch option.Action {
 | 
			
		||||
	config := *attackConfig.(*core.DiskAttackConfig)
 | 
			
		||||
	switch config.Action {
 | 
			
		||||
	case core.DiskFillAction, core.DiskWritePayloadAction:
 | 
			
		||||
		err = os.Remove(option.Path)
 | 
			
		||||
		err = os.Remove(config.Path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Warn(fmt.Sprintf("recover disk: remove %s failed", option.Path), zap.Error(err))
 | 
			
		||||
			log.Warn(fmt.Sprintf("recover disk: remove %s failed", config.Path), zap.Error(err))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,66 @@
 | 
			
		|||
// Copyright 2021 Chaos Mesh Authors.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
package chaosd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"github.com/chaos-mesh/chaosd/pkg/core"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Test_diskAttack_Attack(t *testing.T) {
 | 
			
		||||
	opt := core.DiskOption{
 | 
			
		||||
		CommonAttackConfig: core.CommonAttackConfig{
 | 
			
		||||
			Action: core.DiskFillAction,
 | 
			
		||||
		},
 | 
			
		||||
		Size:              "10M",
 | 
			
		||||
		Path:              "./a",
 | 
			
		||||
		PayloadProcessNum: 1,
 | 
			
		||||
	}
 | 
			
		||||
	conf, err := opt.PreProcess()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	err = DiskAttack.Attack(conf, Environment{})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	f, err := os.Open("./a")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	fi, err := f.Stat()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(10), fi.Size()>>20)
 | 
			
		||||
	err = os.Remove("./a")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	opt.Action = core.DiskWritePayloadAction
 | 
			
		||||
	opt.PayloadProcessNum = 4
 | 
			
		||||
	wConf, err := opt.PreProcess()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	err = DiskAttack.Attack(wConf, Environment{})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	f, err = os.Open("./a")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	fi, err = f.Stat()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, fi.Size()>>20, int64(2))
 | 
			
		||||
	err = os.Remove("./a")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	opt.Action = core.DiskReadPayloadAction
 | 
			
		||||
	opt.PayloadProcessNum = 4
 | 
			
		||||
	opt.Path = "./"
 | 
			
		||||
	_, err = opt.PreProcess()
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -116,6 +116,12 @@ func (s *httpServer) createProcessAttack(c *gin.Context) {
 | 
			
		|||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := attack.Validate(); err != nil {
 | 
			
		||||
		err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
 | 
			
		||||
		handleError(c, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uid, err := s.chaos.ExecuteAttack(chaosd.ProcessAttack, attack, core.ServerMode)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		handleError(c, err)
 | 
			
		||||
| 
						 | 
				
			
			@ -141,6 +147,12 @@ func (s *httpServer) createNetworkAttack(c *gin.Context) {
 | 
			
		|||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := attack.Validate(); err != nil {
 | 
			
		||||
		err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
 | 
			
		||||
		handleError(c, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uid, err := s.chaos.ExecuteAttack(chaosd.NetworkAttack, attack, core.ServerMode)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		handleError(c, err)
 | 
			
		||||
| 
						 | 
				
			
			@ -166,6 +178,12 @@ func (s *httpServer) createStressAttack(c *gin.Context) {
 | 
			
		|||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := attack.Validate(); err != nil {
 | 
			
		||||
		err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
 | 
			
		||||
		handleError(c, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uid, err := s.chaos.ExecuteAttack(chaosd.StressAttack, attack, core.ServerMode)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		handleError(c, err)
 | 
			
		||||
| 
						 | 
				
			
			@ -185,14 +203,20 @@ func (s *httpServer) createStressAttack(c *gin.Context) {
 | 
			
		|||
// @Failure 500 {object} utils.APIError
 | 
			
		||||
// @Router /api/attack/disk [post]
 | 
			
		||||
func (s *httpServer) createDiskAttack(c *gin.Context) {
 | 
			
		||||
	attack := core.NewDiskOption()
 | 
			
		||||
	if err := c.ShouldBindJSON(attack); err != nil {
 | 
			
		||||
	options := core.NewDiskOption()
 | 
			
		||||
	if err := c.ShouldBindJSON(options); err != nil {
 | 
			
		||||
		c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uid, err := s.chaos.ExecuteAttack(chaosd.DiskAttack, attack, core.ServerMode)
 | 
			
		||||
	attackConfig, err := options.PreProcess()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
 | 
			
		||||
		handleError(c, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uid, err := s.chaos.ExecuteAttack(chaosd.DiskAttack, attackConfig, core.ServerMode)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		handleError(c, err)
 | 
			
		||||
		return
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
// Copyright 2021 Chaos Mesh Authors.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pingcap/log"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ExecWithDeadline(t <-chan time.Time, cmd *exec.Cmd) error {
 | 
			
		||||
	done := make(chan error, 1)
 | 
			
		||||
	var output []byte
 | 
			
		||||
	var err error
 | 
			
		||||
	go func() {
 | 
			
		||||
		output, err = cmd.CombinedOutput()
 | 
			
		||||
		done <- err
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-t:
 | 
			
		||||
		if err := cmd.Process.Kill(); err != nil {
 | 
			
		||||
			log.Error("failed to kill process: ", zap.Error(err))
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case err := <-done:
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error(err.Error()+string(output), zap.Error(err))
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Info(string(output))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
// Copyright 2021 Chaos Mesh Authors.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"reflect"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Command struct {
 | 
			
		||||
	Name string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Command) Unmarshal(val interface{}) *exec.Cmd {
 | 
			
		||||
	v := reflect.ValueOf(val)
 | 
			
		||||
 | 
			
		||||
	var options []string
 | 
			
		||||
	for i := 0; i < v.NumField(); i++ {
 | 
			
		||||
		tag := v.Type().Field(i).Tag.Get(c.Name)
 | 
			
		||||
		if v.Field(i).String() == "" || tag == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if tag == "-" {
 | 
			
		||||
			options = append(options, v.Field(i).String())
 | 
			
		||||
		} else {
 | 
			
		||||
			options = append(options, fmt.Sprintf("%s=%v", tag, v.Field(i).String()))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return exec.Command(c.Name, options...) //
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
// Copyright 2021 Chaos Mesh Authors.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestCommand_Unmarshal(t *testing.T) {
 | 
			
		||||
	type dd struct {
 | 
			
		||||
		If    string `dd:"if"`
 | 
			
		||||
		Of    string `dd:"oflag"`
 | 
			
		||||
		Iflag string `dd:"iflag"`
 | 
			
		||||
	}
 | 
			
		||||
	dc := Command{Name: "dd"}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		d    dd
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "0",
 | 
			
		||||
			d: dd{
 | 
			
		||||
				"/dev/zero",
 | 
			
		||||
				"i,2,3",
 | 
			
		||||
				"",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			cmd := dc.Unmarshal(tt.d)
 | 
			
		||||
			fmt.Println(cmd.String())
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue