Fix bug and implement recover for disk chaos (#73)

* 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>

* add & fix comments

Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com>

* prevent user from writing in an existing file.

Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com>
This commit is contained in:
Andrewmatilde 2021-06-11 15:01:06 +08:00 committed by GitHub
parent b282cbde4c
commit b807243261
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 99 additions and 67 deletions

View File

@ -126,7 +126,6 @@ func NewDiskFillCommand(dep fx.Option, options *core.DiskOption) *cobra.Command
cmd.Flags().StringVarP(&options.Percent, "percent", "c", "", cmd.Flags().StringVarP(&options.Percent, "percent", "c", "",
"'percent' how many percent data of disk will fill in the file path") "'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")
cmd.Flags().BoolVarP(&options.DestroyFile, "destroy", "d", false, "destroy file after filled in or allocated")
return cmd return cmd
} }

View File

@ -47,7 +47,7 @@ func TestServer_DiskFill(t *testing.T) {
Kind: core.DiskAttack, Kind: core.DiskAttack,
}, },
Size: "1024M", Size: "1024M",
Path: "temp", Path: "./temp",
FillByFallocate: true, FillByFallocate: true,
PayloadProcessNum: 1, PayloadProcessNum: 1,
}, },
@ -60,7 +60,7 @@ func TestServer_DiskFill(t *testing.T) {
Kind: core.DiskAttack, Kind: core.DiskAttack,
}, },
Size: "24MB", Size: "24MB",
Path: "temp", Path: "./temp",
FillByFallocate: false, FillByFallocate: false,
PayloadProcessNum: 1, PayloadProcessNum: 1,
}, },
@ -71,15 +71,7 @@ func TestServer_DiskFill(t *testing.T) {
fx.Invoke(func(s *chaosd.Server, tests []diskTest) { fx.Invoke(func(s *chaosd.Server, tests []diskTest) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
f, err := os.Create(tt.option.Path) _, err := s.ExecuteAttack(chaosd.DiskAttack, tt.option, core.CommandMode)
if err != nil {
t.Errorf("unexpected err %v when creating temp file", err)
return
}
if f != nil {
_ = f.Close()
}
_, err = s.ExecuteAttack(chaosd.DiskAttack, tt.option, core.CommandMode)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("DiskFill() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("DiskFill() error = %v, wantErr %v", err, tt.wantErr)
return return
@ -116,7 +108,7 @@ func TestServer_DiskPayload(t *testing.T) {
Kind: core.DiskAttack, Kind: core.DiskAttack,
}, },
Size: "24M", Size: "24M",
Path: "temp", Path: "./temp",
PayloadProcessNum: 1, PayloadProcessNum: 1,
}, },
wantErr: false, wantErr: false,
@ -128,7 +120,7 @@ func TestServer_DiskPayload(t *testing.T) {
Kind: core.DiskAttack, Kind: core.DiskAttack,
}, },
Size: "24M", Size: "24M",
Path: "temp", Path: "./temp",
PayloadProcessNum: 1, PayloadProcessNum: 1,
}, },
wantErr: false, wantErr: false,
@ -138,24 +130,20 @@ func TestServer_DiskPayload(t *testing.T) {
fx.Invoke(func(s *chaosd.Server, tests []diskTest) { fx.Invoke(func(s *chaosd.Server, tests []diskTest) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
f, err := os.Create(tt.option.Path) _, err := s.ExecuteAttack(chaosd.DiskAttack, &core.DiskOption{
if err != nil {
t.Errorf("unexpected err %v when creating temp file", err)
return
}
if f != nil {
_ = f.Close()
}
_, err = s.ExecuteAttack(chaosd.DiskAttack, &core.DiskOption{
CommonAttackConfig: core.CommonAttackConfig{ CommonAttackConfig: core.CommonAttackConfig{
Action: core.DiskFillAction, Action: core.DiskFillAction,
Kind: core.DiskAttack, Kind: core.DiskAttack,
}, },
Size: tt.option.Size, PayloadProcessNum: 1,
Path: "temp", Size: tt.option.Size,
FillByFallocate: true, Path: "./temp",
FillByFallocate: true,
}, core.CommandMode) }, core.CommandMode)
if err != nil {
t.Error(err)
return
}
_, err = s.ExecuteAttack(chaosd.DiskAttack, tt.option, core.CommandMode) _, err = s.ExecuteAttack(chaosd.DiskAttack, tt.option, core.CommandMode)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("DiskPayload() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("DiskPayload() error = %v, wantErr %v", err, tt.wantErr)
@ -185,7 +173,6 @@ func writeArgsToDiskOption(args writeArgs) core.DiskOption {
Path: args.Path, Path: args.Path,
Percent: "", Percent: "",
FillByFallocate: false, FillByFallocate: false,
DestroyFile: false,
PayloadProcessNum: args.PayloadProcessNum, PayloadProcessNum: args.PayloadProcessNum,
} }
} }
@ -272,7 +259,6 @@ func readArgsToDiskOption(args readArgs) core.DiskOption {
Path: args.Path, Path: args.Path,
Percent: "", Percent: "",
FillByFallocate: false, FillByFallocate: false,
DestroyFile: false,
PayloadProcessNum: args.PayloadProcessNum, PayloadProcessNum: args.PayloadProcessNum,
} }
} }

View File

@ -16,6 +16,8 @@ package core
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os"
"strconv" "strconv"
"github.com/chaos-mesh/chaosd/pkg/utils" "github.com/chaos-mesh/chaosd/pkg/utils"
@ -33,9 +35,9 @@ type DiskOption struct {
Size string `json:"size"` Size string `json:"size"`
Path string `json:"path"` Path string `json:"path"`
Percent string `json:"percent"` Percent string `json:"percent"`
FillByFallocate bool `json:"fill_by_fallocate"`
DestroyFile bool `json:"destroy_file"`
PayloadProcessNum uint8 `json:"payload_process_num"` PayloadProcessNum uint8 `json:"payload_process_num"`
FillByFallocate bool `json:"fill_by_fallocate"`
} }
var _ AttackConfig = &DiskOption{} var _ AttackConfig = &DiskOption{}
@ -58,11 +60,35 @@ func (d *DiskOption) Validate() error {
return fmt.Errorf("unknown units of size : %s, DiskOption : %v", d.Size, d) return fmt.Errorf("unknown units of size : %s, DiskOption : %v", d.Size, d)
} }
} }
if d.Action == DiskFillAction {
if d.FillByFallocate && byteSize == 0 { 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, "+ 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) "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 { if d.PayloadProcessNum == 0 {

View File

@ -84,12 +84,6 @@ func (diskAttack) diskPayload(payload *core.DiskOption) error {
if err != nil { if err != nil {
return err return err
} }
defer func() {
err := os.Remove(payload.Path)
if err != nil {
log.Error(fmt.Sprintf("unexpected err when removing temp file %s", payload.Path), zap.Error(err))
}
}()
} }
case core.DiskReadPayloadAction: case core.DiskReadPayloadAction:
cmdFormat = DDReadPayloadCommand cmdFormat = DDReadPayloadCommand
@ -157,7 +151,8 @@ func (diskAttack) diskPayload(payload *core.DiskOption) error {
return nil return nil
} }
const DDFillCommand = "dd if=/dev/zero of=%s bs=%s count=%s iflag=fullblock" // 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" const FallocateCommand = "fallocate -l %s %s"
// diskFill will execute a dd command (DDFillCommand or FallocateCommand) // diskFill will execute a dd command (DDFillCommand or FallocateCommand)
@ -172,15 +167,6 @@ func (diskAttack) diskFill(fill *core.DiskOption) error {
} }
} }
defer func() {
if fill.DestroyFile {
err := os.Remove(fill.Path)
if err != nil {
log.Error(fmt.Sprintf("unexpected err when removing file %s", fill.Path), zap.Error(err))
}
}
}()
if fill.Size != "" { if fill.Size != "" {
fill.Size = strings.Trim(fill.Size, " ") fill.Size = strings.Trim(fill.Size, " ")
} else if fill.Percent != "" { } else if fill.Percent != "" {
@ -196,29 +182,56 @@ func (diskAttack) diskFill(fill *core.DiskOption) error {
log.Error("fail to get disk total size", zap.Error(err)) log.Error("fail to get disk total size", zap.Error(err))
return err return err
} }
fill.Size = strconv.FormatUint(totalSize*percent/100, 10) fill.Size = strconv.FormatUint(totalSize*percent/100, 10) + "c"
} }
var cmd *exec.Cmd var cmd *exec.Cmd
if fill.FillByFallocate { if fill.FillByFallocate {
cmd = exec.Command("bash", "-c", fmt.Sprintf(FallocateCommand, fill.Size, fill.Path)) cmd = exec.Command("bash", "-c", fmt.Sprintf(FallocateCommand, fill.Size, fill.Path))
} else { output, err := cmd.CombinedOutput()
//1Unit means the block size. The bytes size dd read | write is (block size) * (size). if err != nil {
cmd = exec.Command("bash", "-c", fmt.Sprintf(DDFillCommand, fill.Path, fill.Size, "1")) log.Error(string(output), zap.Error(err))
} return err
}
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
} else {
log.Info(string(output)) 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 err return nil
} }
func (diskAttack) Recover(exp core.Experiment, _ Environment) error { func (diskAttack) Recover(exp core.Experiment, _ Environment) error {
log.Info("Recover disk attack will do nothing, because delete | truncate data is too dangerous.") config, err := exp.GetRequestCommand()
if err != nil {
return err
}
option := *config.(*core.DiskOption)
switch option.Action {
case core.DiskFillAction, core.DiskWritePayloadAction:
err = os.Remove(option.Path)
if err != nil {
log.Warn(fmt.Sprintf("recover disk: remove %s failed", option.Path), zap.Error(err))
}
}
return nil return nil
} }

View File

@ -88,9 +88,17 @@ func SplitBytesByProcessNum(bytes uint64, processNum uint8) ([]DdArgBlock, error
bytes -= blockSize bytes -= blockSize
} }
} }
ddArgBlocks = append(ddArgBlocks, DdArgBlock{
BlockSize: "1", if bytes == 0 {
Count: strconv.FormatUint(bytes, 10) + "c", ddArgBlocks = append(ddArgBlocks, DdArgBlock{
}) Count: "0",
BlockSize: "1M",
})
} else {
ddArgBlocks = append(ddArgBlocks, DdArgBlock{
Count: "1",
BlockSize: strconv.FormatUint(bytes, 10) + "c",
})
}
return ddArgBlocks, nil return ddArgBlocks, nil
} }