= Adding Custom Plugins in SolrCloud Mode
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

In SolrCloud mode, custom plugins need to be shared across all nodes of the cluster.

When running Solr in SolrCloud mode and you want to use custom code (such as custom analyzers, tokenizers, query parsers, and other plugins), it can be cumbersome to add jars to the classpath on all nodes in your cluster. Using the <<blob-store-api.adoc#blob-store-api,Blob Store API>> and special commands with the <<config-api.adoc#config-api,Config API>>, you can upload jars to a special system-level collection and dynamically load plugins from them at runtime without needing to restart any nodes.

.This Feature is Disabled By Default
[IMPORTANT]
====
In addition to requiring that Solr is running in <<solrcloud.adoc#solrcloud,SolrCloud>> mode, this feature is also disabled by default unless all Solr nodes are run with the `-Denable.runtime.lib=true` option on startup.

Before enabling this feature, users should carefully consider the issues discussed in the <<Securing Runtime Libraries>> section below.
====

== Uploading Jar Files

Use your own service to host the jars or you can use Solr itself to host the jars.

Use the <<blob-store-api.adoc#blob-store-api,Blob Store API>> to upload your jar files to Solr. This will to put your jars in the `.system` collection and distribute them across your SolrCloud nodes. These jars are added to a separate classloader and only accessible to components that are configured with the property `runtimeLib=true`. These components are loaded lazily because the `.system` collection may not be loaded when a particular core is loaded.

== Config API Commands to use Jars as Runtime Libraries

The runtime library feature uses a special set of commands for the <<config-api.adoc#config-api,Config API>> to add, update, or remove jar files currently available in the blob store to the list of runtime libraries.

The following commands are used to manage runtime libs:

* `add-runtimelib`
* `update-runtimelib`
* `delete-runtimelib`

[.dynamic-tabs]
--
[example.tab-pane#v1manage-libs]
====
[.tab-label]*V1 API*

[source,bash]
----
curl http://localhost:8983/solr/techproducts/config -H 'Content-type:application/json' -d '{
   "add-runtimelib": { "name":"jarblobname", "version":2 },
   "update-runtimelib": { "name":"jarblobname", "version":3 },
   "delete-runtimelib": "jarblobname"
}'
----
====

[example.tab-pane#v2manage-libs]
====
[.tab-label]*V2 API*

[source,bash]
----
curl http://localhost:8983/api/collections/techproducts/config -H 'Content-type:application/json' -d '{
   "add-runtimelib": { "name":"jarblobname", "version":2 },
   "update-runtimelib": { "name":"jarblobname", "version":3 },
   "delete-runtimelib": "jarblobname"
}'
----
====
--

The name to use is the name of the blob that you specified when you uploaded your jar to the blob store. You should also include the version of the jar found in the blob store that you want to use. These details are added to `configoverlay.json`.

The default `SolrResourceLoader` does not have visibility to the jars that have been defined as runtime libraries. There is a classloader that can access these jars which is made available only to those components which are specially annotated.

Every pluggable component can have an optional extra attribute called `runtimeLib=true`, which means that the components are not loaded at core load time. Instead, they will be loaded on demand. If all the dependent jars are not available when the component is loaded, an error is thrown.

This example shows creating a ValueSourceParser using a jar that has been loaded to the Blob store.

[.dynamic-tabs]
--
[example.tab-pane#v1add-jar]
====
[.tab-label]*V1 API*

[source,bash]
----
curl http://localhost:8983/solr/techproducts/config -H 'Content-type:application/json' -d '{
  "create-valuesourceparser": {
    "name": "nvl",
    "runtimeLib": true,
    "class": "solr.org.apache.solr.search.function.NvlValueSourceParser,
    "nvlFloatValue": 0.0 }
}'
----
====

[example.tab-pane#v2add-jar]
====
[.tab-label]*V2 API*

[source,bash]
----
curl http://localhost:8983/api/collections/techproducts/config -H 'Content-type:application/json' -d '{
  "create-valuesourceparser": {
    "name": "nvl",
    "runtimeLib": true,
    "class": "solr.org.apache.solr.search.function.NvlValueSourceParser,
    "nvlFloatValue": 0.0 }
}'
----
====
--

== Example: Using external service to host your jars

Hosting your jars externally is more convenient if you have a reliable server to host your jars. There is no need to create and manage a `.system` collection.

Step 1: Download a jar from github to the current directory

[source,bash]
----
 curl -o runtimelibs.jar   -LO https://github.com/apache/lucene-solr/blob/master/solr/core/src/test-files/runtimecode/runtimelibs.jar.bin?raw=true
----
Step 2: Get the `sha256` hash of the jar

[source,bash]
----
 openssl dgst -sha256 runtimelibs.jar
----

Step 3 :  Start solr with runtime lib enabled

[source,bash]
----
 bin/solr start -e cloud -a "-Denable.runtime.lib=true" -noprompt
----

Step 4: Run a local server. Skip this step if you have another place to host your jars. Ensure that the url is set appropriately

[source,bash]
----
 python -m SimpleHTTPServer 8000 &
----

Step 5: Add the jar to your collection `gettingstarted`

[source,bash]
----
 curl http://localhost:8983/solr/gettingstarted/config -H 'Content-type:application/json' -d '{
    "add-package": { "name" : "my-pkg",
    "url":"http://localhost:8000/runtimelibs.jar" ,
    "sha256" : "d01b51de67ae1680a84a813983b1de3b592fc32f1a22b662fc9057da5953abd1b72476388ba342cad21671cd0b805503c78ab9075ff2f3951fdf75fa16981420"}
    }'
----

Step  6 : Create a new request handler '/test' for the collection 'gettingstarted' from the jar we just added

[source,bash]
----
curl http://localhost:8983/solr/gettingstarted/config -H 'Content-type:application/json' -d '{
    "create-requesthandler": { "name" : "/test",
    "class": "org.apache.solr.core.RuntimeLibReqHandler", "package" : "my-pkg" }
    }'
----

Step 7:  Test your request handler

[source,bash]
----
curl  http://localhost:8983/solr/gettingstarted/test
----

output:
[source,json]
----
{
  "responseHeader":{
    "status":0,
    "QTime":0},
  "params":{},
  "context":{
    "webapp":"/solr",
    "path":"/test",
    "httpMethod":"GET"},
  "class":"org.apache.solr.core.RuntimeLibReqHandler",
  "loader":"org.apache.solr.core.MemClassLoader"}
----

=== Updating remote jars

Example:

* Host the new jar to a new url. eg:  http://localhost:8000/runtimelibs_v2.jar
* get the `sha256` hash of the new jar
* run the update-runtime lib command

[source,bash]
----
 curl http://localhost:8983/solr/gettingstarted/config -H 'Content-type:application/json' -d '{
    "update-package": { "name" : "my-pkg",
    "url":"http://localhost:8000/runtimelibs_v2.jar" ,
    "sha256" : "<replace-the-new-sha256-digest-here>"}
    }'
----
NOTE: Always upload your jar to a new url as the Solr cluster is still referring to the old jar. If the existing jar is modified it can cause errors as the hash may not match

== Securing Runtime Libraries

A drawback of this feature is that it could be used to load malicious executable code into the system. However, it is possible to restrict the system to load only trusted jars using http://en.wikipedia.org/wiki/Public_key_infrastructure[PKI] to verify that the executables loaded into the system are trustworthy.

The following steps will allow you enable security for this feature. The instructions assume you have started all your Solr nodes with the `-Denable.runtime.lib=true`.

=== Step 1: Generate an RSA Private Key

The first step is to generate an RSA private key. The example below uses a 512-bit key, but you should use the strength appropriate to your needs.

[source,bash]
----
$ openssl genrsa -out priv_key.pem 512
----

=== Step 2: Output the Public Key

The public portion of the key should be output in DER format so Java can read it.

[source,bash]
----
$ openssl rsa -in priv_key.pem -pubout -outform DER -out pub_key.der
----

=== Step 3: Load the Key to ZooKeeper

The `.der` files that are output from Step 2 should then be loaded to ZooKeeper under a node `/keys/exe` so they are available throughout every node. You can load any number of public keys to that node and all are valid. If a key is removed from the directory, the signatures of that key will cease to be valid. So, before removing the a key, make sure to update your runtime library configurations with valid signatures with the `update-runtimelib` command.

At the current time, you can only use the ZooKeeper `zkCli.sh` (or `zkCli.cmd` on Windows) script to issue these commands (the Solr version has the same name, but is not the same). If you have your own ZooKeeper ensemble running already, you can find the script in `$ZK_INSTALL/bin/zkCli.sh` (or `zkCli.cmd` if you are using Windows).

NOTE: If you are running the embedded ZooKeeper that is included with Solr, you *do not* have this script already; in order to use it, you will need to download a copy of ZooKeeper v{ivy-zookeeper-version} from http://zookeeper.apache.org/. Don't worry about configuring the download, you're just trying to get the command line utility script. When you start the script, you will connect to the embedded ZooKeeper.

To load the keys, you will need to connect to ZooKeeper with `zkCli.sh`, create the directories, and then create the key file, as in the following example.

[source,bash]
----
# Connect to ZooKeeper
# Replace the server location below with the correct ZooKeeper connect string for your installation.
$ .bin/zkCli.sh -server localhost:9983

# After connection, you will interact with the ZK prompt.
# Create the directories
[zk: localhost:9983(CONNECTED) 5] create /keys
[zk: localhost:9983(CONNECTED) 5] create /keys/exe

# Now create the public key file in ZooKeeper
# The second path is the path to the .der file on your local machine
[zk: localhost:9983(CONNECTED) 5] create /keys/exe/pub_key.der /myLocal/pathTo/pub_key.der
----

After this, any attempt to load a jar will fail. All your jars must be signed with one of your private keys for Solr to trust it. The process to sign your jars and use the signature is outlined in Steps 4-6.

=== Step 4: Sign the jar File

Next you need to sign the sha1 digest of your jar file and get the base64 string.

[source,bash]
----
$ openssl dgst -sha1 -sign priv_key.pem myjar.jar | openssl enc -base64
----

The output of this step will be a string that you will need to add the jar to your classpath in Step 6 below.

=== Step 5: Load the jar to the Blob Store

Load your jar to the Blob store, using the <<blob-store-api.adoc#blob-store-api,Blob Store API>>. This step does not require a signature; you will need the signature in Step 6 to add it to your classpath.

[source,bash]
----
curl -X POST -H 'Content-Type: application/octet-stream' --data-binary @{filename}
http://localhost:8983/solr/.system/blob/{blobname}
----

The blob name that you give the jar file in this step will be used as the name in the next step.

=== Step 6: Add the jar to the Classpath

Finally, add the jar to the classpath using the Config API as detailed above. In this step, you will need to provide the signature of the jar that you got in Step 4.

[.dynamic-tabs]
--
[example.tab-pane#v1add-jar2]
====
[.tab-label]*V1 API*

[source,bash]
----
curl http://localhost:8983/solr/techproducts/config -H 'Content-type:application/json'  -d '{
  "add-runtimelib": {
    "name":"blobname",
    "version":2,
    "sig":"mW1Gwtz2QazjfVdrLFHfbGwcr8xzFYgUOLu68LHqWRDvLG0uLcy1McQ+AzVmeZFBf1yLPDEHBWJb5KXr8bdbHN/
           PYgUB1nsr9pk4EFyD9KfJ8TqeH/ijQ9waa/vjqyiKEI9U550EtSzruLVZ32wJ7smvV0fj2YYhrUaaPzOn9g0=" }
}'
----
====

[example.tab-pane#v2add-jar2]
====
[.tab-label]*V2 API*

[source,bash]
----
curl http://localhost:8983/api/collections/techproducts/config -H 'Content-type:application/json'  -d '{
  "add-runtimelib": {
    "name":"blobname",
    "version":2,
    "sig":"mW1Gwtz2QazjfVdrLFHfbGwcr8xzFYgUOLu68LHqWRDvLG0uLcy1McQ+AzVmeZFBf1yLPDEHBWJb5KXr8bdbHN/
           PYgUB1nsr9pk4EFyD9KfJ8TqeH/ijQ9waa/vjqyiKEI9U550EtSzruLVZ32wJ7smvV0fj2YYhrUaaPzOn9g0=" }
}'
----
====
--
