Getting started with a simple plugin How they fit into Elasticsearch architecture How they fit into Elasticsearch architecture Examples walkthrough Examples walkthrough Development Development Testing Testing Distribution Distribution Limitations Limitations Not covered Not covered
there is no configuration option for your need If you need unexposed (but public) API If you need unexposed (but public) API To enforce specific behaviour To enforce specific behaviour If scripting is simply not enough If scripting is simply not enough Site plugin vs JVM Plugin Site plugin vs JVM Plugin Here Here Plugin == JVM Plugin Plugin == JVM Plugin
Java (1.7+) They live within a node (or transport client) They live within a node (or transport client) They can access all internals They can access all internals They can make a node stop working (or They can make a node stop working (or even worse) even worse)
public MyPlugin(Settings settings) { this.settings = settings; } @Override public String name() { return "MyPlugin"; } @Override public String description() { return "This is the description for my plugin"; } } Extend AbstractPlugin class Extend AbstractPlugin class ... but it does not anything useful yet ... but it does not anything useful yet
@Override public Collection<Class<? extends Module>> modules() { return ImmutableList.of(MyModule.class); } //Define own services @Override public Collection<Class<? extends LifecycleComponent>> services() { return ImmutableList.of(MyService.class); } } So lets define our own modules So lets define our own modules or services which do something meaningful or services which do something meaningful
@Override public void processModule(Module module) { if ((module instanceof RestModule)) { ((RestModule)module).addRestAction(MyRestAction.class); } if ((module instanceof ???)) { ((???)module).doModuleSpecificStuffHere(); } } } or do something with core modules or do something with core modules to accomplish your needs to accomplish your needs
pronounced "juice" pronounced "juice" Dependency injection Dependency injection Inversion of control Inversion of control Guice code is copied in ES codebase (slightly Guice code is copied in ES codebase (slightly modified for lower memory footprint) modified for lower memory footprint) So elasticsearch is build upon guice modules So elasticsearch is build upon guice modules Google guice Google guice
they don't shut down Modules start up, but they don't shut down Modules should be tested Modules should be tested Modules can be overridden Modules can be overridden Services have a lifecycle Services have a lifecycle because of because of
Write them into elasticsearch Write them into elasticsearch (Make them visible through kibana) (Make them visible through kibana) Transport SSL/TLS Plugin Transport SSL/TLS Plugin Implement and enforce SSL/TLS encryption Implement and enforce SSL/TLS encryption for transport protocol for transport protocol
listener on ShardIndexingService ShardIndexingService Write changes into elasticsearch via Write changes into elasticsearch via BulkProcessor BulkProcessor
IndicesService indicesService, Client client, ClusterService clusterService, TransportFlushAction tfa) { super(settings); this.indicesService = indicesService; this.clusterService = clusterService; ... } @Override protected void doStart() throws ElasticsearchException { ... this.indicesService.indicesLifecycle().addListener(auditIndicesLsListener); } Define a new service called AuditService Define a new service called AuditService Register a indices lifecycle listener Register a indices lifecycle listener Dependency Injection by guice
IndexShard indexShard) { if (indexShard.routingEntry().primary() && !indexShard.indexService().index().name().equals(auditIndexName)) { AuditIndexOpListener auditListener = new AuditIndexOpListener(indexShard); indexShard.indexingService().addListener(auditListener); } } If a shard starts, register If a shard starts, register an IndexingOperationListener an IndexingOperationListener
AuditIndexOpListener(IndexShard indexShard) { this.indexShard = indexShard; } @Override public void postIndex(Index index) { String nodeName = indexShard.nodeName(); String indexName = indexShard.indexService().index().name(); Change change = new Change(nodeName, indexName, ...); //store it in elasticsearch (or anywhere else) IndexRequest ir = new IndexRequest().source(change.sourceAsMap()); addToBulkIndex(ir); } } Let the IndexingOperationListener store the Let the IndexingOperationListener store the change in elasticsearch (or anywhere else) change in elasticsearch (or anywhere else)
{ //nothing to bind here } @Override public void processModule(Module module) { if ((module instanceof ActionModule)) { ((ActionModule)module).registerAction(FlushAction.INSTANCE, TransportFlushAction.class); } if ((module instanceof RestModule)) { ((RestModule)module).addRestAction(AuditRestAction.class); } } } Define the AuditModule (not covered today) Define the AuditModule (not covered today) API for flushing outstanding bulk requests API for flushing outstanding bulk requests
{ return "AuditPlugin"; } public String description() { return "This is the description for the AuditPlugin"; } @Override public Collection<Class<? extends Module>> modules() { return ImmutableList.of(AuditModule.class); } @Override public Collection<Class<? extends LifecycleComponent>> services() { return ImmutableList.of(AuditService.class); } } Last but not least define the AuditPlugin itself Last but not least define the AuditPlugin itself
tcp Elasticsearch uses Netty for tcp communication communication Extend NettyTransport and inject SslHandler Extend NettyTransport and inject SslHandler into pipeline into pipeline Replace original Netty transport in Replace original Netty transport in TransportModule TransportModule Expose some SSL/TLS related informations Expose some SSL/TLS related informations through a new REST API through a new REST API
transportModule) { transportModule.setTransport(SSLNettyTransport.class, name()); } public void onModule(RestModule restModule) { restModule.addRestAction(TSslRestAction.class); } } Replace transport in TransportModule Replace transport in TransportModule Register a custom rest action for ssl infos Register a custom rest action for ssl infos
extending ElasticsearchIntegrationTest Extend Extend Start nodes directly in the test method Start nodes directly in the test method Test in real elasticsearch node/cluster Test in real elasticsearch node/cluster https://github.com/tlrx/elasticsearch-test https://github.com/tlrx/elasticsearch-test MultiJvmUnitTest MultiJvmUnitTest DevOps magic does help here DevOps magic does help here Always test with multiple nodes (cluster scenario)
want use the randomized testing suite, include test dependencies in pom include test dependencies in pom <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-test-framework</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <type>test-jar</type> <scope>test</scope> </dependency>
because ES is using Maven because ES is using Maven Plugins are best published in Plugins are best published in Or distributed as .zip file Or distributed as .zip file maven central maven central # Install from maven central bin/plugin --install de.saly/elasticsearch-sample-plugin-tssl/1.1 # Install from a .zip file bin/plugin --url file:///Users/.../elasticsearch-sample-plugin-tssl-1.1.zip \ --install elasticsearch-sample-plugin-tssl
elasticsearch.yml elasticsearch.yml # Load this plugin from the classpath # (does only makes sense if plugins.load_classpath_plugins is false) # Order is respected plugin.types: org.company.MyPlugin,com.guhgle.FantasticPlugin # If a plugin listed here is not installed for current node, the node will not start. plugin.mandatory: mapper-attachments,lang-groovy # If its true (which is the default) load all plugins which are in the classpath # No order guarantee plugins.load_classpath_plugins: true
are allowed to do anything Plugins are allowed to do anything With ES 2.0 maybe Java SecurityManager With ES 2.0 maybe Java SecurityManager will be will be utilised utilised No isolation No isolation Same classloader for ES and all plugins Same classloader for ES and all plugins Plugins can interfere with others Plugins can interfere with others your dependencies your dependencies Install plugins on as few nodes as possible Install plugins on as few nodes as possible Consider using dedicated plugin nodes Consider using dedicated plugin nodes Shade Shade
Manipulating the HTTP layer Manipulating the HTTP layer Plugin scopes Plugin scopes (Rest)ActionFilter (Rest)ActionFilter Custom analyzers Custom analyzers Custom mapping types Custom mapping types Site plugins Site plugins ... maybe there will be a Part II of this talk ... maybe there will be a Part II of this talk