mirror of https://github.com/chaos-mesh/chaosd.git
Fix issue about disk attack cannot work well in chaos-mesh:physical machine chaos (#236)
* add default value of PayloadProcessNum&FillByFAllocate Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * recover unit-test Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * recover unit-test Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * Enable fill | write in dir. Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * Enable fill | write in dir. Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * Enable fill | write in dir. Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * fix lint Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * fix log Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * ignore some unhandled errors & fix unexported returned value in exported function Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * fix deprecated function Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * complete async disk attack in server side Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * better input args type in command pool Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * complete test for command pool Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * add license Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * add license Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * fix comment Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * add output channel to pools Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * manually test & fix disk attack Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * fix ut in disk attack Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> * fix Boilerplate header Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> --------- Signed-off-by: andrewmatilde <davis6813585853062@outlook.com> Co-authored-by: Cwen Yin <cwenyin0@gmail.com>
This commit is contained in:
parent
e7715f72b9
commit
19a157239e
8
go.mod
8
go.mod
|
|
@ -1,6 +1,7 @@
|
|||
module github.com/chaos-mesh/chaosd
|
||||
|
||||
require (
|
||||
github.com/Jeffail/tunny v0.1.4
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a
|
||||
github.com/chaos-mesh/chaos-mesh v0.9.1-0.20220812140450-4bc7ef589c13
|
||||
|
|
@ -24,11 +25,13 @@ require (
|
|||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samber/lo v1.37.0
|
||||
github.com/samber/mo v1.8.0
|
||||
github.com/segmentio/kafka-go v0.4.31
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe
|
||||
github.com/swaggo/gin-swagger v1.5.0
|
||||
github.com/swaggo/swag v1.8.3
|
||||
|
|
@ -135,9 +138,10 @@ require (
|
|||
go.uber.org/dig v1.14.1 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
|
|
|
|||
18
go.sum
18
go.sum
|
|
@ -70,6 +70,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/Jeffail/tunny v0.1.4 h1:chtpdz+nUtaYQeCKlNBg6GycFF/kGVHOr6A3cmzTJXs=
|
||||
github.com/Jeffail/tunny v0.1.4/go.mod h1:P8xAx4XQl0xsuhjX1DtfaMDCSuavzdb2rwbd0lk+fvo=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
|
|
@ -947,6 +949,10 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
|||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
||||
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
|
||||
github.com/samber/mo v1.8.0 h1:vYjHTfg14JF9tD2NLhpoUsRi9bjyRoYwa4+do0nvbVw=
|
||||
github.com/samber/mo v1.8.0/go.mod h1:BfkrCPuYzVG3ZljnZB783WIJIGk1mcZr9c9CPf8tAxs=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
|
|
@ -1005,6 +1011,8 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag
|
|||
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
|
|
@ -1013,8 +1021,11 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc=
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
|
|
@ -1169,6 +1180,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
|
|
@ -1281,8 +1294,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ package core
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
|
@ -90,6 +89,16 @@ func NewDiskOption() *DiskOption {
|
|||
}
|
||||
}
|
||||
|
||||
func NewDiskOptionForServer() *DiskOption {
|
||||
return &DiskOption{
|
||||
CommonAttackConfig: CommonAttackConfig{
|
||||
Kind: DiskServerAttack,
|
||||
},
|
||||
PayloadProcessNum: 1,
|
||||
FillByFallocate: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (opt *DiskOption) PreProcess() (*DiskAttackConfig, error) {
|
||||
if err := opt.CommonAttackConfig.Validate(); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -191,7 +200,7 @@ func initPath(opt *DiskOption) (string, error) {
|
|||
// 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 {
|
||||
if err := os.WriteFile(opt.Path, b, 0600); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.Remove(opt.Path); err != nil {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 Chaos Mesh Authors.
|
||||
// Copyright 2023 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.
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
// 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 (
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ const (
|
|||
NetworkAttack = "network"
|
||||
StressAttack = "stress"
|
||||
DiskAttack = "disk"
|
||||
DiskServerAttack = "disk-server"
|
||||
ClockAttack = "clock"
|
||||
HostAttack = "host"
|
||||
JVMAttack = "jvm"
|
||||
|
|
@ -109,6 +110,8 @@ func GetAttackByKind(kind string) *AttackConfig {
|
|||
attackConfig = &StressCommand{}
|
||||
case DiskAttack:
|
||||
attackConfig = &DiskAttackConfig{}
|
||||
case DiskServerAttack:
|
||||
attackConfig = &DiskAttackConfig{}
|
||||
case JVMAttack:
|
||||
attackConfig = &JVMCommand{}
|
||||
case ClockAttack:
|
||||
|
|
|
|||
|
|
@ -141,8 +141,8 @@ type JVMStressSpec struct {
|
|||
// only when SQL match the Database, Table and SQLType, chaosd will inject fault
|
||||
// for example:
|
||||
//
|
||||
// SQL is "select * from test.t1",
|
||||
// only when ((Database == "test" || Database == "") && (Table == "t1" || Table == "") && (SQLType == "select" || SQLType == "")) is true, chaosd will inject fault
|
||||
// SQL is "select * from test.t1",
|
||||
// only when ((Database == "test" || Database == "") && (Table == "t1" || Table == "") && (SQLType == "select" || SQLType == "")) is true, chaosd will inject fault
|
||||
type JVMMySQLSpec struct {
|
||||
// the version of mysql-connector-java, only support 5.X.X(set to 5) and 8.X.X(set to 8) now
|
||||
MySQLConnectorVersion string `json:"mysql-connector-version,omitempty"`
|
||||
|
|
|
|||
|
|
@ -14,17 +14,15 @@
|
|||
package chaosd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pingcap/log"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/chaos-mesh/chaosd/pkg/server/utils"
|
||||
pkgUtils "github.com/chaos-mesh/chaosd/pkg/utils"
|
||||
|
||||
"github.com/chaos-mesh/chaosd/pkg/core"
|
||||
)
|
||||
|
|
@ -33,88 +31,71 @@ type diskAttack struct{}
|
|||
|
||||
var DiskAttack AttackType = diskAttack{}
|
||||
|
||||
func (disk diskAttack) Attack(options core.AttackConfig, env Environment) error {
|
||||
func handleDiskAttackOutput(output []byte, err error, c chan interface{}) {
|
||||
if err != nil {
|
||||
log.Error(string(output), zap.Error(err))
|
||||
c <- err
|
||||
}
|
||||
log.Info(string(output))
|
||||
}
|
||||
|
||||
func (diskAttack) Attack(options core.AttackConfig, env Environment) error {
|
||||
err := ApplyDiskAttack(options, env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleOutputChannelError(c chan interface{}) error {
|
||||
close(c)
|
||||
var multiErrs error
|
||||
for i := range c {
|
||||
if err, ok := i.(error); ok {
|
||||
multiErrs = multierror.Append(multiErrs, err)
|
||||
}
|
||||
}
|
||||
if multiErrs != nil {
|
||||
return multiErrs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ApplyDiskAttack(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")
|
||||
}
|
||||
poolSize := getPoolSize(attackConf)
|
||||
outputChan := make(chan interface{}, poolSize+1)
|
||||
if attackConf.Action == core.DiskFillAction {
|
||||
if attackConf.FAllocateOption != nil {
|
||||
cmd := core.FAllocateCommand.Unmarshal(*attackConf.FAllocateOption)
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
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
|
||||
cmdPool := pkgUtils.NewCommandPools(context.Background(), nil, poolSize)
|
||||
env.Chaos.CmdPools[env.AttackUid] = cmdPool
|
||||
fillDisk(attackConf, cmdPool, NewOutputHandler(handleDiskAttackOutput, outputChan))
|
||||
cmdPool.Wait()
|
||||
cmdPool.Close()
|
||||
return handleOutputChannelError(outputChan)
|
||||
}
|
||||
|
||||
if attackConf.DdOptions != nil {
|
||||
duration, _ := options.ScheduleDuration()
|
||||
var deadline <-chan time.Time
|
||||
if duration != nil {
|
||||
deadline = time.After(*duration)
|
||||
var cmdPool *pkgUtils.CommandPools
|
||||
deadline := getDeadline(options)
|
||||
if deadline != nil {
|
||||
cmdPool = pkgUtils.NewCommandPools(context.Background(), deadline, poolSize)
|
||||
}
|
||||
cmdPool = pkgUtils.NewCommandPools(context.Background(), nil, poolSize)
|
||||
env.Chaos.CmdPools[env.AttackUid] = cmdPool
|
||||
|
||||
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(*attackConf.DdOptions))
|
||||
for _, ddOpt := range *attackConf.DdOptions {
|
||||
cmd := core.DdCommand.Unmarshal(ddOpt)
|
||||
|
||||
go func(cmd *exec.Cmd) {
|
||||
defer wg.Done()
|
||||
err := utils.ExecWithDeadline(deadline, cmd)
|
||||
if err != nil {
|
||||
log.Error(cmd.String(), zap.Error(err))
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
errs = multierror.Append(errs, err)
|
||||
return
|
||||
}
|
||||
}(cmd)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
applyPayload(attackConf, cmdPool, NewOutputHandler(handleDiskAttackOutput, outputChan))
|
||||
cmdPool.Wait()
|
||||
cmdPool.Close()
|
||||
return handleOutputChannelError(outputChan)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (diskAttack) Recover(exp core.Experiment, _ Environment) error {
|
||||
func (diskAttack) Recover(exp core.Experiment, env Environment) error {
|
||||
attackConfig, err := exp.GetRequestCommand()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -127,5 +108,10 @@ func (diskAttack) Recover(exp core.Experiment, _ Environment) error {
|
|||
log.Warn(fmt.Sprintf("recover disk: remove %s failed", config.Path), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
if cmdPool, ok := env.Chaos.CmdPools[exp.Uid]; ok {
|
||||
cmdPool.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pingcap/log"
|
||||
"go.uber.org/zap"
|
||||
|
||||
pkgUtils "github.com/chaos-mesh/chaosd/pkg/utils"
|
||||
|
||||
"github.com/chaos-mesh/chaosd/pkg/core"
|
||||
)
|
||||
|
||||
type diskServerAttack struct{}
|
||||
|
||||
var DiskServerAttack AttackType = diskServerAttack{}
|
||||
|
||||
func handleDiskServerOutput(output []byte, err error, _ chan interface{}) {
|
||||
if err != nil {
|
||||
log.Error(string(output), zap.Error(err))
|
||||
}
|
||||
log.Info(string(output))
|
||||
}
|
||||
|
||||
func (diskServerAttack) Attack(options core.AttackConfig, env Environment) error {
|
||||
err := ApplyDiskServerAttack(options, env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type OutputHandler struct {
|
||||
StdoutHandler func([]byte, error, chan interface{})
|
||||
OutputChan chan interface{}
|
||||
}
|
||||
|
||||
func NewOutputHandler(
|
||||
handler func([]byte, error, chan interface{}),
|
||||
outputChan chan interface{}) *OutputHandler {
|
||||
return &OutputHandler{
|
||||
StdoutHandler: handler,
|
||||
OutputChan: outputChan,
|
||||
}
|
||||
}
|
||||
|
||||
func getPoolSize(attackConf *core.DiskAttackConfig) int {
|
||||
poolSize := 1
|
||||
if attackConf.DdOptions != nil && len(*attackConf.DdOptions) > 0 {
|
||||
poolSize = len(*attackConf.DdOptions)
|
||||
}
|
||||
return poolSize
|
||||
}
|
||||
|
||||
func fillDisk(
|
||||
attackConf *core.DiskAttackConfig,
|
||||
cmdPool *pkgUtils.CommandPools,
|
||||
outputHandler *OutputHandler) {
|
||||
if attackConf.FAllocateOption != nil {
|
||||
name, args := core.FAllocateCommand.GetCmdArgs(*attackConf.FAllocateOption)
|
||||
runner := pkgUtils.NewCommandRunner(name, args).
|
||||
WithOutputHandler(outputHandler.StdoutHandler, outputHandler.OutputChan)
|
||||
cmdPool.Start(runner)
|
||||
return
|
||||
}
|
||||
|
||||
for _, DdOption := range *attackConf.DdOptions {
|
||||
name, args := core.DdCommand.GetCmdArgs(DdOption)
|
||||
runner := pkgUtils.NewCommandRunner(name, args).
|
||||
WithOutputHandler(outputHandler.StdoutHandler, outputHandler.OutputChan)
|
||||
cmdPool.Start(runner)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getDeadline(options core.AttackConfig) *time.Time {
|
||||
duration, _ := options.ScheduleDuration()
|
||||
if duration != nil {
|
||||
deadline := time.Now().Add(*duration)
|
||||
return &deadline
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyPayload(
|
||||
attackConf *core.DiskAttackConfig,
|
||||
cmdPool *pkgUtils.CommandPools,
|
||||
outputHandler *OutputHandler) {
|
||||
if len(*attackConf.DdOptions) == 0 {
|
||||
return
|
||||
}
|
||||
rest := (*attackConf.DdOptions)[len(*attackConf.DdOptions)-1]
|
||||
*attackConf.DdOptions = (*attackConf.DdOptions)[:len(*attackConf.DdOptions)-1]
|
||||
name, args := core.DdCommand.GetCmdArgs(rest)
|
||||
runner := pkgUtils.NewCommandRunner(name, args).
|
||||
WithOutputHandler(outputHandler.StdoutHandler, outputHandler.OutputChan)
|
||||
cmdPool.Start(runner)
|
||||
|
||||
for _, ddOpt := range *attackConf.DdOptions {
|
||||
name, args := core.DdCommand.GetCmdArgs(ddOpt)
|
||||
runner := pkgUtils.NewCommandRunner(name, args).
|
||||
WithOutputHandler(outputHandler.StdoutHandler, outputHandler.OutputChan)
|
||||
cmdPool.Start(runner)
|
||||
}
|
||||
}
|
||||
|
||||
func ApplyDiskServerAttack(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")
|
||||
}
|
||||
poolSize := getPoolSize(attackConf)
|
||||
if attackConf.Action == core.DiskFillAction {
|
||||
cmdPool := pkgUtils.NewCommandPools(context.Background(), nil, poolSize)
|
||||
env.Chaos.CmdPools[env.AttackUid] = cmdPool
|
||||
fillDisk(attackConf, cmdPool, NewOutputHandler(handleDiskServerOutput, nil))
|
||||
return nil
|
||||
}
|
||||
|
||||
if attackConf.DdOptions != nil {
|
||||
var cmdPool *pkgUtils.CommandPools
|
||||
deadline := getDeadline(options)
|
||||
if deadline != nil {
|
||||
cmdPool = pkgUtils.NewCommandPools(context.Background(), deadline, poolSize)
|
||||
}
|
||||
cmdPool = pkgUtils.NewCommandPools(context.Background(), nil, poolSize)
|
||||
env.Chaos.CmdPools[env.AttackUid] = cmdPool
|
||||
|
||||
applyPayload(attackConf, cmdPool, NewOutputHandler(handleDiskServerOutput, nil))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (diskServerAttack) Recover(exp core.Experiment, env Environment) error {
|
||||
attackConfig, err := exp.GetRequestCommand()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config := *attackConfig.(*core.DiskAttackConfig)
|
||||
|
||||
switch config.Action {
|
||||
case core.DiskFillAction, core.DiskWritePayloadAction:
|
||||
err = os.Remove(config.Path)
|
||||
if err != nil {
|
||||
log.Warn(fmt.Sprintf("recover disk: remove %s failed", config.Path), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
if cmdPool, ok := env.Chaos.CmdPools[exp.Uid]; ok {
|
||||
log.Info(fmt.Sprintf("stop disk attack,read: %s", config.Path))
|
||||
cmdPool.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 Chaos Mesh Authors.
|
||||
// Copyright 2023 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.
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
// 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 (
|
||||
|
|
@ -19,6 +20,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/chaos-mesh/chaosd/pkg/core"
|
||||
"github.com/chaos-mesh/chaosd/pkg/utils"
|
||||
)
|
||||
|
||||
func Test_diskAttack_Attack(t *testing.T) {
|
||||
|
|
@ -30,9 +32,15 @@ func Test_diskAttack_Attack(t *testing.T) {
|
|||
Path: "./a",
|
||||
PayloadProcessNum: 1,
|
||||
}
|
||||
env := Environment{
|
||||
AttackUid: "a",
|
||||
Chaos: &Server{
|
||||
CmdPools: make(map[string]*utils.CommandPools),
|
||||
},
|
||||
}
|
||||
conf, err := opt.PreProcess()
|
||||
assert.NoError(t, err)
|
||||
err = DiskAttack.Attack(conf, Environment{})
|
||||
err = DiskAttack.Attack(conf, env)
|
||||
assert.NoError(t, err)
|
||||
|
||||
f, err := os.Open("./a")
|
||||
|
|
@ -47,7 +55,7 @@ func Test_diskAttack_Attack(t *testing.T) {
|
|||
opt.PayloadProcessNum = 4
|
||||
wConf, err := opt.PreProcess()
|
||||
assert.NoError(t, err)
|
||||
err = DiskAttack.Attack(wConf, Environment{})
|
||||
err = DiskAttack.Attack(wConf, env)
|
||||
assert.NoError(t, err)
|
||||
|
||||
f, err = os.Open("./a")
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ func (s *Server) RecoverAttack(uid string) error {
|
|||
attackType = StressAttack
|
||||
case core.DiskAttack:
|
||||
attackType = DiskAttack
|
||||
case core.DiskServerAttack:
|
||||
attackType = DiskServerAttack
|
||||
case core.JVMAttack:
|
||||
attackType = JVMAttack
|
||||
case core.ClockAttack:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/chaos-mesh/chaosd/pkg/config"
|
||||
"github.com/chaos-mesh/chaosd/pkg/core"
|
||||
"github.com/chaos-mesh/chaosd/pkg/scheduler"
|
||||
"github.com/chaos-mesh/chaosd/pkg/utils"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
|
@ -30,6 +31,8 @@ type Server struct {
|
|||
tcRule core.TCRuleStore
|
||||
conf *config.Config
|
||||
svr *chaosdaemon.DaemonServer
|
||||
|
||||
CmdPools map[string]*utils.CommandPools
|
||||
}
|
||||
|
||||
func NewServer(
|
||||
|
|
@ -51,5 +54,6 @@ func NewServer(
|
|||
iptablesRule: iptables,
|
||||
tcRule: tc,
|
||||
svr: svr,
|
||||
CmdPools: make(map[string]*utils.CommandPools),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ package httpserver
|
|||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
|
@ -37,7 +37,7 @@ var (
|
|||
errMissingClientCert = utils.ErrAuth.New("Sorry, but you need to provide a client certificate to continue")
|
||||
)
|
||||
|
||||
func (s *httpServer) serverMode() string {
|
||||
func (s *HttpServer) serverMode() string {
|
||||
if len(s.conf.SSLCertFile) > 0 {
|
||||
if len(s.conf.SSLClientCAFile) > 0 {
|
||||
return MTLSServer
|
||||
|
|
@ -47,7 +47,7 @@ func (s *httpServer) serverMode() string {
|
|||
return HTTPServer
|
||||
}
|
||||
|
||||
func (s *httpServer) startHttpsServer() (err error) {
|
||||
func (s *HttpServer) startHttpsServer() (err error) {
|
||||
mode := s.serverMode()
|
||||
if mode == HTTPServer {
|
||||
return nil
|
||||
|
|
@ -60,7 +60,7 @@ func (s *httpServer) startHttpsServer() (err error) {
|
|||
if mode == MTLSServer {
|
||||
log.Info("starting HTTPS server with Client Auth", zap.String("address", httpsServerAddr))
|
||||
|
||||
caCert, ioErr := ioutil.ReadFile(s.conf.SSLClientCAFile)
|
||||
caCert, ioErr := os.ReadFile(s.conf.SSLClientCAFile)
|
||||
if ioErr != nil {
|
||||
err = ioErr
|
||||
return
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
"github.com/chaos-mesh/chaosd/pkg/core"
|
||||
)
|
||||
|
||||
func (s *httpServer) listExperiments(c *gin.Context) {
|
||||
func (s *HttpServer) listExperiments(c *gin.Context) {
|
||||
mode, ok := c.GetQuery("launch_mode")
|
||||
var chaosList []*core.Experiment
|
||||
var err error
|
||||
|
|
@ -32,17 +32,17 @@ func (s *httpServer) listExperiments(c *gin.Context) {
|
|||
chaosList, err = s.exp.List(context.Background())
|
||||
}
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, chaosList)
|
||||
}
|
||||
|
||||
func (s *httpServer) listExperimentRuns(c *gin.Context) {
|
||||
func (s *HttpServer) listExperimentRuns(c *gin.Context) {
|
||||
uid := c.Param("uid")
|
||||
runsList, err := s.chaos.ExpRun.ListByExperimentUID(context.Background(), uid)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, runsList)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import (
|
|||
"github.com/chaos-mesh/chaosd/pkg/swaggerserver"
|
||||
)
|
||||
|
||||
type httpServer struct {
|
||||
type HttpServer struct {
|
||||
conf *config.Config
|
||||
chaos *chaosd.Server
|
||||
exp core.ExperimentStore
|
||||
|
|
@ -40,15 +40,15 @@ func NewServer(
|
|||
conf *config.Config,
|
||||
chaos *chaosd.Server,
|
||||
exp core.ExperimentStore,
|
||||
) *httpServer {
|
||||
return &httpServer{
|
||||
) *HttpServer {
|
||||
return &HttpServer{
|
||||
conf: conf,
|
||||
chaos: chaos,
|
||||
exp: exp,
|
||||
}
|
||||
}
|
||||
|
||||
func Register(s *httpServer, scheduler scheduler.Scheduler) {
|
||||
func Register(s *HttpServer, scheduler scheduler.Scheduler) {
|
||||
if s.conf.Platform != config.LocalPlatform {
|
||||
return
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ func Register(s *httpServer, scheduler scheduler.Scheduler) {
|
|||
scheduler.Start()
|
||||
}
|
||||
|
||||
func (s *httpServer) startHttpServer() error {
|
||||
func (s *HttpServer) startHttpServer() error {
|
||||
httpServerAddr := s.conf.Address()
|
||||
log.Info("starting HTTP server", zap.String("address", httpServerAddr))
|
||||
e := gin.Default()
|
||||
|
|
@ -78,7 +78,7 @@ func (s *httpServer) startHttpServer() error {
|
|||
return e.Run(httpServerAddr)
|
||||
}
|
||||
|
||||
func (s *httpServer) handler(engine *gin.Engine) {
|
||||
func (s *HttpServer) handler(engine *gin.Engine) {
|
||||
api := engine.Group("/api")
|
||||
{
|
||||
api.GET("/swagger/*any", swaggerserver.Handler())
|
||||
|
|
@ -107,7 +107,7 @@ func (s *httpServer) handler(engine *gin.Engine) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *httpServer) systemHandler(engine *gin.Engine) {
|
||||
func (s *HttpServer) systemHandler(engine *gin.Engine) {
|
||||
api := engine.Group("/api")
|
||||
system := api.Group("/system")
|
||||
{
|
||||
|
|
@ -125,10 +125,10 @@ func (s *httpServer) systemHandler(engine *gin.Engine) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/process [post]
|
||||
func (s *httpServer) createProcessAttack(c *gin.Context) {
|
||||
func (s *HttpServer) createProcessAttack(c *gin.Context) {
|
||||
attack := core.NewProcessCommand()
|
||||
if err := c.ShouldBindJSON(attack); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -157,10 +157,10 @@ func (s *httpServer) createProcessAttack(c *gin.Context) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/network [post]
|
||||
func (s *httpServer) createNetworkAttack(c *gin.Context) {
|
||||
func (s *HttpServer) createNetworkAttack(c *gin.Context) {
|
||||
attack := core.NewNetworkCommand()
|
||||
if err := c.ShouldBindJSON(attack); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -189,10 +189,10 @@ func (s *httpServer) createNetworkAttack(c *gin.Context) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/stress [post]
|
||||
func (s *httpServer) createStressAttack(c *gin.Context) {
|
||||
func (s *HttpServer) createStressAttack(c *gin.Context) {
|
||||
attack := core.NewStressCommand()
|
||||
if err := c.ShouldBindJSON(attack); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -221,10 +221,10 @@ func (s *httpServer) createStressAttack(c *gin.Context) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/disk [post]
|
||||
func (s *httpServer) createDiskAttack(c *gin.Context) {
|
||||
options := core.NewDiskOption()
|
||||
func (s *HttpServer) createDiskAttack(c *gin.Context) {
|
||||
options := core.NewDiskOptionForServer()
|
||||
if err := c.ShouldBindJSON(options); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -236,7 +236,7 @@ func (s *httpServer) createDiskAttack(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
uid, err := s.chaos.ExecuteAttack(chaosd.DiskAttack, attackConfig, core.ServerMode)
|
||||
uid, err := s.chaos.ExecuteAttack(chaosd.DiskServerAttack, attackConfig, core.ServerMode)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
|
|
@ -254,10 +254,10 @@ func (s *httpServer) createDiskAttack(c *gin.Context) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/clock [post]
|
||||
func (s *httpServer) createClockAttack(c *gin.Context) {
|
||||
func (s *HttpServer) createClockAttack(c *gin.Context) {
|
||||
options := core.NewClockOption()
|
||||
if err := c.ShouldBindJSON(options); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -286,10 +286,10 @@ func (s *httpServer) createClockAttack(c *gin.Context) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/http [post]
|
||||
func (s *httpServer) createHTTPAttack(c *gin.Context) {
|
||||
func (s *HttpServer) createHTTPAttack(c *gin.Context) {
|
||||
attack := core.NewHTTPAttackOption()
|
||||
if err := c.ShouldBindJSON(attack); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -318,10 +318,10 @@ func (s *httpServer) createHTTPAttack(c *gin.Context) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/jvm [post]
|
||||
func (s *httpServer) createJVMAttack(c *gin.Context) {
|
||||
func (s *HttpServer) createJVMAttack(c *gin.Context) {
|
||||
options := core.NewJVMCommand()
|
||||
if err := c.ShouldBindJSON(options); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -350,10 +350,10 @@ func (s *httpServer) createJVMAttack(c *gin.Context) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/kafka [post]
|
||||
func (s *httpServer) createKafkaAttack(c *gin.Context) {
|
||||
func (s *HttpServer) createKafkaAttack(c *gin.Context) {
|
||||
options := core.NewKafkaCommand()
|
||||
if err := c.ShouldBindJSON(options); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -382,10 +382,10 @@ func (s *httpServer) createKafkaAttack(c *gin.Context) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/vm [post]
|
||||
func (s *httpServer) createVMAttack(c *gin.Context) {
|
||||
func (s *HttpServer) createVMAttack(c *gin.Context) {
|
||||
options := core.NewVMOption()
|
||||
if err := c.ShouldBindJSON(options); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -414,10 +414,10 @@ func (s *httpServer) createVMAttack(c *gin.Context) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/redis [post]
|
||||
func (s *httpServer) createRedisAttack(c *gin.Context) {
|
||||
func (s *HttpServer) createRedisAttack(c *gin.Context) {
|
||||
attack := core.NewRedisCommand()
|
||||
if err := c.ShouldBindJSON(attack); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -446,10 +446,10 @@ func (s *httpServer) createRedisAttack(c *gin.Context) {
|
|||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/user_defined [post]
|
||||
func (s *httpServer) createUserDefinedAttack(c *gin.Context) {
|
||||
func (s *HttpServer) createUserDefinedAttack(c *gin.Context) {
|
||||
attack := core.NewUserDefinedOption()
|
||||
if err := c.ShouldBindJSON(attack); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -477,7 +477,7 @@ func (s *httpServer) createUserDefinedAttack(c *gin.Context) {
|
|||
// @Success 200 {object} utils.Response
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/{uid} [delete]
|
||||
func (s *httpServer) recoverAttack(c *gin.Context) {
|
||||
func (s *HttpServer) recoverAttack(c *gin.Context) {
|
||||
uid := c.Param("uid")
|
||||
err := s.chaos.RecoverAttack(uid)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ type healthInfo struct {
|
|||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (s *httpServer) healthcheck(c *gin.Context) {
|
||||
func (s *HttpServer) healthcheck(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, healthInfo{Status: 0})
|
||||
}
|
||||
|
||||
func (s *httpServer) version(c *gin.Context) {
|
||||
func (s *HttpServer) version(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, version.Get())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
|
|
@ -24,6 +25,16 @@ type Command struct {
|
|||
}
|
||||
|
||||
func (c Command) Unmarshal(val interface{}) *exec.Cmd {
|
||||
name, args := c.GetCmdArgs(val)
|
||||
return exec.Command(name, args...)
|
||||
}
|
||||
|
||||
func (c Command) UnmarshalWithCtx(ctx context.Context, val interface{}) *exec.Cmd {
|
||||
name, args := c.GetCmdArgs(val)
|
||||
return exec.CommandContext(ctx, name, args...)
|
||||
}
|
||||
|
||||
func (c Command) GetCmdArgs(val interface{}) (string, []string) {
|
||||
v := reflect.ValueOf(val)
|
||||
|
||||
var options []string
|
||||
|
|
@ -39,5 +50,5 @@ func (c Command) Unmarshal(val interface{}) *exec.Cmd {
|
|||
options = append(options, fmt.Sprintf("%s=%v", tag, v.Field(i).String()))
|
||||
}
|
||||
}
|
||||
return exec.Command(c.Name, options...) //
|
||||
return c.Name, options
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 Chaos Mesh Authors.
|
||||
// Copyright 2023 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.
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
// 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 (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2023 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Jeffail/tunny"
|
||||
"github.com/samber/lo"
|
||||
"github.com/samber/mo"
|
||||
)
|
||||
|
||||
// CommandPools is a group of commands runner
|
||||
type CommandPools struct {
|
||||
cancel context.CancelFunc
|
||||
pools *tunny.Pool
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewCommandPools returns a new CommandPools
|
||||
func NewCommandPools(ctx context.Context, deadline *time.Time, size int) *CommandPools {
|
||||
var ctx2 context.Context
|
||||
var cancel context.CancelFunc
|
||||
if deadline != nil {
|
||||
ctx2, cancel = context.WithDeadline(ctx, *deadline)
|
||||
} else {
|
||||
ctx2, cancel = context.WithCancel(ctx)
|
||||
}
|
||||
return &CommandPools{
|
||||
cancel: cancel,
|
||||
pools: tunny.NewFunc(size, func(payload interface{}) interface{} {
|
||||
cmdPayload, ok := payload.(lo.Tuple2[string, []string])
|
||||
if !ok {
|
||||
return mo.Err[[]byte](fmt.Errorf("payload is not CommandPayload"))
|
||||
}
|
||||
name, args := cmdPayload.Unpack()
|
||||
cmd := exec.CommandContext(ctx2, name, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return mo.Err[[]byte](fmt.Errorf("%s: %s", err, string(output)))
|
||||
}
|
||||
return mo.Ok[[]byte](output)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
type CommandRunner struct {
|
||||
Name string
|
||||
Args []string
|
||||
|
||||
outputHandler func([]byte, error, chan interface{})
|
||||
outputChanel chan interface{}
|
||||
}
|
||||
|
||||
func NewCommandRunner(name string, args []string) *CommandRunner {
|
||||
return &CommandRunner{
|
||||
Name: name,
|
||||
Args: args,
|
||||
outputHandler: func(bytes []byte, err error, c chan interface{}) {},
|
||||
outputChanel: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CommandRunner) WithOutputHandler(
|
||||
handler func([]byte, error, chan interface{}),
|
||||
outputChanel chan interface{},
|
||||
) *CommandRunner {
|
||||
r.outputHandler = handler
|
||||
r.outputChanel = outputChanel
|
||||
return r
|
||||
}
|
||||
|
||||
func (p *CommandPools) Process(name string, args []string) ([]byte, error) {
|
||||
result, ok := p.pools.Process(lo.Tuple2[string, []string]{
|
||||
A: name,
|
||||
B: args,
|
||||
}).(mo.Result[[]byte])
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("payload is not Result[[]byte]")
|
||||
}
|
||||
return result.Get()
|
||||
}
|
||||
|
||||
// Start command async.
|
||||
func (p *CommandPools) Start(runner *CommandRunner) {
|
||||
p.wg.Add(1)
|
||||
go func() {
|
||||
output, err := p.Process(runner.Name, runner.Args)
|
||||
runner.outputHandler(output, err, runner.outputChanel)
|
||||
p.wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *CommandPools) Wait() {
|
||||
p.wg.Wait()
|
||||
}
|
||||
|
||||
func (p *CommandPools) Close() {
|
||||
p.cancel()
|
||||
p.Wait()
|
||||
p.pools.Close()
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2023 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 (
|
||||
"context"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pingcap/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestCommandPools_Cancel(t *testing.T) {
|
||||
now := time.Now()
|
||||
cmdPools := NewCommandPools(context.Background(), nil, 1)
|
||||
var gErr []error
|
||||
runner := NewCommandRunner("sleep", []string{"10s"}).
|
||||
WithOutputHandler(func(output []byte, err error, _ chan interface{}) {
|
||||
if err != nil {
|
||||
log.Error(string(output), zap.Error(err))
|
||||
gErr = append(gErr, err)
|
||||
}
|
||||
log.Info(string(output))
|
||||
}, nil)
|
||||
cmdPools.Start(runner)
|
||||
cmdPools.Close()
|
||||
assert.Less(t, time.Since(now).Seconds(), 10.0)
|
||||
assert.Equal(t, 1, len(gErr))
|
||||
}
|
||||
|
||||
func TestCommandPools_Deadline(t *testing.T) {
|
||||
now := time.Now()
|
||||
deadline := time.Now().Add(time.Millisecond * 50)
|
||||
cmdPools := NewCommandPools(context.Background(), &deadline, 1)
|
||||
var gErr []error
|
||||
runner := NewCommandRunner("sleep", []string{"10s"}).
|
||||
WithOutputHandler(func(output []byte, err error, _ chan interface{}) {
|
||||
if err != nil {
|
||||
log.Error(string(output), zap.Error(err))
|
||||
gErr = append(gErr, err)
|
||||
}
|
||||
log.Info(string(output))
|
||||
}, nil)
|
||||
cmdPools.Start(runner)
|
||||
cmdPools.Wait()
|
||||
assert.Less(t, math.Abs(float64(time.Since(now).Milliseconds()-50)), 10.0)
|
||||
assert.Equal(t, 1, len(gErr))
|
||||
|
||||
}
|
||||
|
||||
func TestCommandPools_Normal(t *testing.T) {
|
||||
now := time.Now()
|
||||
cmdPools := NewCommandPools(context.Background(), nil, 1)
|
||||
var gErr []error
|
||||
runner := NewCommandRunner("sleep", []string{"1s"}).
|
||||
WithOutputHandler(func(output []byte, err error, _ chan interface{}) {
|
||||
if err != nil {
|
||||
log.Error(string(output), zap.Error(err))
|
||||
gErr = append(gErr, err)
|
||||
}
|
||||
log.Info(string(output))
|
||||
}, nil)
|
||||
cmdPools.Start(runner)
|
||||
cmdPools.Wait()
|
||||
assert.Less(t, time.Since(now).Seconds(), 2.0)
|
||||
assert.Equal(t, 0, len(gErr))
|
||||
}
|
||||
Loading…
Reference in New Issue