Merge branch 'main' into file

This commit is contained in:
WangXiang 2022-02-17 16:50:46 +08:00 committed by GitHub
commit 684c9f93b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 336 additions and 123 deletions

View File

@ -48,9 +48,10 @@ jobs:
run: |
# download tools
curl -fsSL -o byteman.tar.gz https://mirrors.chaos-mesh.org/latest/byteman.tar.gz
BYTEMAN_DIR=byteman-chaos-mesh-download-v4.0.18-0.9
curl -fsSL -o ${BYTEMAN_DIR}.tar.gz https://mirrors.chaos-mesh.org/${BYTEMAN_DIR}.tar.gz
curl -fsSL -o stress-ng https://mirrors.chaos-mesh.org/latest/stress-ng
tar zxvf byteman.tar.gz
tar zxvf ${BYTEMAN_DIR}.tar.gz
chmod +x ./stress-ng
# prepare package
@ -58,7 +59,7 @@ jobs:
mkdir chaosd-latest-linux-amd64/tools
mv bin/chaosd chaosd-latest-linux-amd64/
mv bin/PortOccupyTool chaosd-latest-linux-amd64/tools/
mv byteman chaosd-latest-linux-amd64/tools/
mv ${BYTEMAN_DIR} chaosd-latest-linux-amd64/tools/byteman
mv stress-ng chaosd-latest-linux-amd64/tools/
# upload package

View File

@ -49,9 +49,10 @@ jobs:
GIT_TAG=${GITHUB_REF##*/}
# download tools
curl -fsSL -o byteman.tar.gz https://mirrors.chaos-mesh.org/latest/byteman.tar.gz
BYTEMAN_DIR=byteman-chaos-mesh-download-v4.0.18-0.9
curl -fsSL -o ${BYTEMAN_DIR}.tar.gz https://mirrors.chaos-mesh.org/${BYTEMAN_DIR}.tar.gz
curl -fsSL -o stress-ng https://mirrors.chaos-mesh.org/latest/stress-ng
tar zxvf byteman.tar.gz
tar zxvf ${BYTEMAN_DIR}.tar.gz
chmod +x ./stress-ng
# prepare package
@ -59,7 +60,7 @@ jobs:
mkdir chaosd-${GIT_TAG}-linux-amd64/tools
mv bin/chaosd chaosd-${GIT_TAG}-linux-amd64/
mv bin/PortOccupyTool chaosd-${GIT_TAG}-linux-amd64/tools/
mv byteman chaosd-${GIT_TAG}-linux-amd64/tools/
mv ${BYTEMAN_DIR} chaosd-${GIT_TAG}-linux-amd64/tools/byteman
mv stress-ng chaosd-${GIT_TAG}-linux-amd64/tools/
# upload package

View File

@ -23,6 +23,7 @@ import (
)
const (
// jvm action
JVMLatencyAction = "latency"
JVMExceptionAction = "exception"
JVMReturnAction = "return"
@ -30,21 +31,56 @@ const (
JVMGCAction = "gc"
JVMRuleFileAction = "rule-file"
JVMRuleDataAction = "rule-data"
// for action 'gc' and 'stress'
GCHelper = "org.chaos_mesh.byteman.helper.GCHelper"
StressHelper = "org.chaos_mesh.byteman.helper.StressHelper"
// the trigger point for 'gc' and 'stress'
TriggerClass = "org.chaos_mesh.chaos_agent.TriggerThread"
TriggerMethod = "triggerFunc"
)
// byteman rule template
const (
SimpleRuleTemplate = `
RULE {{.Name}}
CLASS {{.Class}}
METHOD {{.Method}}
AT ENTRY
IF true
DO
{{.Do}};
ENDRULE
`
CompleteRuleTemplate = `
RULE {{.Name}}
CLASS {{.Class}}
METHOD {{.Method}}
HELPER {{.Helper}}
AT ENTRY
BIND {{.Bind}};
IF {{.Condition}}
DO
{{.Do}};
ENDRULE
`
)
type JVMCommand struct {
CommonAttackConfig
JVMCommonSpec
JVMClassMethodSpec
JVMStressSpec
// rule name, should be unique, and will generate by chaosd automatically
Name string `json:"name,omitempty"`
// Java class
Class string `json:"class,omitempty"`
// the method in Java class
Method string `json:"method,omitempty"`
// fault action, values can be latency, exception, return, stress
// fault action, values can be latency, exception, return, stress, gc, rule-file, rule-data
Action string `json:"action,omitempty"`
// the return value for action 'return'
@ -56,35 +92,50 @@ type JVMCommand struct {
// the latency duration for action 'latency'
LatencyDuration int `json:"latency,omitempty"`
// the CPU core number, only set it when action is stress
CPUCount int `json:"cpu-count,omitempty"`
// btm rule file path for action 'rule-file'
RuleFile string `json:"rule-file,omitempty"`
// the memory type to be located, only set it when action is stress, the value can be 'stack' or 'heap'
MemoryType string `json:"mem-type,omitempty"`
// attach or agent
Type string
// RuleData used to save the rule file's data, will use it when recover, for action 'rule-data'
RuleData string `json:"rule-data,omitempty"`
}
type JVMCommonSpec struct {
// the port of agent server
Port int `json:"port,omitempty"`
// the pid of Java process which needs to attach
// the pid of Java process which need to attach
Pid int `json:"pid,omitempty"`
}
// btm rule file path
RuleFile string `json:"rule-file,omitempty"`
type JVMClassMethodSpec struct {
// Java class
Class string `json:"class,omitempty"`
// RuleData used to save the rule file's data, will use it when recover
RuleData string `json:"rule-data,omitempty"`
// the method in Java class
Method string `json:"method,omitempty"`
}
// below is only used for template
Do string `json:"-"`
type JVMStressSpec struct {
// the CPU core number need to use, only set it when action is stress
CPUCount int `json:"cpu-count,omitempty"`
StressType string `json:"-"`
// the memory type need to locate, only set it when action is stress, the value can be 'stack' or 'heap'
MemoryType string `json:"mem-type,omitempty"`
}
StressValueName string `json:"-"`
type BytemanTemplateSpec struct {
Name string
Class string
Method string
Helper string
Bind string
Condition string
Do string
StressValue string `json:"-"`
// below is only used for stress template
StressType string
StressValueName string
StressValue string
}
func (j *JVMCommand) Validate() error {
@ -108,7 +159,7 @@ func (j *JVMCommand) Validate() error {
return errors.New("class not provided")
}
if len(j.Method) == 0 {
if len(j.JVMClassMethodSpec.Method) == 0 {
return errors.New("method not provided")
}
case JVMRuleFileAction:

View File

@ -32,62 +32,86 @@ func TestJVMCommand(t *testing.T) {
},
{
&JVMCommand{
Pid: 1234,
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
},
"action not provided",
},
{
&JVMCommand{
Pid: 1234,
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Action: "test",
},
"action test not supported",
},
{
&JVMCommand{
Pid: 1234,
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Action: JVMLatencyAction,
},
"class not provided",
},
{
&JVMCommand{
Pid: 1234,
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Action: JVMExceptionAction,
Class: "test",
JVMClassMethodSpec: JVMClassMethodSpec{
Class: "test",
},
},
"method not provided",
},
{
&JVMCommand{
Pid: 1234,
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Action: JVMExceptionAction,
Class: "test",
Method: "test",
JVMClassMethodSpec: JVMClassMethodSpec{
Class: "test",
Method: "test",
},
},
"",
},
{
&JVMCommand{
Pid: 1234,
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Action: JVMStressAction,
},
"must set one of cpu-count and mem-type",
},
{
&JVMCommand{
Pid: 1234,
Action: JVMStressAction,
CPUCount: 1,
MemoryType: "heap",
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Action: JVMStressAction,
JVMStressSpec: JVMStressSpec{
CPUCount: 1,
MemoryType: "heap",
},
},
"inject stress on both CPU and memory is not support now",
},
{
&JVMCommand{
Pid: 1234,
Action: JVMStressAction,
CPUCount: 1,
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Action: JVMStressAction,
JVMStressSpec: JVMStressSpec{
CPUCount: 1,
},
},
"",
},

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"text/template"
@ -29,30 +30,6 @@ import (
"github.com/chaos-mesh/chaosd/pkg/core"
)
const ruleTemplate = `
RULE {{.Name}}
CLASS {{.Class}}
METHOD {{.Method}}
AT ENTRY
IF true
DO
{{.Do}};
ENDRULE
`
const stressRuleTemplate = `
RULE {{.Name}}
STRESS {{.StressType}}
{{.StressValueName}} {{.StressValue}}
ENDRULE
`
const gcRuleTemplate = `
RULE {{.Name}}
GC
ENDRULE
`
type jvmAttack struct{}
var JVMAttack AttackType = jvmAttack{}
@ -83,13 +60,25 @@ func (j jvmAttack) Attack(options core.AttackConfig, env Environment) (err error
log.Debug(string(output), zap.Error(err))
}
// submit helper jar
bmSubmitCmd := fmt.Sprintf(bmSubmitCommand, attack.Port, "b", fmt.Sprintf("%s/lib/byteman-helper.jar", os.Getenv("BYTEMAN_HOME")))
cmd = exec.Command("bash", "-c", bmSubmitCmd)
output, err = cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return err
}
if len(output) > 0 {
log.Info("submit helper", zap.String("output", string(output)))
}
// submit rules
ruleFile, err := j.generateRuleFile(attack)
if err != nil {
return err
}
bmSubmitCmd := fmt.Sprintf(bmSubmitCommand, attack.Port, "l", ruleFile)
bmSubmitCmd = fmt.Sprintf(bmSubmitCommand, attack.Port, "l", ruleFile)
cmd = exec.Command("bash", "-c", bmSubmitCmd)
output, err = cmd.CombinedOutput()
if err != nil {
@ -127,49 +116,11 @@ func (j jvmAttack) generateRuleFile(attack *core.JVMCommand) (string, error) {
return attack.RuleFile, nil
}
if len(attack.Do) == 0 {
switch attack.Action {
case core.JVMLatencyAction:
attack.Do = fmt.Sprintf("Thread.sleep(%d)", attack.LatencyDuration)
case core.JVMExceptionAction:
attack.Do = fmt.Sprintf("throw new %s", attack.ThrowException)
case core.JVMReturnAction:
attack.Do = fmt.Sprintf("return %s", attack.ReturnValue)
case core.JVMStressAction:
if attack.CPUCount > 0 {
attack.StressType = "CPU"
attack.StressValueName = "CPUCOUNT"
attack.StressValue = fmt.Sprintf("%d", attack.CPUCount)
} else {
attack.StressType = "MEMORY"
attack.StressValueName = "MEMORYTYPE"
attack.StressValue = attack.MemoryType
}
}
}
buf := new(bytes.Buffer)
var t *template.Template
switch attack.Action {
case core.JVMStressAction:
t = template.Must(template.New("byteman rule").Parse(stressRuleTemplate))
case core.JVMExceptionAction, core.JVMLatencyAction, core.JVMReturnAction:
t = template.Must(template.New("byteman rule").Parse(ruleTemplate))
case core.JVMGCAction:
t = template.Must(template.New("byteman rule").Parse(gcRuleTemplate))
default:
return "", errors.Errorf("jvm action %s not supported", attack.Action)
}
if t == nil {
return "", errors.Errorf("parse byeman rule template failed")
}
err = t.Execute(buf, attack)
attack.RuleData, err = generateRuleData(attack)
if err != nil {
log.Error("executing template", zap.Error(err))
return "", err
}
attack.RuleData = buf.String()
filename, err := writeDataIntoFile(attack.RuleData, "rule.btm")
if err != nil {
return "", err
@ -204,6 +155,64 @@ func (j jvmAttack) Recover(exp core.Experiment, env Environment) error {
return nil
}
func generateRuleData(attack *core.JVMCommand) (string, error) {
bytemanTemplateSpec := core.BytemanTemplateSpec{
Name: attack.Name,
Class: attack.Class,
Method: attack.Method,
}
switch attack.Action {
case core.JVMLatencyAction:
bytemanTemplateSpec.Do = fmt.Sprintf("Thread.sleep(%d)", attack.LatencyDuration)
case core.JVMExceptionAction:
bytemanTemplateSpec.Do = fmt.Sprintf("throw new %s", attack.ThrowException)
case core.JVMReturnAction:
bytemanTemplateSpec.Do = fmt.Sprintf("return %s", attack.ReturnValue)
case core.JVMStressAction:
bytemanTemplateSpec.Helper = core.StressHelper
bytemanTemplateSpec.Class = core.TriggerClass
bytemanTemplateSpec.Method = core.TriggerMethod
// the bind and condition is useless, only used for fill the template
bytemanTemplateSpec.Bind = "flag:boolean=true"
bytemanTemplateSpec.Condition = "true"
if attack.CPUCount > 0 {
bytemanTemplateSpec.Do = fmt.Sprintf("injectCPUStress(\"%s\", %d)", attack.Name, attack.CPUCount)
} else {
bytemanTemplateSpec.Do = fmt.Sprintf("injectMemStress(\"%s\", %s)", attack.Name, attack.MemoryType)
}
case core.JVMGCAction:
bytemanTemplateSpec.Helper = core.GCHelper
bytemanTemplateSpec.Class = core.TriggerClass
bytemanTemplateSpec.Method = core.TriggerMethod
// the bind and condition is useless, only used for fill the template
bytemanTemplateSpec.Bind = "flag:boolean=true"
bytemanTemplateSpec.Condition = "true"
bytemanTemplateSpec.Do = "gc()"
}
buf := new(bytes.Buffer)
var t *template.Template
switch attack.Action {
case core.JVMStressAction, core.JVMGCAction:
t = template.Must(template.New("byteman rule").Parse(core.CompleteRuleTemplate))
case core.JVMExceptionAction, core.JVMLatencyAction, core.JVMReturnAction:
t = template.Must(template.New("byteman rule").Parse(core.SimpleRuleTemplate))
default:
return "", errors.Errorf("jvm action %s not supported", attack.Action)
}
if t == nil {
return "", errors.Errorf("parse byeman rule template failed")
}
err := t.Execute(buf, bytemanTemplateSpec)
if err != nil {
log.Error("executing template", zap.Error(err))
return "", err
}
return buf.String(), nil
}
func writeDataIntoFile(data string, filename string) (string, error) {
tmpfile, err := ioutil.TempFile("", filename)
if err != nil {

View File

@ -0,0 +1,119 @@
// 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 (
"testing"
. "github.com/onsi/gomega"
"github.com/chaos-mesh/chaosd/pkg/core"
)
func TestGenerateRuleData(t *testing.T) {
g := NewGomegaWithT(t)
testCases := []struct {
cmd *core.JVMCommand
ruleData string
}{
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMExceptionAction,
JVMClassMethodSpec: core.JVMClassMethodSpec{
Class: "testClass",
Method: "testMethod",
},
ThrowException: "java.io.IOException(\"BOOM\")",
},
"\nRULE test\nCLASS testClass\nMETHOD testMethod\nAT ENTRY\nIF true\nDO\n\tthrow new java.io.IOException(\"BOOM\");\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMReturnAction,
JVMClassMethodSpec: core.JVMClassMethodSpec{
Class: "testClass",
Method: "testMethod",
},
ReturnValue: "\"test\"",
},
"\nRULE test\nCLASS testClass\nMETHOD testMethod\nAT ENTRY\nIF true\nDO\n\treturn \"test\";\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMLatencyAction,
JVMClassMethodSpec: core.JVMClassMethodSpec{
Class: "testClass",
Method: "testMethod",
},
LatencyDuration: 5000,
},
"\nRULE test\nCLASS testClass\nMETHOD testMethod\nAT ENTRY\nIF true\nDO\n\tThread.sleep(5000);\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMStressAction,
JVMStressSpec: core.JVMStressSpec{
CPUCount: 1,
},
},
"\nRULE test\nCLASS org.chaos_mesh.chaos_agent.TriggerThread\nMETHOD triggerFunc\nHELPER org.chaos_mesh.byteman.helper.StressHelper\nAT ENTRY\nBIND flag:boolean=true;\nIF true\nDO\n\tinjectCPUStress(\"test\", 1);\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMStressAction,
JVMStressSpec: core.JVMStressSpec{
MemoryType: "heap",
},
},
"\nRULE test\nCLASS org.chaos_mesh.chaos_agent.TriggerThread\nMETHOD triggerFunc\nHELPER org.chaos_mesh.byteman.helper.StressHelper\nAT ENTRY\nBIND flag:boolean=true;\nIF true\nDO\n\tinjectMemStress(\"test\", heap);\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMGCAction,
},
"\nRULE test\nCLASS org.chaos_mesh.chaos_agent.TriggerThread\nMETHOD triggerFunc\nHELPER org.chaos_mesh.byteman.helper.GCHelper\nAT ENTRY\nBIND flag:boolean=true;\nIF true\nDO\n\tgc();\nENDRULE\n",
},
}
for _, testCase := range testCases {
ruleData, err := generateRuleData(testCase.cmd)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(ruleData).Should(Equal(testCase.ruleData))
}
}

View File

@ -13,15 +13,28 @@
# See the License for the specific language governing permissions and
# limitations under the License.
set -u
set -eu
cur=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
cd $cur
bin_path=../../../bin
echo "download && build && run Java example program"
git clone https://github.com/WangXiangUSTC/byteman-example.git
echo "download byteman example"
if [[ ! (-e byteman-example) ]]; then
git clone https://github.com/WangXiangUSTC/byteman-example.git
fi
echo "download byteman && set environment variable"
byteman_dir="byteman-chaos-mesh-download-v4.0.18-0.9"
if [[ ! (-e ${byteman_dir}.tar.gz) ]]; then
curl -fsSL -o ${byteman_dir}.tar.gz https://mirrors.chaos-mesh.org/${byteman_dir}.tar.gz
tar zxvf ${byteman_dir}.tar.gz
fi
export BYTEMAN_HOME=$cur/${byteman_dir}
export PATH=$PATH:${BYTEMAN_HOME}/bin
echo "build && run Java example program helloworld"
cd byteman-example/example.helloworld
javac HelloWorld/Main.java
jar cfme HelloWorld.jar Manifest.txt HelloWorld.Main HelloWorld/Main.class
@ -33,19 +46,14 @@ cat helloworld.log
# TODO: get the PID more accurately
pid=`pgrep -n java`
echo "download byteman && set environment variable"
curl -fsSL -o chaosd-byteman-download.tar.gz https://mirrors.chaos-mesh.org/jvm/chaosd-byteman-download.tar.gz
tar zxvf chaosd-byteman-download.tar.gz
export BYTEMAN_HOME=$cur/chaosd-byteman-download
export PATH=$PATH:${BYTEMAN_HOME}/bin
echo "run chaosd to inject failure into JVM, and check"
$bin_path/chaosd attack jvm install --port 9288 --pid $pid
$bin_path/chaosd attack jvm submit return --class Main --method getnum --port 9288 --value 99999
$bin_path/chaosd attack jvm return --class Main --method getnum --port 9288 --value 99999 --pid $pid
sleep 1
check_contains "99999" helloworld.log
$bin_path/chaosd attack jvm submit exception --class Main --method sayhello --port 9288 --exception 'java.io.IOException("BOOM")'
$bin_path/chaosd attack jvm exception --class Main --method sayhello --port 9288 --exception 'java.io.IOException("BOOM")' --pid $pid
sleep 1
check_contains "BOOM" helloworld.log
# TODO: add test for latency, stress and gc