Elasticsearch 2.X 版本Java插件开发简述

1:elasticsearch插件分类简述

2:Java插件开发要点

3:如何针对不同版本elasticsearch提供多版本的插件

4:插件具有外部依赖时遇到的一些问题(2016-09-07更新)


elasticsearch插件分类简述  

elasticsearch插件分为Site插件及Java插件,前者比如使用最广泛的head插件,而后者比如elastic官方提供的商业插件shield。

需要注意的是Site插件从elasticsearch2.3.0版本开始已被标记为Deprecated,并将从5.0.0版本开始被正式移除,相关的Site插件将被整合到kibana中,或者单独部署web server。具体如何整合我还不太清楚。

下文主要记录Java插件的开发要点,以maven管理为例。


Java插件开发要点

以下一一个简单功能为例来说明开发流程,该插件记录所有对部署elasticsearch节点的请求,并且根据指定的配置参数,过滤掉敏感操作(DELETE)

1:Java插件入口

继承org.elasticsearch.plugins.Plugin类的入口类,并实现onModule方法。该类中可以通过settings访问elasticsearch配置文件,获取配置信息,并加载下一步的handler,具体实现代码如下所示:

 1 package org.elasticsearch.es.plugin;
 2 
 3 import org.elasticsearch.common.settings.Settings;
 4 import org.elasticsearch.rest.RestModule;
 5 import org.elasticsearch.plugins.Plugin;
 6 
 7 @SuppressWarnings("static-method")
 8 public class MyRestPlugin extends Plugin {
 9 
10     private final Settings settings;
11 
12     public MyRestPlugin(Settings settings){
13         this.settings = settings;
14     }
15 
16     @Override
17     public String name(){
18         return "MyRest";
19     }
20     
21     @Override
22     public String description(){
23         return "ElasticSearch Plugin";
24     }
25 
26     public void onModule(RestModule module){
27         String isPluginEnabled = settings.get("ld.enabled");
28         MethodAuthenticator.setEnabledStr(isPluginEnabled);
29         if(isPluginEnabled != null && isPluginEnabled.toLowerCase().equals("true")){
30             MethodAuthenticator.setIsPluginEnabled(true);
31             String[] denyMethods = settings.getAsArray("ld.deny", new String[]{});
32             if(denyMethods != null){
33                 MethodAuthenticator.setDenyMethods(denyMethods);
34             }
35         } else {
36             MethodAuthenticator.setIsPluginEnabled(false);
37         }
38 
39         module.addRestAction(MyRestHandler.class);
40     }
41     
42 }
View Code

配置文件信息放入authenticator静态类,代码如下:

 1 package org.elasticsearch.es.plugin;
 2 
 3 import java.util.Arrays;
 4 
 5 public class MethodAuthenticator{
 6     
 7     private static String enabledStr;
 8     private static boolean isPluginEnabled;
 9     private static String[] denyMethods;
10 
11 //    public MethodAuthenticator(){
12 //
13 //    }
14 
15     public static boolean isMethodEnabled(String method){
16         if(denyMethods == null) {
17             MyLogger.debug("The deny methods is null");
18             return true;
19         }
20         if(Arrays.asList(denyMethods).contains(method)){
21             return false;
22         } else {
23             return true;
24         }
25     }
26     
27     public static String getEnabledStr(){
28         return enabledStr;
29     }
30     
31     public static void setEnabledStr(String enabledStr){
32         MethodAuthenticator.enabledStr = enabledStr;
33     }
34 
35     public static boolean getIsPluginEnabled() {
36         return isPluginEnabled;
37     }
38 
39     public static String[] getDenyMethods() {
40         return denyMethods;
41     }
42 
43     public static void setIsPluginEnabled(boolean isPluginEnabled) {
44         MethodAuthenticator.isPluginEnabled = isPluginEnabled;
45     }
46 
47     public static void setDenyMethods(String[] denyMethods) {
48         MethodAuthenticator.denyMethods = denyMethods;
49     }
50 }
View Code

2:相关的Handler

继承org.elasticsearch.rest.BaseRestHandler类的handler,将filter注入controller,实现代码如下:

 1 package org.elasticsearch.es.plugin;
 2 
 3 import org.elasticsearch.rest.*;
 4 
 5 import org.elasticsearch.client.Client;
 6 import org.elasticsearch.common.inject.Inject;
 7 import org.elasticsearch.common.settings.Settings;
 8 
 9 public class MyRestHandler extends BaseRestHandler {
10     @Inject
11     public MyRestHandler(Settings settings, RestController restController, Client client){
12         super(settings, restController, client);
13         RestFilter filter = new MyRestFilter(client);
14         restController.registerFilter(filter);
15     }
16 
17     @Override
18     protected void handleRequest(RestRequest restRequest, RestChannel restChannel, Client client)
19             throws Exception {
20         // TODO Auto-generated method stub
21         
22     }
23 }
View Code

3:注入Filter

继承org.elasticsearch.rest.RestFilter类的filter,在过滤器中实现对请求的记录及过滤,实现代码如下:

 1 package org.elasticsearch.es.plugin;
 2 
 3 import org.elasticsearch.client.Client;
 4 import org.elasticsearch.rest.RestFilter;
 5 import org.elasticsearch.rest.RestRequest;
 6 import org.elasticsearch.rest.RestChannel;
 7 import org.elasticsearch.rest.RestFilterChain;
 8 import org.elasticsearch.rest.RestStatus;
 9 import org.elasticsearch.rest.BytesRestResponse;
10 
11 import java.net.InetSocketAddress;
12 
13 
14 public class MyRestFilter extends RestFilter{
15     Client client;
16     public MyRestFilter(Client client){
17         this.client = client;
18     }
19     
20     @Override
21     public void process(RestRequest request, RestChannel channel, RestFilterChain filterChain) throws Exception{
22         try{
23             if(MethodAuthenticator.getIsPluginEnabled()){
24                 MyLogger.info(getLogInfo(request));
25                 String methodStr = request.method().name().toLowerCase();
26                 if(MethodAuthenticator.isMethodEnabled(methodStr)){
27                     //MyLogger.info("The request method " + methodStr + " is allowed");
28                     filterChain.continueProcessing(request, channel);
29                 } else {
30                     MyLogger.info("The request method " + methodStr + " is denyed");
31                     BytesRestResponse res = new BytesRestResponse(RestStatus.FORBIDDEN, "Forbidden Method Request by MyRest plugin");
32                     channel.sendResponse(res);
33                 }
34             } else {
35                 //MyLogger.info("MyRest is disabled");
36                 filterChain.continueProcessing(request, channel);
37             }
38         } catch (Exception exp) {
39             channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, "My Rest Filter internal exception"));
40         }
41         return ;
42     }
43     
44     private String getLogInfo(RestRequest request) throws Exception {
45         try{
46             String pathStr = request.path();
47             String ipaddr = ((InetSocketAddress)request.getRemoteAddress()).getAddress().getHostAddress();
48             return "The [" + request.method().name() + "] request [" + pathStr + "] is from [" + ipaddr + "]"; 
49         } catch (Exception exp) {
50             MyLogger.error("Request resolve failed: ", exp);
51         }
52         return "The request resolve failed";
53     }
54 }
View Code

日志记录使用elasticsearch的logger,入口static类代码如下所示:

 1 package org.elasticsearch.es.plugin;
 2 
 3 import org.elasticsearch.common.logging.ESLogger;
 4 import org.elasticsearch.common.logging.ESLoggerFactory;
 5 
 6 public class MyLogger {
 7     
 8     private static ESLogger esLogger;
 9     static {
10         esLogger = ESLoggerFactory.getLogger("myRest");
11     }
12     
13     public static void debug(String msg){
14         esLogger.debug(msg);
15     }
16     
17     public static void info(String msg){
18         esLogger.info(msg);
19     }
20     
21     public static void warn(String msg){
22         esLogger.warn(msg);
23     }
24     
25     public static void error(String msg){
26         esLogger.error(msg);
27     }
28     
29     public static void error(String msg, Exception exp){
30         esLogger.error(msg, exp);
31     }
32 }
View Code

4:插件配置信息

由于插件使用elasticsearch配置文件保存配置信息,因此在配置文件中加入如下信息:  

默认elasticsearch配置文件路径为:/etc/elasticsearch/elasticsearch.yml  

  ld.enabled: false  
  ld.deny: ["delete"]

需要注意的是配置文件中配置项前面加入2个空格字符,否则可能导致elasticsearch不能启动;

此外,插件正常工作还依赖于plugin-descriptor.properties文件(elasticsearch-1.x版本中是es-plugin.properties文件),该文件配置信息如下例:

name=myRest
description=GridsumLawDissectorElasticSearchPlugin
version=0.0.1
jvm=true
site=false
classname=org.elasticsearch.es.plugin.MyRestPlugin
java.version=1.8
elasticsearch.version=2.3.3
View Code

我这里以elasticsearch-2.3.3版本为例,可按需求改为2.X的任意版本。

插件描述文件如下所示(其中format指定插件以zip形式安装):

<?xml version="1.0" encoding="UTF-8"?>
<assembly
    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <id>plugin</id>
    <formats>
        <format>zip</format>
    </formats>    
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/</outputDirectory>
            <useProjectArtifact>true</useProjectArtifact>
            <useTransitiveFiltering>true</useTransitiveFiltering>
        </dependencySet>
    </dependencySets>
</assembly>
View Code

5:编译部署

插件编译使用maven即可:

mvn clean package

需要注意的是如果首次编译可能需要较长时间,maven会下载目标版本的org.elasticsearch.jar。

版本信息在maven对应的pom文件中指定。

编译完成后,将打包的jar文件与plugin-descriptor.properties文件打包为zip安装包,拷贝至elasticsearch的部署节点。

使用如下方式安装插件:

/usr/share/elasticsearch/bin/plugin install file:///opt/myrest.zip

指令中需要说明的是:

1)elasticsearch安装目录下的bin目录下的plugin脚本;

2)由于直接通过打包安装文件安装,因此指定为file://;

3)紧跟打包zip文件的路径即可。

部署完毕后,确保elasticsearch.yml中的配置信息正确,即可重启elasticsearch节点:

systemctl restart elasticsearch

6:验证插件

通过监控elasticsearch日志可看到插件记录的所有请求目标及来源:

tail -f /var/log/elasticsearch/xx.xx.log

上述命令中为log的默认路径,查看以集群clustername命名的日志文件即可。

如果配置参数中过滤方法设置了DELETE,则可通过发送DELETE请求来验证,插件将过滤该请求,并返回403。


如何针对不同版本elasticsearch提供多版本的插件

如前文所述,elasticsearch从2.X版本开始JAVA插件相关的Plugin,BaseRestHandler,RestFilter等抽象类都相同,区别只在于依赖的org.elasticsearch,因此,JAVA插件对elasticsearch2.X以上各版本的支持,只需要修改对应的依赖版本信息并重新打包部署即可。

具体的两个需要修改依赖版本信息的文件分别是:

plugin-descriptor.properties  
pom.xml

当然这是以maven为例,如果不是以maven,则只需修改第一个文件的版本信息,并手动引用对应版本的org.elasticsearch,重新编译打包即可。


插件具有外部依赖时遇到的一些问题(2016-09-07更新)

 最近由于插件功能扩展,需要用到json序列化,使用了fastJson的1.2.16版本,如下是dependency:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.16</version>
</dependency>

遇到的问题主要是在插件部署环节,主要有2个问题:

1:插件发布并未包含依赖的jar包;

2:fastJson.jar需要许可权(不知道表述是否正确,本人不是javaer),以下为问题2的异常信息:

java.security.AccessControlException: access denied (java.lang.RuntimePermission getClassLoader)
...
...
...

解决方法:

1:在插件发布后打包zip过程中加入fastjson-1.2.16.jar即可,如果打包的package中未包含该jar包,可以从maven的repository路径中获取;

2:更改java.policy文件许可权(默认路径/usr/java/jdk1.8.0_73/jre/lib/security/java.policy),jdk版本为目标主机的jdk版本,在grant中加入以下许可信息:

permission java.lang.RuntimePermission "createClassLoader";
permission java.lang.RuntimePermission "getClassLoader";

重启elasticsearch进程即可。

java.policy如找不到,可以在/usr/lib/jvm/java-1.8.0/jre/lib/security路径下找到。

原文地址:https://www.cnblogs.com/you-you-111/p/5736830.html