You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2017/01/31 05:57:52 UTC

[2/5] lucene-solr:master: SOLR-8029: Added new style APIs and a framework for creating new APIs and mapping old APIs to new

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/core.config.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.Commands.json b/solr/core/src/resources/apispec/core.config.Commands.json
new file mode 100644
index 0000000..256306b
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.Commands.json
@@ -0,0 +1,215 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+API",
+  "description": "The Config API enables manipulating various aspects of your solrconfig.xml using REST-like API calls. All properties set with this API update a file called configoverlay.json, but not the solrconfig.xml file itself.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/config"
+    ]
+  },
+  "commands": {
+    "set-property:": {
+      "type": "object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+API#ConfigAPI-Commandstomodifytheconfig",
+      "description": "Sets one or more of several pre-defined properties. These properties set cache sizes and classes, commit rules, JMX settings, and request dispatcher settings. See the documentation for the list of properties that are supported. If a property is set that already exists, it will be overwritten.",
+      "additionalProperties": true
+    },
+    "unset-property": {
+      "type":"array",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+API#ConfigAPI-Commandstomodifytheconfig",
+      "description": "Removes one or more of several pre-defined properties. These properties set cache sizes and classes, commit rules, JMX settings, and request dispatcher settings. See the documentation for the list of properties that are supported. The value of the property does not need to be defined with the list of properties, only the name of the property.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-requesthandler": {
+      "#include":"core.config.Commands.addRequestHandler.properties",
+      "required": [
+        "name",
+        "class"
+      ]
+    },
+    "update-requesthandler": {
+      "#include":"core.config.Commands.addRequestHandler.properties",
+      "required": [
+        "name"
+      ]
+    },
+    "delete-requesthandler": {
+      "type": "array",
+      "description": "Deletes one or more request handlers, using the name given when the request handler was created. Define more than one request handler by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-searchcomponent": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-searchcomponent": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-searchcomponent": {
+      "type": "array",
+      "description": "Deletes one or more search components, using the name given when the search component was created. Define more than one search component by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-initparams": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "Name by which it is added, so that it can be updated by name"
+        }
+      },
+      "additionalProperties": true
+    },
+    "update-initparams": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "Name by which it is added"
+        }
+      },
+      "required": [
+        "name"
+      ],
+      "additionalProperties": true
+    },
+    "delete-initparams": {
+      "type": "array",
+      "description": "Deletes one or more init params, using the name given when the init param set was created. Define more than one init params by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-queryresponsewriter": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-queryresponsewriter": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-queryresponsewriter": {
+      "type": "array",
+      "description": "Deletes one or more query response writers, using the name given when the response writer was created. Define more than one response writer by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-queryparser": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-queryparser": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-queryparser": {
+      "type": "array",
+      "items": {
+        "type": "string"
+      },
+      "description": "Deletes one or more query parsers, using the name given when the query parser was created. Define more than one query parser by separating the list of names with commas."
+    },
+    "add-valuesourceparser": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-valuesourceparser": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-valuesourceparser": {
+      "type": "array",
+      "description": "Deletes one or more ValueSourceParsers, using the name given when the ValueSourceParser was created. Define more than one ValueSourceParsers by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-transformer": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-transformer": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-transformer": {
+      "type": "array",
+      "description": "Deletes one or more document transformers, using the name given when the document transformer was created. Define more than one document transformers by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-updateprocessor": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-updateprocessor": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-updateprocessor": {
+      "type": "array",
+      "description": "Deletes one or more update processors, using the name given when the update processor was created. Define more than one update processors by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-queryconverter": {
+      "#include": "core.config.Commands.generic"
+    },
+    "update-queryconverter": {
+      "#include": "core.config.Commands.generic"
+    },
+    "delete-queryconverter": {
+      "type": "array",
+      "description": "Deletes one or more query converters, using the name given when the query converter was created. Define more than one query converters by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-listener": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "Name by which it is added, so that it can be updated by name"
+        }
+      },
+      "required": [
+        "name"
+      ],
+      "additionalProperties": true
+    },
+    "update-listener": {
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "Name by which it is added"
+        }
+      },
+      "required": [
+        "name"
+      ],
+      "additionalProperties": true
+    },
+    "delete-listener": {
+      "type": "array",
+      "description": "Deletes one or more listeners, using the name given when the listener was created. Define more than one listener by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "add-runtimelib": {
+      "#include": "core.config.Commands.runtimeLib"
+    },
+    "update-runtimelib": {
+      "#include": "core.config.Commands.runtimeLib"
+    },
+    "delete-runtimelib": {
+      "type":"array",
+      "description": "Deletes one or more runtime libraries (runtimeLibs), using the name given when the runtimeLib was created. Define more than one runtimeLibs by separating the list of names with commas.",
+      "items": {
+        "type": "string"
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/core.config.Commands.runtimeLib.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.Commands.runtimeLib.json b/solr/core/src/resources/apispec/core.config.Commands.runtimeLib.json
new file mode 100644
index 0000000..8e2fb2d
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.Commands.runtimeLib.json
@@ -0,0 +1,23 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Adding+Custom+Plugins+in+SolrCloud+Mode",
+  "description": "Allows you to register .jars that have been uploaded to the .system collection in Solr. Note that uploading the .jar must occur before using this API.",
+  "type": "object",
+  "properties": {
+    "name": {
+      "description": "The name of the .jar blob in .system collection. This is the name you provided when you uploaded it.",
+      "type": "string"
+    },
+    "version": {
+      "type": "integer",
+      "description": "The version of the blob in .system collection. Be sure to use the correct version if you have multiple versions of the same .jar uploaded."
+    },
+    "sig": {
+      "type": "string",
+      "description": "The sha1 signature of the .jar, if it was signed before uploading. If you signed the sha1 digest of your .jar file prior to uploading it to the .system collection, this is where you need to provide the signature."
+    }
+  },
+  "required": [
+    "name",
+    "version"
+  ]
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/core.config.Params.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.Params.Commands.json b/solr/core/src/resources/apispec/core.config.Params.Commands.json
new file mode 100644
index 0000000..474f1a6
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.Params.Commands.json
@@ -0,0 +1,31 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Request+Parameters+API",
+  "description": "Create, update and delete request parameter sets (paramsets) to override or replace parameters defined in solrconfig.xml. Parameter sets are used with request handlers by setting the useParams attribute to the paramset name in the definition of the request handler or with individual requests to Solr. Parameter sets defined with this API are stored in a file params.json in ZooKeeper or on the filesystem when not using SolrCloud. Note this API does not directly update solrconfig.xml. ",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/config/params"
+    ]
+  },
+  "commands": {
+    "set:": {
+      "type":"object",
+      "description":"Add or overwrite one or more paramsets. Each paramset definition includes a paramset name, followed by key-value pairs of the parameter and value to be set.",
+      "additionalProperties": true
+    },
+    "unset": {
+      "type":"array",
+      "description": "Delete one or more paramsets.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "update": {
+      "type":"object",
+      "description": "Update one or more paramsets. This command will attempt to merge an existing paramset with the new values. Each paramset definition includes a paramset name, followed by key-value pairs of the parameters and values to be updated.",
+      "additionalProperties": true
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/core.config.Params.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.Params.json b/solr/core/src/resources/apispec/core.config.Params.json
new file mode 100644
index 0000000..cff0350
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.Params.json
@@ -0,0 +1,13 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Request+Parameters+API",
+  "description": "List all parameter sets (paramsets). Individual paramsets can be requested by paramset name.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/config/params",
+      "/config/params/{params_set}"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/core.config.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.config.json b/solr/core/src/resources/apispec/core.config.json
new file mode 100644
index 0000000..2633fd9
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.config.json
@@ -0,0 +1,18 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+API",
+  "description": "Gets the Solr configuration for a collection.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/config",
+      "/config/overlay",
+      "/config/query",
+      "/config/jmx",
+      "/config/requestDispatcher",
+      "/config/znodeVersion",
+      "/config/{plugin}"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/core.system.blob.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.system.blob.json b/solr/core/src/resources/apispec/core.system.blob.json
new file mode 100644
index 0000000..96fedcf
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.system.blob.json
@@ -0,0 +1,20 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Blob+Store+API",
+  "description": "Lists blobs in the blob store (the .system collection). The list can be limited by name or name and version.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/blob",
+      "/blob/{name}",
+      "/blob/{name}/{version}"
+    ],
+    "params": {
+      "wt": {
+        "type":"string",
+        "description": "Use the value 'filestream' to get the file content. Use other response writers (such as xml, or json) to fetch only the metadata."
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/core.system.blob.upload.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/core.system.blob.upload.json b/solr/core/src/resources/apispec/core.system.blob.upload.json
new file mode 100644
index 0000000..854e544
--- /dev/null
+++ b/solr/core/src/resources/apispec/core.system.blob.upload.json
@@ -0,0 +1,12 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/Blob+Store+API",
+  "description": "Uploads a blob to the blob store. Note that the blob store is a specially named collection (which must be '.system') which must be created before uploading a blob to it.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/blob/{name}"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/cores.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cores.Commands.json b/solr/core/src/resources/apispec/cores.Commands.json
new file mode 100644
index 0000000..d6419cf
--- /dev/null
+++ b/solr/core/src/resources/apispec/cores.Commands.json
@@ -0,0 +1,85 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API",
+  "description": "Actions on non-specific cores. See the /cores/{core} endpoint for actions on specific, named, cores.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/cores"
+    ]
+  },
+  "commands": {
+    "create": {
+      "type" : "object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-CREATE",
+      "description": "Creates a new core. If you are using SolrCloud, you should use the Collections API instead. While a core for a SolrCloud cluster can be created with this API, it is considered an expert-level action. The Collections API has commands for creating new shards and replicas that ensure the safety of those actions within your cluster.",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The core name to create. If a core with this name already exists, an error will be returned."
+        },
+        "instanceDir": {
+          "type": "string",
+          "description": "The core instance directory, where files for this core should be stored. While this parameter is not required, if it is not defined it will default to a path relative to Solr Home that includes the name you've given the new core. This location MUST EXIST prior to creating the core, and it must include a conf directory that includes solrconfig.xml and your schema, either as a schema.xml file or using the managed schema feature."
+        },
+        "schema": {
+          "type": "string",
+          "description": "Name of the schema file to use for the core. Please note that if you are using a 'managed schema' (Solr's default behavior) then any value for this property that does not match the effective managedSchemaResourceName will be read once, backed up, and converted for managed schema use. If you are using the default name (schema.xml or the managed schema name), you do not need to define the schema file name."
+        },
+        "dataDir": {
+          "type": "string",
+          "description": "Name of the data directory relative to instanceDir. This is where the index files will be stored."
+        },
+        "config": {
+          "type": "string",
+          "description": "Name of the config file (i.e., solrconfig.xml) relative to instanceDir. If you are using the default name (solrconfig.xml), you do not need to define the config file name."
+        },
+        "configSet": {
+          "type": "string",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Config+Sets",
+          "description": "The name of a config set to use. The config set must already exist. The solr.xml file defines the location of the configset base directory, and configuration files can be shared between cores by defining sub-directories. The files in the named configSet will be used for the schema and config properties instead of defining them explicitly."
+        },
+        "loadOnStartup": {
+          "type": "boolean",
+          "description": "If true, the core will be loaded on startup. Set to false to enable lazy loading, where the core will only be loaded if it is referenced or called.",
+          "default": "true"
+        },
+        "transient": {
+          "type": "boolean",
+          "description": "Allows Solr to unload the core if resources are required.",
+          "default": "false"
+        },
+        "shard": {
+          "type": "string",
+          "description": "In SolrCloud mode, the shard this core should belong to."
+        },
+        "collection": {
+          "type": "string",
+          "description": "The name of the collection this core belongs to."
+        },
+        "props": {
+          "type": "object",
+          "documentation": "https://cwiki.apache.org/confluence/display/solr/Defining+core.properties",
+          "description": "Allows adding core.properties for the collection.",
+          "additionalProperties": true
+        },
+        "coreNodeName": {
+          "type": "string",
+          "description": "The replica name."
+        },
+        "numShards": {
+          "type":"number",
+          "description":"The number of shards to create for this core."
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      },
+      "required": [
+        "name"
+      ]
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/cores.Status.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cores.Status.json b/solr/core/src/resources/apispec/cores.Status.json
new file mode 100644
index 0000000..155bcf5
--- /dev/null
+++ b/solr/core/src/resources/apispec/cores.Status.json
@@ -0,0 +1,20 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-STATUS",
+  "description": "Provides status and other information about the status of each core. Individual cores can be requested by core name.",
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/cores",
+      "/cores/{core}"
+    ],
+    "params": {
+      "indexInfo": {
+        "type": "boolean",
+        "description": "If true, index information will be returned, such as information about number of documents, deletions, segments, etc. In a large cluster with more than hundreds of cores, this can take a long time to retrieve. If you have a large cluster, consider setting this to false.",
+        "default": true
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/cores.core.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cores.core.Commands.json b/solr/core/src/resources/apispec/cores.core.Commands.json
new file mode 100644
index 0000000..5049a3a
--- /dev/null
+++ b/solr/core/src/resources/apispec/cores.core.Commands.json
@@ -0,0 +1,136 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API",
+  "description": "Actions that are peformed on individual cores, such as reloading, swapping cores, renaming, and others.",
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/cores/{core}"
+    ]
+  },
+  "commands": {
+    "reload": {
+      "type":"object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-RELOAD",
+      "description": "Reloads a core. This is useful when you have made changes on disk such as editing the schema or solrconfig.xml files. Most APIs reload cores automatically, so this should not be necessary if changes were made with those APIs."
+    },
+    "swap": {
+      "type":"object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-SWAP",
+      "description": "Swaps the names of two existing Solr cores. This can be used to swap new content into production. The former core can be swapped back if necessary. Using this API is not supported in SolrCloud mode.",
+      "properties": {
+        "with": {
+          "type": "string",
+          "description": "The name of the other core to be swapped (the first core name is included in the request)."
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      },
+      "required": [
+        "with"
+      ]
+    },
+    "rename": {
+      "type": "object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-RENAME",
+      "description": "Change the name of a core.",
+      "properties": {
+        "to": {
+          "type": "string",
+          "description": "The new name for the core."
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      },
+      "required": [
+        "to"
+      ]
+    },
+    "unload": {
+      "type": "object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-UNLOAD",
+      "description": "Removes a core. Active requests would continue to be processed, but new requests will not be sent to the new core. If a core is registered under more than one name, only the name given in the request is removed.",
+      "properties": {
+        "deleteIndex": {
+          "type": "boolean",
+          "description": "If true, the index will be removed while unloading the core.",
+          "default": "false"
+        },
+        "deleteDataDir": {
+          "type": "boolean",
+          "description": "If true, the data directory and all sub-directories will be removed while unloading the core.",
+          "default": "false"
+        },
+        "deleteInstanceDir": {
+          "type": "boolean",
+          "description": "If true, everything related to the core, including the index, data, and conf directories, will be removed while unloading the core.",
+          "default": "false"
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      }
+    },
+    "merge-indexes": {
+      "type":"object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-MERGEINDEXES",
+      "description":"Merges one or more indexes to another index. The indexes must have completed commits, and should be locked against writes until the merge is complete to avoid index corruption. The target core (which is the core that should be used as the endpoint for this command) must exist before using this command. A commit should also be performed on this core after the merge is complete.",
+      "properties": {
+        "indexDir": {
+          "type": "array",
+          "description": "A comma-separated list index directories for each source core that will be merged with the target core.",
+          "items": {
+            "type": "string"
+          }
+        },
+        "srcCore": {
+          "type": "array",
+          "description": "A comma-separated list of the names of each source core to be merged with the target core.",
+          "items": {
+            "type": "string"
+          }
+        },
+        "async": {
+          "type": "string",
+          "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined."
+        }
+      }
+    },
+    "split":  { "#include": "cores.core.Commands.split"},
+    "request-recovery": {
+      "type":"object",
+      "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-REQUESTRECOVERY",
+      "description": "Manually asks a core to recover by synching with a leader. It may help SolrCloud clusters where a node refuses to come back up. However, it is considered an expert-level command, and should be used very carefully."
+    },
+    "force-prepare-for-leadership": {
+      "type": "object",
+      "description": "An internal API used by the Collections API to force leader election. This should not be used directly by end-users."
+    },
+    "prep-recovery": {
+      "type": "object",
+      "additionalProperties": true,
+      "description": "An internal API used by the Collections API. This should not be used directly by end-users."
+    },
+    "request-apply-updates": {
+      "type": "object",
+      "additionalProperties": true,
+      "description": "An internal API used by the Collections API. This should not be used directly by end-users."
+    },
+    "request-sync-shard": {
+      "type": "object",
+      "additionalProperties": true,
+      "description": "An internal API used by the Collections API. This should not be used directly by end-users."
+    },
+    "request-buffer-updates": {
+      "type": "object",
+      "additionalProperties": true,
+      "description": "An internal API used by the Collections API. This should not be used directly by end-users."
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/cores.core.Commands.split.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/cores.core.Commands.split.json b/solr/core/src/resources/apispec/cores.core.Commands.split.json
new file mode 100644
index 0000000..3aa0a91
--- /dev/null
+++ b/solr/core/src/resources/apispec/cores.core.Commands.split.json
@@ -0,0 +1,34 @@
+{
+  "documentation": "https://cwiki.apache.org/confluence/display/solr/CoreAdmin+API#CoreAdminAPI-SPLIT",
+  "description": "Allows splitting an index into two or more new indexes.",
+  "type": "object",
+  "properties": {
+    "path": {
+      "type": "array",
+      "description": "Directory path(s) in which a piece of the index will be written. This allows splitting the index before creating the cores to contain them. Note if using this approach that the indexes will not be able to receive updates until a new core has been created to handle the incoming updates. If you have already created the new cores, you should define the targetCore property instead.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "targetCore": {
+      "type": "array",
+      "description": "The target Solr core(s) to which a piece of the index will be merged (if the target core already contains data). This requires that the cores have already been created. If the cores have not yet been created, use the path property instead.",
+      "items": {
+        "type": "string"
+      }
+    },
+    "splitKey": {
+      "type":"string",
+      "description": "A route key to use for splitting the index. This parameter is optional, but should not be defined if the ranges parameter is also defined."
+    },
+    "ranges": {
+      "type": "string",
+      "description": "A comma-separated list of hexadecimal hash ranges that will be used to split the core. This parameter is optional, but should not be defined if the splitKey parameter is also defined."
+    },
+    "async": {
+      "type": "string",
+      "description": "Defines a request ID that can be used to track this action after it's submitted. The action will be processed asynchronously when this is defined. This command can be long-running, so running it asynchronously is recommended."
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/emptySpec.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/emptySpec.json b/solr/core/src/resources/apispec/emptySpec.json
new file mode 100644
index 0000000..d95bff9
--- /dev/null
+++ b/solr/core/src/resources/apispec/emptySpec.json
@@ -0,0 +1,11 @@
+{
+  "methods": [
+    "GET",
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "$handlerName"
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/node.Commands.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/node.Commands.json b/solr/core/src/resources/apispec/node.Commands.json
new file mode 100644
index 0000000..11b3c89
--- /dev/null
+++ b/solr/core/src/resources/apispec/node.Commands.json
@@ -0,0 +1,24 @@
+{
+  "methods": [
+    "POST"
+  ],
+  "url": {
+    "paths": [
+      "/node"
+    ]
+  },
+  "commands": {
+    "overseer-op": {
+      "type": "object",
+      "additionalProperties": true
+    },
+    "rejoin-leader-election": {
+      "type": "object",
+      "additionalProperties": true
+    },
+    "invoke":{
+      "type": "object",
+      "additionalProperties": true
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/node.Info.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/node.Info.json b/solr/core/src/resources/apispec/node.Info.json
new file mode 100644
index 0000000..e7752e6
--- /dev/null
+++ b/solr/core/src/resources/apispec/node.Info.json
@@ -0,0 +1,11 @@
+{
+   "description": "Provides information about system properties, threads, logging settings, and system details for a node.",
+  "methods": ["GET"],
+  "url": {
+    "paths": [
+      "/node/properties",
+      "/node/threads",
+      "/node/logging",
+      "/node/system"]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/resources/apispec/node.invoke.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/node.invoke.json b/solr/core/src/resources/apispec/node.invoke.json
new file mode 100644
index 0000000..c8a9f69
--- /dev/null
+++ b/solr/core/src/resources/apispec/node.invoke.json
@@ -0,0 +1,16 @@
+{
+  "methods": [
+    "GET"
+  ],
+  "url": {
+    "paths": [
+      "/node/invoke"
+    ],
+    "params": {
+      "class": {
+        "type": "string",
+        "description": "Name of the class that must be invoked. "
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test-files/solr/collection1/conf/solrconfig-managed-schema.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-managed-schema.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-managed-schema.xml
index 467deca..31bbbb3 100644
--- a/solr/core/src/test-files/solr/collection1/conf/solrconfig-managed-schema.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-managed-schema.xml
@@ -40,7 +40,7 @@
   </requestHandler>
 
 
-  <requestHandler name="/dump" class="DumpRequestHandler" initParams="a">
+  <requestHandler name="/dump" class="DumpRequestHandler" initParams="a" registerPath="/,/v2">
     <lst name="defaults">
       <str name="a">${my.custom.variable.a:A}</str>
       <str name="b">${my.custom.variable.b:B}</str>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/api/TestPathTrie.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/api/TestPathTrie.java b/solr/core/src/test/org/apache/solr/api/TestPathTrie.java
new file mode 100644
index 0000000..d4cbf32
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/api/TestPathTrie.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.api;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.util.PathTrie;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonMap;
+import static org.apache.solr.api.ApiBag.HANDLER_NAME;
+
+public class TestPathTrie extends SolrTestCaseJ4 {
+
+  public void testPathTrie() {
+    PathTrie<String> pathTrie = new PathTrie<>(ImmutableSet.of("_introspect"));
+    pathTrie.insert("/", emptyMap(), "R");
+    pathTrie.insert("/aa", emptyMap(), "d");
+    pathTrie.insert("/aa/bb/{cc}/dd", emptyMap(), "a");
+    pathTrie.insert("/$handlerName/{cc}/dd", singletonMap(HANDLER_NAME, "test"), "test");
+    pathTrie.insert("/aa/bb/{cc}/{xx}", emptyMap(), "b");
+    pathTrie.insert("/aa/bb", emptyMap(), "c");
+
+    HashMap templateValues = new HashMap<>();
+    assertEquals("R", pathTrie.lookup("/", templateValues, null));
+    assertEquals("d", pathTrie.lookup("/aa", templateValues, null));
+    assertEquals("a", pathTrie.lookup("/aa/bb/hello/dd", templateValues, null));
+    templateValues.clear();
+    assertEquals("test", pathTrie.lookup("/test/hello/dd", templateValues, null));
+    assertEquals("hello", templateValues.get("cc"));
+    templateValues.clear();
+    assertEquals("b", pathTrie.lookup("/aa/bb/hello/world", templateValues, null));
+    assertEquals("hello", templateValues.get("cc"));
+    assertEquals("world", templateValues.get("xx"));
+    Set<String> subPaths =  new HashSet<>();
+    templateValues.clear();
+    pathTrie.lookup("/aa",templateValues, subPaths);
+    assertEquals(3, subPaths.size());
+
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/cloud/rule/RulesTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/rule/RulesTest.java b/solr/core/src/test/org/apache/solr/cloud/rule/RulesTest.java
index 6955418..13649e1 100644
--- a/solr/core/src/test/org/apache/solr/cloud/rule/RulesTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/rule/RulesTest.java
@@ -22,10 +22,12 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.lucene.util.LuceneTestCase;
+import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.response.SimpleSolrResponse;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.params.ModifiableSolrParams;
@@ -36,6 +38,7 @@ import org.junit.rules.ExpectedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
 import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
 import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH;
 import static org.junit.matchers.JUnitMatchers.containsString;
@@ -161,6 +164,21 @@ public class RulesTest extends SolrCloudTestCase {
 
   }
 
+  @Test
+  public void testInvokeApi() throws Exception {
+    JettySolrRunner jetty = cluster.getRandomJetty(random());
+    try (SolrClient client = getHttpSolrClient(jetty.getBaseUrl().toString())) {
+      GenericSolrRequest req =  new GenericSolrRequest(GET, "/v2/node/invoke", new ModifiableSolrParams()
+          .add("class", ImplicitSnitch.class.getName())
+          .add("cores", "1")
+          .add("freedisk", "1")
+      );
+      SimpleSolrResponse rsp = req.process(client);
+      assertNotNull(((Map) rsp.getResponse().get(ImplicitSnitch.class.getName())).get("cores"));
+      assertNotNull(((Map) rsp.getResponse().get(ImplicitSnitch.class.getName())).get("freedisk"));
+    }
+  }
+
 
   @Test
   public void testModifyColl() throws Exception {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java b/solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java
index ff5e1cb..dda1eb3 100644
--- a/solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java
+++ b/solr/core/src/test/org/apache/solr/core/BlobStoreTestRequestHandler.java
@@ -58,4 +58,5 @@ public class BlobStoreTestRequestHandler extends DumpRequestHandler implements R
     run();
 
   }
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
index 004039c..695e869 100644
--- a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
+++ b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
@@ -112,6 +112,7 @@ public class SolrCoreTest extends SolrTestCaseJ4 {
       ++ihCount; assertEquals(pathToClassMap.get("/analysis/document"), "solr.DocumentAnalysisRequestHandler");
       ++ihCount; assertEquals(pathToClassMap.get("/analysis/field"), "solr.FieldAnalysisRequestHandler");
       ++ihCount; assertEquals(pathToClassMap.get("/debug/dump"), "solr.DumpRequestHandler");
+      ++ihCount; assertEquals(pathToClassMap.get("update"), "solr.UpdateRequestHandlerApi");
     }
     assertEquals("wrong number of implicit handlers", ihCount, implicitHandlers.size());
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/core/TestDynamicLoading.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/TestDynamicLoading.java b/solr/core/src/test/org/apache/solr/core/TestDynamicLoading.java
index bd20b1e..8479ae4 100644
--- a/solr/core/src/test/org/apache/solr/core/TestDynamicLoading.java
+++ b/solr/core/src/test/org/apache/solr/core/TestDynamicLoading.java
@@ -88,7 +88,7 @@ public class TestDynamicLoading extends AbstractFullDistribZkTestBase {
 
 
     payload = "{\n" +
-        "'create-requesthandler' : { 'name' : '/test1', 'class': 'org.apache.solr.core.BlobStoreTestRequestHandler' , 'runtimeLib' : true }\n" +
+        "'create-requesthandler' : { 'name' : '/test1', 'class': 'org.apache.solr.core.BlobStoreTestRequestHandler' ,registerPath: '/,/v2',  'runtimeLib' : true }\n" +
         "}";
 
     client = restTestHarnesses.get(random().nextInt(restTestHarnesses.size()));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
index c182495..021efac 100644
--- a/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
+++ b/solr/core/src/test/org/apache/solr/core/TestSolrConfigHandler.java
@@ -36,12 +36,14 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.handler.DumpRequestHandler;
 import org.apache.solr.handler.TestBlobHandler;
 import org.apache.solr.handler.TestSolrConfigHandlerConcurrent;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.search.SolrCache;
+import org.apache.solr.util.RESTfulServerProvider;
 import org.apache.solr.util.RestTestBase;
 import org.apache.solr.util.RestTestHarness;
 import org.eclipse.jetty.servlet.ServletHolder;
@@ -82,6 +84,10 @@ public class TestSolrConfigHandler extends RestTestBase {
 
     createJettyAndHarness(tmpSolrHome.getAbsolutePath(), "solrconfig-managed-schema.xml", "schema-rest.xml",
         "/solr", true, extraServlets);
+    if (random().nextBoolean()) {
+      log.info("These tests are run with V2 API");
+      restTestHarness.setServerProvider(() -> jetty.getBaseUrl().toString() + "/v2/cores/" + DEFAULT_TEST_CORENAME);
+    }
   }
 
   @After
@@ -451,7 +457,7 @@ public class TestSolrConfigHandler extends RestTestBase {
 
     map = getRespMap("/dump100?wt=json&json.nl=arrmap&initArgs=true", writeHarness);
     List initArgs = (List) map.get("initArgs");
-    assertEquals(2, initArgs.size());
+    assertTrue(initArgs.size() >= 2);
     assertTrue(((Map)initArgs.get(0)).containsKey("suggester"));
     assertTrue(((Map)initArgs.get(1)).containsKey("suggester"));
 
@@ -525,9 +531,20 @@ public class TestSolrConfigHandler extends RestTestBase {
         continue;
 
       }
-      if (Objects.equals(expected, Utils.getObjectByPath(m, false, jsonPath))) {
-        success = true;
-        break;
+      Object actual = Utils.getObjectByPath(m, false, jsonPath);
+
+      if (expected instanceof ValidatingJsonMap.PredicateWithErrMsg) {
+        ValidatingJsonMap.PredicateWithErrMsg predicate = (ValidatingJsonMap.PredicateWithErrMsg) expected;
+        if (predicate.test(actual) == null) {
+          success = true;
+          break;
+        }
+
+      } else {
+        if (Objects.equals(expected, actual)) {
+          success = true;
+          break;
+        }
       }
       Thread.sleep(100);
 
@@ -568,7 +585,7 @@ public class TestSolrConfigHandler extends RestTestBase {
         10);
 
     payload = "{\n" +
-        "'create-requesthandler' : { 'name' : '/d', 'class': 'org.apache.solr.handler.DumpRequestHandler' }\n" +
+        "'create-requesthandler' : { 'name' : '/d', registerPath :'/,/v2' , 'class': 'org.apache.solr.handler.DumpRequestHandler' }\n" +
         "}";
 
     TestSolrConfigHandler.runConfigCommand(harness, "/config?wt=json", payload);
@@ -598,7 +615,7 @@ public class TestSolrConfigHandler extends RestTestBase {
         5);
 
     payload = "{\n" +
-        "'create-requesthandler' : { 'name' : '/dump1', 'class': 'org.apache.solr.handler.DumpRequestHandler', 'useParams':'x' }\n" +
+        "'create-requesthandler' : { 'name' : '/dump1', registerPath :'/,/v2' , 'class': 'org.apache.solr.handler.DumpRequestHandler', 'useParams':'x' }\n" +
         "}";
 
     TestSolrConfigHandler.runConfigCommand(harness, "/config?wt=json", payload);
@@ -643,7 +660,7 @@ public class TestSolrConfigHandler extends RestTestBase {
 
     TestSolrConfigHandler.testForResponseElement(harness,
         null,
-        "/dump?wt=json&useParams=y",
+        "/dump1?wt=json&useParams=y",
         null,
         Arrays.asList("params", "c"),
         "CY val",
@@ -745,6 +762,60 @@ public class TestSolrConfigHandler extends RestTestBase {
         null,
         10);
 
+    payload = "{\n" +
+        "  'create-requesthandler': {\n" +
+        "    'name': 'aRequestHandler',\n" +
+        "    'registerPath': '/v2',\n" +
+        "    'class': 'org.apache.solr.handler.DumpRequestHandler',\n" +
+        "    'spec': {\n" +
+        "      'methods': [\n" +
+        "        'GET',\n" +
+        "        'POST'\n" +
+        "      ],\n" +
+        "      'url': {\n" +
+        "        'paths': [\n" +
+        "          '/something/{part1}/fixed/{part2}'\n" +
+        "        ]\n" +
+        "      }\n" +
+        "    }\n" +
+        "  }\n" +
+        "}";
+
+    TestSolrConfigHandler.runConfigCommand(harness, "/config?wt=json", payload);
+    TestSolrConfigHandler.testForResponseElement(harness,
+        null,
+        "/config/overlay?wt=json",
+        null,
+        Arrays.asList("overlay", "requestHandler", "aRequestHandler", "class"),
+        "org.apache.solr.handler.DumpRequestHandler",
+        10);
+    RESTfulServerProvider oldProvider = restTestHarness.getServerProvider();
+    restTestHarness.setServerProvider(() -> jetty.getBaseUrl().toString() + "/v2/cores/" + DEFAULT_TEST_CORENAME);
+
+    Map rsp = TestSolrConfigHandler.testForResponseElement(
+        harness,
+        null,
+        "/something/part1_Value/fixed/part2_Value?urlTemplateValues=part1&urlTemplateValues=part2",
+        null,
+        Arrays.asList("urlTemplateValues"),
+        new ValidatingJsonMap.PredicateWithErrMsg() {
+          @Override
+          public String test(Object o) {
+            if (o instanceof Map) {
+              Map m = (Map) o;
+              if ("part1_Value".equals(m.get("part1"))  && "part2_Value".equals(m.get("part2"))) return null;
+
+            }
+            return "no match";
+          }
+
+          @Override
+          public String toString() {
+            return "{part1:part1_Value, part2 : part2_Value]";
+          }
+        },
+        10);
+    restTestHarness.setServerProvider(oldProvider);
 
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
new file mode 100644
index 0000000..1af5d93
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.TestSolrConfigHandler;
+import org.apache.solr.util.RESTfulServerProvider;
+import org.apache.solr.util.RestTestHarness;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class V2ApiIntegrationTest extends SolrCloudTestCase {
+  private List<RestTestHarness> restTestHarnesses = new ArrayList<>();
+
+  private static String COLL_NAME = "collection1";
+
+  private void setupHarnesses() {
+    for (final JettySolrRunner jettySolrRunner : cluster.getJettySolrRunners()) {
+      RestTestHarness harness = new RestTestHarness(new ServerProvider(jettySolrRunner));
+      restTestHarnesses.add(harness);
+    }
+  }
+  static class ServerProvider implements RESTfulServerProvider {
+
+    final JettySolrRunner jettySolrRunner;
+    String baseurl;
+
+    ServerProvider(JettySolrRunner jettySolrRunner) {
+      this.jettySolrRunner = jettySolrRunner;
+      baseurl = jettySolrRunner.getBaseUrl().toString() + "/" + COLL_NAME;
+    }
+
+    @Override
+    public String getBaseURL() {
+      return baseurl;
+    }
+
+  }
+
+  @BeforeClass
+  public static void createCluster() throws Exception {
+    System.setProperty("managed.schema.mutable", "true");
+    configureCluster(2)
+        .addConfig("conf1", TEST_PATH().resolve("configsets").resolve("cloud-managed").resolve("conf"))
+        .configure();
+    CollectionAdminRequest.createCollection(COLL_NAME, "conf1", 1, 2)
+        .process(cluster.getSolrClient());
+  }
+
+  @Test
+  public void test() throws Exception {
+    try {
+      setupHarnesses();
+      testApis();
+
+    } finally {
+      for (RestTestHarness r : restTestHarnesses) {
+        r.close();
+      }
+    }
+  }
+
+  private void testApis() throws Exception {
+    RestTestHarness restHarness = restTestHarnesses.get(0);
+    ServerProvider serverProvider = (ServerProvider) restHarness.getServerProvider();
+    serverProvider.baseurl = serverProvider.jettySolrRunner.getBaseUrl()+"/v2/c/"+ COLL_NAME;
+    Map result = TestSolrConfigHandler.getRespMap("/get/_introspect", restHarness);
+    assertEquals("/c/collection1/get", Utils.getObjectByPath(result, true, "/spec[0]/url/paths[0]"));
+    serverProvider.baseurl = serverProvider.jettySolrRunner.getBaseUrl()+"/v2/collections/"+ COLL_NAME;
+    result = TestSolrConfigHandler.getRespMap("/get/_introspect", restHarness);
+    assertEquals("/collections/collection1/get", Utils.getObjectByPath(result, true, "/spec[0]/url/paths[0]"));
+
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
new file mode 100644
index 0000000..b784f87
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
@@ -0,0 +1,219 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.api.V2HttpCall.CompositeApi;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.common.params.MapSolrParams;
+import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.PluginBag;
+import org.apache.solr.handler.PingRequestHandler;
+import org.apache.solr.handler.SchemaHandler;
+import org.apache.solr.handler.SolrConfigHandler;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.request.SolrRequestHandler;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.V2HttpCall;
+import org.apache.solr.util.CommandOperation;
+import org.apache.solr.util.PathTrie;
+
+import static org.apache.solr.api.ApiBag.EMPTY_SPEC;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH;
+import static org.apache.solr.common.params.CommonParams.CONFIGSETS_HANDLER_PATH;
+import static org.apache.solr.common.params.CommonParams.CORES_HANDLER_PATH;
+import static org.apache.solr.common.util.ValidatingJsonMap.NOT_NULL;
+
+public class TestApiFramework extends SolrTestCaseJ4 {
+
+  public void testFramework() {
+    Map<String, Object[]> calls = new HashMap<>();
+    Map<String, Object> out = new HashMap<>();
+    CoreContainer mockCC = TestCoreAdminApis.getCoreContainerMock(calls, out);
+    PluginBag<SolrRequestHandler> containerHandlers = new PluginBag<>(SolrRequestHandler.class, null, false);
+    containerHandlers.put(COLLECTIONS_HANDLER_PATH, new TestCollectionAPIs.MockCollectionsHandler());
+    containerHandlers.put(CORES_HANDLER_PATH, new CoreAdminHandler(mockCC));
+    containerHandlers.put(CONFIGSETS_HANDLER_PATH, new ConfigSetsHandler(mockCC));
+    out.put("getRequestHandlers", containerHandlers);
+
+    PluginBag<SolrRequestHandler> coreHandlers = new PluginBag<>(SolrRequestHandler.class, null, false);
+    coreHandlers.put("/schema", new SchemaHandler());
+    coreHandlers.put("/config", new SolrConfigHandler());
+    coreHandlers.put("/admin/ping", new PingRequestHandler());
+
+    Map<String, String> parts = new HashMap<>();
+    String fullPath = "/collections/hello/shards";
+    Api api = V2HttpCall.getApiInfo(containerHandlers, fullPath, "POST",
+       fullPath, parts);
+    assertNotNull(api);
+    assertConditions(api.getSpec(), Utils.makeMap(
+        "/methods[0]", "POST",
+        "/commands/create", NOT_NULL));
+    assertEquals("hello", parts.get("collection"));
+
+
+    parts = new HashMap<>();
+    api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello/shards", "POST",
+      null, parts);
+    assertConditions(api.getSpec(), Utils.makeMap(
+        "/methods[0]", "POST",
+        "/commands/split", NOT_NULL,
+        "/commands/add-replica", NOT_NULL
+    ));
+
+
+    parts = new HashMap<>();
+    api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello/shards/shard1", "POST",
+        null, parts);
+    assertConditions(api.getSpec(), Utils.makeMap(
+        "/methods[0]", "POST",
+        "/commands/force-leader", NOT_NULL
+    ));
+    assertEquals("hello", parts.get("collection"));
+    assertEquals("shard1", parts.get("shard"));
+
+
+    parts = new HashMap<>();
+    api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello", "POST",
+       null, parts);
+    assertConditions(api.getSpec(), Utils.makeMap(
+        "/methods[0]", "POST",
+        "/commands/add-replica-property", NOT_NULL,
+        "/commands/delete-replica-property", NOT_NULL
+    ));
+    assertEquals("hello", parts.get("collection"));
+
+    api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello/shards/shard1/replica1", "DELETE",
+       null, parts);
+    assertConditions(api.getSpec(), Utils.makeMap(
+        "/methods[0]", "DELETE",
+        "/url/params/onlyIfDown/type", "boolean"
+    ));
+    assertEquals("hello", parts.get("collection"));
+    assertEquals("shard1", parts.get("shard"));
+    assertEquals("replica1", parts.get("replica"));
+
+    SolrQueryResponse rsp = invoke(containerHandlers, null, "/collections/_introspect", GET, mockCC);
+
+    assertConditions(rsp.getValues().asMap(2), Utils.makeMap(
+        "/spec[0]/methods[0]", "DELETE",
+        "/spec[1]/methods[0]", "POST",
+        "/spec[2]/methods[0]", "GET"
+
+    ));
+
+    rsp = invoke(coreHandlers, "/schema/_introspect", "/collections/hello/schema/_introspect", GET, mockCC);
+    assertConditions(rsp.getValues().asMap(2), Utils.makeMap(
+        "/spec[0]/methods[0]", "POST",
+        "/spec[0]/commands", NOT_NULL,
+        "/spec[1]/methods[0]", "GET"));
+
+    rsp = invoke(coreHandlers, "/", "/collections/hello/_introspect", GET, mockCC);
+    assertConditions(rsp.getValues().asMap(2), Utils.makeMap(
+        "/availableSubPaths", NOT_NULL,
+        "availableSubPaths /collections/hello/config/jmx", NOT_NULL,
+        "availableSubPaths /collections/hello/schema", NOT_NULL,
+        "availableSubPaths /collections/hello/shards", NOT_NULL,
+        "availableSubPaths /collections/hello/shards/{shard}", NOT_NULL,
+        "availableSubPaths /collections/hello/shards/{shard}/{replica}", NOT_NULL
+    ));
+
+  }
+  public void testTrailingTemplatePaths(){
+    PathTrie<Api> registry =  new PathTrie<>();
+    Api api = new Api(EMPTY_SPEC) {
+      @Override
+      public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
+
+      }
+    };
+    Api intropsect = new ApiBag.IntrospectApi(api,false);
+    ApiBag.registerIntrospect(Collections.emptyMap(),registry,"/c/.system/blob/{name}",intropsect);
+    ApiBag.registerIntrospect(Collections.emptyMap(), registry, "/c/.system/{x}/{name}", intropsect);
+    assertEquals(intropsect, registry.lookup("/c/.system/blob/random_string/_introspect", new HashMap<>()));
+    assertEquals(intropsect, registry.lookup("/c/.system/blob/_introspect", new HashMap<>()));
+    assertEquals(intropsect, registry.lookup("/c/.system/_introspect", new HashMap<>()));
+    assertEquals(intropsect, registry.lookup("/c/.system/v1/_introspect", new HashMap<>()));
+    assertEquals(intropsect, registry.lookup("/c/.system/v1/v2/_introspect", new HashMap<>()));
+  }
+  private SolrQueryResponse invoke(PluginBag<SolrRequestHandler> reqHandlers, String path,
+                                   String fullPath, SolrRequest.METHOD method,
+                                   CoreContainer mockCC) {
+    HashMap<String, String> parts = new HashMap<>();
+    boolean containerHandlerLookup = mockCC.getRequestHandlers() == reqHandlers;
+    path = path == null ? fullPath : path;
+    Api api = null;
+    if (containerHandlerLookup) {
+      api = V2HttpCall.getApiInfo(reqHandlers, path, "GET", fullPath, parts);
+    } else {
+      api = V2HttpCall.getApiInfo(mockCC.getRequestHandlers(), fullPath, "GET", fullPath, parts);
+      if (api == null) api = new CompositeApi(null);
+      if (api instanceof CompositeApi) {
+        CompositeApi compositeApi = (CompositeApi) api;
+        api = V2HttpCall.getApiInfo(reqHandlers, path, "GET", fullPath, parts);
+        compositeApi.add(api);
+        api = compositeApi;
+      }
+    }
+
+    SolrQueryResponse rsp = new SolrQueryResponse();
+    LocalSolrQueryRequest req = new LocalSolrQueryRequest(null, new MapSolrParams(new HashMap<>())){
+      @Override
+      public List<CommandOperation> getCommands(boolean validateInput) {
+        return Collections.emptyList();
+      }
+    };
+
+    api.call(req,rsp);
+    return rsp;
+
+  }
+
+
+  private void assertConditions(Map root, Map conditions) {
+    for (Object o : conditions.entrySet()) {
+      Map.Entry e = (Map.Entry) o;
+      String path = (String) e.getKey();
+      List<String> parts = StrUtils.splitSmart(path, path.charAt(0) == '/' ?  '/':' ');
+      if (parts.get(0).isEmpty()) parts.remove(0);
+      Object val = Utils.getObjectByPath(root, false, parts);
+      if (e.getValue() instanceof ValidatingJsonMap.PredicateWithErrMsg) {
+        ValidatingJsonMap.PredicateWithErrMsg value = (ValidatingJsonMap.PredicateWithErrMsg) e.getValue();
+        String err = value.test(val);
+        if(err != null){
+          assertEquals(err + " for " + e.getKey() + " in :" + Utils.toJSONString(root), e.getValue(), val);
+        }
+
+      } else {
+        assertEquals("incorrect value for path " + e.getKey() + " in :" + Utils.toJSONString(root), e.getValue(), val);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
new file mode 100644
index 0000000..7f072ec
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.io.StringReader;
+import java.lang.invoke.MethodHandles;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.common.params.MultiMapSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.Pair;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.servlet.SolrRequestParsers;
+import org.apache.solr.util.CommandOperation;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
+import static org.apache.solr.common.util.Utils.fromJSONString;
+
+public class TestCollectionAPIs extends SolrTestCaseJ4 {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+
+  public void testCommands() throws Exception {
+    MockCollectionsHandler collectionsHandler = new MockCollectionsHandler();
+    ApiBag apiBag = new ApiBag(false);
+    Collection<Api> apis = collectionsHandler.getApis();
+    for (Api api : apis) apiBag.register(api, Collections.EMPTY_MAP);
+    //test a simple create collection call
+    compareOutput(apiBag, "/collections", POST,
+        "{create:{name:'newcoll', config:'schemaless', numShards:2, replicationFactor:2 }}", null,
+        "{name:newcoll, fromApi:'true', replicationFactor:'2', collection.configName:schemaless, numShards:'2', stateFormat:'2', operation:create}");
+
+    //test a create collection with custom properties
+    compareOutput(apiBag, "/collections", POST,
+        "{create:{name:'newcoll', config:'schemaless', numShards:2, replicationFactor:2, properties:{prop1:'prop1val', prop2: prop2val} }}", null,
+        "{name:newcoll, fromApi:'true', replicationFactor:'2', collection.configName:schemaless, numShards:'2', stateFormat:'2', operation:create, property.prop1:prop1val, property.prop2:prop2val}");
+
+
+    compareOutput(apiBag, "/collections", POST,
+        "{create-alias:{name: aliasName , collections:[c1,c2] }}", null, "{operation : createalias, name: aliasName, collections:[c1,c2] }");
+
+    compareOutput(apiBag, "/collections", POST,
+        "{delete-alias:{ name: aliasName}}", null, "{operation : deletealias, name: aliasName}");
+
+    compareOutput(apiBag, "/collections/collName", POST,
+        "{reload:{}}", null,
+        "{name:collName, operation :reload}");
+
+    compareOutput(apiBag, "/collections/collName", DELETE,
+        null, null,
+        "{name:collName, operation :delete}");
+
+    compareOutput(apiBag, "/collections/collName/shards/shard1", DELETE,
+        null, null,
+        "{collection:collName, shard: shard1 , operation :deleteshard }");
+
+    compareOutput(apiBag, "/collections/collName/shards/shard1/replica1?deleteDataDir=true&onlyIfDown=true", DELETE,
+        null, null,
+        "{collection:collName, shard: shard1, replica :replica1 , deleteDataDir:'true', onlyIfDown: 'true', operation :deletereplica }");
+
+    compareOutput(apiBag, "/collections/collName/shards", POST,
+        "{split:{shard:shard1, ranges: '0-1f4,1f5-3e8,3e9-5dc', coreProperties : {prop1:prop1Val, prop2:prop2Val} }}", null,
+        "{collection: collName , shard : shard1, ranges :'0-1f4,1f5-3e8,3e9-5dc', operation : splitshard, property.prop1:prop1Val, property.prop2: prop2Val}"
+    );
+
+    compareOutput(apiBag, "/collections/collName/shards", POST,
+        "{add-replica:{shard: shard1, node: 'localhost_8978' , coreProperties : {prop1:prop1Val, prop2:prop2Val} }}", null,
+        "{collection: collName , shard : shard1, node :'localhost_8978', operation : addreplica, property.prop1:prop1Val, property.prop2: prop2Val}"
+    );
+
+    compareOutput(apiBag, "/collections/collName/shards", POST,
+        "{split:{ splitKey:id12345, coreProperties : {prop1:prop1Val, prop2:prop2Val} }}", null,
+        "{collection: collName , split.key : id12345 , operation : splitshard, property.prop1:prop1Val, property.prop2: prop2Val}"
+    );
+
+    compareOutput(apiBag, "/collections/collName", POST,
+        "{add-replica-property : {name:propA , value: VALA, shard: shard1, replica:replica1}}", null,
+        "{collection: collName, shard: shard1, replica : replica1 , property : propA , operation : addreplicaprop, property.value : 'VALA'}"
+    );
+
+    compareOutput(apiBag, "/collections/collName", POST,
+        "{delete-replica-property : {property: propA , shard: shard1, replica:replica1} }", null,
+        "{collection: collName, shard: shard1, replica : replica1 , property : propA , operation : deletereplicaprop}"
+    );
+
+    compareOutput(apiBag, "/collections/collName", POST,
+        "{modify : {rule : 'replica:*,cores:<5', autoAddReplicas : false} }", null,
+        "{collection: collName, operation : modifycollection , autoAddReplicas : 'false', rule : [{replica: '*', cores : '<5' }]}"
+    );
+    compareOutput(apiBag, "/cluster", POST,
+        "{add-role : {role : overseer, node : 'localhost_8978'} }", null,
+        "{operation : addrole ,role : overseer, node : 'localhost_8978'}"
+    );
+
+    compareOutput(apiBag, "/cluster", POST,
+        "{remove-role : {role : overseer, node : 'localhost_8978'} }", null,
+        "{operation : removerole ,role : overseer, node : 'localhost_8978'}"
+    );
+
+    compareOutput(apiBag, "/collections/coll1", POST,
+        "{balance-shard-unique : {property: preferredLeader} }", null,
+        "{operation : balanceshardunique ,collection : coll1, property : preferredLeader}"
+    );
+
+    compareOutput(apiBag, "/collections/coll1", POST,
+        "{migrate-docs : {forwardTimeout: 1800, target: coll2, splitKey: 'a123!'} }", null,
+        "{operation : migrate ,collection : coll1, target.collection:coll2, forward.timeout:1800, split.key:'a123!'}"
+    );
+
+  }
+
+  static ZkNodeProps compareOutput(final ApiBag apiBag, final String path, final SolrRequest.METHOD method,
+                            final String payload, final CoreContainer cc, String expectedOutputMapJson) throws Exception {
+    Pair<SolrQueryRequest, SolrQueryResponse> ctx = makeCall(apiBag, path, method, payload, cc);
+    ZkNodeProps output = (ZkNodeProps) ctx.second().getValues().get(ZkNodeProps.class.getName());
+    Map expected = (Map) fromJSONString(expectedOutputMapJson);
+    assertMapEqual(expected, output);
+    return output;
+
+  }
+
+  public static Pair<SolrQueryRequest, SolrQueryResponse> makeCall(final ApiBag apiBag, String path,
+                                                                   final SolrRequest.METHOD method,
+                                                                   final String payload, final CoreContainer cc) throws Exception {
+    SolrParams queryParams = new MultiMapSolrParams(Collections.EMPTY_MAP);
+    if (path.indexOf('?') > 0) {
+      String queryStr = path.substring(path.indexOf('?') + 1);
+      path = path.substring(0, path.indexOf('?'));
+      queryParams = SolrRequestParsers.parseQueryString(queryStr);
+    }
+    final HashMap<String, String> parts = new HashMap<>();
+    Api api = apiBag.lookup(path, method.toString(), parts);
+    if (api == null) throw new RuntimeException("No handler at path :" + path);
+    SolrQueryResponse rsp = new SolrQueryResponse();
+    LocalSolrQueryRequest req = new LocalSolrQueryRequest(null, queryParams) {
+      @Override
+      public List<CommandOperation> getCommands(boolean validateInput) {
+        if (payload == null) return Collections.emptyList();
+        return ApiBag.getCommandOperations(new StringReader(payload), api.getCommandSchema(), true);
+      }
+
+      @Override
+      public Map<String, String> getPathTemplateValues() {
+        return parts;
+      }
+
+      @Override
+      public String getHttpMethod() {
+        return method.toString();
+      }
+    };
+    try {
+      api.call(req, rsp);
+    } catch (ApiBag.ExceptionWithErrObject e) {
+      throw new RuntimeException(e.getMessage() + Utils.toJSONString(e.getErrs()), e);
+
+    }
+    return new Pair<>(req, rsp);
+  }
+
+  private static void assertMapEqual(Map expected, ZkNodeProps actual) {
+    assertEquals(errorMessage(expected, actual), expected.size(), actual.getProperties().size());
+    for (Object o : expected.entrySet()) {
+      Map.Entry e = (Map.Entry) o;
+      Object actualVal = actual.get((String) e.getKey());
+      if (actualVal instanceof String[]) {
+        actualVal = Arrays.asList((String[]) actualVal);
+      }
+      assertEquals(errorMessage(expected, actual), String.valueOf(e.getValue()), String.valueOf(actualVal));
+    }
+  }
+
+  private static String errorMessage(Map expected, ZkNodeProps actual) {
+    return "expected: " + Utils.toJSONString(expected) + "\nactual: " + Utils.toJSONString(actual);
+
+  }
+
+  static class MockCollectionsHandler extends CollectionsHandler {
+    LocalSolrQueryRequest req;
+
+    MockCollectionsHandler() {
+    }
+
+    @Override
+    void invokeAction(SolrQueryRequest req, SolrQueryResponse rsp,
+                      CoreContainer cores,
+                      CollectionParams.CollectionAction action,
+                      CollectionOperation operation) throws Exception {
+      Map<String, Object> result = operation.execute(req, rsp, this);
+      if (result != null) {
+        result.put(QUEUE_OPERATION, operation.action.toLower());
+        rsp.add(ZkNodeProps.class.getName(), new ZkNodeProps(result));
+      }
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java b/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
new file mode 100644
index 0000000..d2c96a6
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestConfigsApi.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+
+import java.util.Map;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.zookeeper.KeeperException;
+
+import static java.util.Collections.EMPTY_MAP;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
+import static org.apache.solr.handler.admin.TestCollectionAPIs.compareOutput;
+
+public class TestConfigsApi extends SolrTestCaseJ4 {
+
+
+  public void testCommands() throws Exception {
+
+    ConfigSetsHandler handler = new ConfigSetsHandler(null) {
+      @Override
+      protected void sendToZk(SolrQueryResponse rsp,
+                              ConfigSetOperation operation,
+                              Map<String, Object> result)
+          throws KeeperException, InterruptedException {
+        result.put(QUEUE_OPERATION, operation.action.toLower());
+        rsp.add(ZkNodeProps.class.getName(), new ZkNodeProps(result));
+      }
+    };
+    ApiBag apiBag = new ApiBag(false);
+    for (Api api : handler.getApis()) apiBag.register(api, EMPTY_MAP);
+    compareOutput(apiBag, "/cluster/configs/sample", DELETE, null, null,
+        "{name :sample, operation:delete}");
+
+    compareOutput(apiBag, "/cluster/configs", POST, "{create:{name : newconf, baseConfigSet: sample }}", null,
+        "{operation:create, name :newconf,  baseConfigSet: sample }");
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/handler/admin/TestCoreAdminApis.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestCoreAdminApis.java b/solr/core/src/test/org/apache/solr/handler/admin/TestCoreAdminApis.java
new file mode 100644
index 0000000..f263f5e
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestCoreAdminApis.java
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.easymock.EasyMock;
+
+import static org.apache.solr.common.util.Utils.fromJSONString;
+import static org.easymock.EasyMock.anyBoolean;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.getCurrentArguments;
+
+public class TestCoreAdminApis extends SolrTestCaseJ4 {
+
+  public void testCalls() throws Exception {
+    Map<String, Object[]> calls = new HashMap<>();
+    CoreContainer mockCC = getCoreContainerMock(calls, new HashMap<>());
+
+    CoreAdminHandler  coreAdminHandler = new CoreAdminHandler(mockCC);
+    ApiBag apiBag = new ApiBag(false);
+    for (Api api : coreAdminHandler.getApis()) {
+      apiBag.register(api, Collections.EMPTY_MAP);
+    }
+    TestCollectionAPIs.makeCall(apiBag, "/cores", SolrRequest.METHOD.POST,
+        "{create:{name: hello, instanceDir : someDir, schema: 'schema.xml'}}", mockCC);
+    Object[] params = calls.get("create");
+    assertEquals("hello" ,params[0]);
+    assertEquals(fromJSONString("{schema : schema.xml}") ,params[2]);
+
+    TestCollectionAPIs.makeCall(apiBag, "/cores/core1", SolrRequest.METHOD.POST,
+        "{swap:{with: core2}}", mockCC);
+    params = calls.get("swap");
+    assertEquals("core1" ,params[0]);
+    assertEquals("core2" ,params[1]);
+
+    TestCollectionAPIs.makeCall(apiBag, "/cores/core1", SolrRequest.METHOD.POST,
+        "{rename:{to: core2}}", mockCC);
+    params = calls.get("swap");
+    assertEquals("core1" ,params[0]);
+    assertEquals("core2" ,params[1]);
+
+    TestCollectionAPIs.makeCall(apiBag, "/cores/core1", SolrRequest.METHOD.POST,
+        "{unload:{deleteIndex : true}}", mockCC);
+    params = calls.get("unload");
+    assertEquals("core1" ,params[0]);
+    assertEquals(Boolean.TRUE ,params[1]);
+  }
+
+  public static CoreContainer getCoreContainerMock(final Map<String, Object[]> in,Map<String,Object> out ) {
+    CoreContainer mockCC = EasyMock.createMock(CoreContainer.class);
+    EasyMock.reset(mockCC);
+    mockCC.create(anyObject(String.class), anyObject(Path.class) , anyObject(Map.class), anyBoolean());
+    EasyMock.expectLastCall().andAnswer(() -> {
+      in.put("create", getCurrentArguments());
+      return null;
+    }).anyTimes();
+    mockCC.swap(anyObject(String.class), anyObject(String.class));
+    EasyMock.expectLastCall().andAnswer(() -> {
+      in.put("swap", getCurrentArguments());
+      return null;
+    }).anyTimes();
+
+    mockCC.rename(anyObject(String.class), anyObject(String.class));
+    EasyMock.expectLastCall().andAnswer(() -> {
+      in.put("rename", getCurrentArguments());
+      return null;
+    }).anyTimes();
+
+    mockCC.unload(anyObject(String.class), anyBoolean(),
+        anyBoolean(), anyBoolean());
+    EasyMock.expectLastCall().andAnswer(() -> {
+      in.put("unload", getCurrentArguments());
+      return null;
+    }).anyTimes();
+
+    mockCC.getCoreRootDirectory();
+    EasyMock.expectLastCall().andAnswer(() -> Paths.get("coreroot")).anyTimes();
+    mockCC.getContainerProperties();
+    EasyMock.expectLastCall().andAnswer(() -> new Properties()).anyTimes();
+
+    mockCC.getRequestHandlers();
+    EasyMock.expectLastCall().andAnswer(() -> out.get("getRequestHandlers")).anyTimes();
+
+    EasyMock.replay(mockCC);
+    return mockCC;
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
index d5db82e..ea8fd7b 100644
--- a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
+++ b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
@@ -27,6 +27,7 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.schema.SimilarityFactory;
 import org.apache.solr.search.similarities.SchemaSimilarityFactory;
+import org.apache.solr.util.RESTfulServerProvider;
 import org.apache.solr.util.RestTestBase;
 import org.apache.solr.util.RestTestHarness;
 
@@ -34,9 +35,12 @@ import org.junit.After;
 import org.junit.Before;
 import org.noggit.JSONParser;
 import org.noggit.ObjectBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.StringReader;
+import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -45,6 +49,8 @@ import java.util.Set;
 
 
 public class TestBulkSchemaAPI extends RestTestBase {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
 
   private static File tmpSolrHome;
 
@@ -58,6 +64,15 @@ public class TestBulkSchemaAPI extends RestTestBase {
 
     createJettyAndHarness(tmpSolrHome.getAbsolutePath(), "solrconfig-managed-schema.xml", "schema-rest.xml",
         "/solr", true, null);
+    if (random().nextBoolean()) {
+      log.info("These tests are run with V2 API");
+      restTestHarness.setServerProvider(new RESTfulServerProvider() {
+        @Override
+        public String getBaseURL() {
+          return jetty.getBaseUrl().toString() + "/v2/cores/" + DEFAULT_TEST_CORENAME;
+        }
+      });
+    }
   }
 
   @After

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
index 137fcdd..397f4e8 100644
--- a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
@@ -85,6 +85,10 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
 
     String authcPrefix = "/admin/authentication";
     String authzPrefix = "/admin/authorization";
+    if(random().nextBoolean()){
+      authcPrefix = "/v2/cluster/security/authentication";
+      authzPrefix = "/v2/cluster/security/authorization";
+    }
 
     NamedList<Object> rsp;
     HttpClient cl = null;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/71abe130/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java b/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java
index 0f70d73..03656c5 100644
--- a/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java
+++ b/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java
@@ -312,7 +312,7 @@ public class TestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
     perms.runCmd("{set-permission : {name: config-edit, role: admin } }", true);
     assertEquals("config-edit",  getObjectByPath(perms.conf, false, "permissions[0]/name"));
     assertEquals(1 , perms.getVal("permissions[0]/index"));
-    assertEquals("admin" ,  perms.getVal("permissions[0]/role"));
+    assertEquals("admin", perms.getVal("permissions[0]/role"));
     perms.runCmd("{set-permission : {name: config-edit, role: [admin, dev], index:2 } }", false);
     perms.runCmd("{set-permission : {name: config-edit, role: [admin, dev], index:1}}", true);
     Collection roles = (Collection) perms.getVal("permissions[0]/role");
@@ -324,19 +324,19 @@ public class TestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
     assertEquals("x", perms.getVal("permissions[1]/collection"));
     assertEquals("/a/b", perms.getVal("permissions[1]/path"));
     perms.runCmd("{update-permission : {index : 2, method : POST }}", true);
-    assertEquals("POST" , perms.getVal("permissions[1]/method"));
+    assertEquals("POST", perms.getVal("permissions[1]/method"));
     perms.runCmd("{set-permission : {name : read, collection : y, role:[guest, dev] ,  before :2}}", true);
     assertNotNull(perms.getVal("permissions[2]"));
     assertEquals("y", perms.getVal("permissions[1]/collection"));
     assertEquals("read", perms.getVal("permissions[1]/name"));
     perms.runCmd("{delete-permission : 3}", true);
     assertTrue(captureErrors(perms.parsedCommands).isEmpty());
-    assertEquals("y",perms.getVal("permissions[1]/collection"));
+    assertEquals("y", perms.getVal("permissions[1]/collection"));
   }
 
   static class  Perms {
     Map conf =  new HashMap<>();
-    RuleBasedAuthorizationPlugin plugin = new RuleBasedAuthorizationPlugin();
+    ConfigEditablePlugin plugin = new RuleBasedAuthorizationPlugin();
     List<CommandOperation> parsedCommands;
 
     public void runCmd(String cmds, boolean failOnError) throws IOException {