Skip to main content
Version: 1.7.2

插件篇

本篇介绍 chaosblade-exec-jvm 插件的扩展和使用。

插件的介绍

一个简单的插件包含PluginPointCutEnhancerModelSpecActionSpecActionExecutor

调用插件的时序图

一个简单的插件调用的顺序图,非DirectlyInjectionActionActionFlag时序图

插件扩展的步骤

首先 fork chaosblade-exec-jvm

1、新建一个工程

在 chaosblade-exec-plugin 模块下新建子模块,如 chaosblade-exec-plugin-servlet

2、自定义 Enhancer

例如 ServletEnhancer,获取 ContextPath、RequestURI、Method 等,将获取到的参数放到 MatcherModel,返回 EnhancerModel,Inject 阶段会与输入的参数做比对。

public class ServletEnhancer extends BeforeEnhancer {

@Override
public EnhancerModel doBeforeAdvice(ClassLoader classLoader,
String className,
Object object,
Method method,
Object[] methodArguments
) throws Exception {
Object request = methodArguments[0];
// 执行被增强类的方法,获取一些需要的值
String queryString = ReflectUtil.invokeMethod(request, "getQueryString", new Object[] {}, false);
String contextPath = ReflectUtil.invokeMethod(request, "getContextPath", new Object[] {}, false);
String requestURI = ReflectUtil.invokeMethod(request, "getRequestURI", new Object[] {}, false);
String requestMethod = ReflectUtil.invokeMethod(request, "getMethod", new Object[] {}, false);

String requestPath = StringUtils.isBlank(contextPath) ? requestURI : requestURI.replaceFirst(contextPath, "");

MatcherModel matcherModel = new MatcherModel();
matcherModel.add(ServletConstant.QUERY_STRING_KEY, queryString);
matcherModel.add(ServletConstant.METHOD_KEY, requestMethod);
matcherModel.add(ServletConstant.REQUEST_PATH_KEY, requestPath);

return new EnhancerModel(classLoader, matcherModel);
}
}

需不同的通知可继承不同的类

  • beforeAdvice:继承 com.alibaba.chaosblade.exec.common.aop.BeforeEnhancer
  • afterAdvice:继承 com.alibaba.chaosblade.exec.common.aop.AfterEnhancer

3、自定义 PointCut

例如 ServletPointCut 拦截类:spring 的 FrameworkServlet、webx 的 WebxFrameworkFilter、及父类为 HttpServletBean 或 HttpServlet 的子类。

拦截方法:doGet、doPost、doDelete、doPut、doFilter

public class ServletPointCut implements PointCut {

public static final String SPRING_FRAMEWORK_SERVLET = "org.springframework.web.servlet.FrameworkServlet";
public static final String ALIBABA_WEBX_FRAMEWORK_FILTER = "com.alibaba.citrus.webx.servlet.WebxFrameworkFilter";
public static final String SPRING_HTTP_SERVLET_BEAN = "org.springframework.web.servlet.HttpServletBean";
public static final String HTTP_SERVLET = "javax.servlet.http.HttpServlet";

public static Set<String> enhanceMethodSet = new HashSet<String>();
public static Set<String> enhanceMethodFilterSet = new HashSet<String>();

static {
enhanceMethodSet.add("doGet");
enhanceMethodSet.add("doPost");
enhanceMethodSet.add("doDelete");
enhanceMethodSet.add("doPut");
enhanceMethodFilterSet.add("doFilter");
}

@Override
public ClassMatcher getClassMatcher() {
OrClassMatcher orClassMatcher = new OrClassMatcher();
orClassMatcher.or(new NameClassMatcher(SPRING_FRAMEWORK_SERVLET)).or(
new NameClassMatcher(ALIBABA_WEBX_FRAMEWORK_FILTER)).or(
new SuperClassMatcher(SPRING_HTTP_SERVLET_BEAN, HTTP_SERVLET));
return orClassMatcher;
}

@Override
public MethodMatcher getMethodMatcher() {
AndMethodMatcher andMethodMatcher = new AndMethodMatcher();
OrMethodMatcher orMethodMatcher = new OrMethodMatcher();
orMethodMatcher.or(new ManyNameMethodMatcher(enhanceMethodSet)).or(new ManyNameMethodMatcher
(enhanceMethodFilterSet));
andMethodMatcher.and(orMethodMatcher).and(new ParameterMethodMatcher(1, ParameterMethodMatcher.GREAT_THAN));
return andMethodMatcher;
}
}
  • 继承 com.alibaba.chaosblade.exec.common.aop.PointCut
  • getClassMatcher:类匹配
    • SuperClassMatcher:父类名称匹配
    • OrClassMatcher:多个匹配
    • NameClassMatcher:类名匹配
  • getMethodMatcher:类方法匹配
    • ManyNameMethodMatcher:方法名集合
    • NameMethodMatcher:方法名称匹配
    • OrMethodMatcher:多个方法匹配
    • AndMethodMatcher:多条件匹配
    • ParameterMethodMatcher:参数匹配

4、自定义 Spec

举个例子:命令[./blade create servlet delay --time=3000] 对于命令而言主要分为 phases、target、action、flag,phases 相对插件而言不需要很强的灵活性,因此由 chaosblade-exec-service 模块管理,对于自定义插件只需要扩展 ModelSpec(target)、action、 flag。

ModelSpec

ModelSpec 的 getTarget()方法对于命令中 target 部分的名称,如 servlet、dubbo 等,createNewMatcherSpecs()方法添加 ModelSpec 下的 FlagSpec,例如 ServletModelSpec 的 getTarget()返回 servlet,createNewMatcherSpecs()包含很多 flagSpec,那么 ModelSpec 支持的命令如下:

./blade create servlet --method=post --requestpath=/index --contextpath=/shop

--参数可任意组合。

public class ServletModelSpec extends FrameworkModelSpec {

@Override
public String getTarget() {
return "servlet";
}

@Override
public String getShortDesc() {
return "java servlet experiment";
}

@Override
public String getLongDesc() {
return "Java servlet experiment, support path, query string, context path and request method matcher";
}

@Override
public String getExample() {
return "servlet --requestpath /hello --method post";
}

@Override
protected List<MatcherSpec> createNewMatcherSpecs() {
ArrayList<MatcherSpec> matcherSpecs = new ArrayList<MatcherSpec>();
matcherSpecs.add(new ServletContextPathMatcherSpec());
matcherSpecs.add(new ServletQueryStringMatcherSpec());
matcherSpecs.add(new ServletMethodMatcherSpec());
matcherSpecs.add(new ServletRequestPathMatcherSpec());
return matcherSpecs;
}
}

ModelSpec 的实现方式如下:

  • 实现 com.alibaba.chaosblade.exec.common.model.ModelSpec
  • 继承 BaseModelSpec,实现了对 CreateHandler 阶段的输入参数的校验
  • 继承 FrameworkModelSpec 包含 DelayActionSpec、ThrowCustomExceptionActionSpec,默认实现了不同 target 的延迟侵入和异常侵入。

ActionSpec

例如 DelayActionSpec,支持参数 --time=xx --offset=xx

./blade create servlet --method=post delay --time=3000 ----offset=10

public class DelayActionSpec extends BaseActionSpec {

private static TimeFlagSpec timeFlag = new TimeFlagSpec();
private static TimeOffsetFlagSpec offsetFlag = new TimeOffsetFlagSpec();

public DelayActionSpec() {
//添加 actionExecutor
super(new DefaultDelayExecutor(timeFlag, offsetFlag));
}

@Override
public String getName() {
return "delay";
}

@Override
public String[] getAliases() {
return new String[0];
}

@Override
public String getShortDesc() {
return "delay time";
}

@Override
public String getLongDesc() {
return "delay time...";
}

@Override
public List<FlagSpec> getActionFlags() {
return Arrays.asList(timeFlag, offsetFlag);
}

@Override
public PredicateResult predicate(ActionModel actionModel) {
if (StringUtil.isBlank(actionModel.getFlag(timeFlag.getName()))){
return PredicateResult.fail("less time argument");
}
return PredicateResult.success();
}
}

ActionSpec 的 getName()方法对应命令中 action 部分的名称,如 delay、throwCustomExceptionde 等,ActionSpec 由 ModelSpec 的 addActionSpec()方法添加,可以有以下方式实现:

  • 实现 com.alibaba.chaosblade.exec.common.model.action.ActionSpec

  • 继承 BaseActionSpec,实现了对 CreateHandler 阶段的输入参数的校验

FlagSpec

例如 TimeOffsetFlagSpec, 支持--offset=10 的参数

public class TimeOffsetFlagSpec implements FlagSpec {
@Override
public String getName() {
return "offset";
}

@Override
public String getDesc() {
return "delay offset for the time";
}

@Override
public boolean noArgs() {
return false;
}

@Override
public boolean required() {
return false;
}
}

FlagSpec 的 getName()方法对应命令中 flag 部分的名称,如--time 等

  • 实现 com.alibaba.chaosblade.exec.common.model.FlagSpec,由 ActionSpec 的 getFlagSpec 方法添加

  • 继承 com.alibaba.chaosblade.exec.common.model.matcher.MatcherSpec,由 ActionSpec 的 addActionSpec 添加,CreateHandler 阶段会做参数校验

  • 继承 com.alibaba.chaosblade.exec.common.model.matcher.BasePredicateMatcherSpec

ActionExecutor

ActionExecutor 执行器作为 BaseActionSpec 的构造参数,ActionExecutor 可以自定义一些增强业务的操作,如修改方法的参数、篡改方法的返回值等。

public interface ActionExecutor {

/**
* Run executor
*
* @param enhancerModel
* @throws Exception
*/
void run(EnhancerModel enhancerModel) throws Exception;
}

实现 ActionExecutor 的接口,EnhancerModel 里面可以拿到命令行输入的参数以及原始方法的参数,类型,返回值、异常,做一些增强业务操作。

// 延迟多少毫秒
Long time = Long.valueOf(enhancerModel.getActionFlag("time"));
TimeUnit.MILLISECONDS.sleep(time);

5、自定义 Plugin

继承 com.alibaba.chaosblade.exec.common.aop.Plugin,自定义 target 名称,添加 Enhancer、PointCut、ModelSpec 即可,实现类需要全路径名复制到 : resources/META-INF/services/com.alibaba.chaosblade.exec.common.aop.Plugin 挂载 Agent,模块激活后 plugin 自动加载。

public class ServletPlugin implements Plugin {

@Override
public String getName() {
return "servlet";
}

@Override
public ModelSpec getModelSpec() {
return new ServletModelSpec();
}

@Override
public PointCut getPointCut() {
return new ServletPointCut();
}

@Override
public Enhancer getEnhancer() {
return new ServletEnhancer();
}
}

打包和执行

首先提交代码 push 到自己的仓库

准备

需要 go 环境、java_home 、maven

  • clone
git clone https://github.com/chaosblade-io/chaosblade
  • 修改 Makefile
cd chaosblade
vi Makefile

把 Makefile 里面的 BLADE_EXEC_JVM_PROJECT 改成修改成你 fork 的仓库地址,保存退出

  • 编译
make build_linux
  • 挂载 agent:--pid 3356 是被攻击应用的 jvm 进程号 每次挂载对应一个 uid,卸载 agent 的时候需要 uid
cd target/chaosblade-0.6.0/
./blade prepare jvm --pid 3356

混沌实验

每个实验对应一个 uid,后续的查询、销毁实验都要用到此 uid,如果遗忘了 uid,可以通过 blade status --type create 命令进行查询。 create 可以简写为 c,即 blade create 可以简写为 blade c。一个简单的例子,对 servlet 容器,api 接口延迟 3 秒。

  • 创建混沌实验
./blade create servlet delay --time=3000 --requestpath=/index

此时访问 Java 应用/index 应用将延迟 3 秒后响应。

  • 销毁
./blade create destroy 863c8c5a2c2c3deb

卸载 agent

./blade destroy 6a0863a4f0da8a38