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 2020/09/17 00:52:25 UTC

[lucene-solr] branch branch_8x updated: SOLR-14871: Use Annotations for v2 APIs in /cluster path

This is an automated email from the ASF dual-hosted git repository.

noble pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/branch_8x by this push:
     new 298dcbf  SOLR-14871: Use Annotations for v2 APIs in /cluster path
298dcbf is described below

commit 298dcbf7aad00ff44434ceacc3a89f0449ec72bf
Author: noblepaul <no...@gmail.com>
AuthorDate: Thu Sep 17 10:51:56 2020 +1000

    SOLR-14871: Use Annotations for v2 APIs in /cluster path
---
 .../src/java/org/apache/solr/api/AnnotatedApi.java |  12 +-
 .../src/java/org/apache/solr/api/PayloadObj.java   |   4 +-
 .../java/org/apache/solr/core/CoreContainer.java   |   4 +
 .../java/org/apache/solr/handler/ClusterAPI.java   | 175 +++++++++++++++++++++
 .../org/apache/solr/handler/SolrConfigHandler.java |   2 +-
 .../solr/handler/admin/CollectionHandlerApi.java   |  53 -------
 .../solr/handler/admin/CollectionsHandler.java     |  27 ++--
 .../java/org/apache/solr/schema/IndexSchema.java   |   2 +-
 .../solr/handler/admin/TestCollectionAPIs.java     |  10 ++
 .../client/solrj/request/CollectionApiMapping.java |  36 +----
 .../solrj/request/beans/ClusterPropInfo.java       |  72 +++++++++
 .../solr/common/params/DefaultSolrParams.java      |   2 +-
 .../apache/solr/common/util/JsonSchemaCreator.java |  14 +-
 .../java/org/apache/solr/common/util/Utils.java    |   2 +-
 .../src/resources/apispec/cluster.Commands.json    | 174 --------------------
 .../apispec/cluster.commandstatus.delete.json      |  10 --
 .../resources/apispec/cluster.commandstatus.json   |  20 ---
 solr/solrj/src/resources/apispec/cluster.json      |  14 --
 .../apache/solr/common/util/JsonValidatorTest.java |   8 -
 19 files changed, 310 insertions(+), 331 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java b/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java
index 7b89201..75bfd1b 100644
--- a/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java
+++ b/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java
@@ -38,7 +38,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SpecProvider;
-import org.apache.solr.common.util.*;
+import org.apache.solr.common.util.CommandOperation;
+import org.apache.solr.common.util.ContentStream;
+import org.apache.solr.common.util.JsonSchemaCreator;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.security.AuthorizationContext;
@@ -265,6 +269,7 @@ public class AnnotatedApi extends Api implements PermissionNameProvider , Closea
 
     @SuppressWarnings({"unchecked"})
     void invoke(SolrQueryRequest req, SolrQueryResponse rsp, CommandOperation cmd) {
+      Object original = null;
       try {
         Object o = null;
         String commandName = null;
@@ -280,12 +285,13 @@ public class AnnotatedApi extends Api implements PermissionNameProvider , Closea
             }
           } else {
             commandName = cmd.name;
-            o = cmd.getCommandData();
+            original = cmd.getCommandData();
+            o = original;
             if (o instanceof Map && parameterClass != null && parameterClass != Map.class) {
               o = mapper.readValue(Utils.toJSONString(o), parameterClass);
             }
           }
-          PayloadObj<Object> payloadObj = new PayloadObj<>(commandName, o, o, req, rsp);
+          PayloadObj<Object> payloadObj = new PayloadObj<>(commandName, original, o, req, rsp);
           cmd = payloadObj;
           method.invoke(obj, payloadObj);
           checkForErrorInPayload(cmd);
diff --git a/solr/core/src/java/org/apache/solr/api/PayloadObj.java b/solr/core/src/java/org/apache/solr/api/PayloadObj.java
index 7941304..df4c1e4 100644
--- a/solr/core/src/java/org/apache/solr/api/PayloadObj.java
+++ b/solr/core/src/java/org/apache/solr/api/PayloadObj.java
@@ -31,8 +31,8 @@ public class PayloadObj<T> extends CommandOperation {
     final SolrQueryRequest req;
     final SolrQueryResponse rsp;
 
-    public PayloadObj(String operationName, Object metaData, T obj, SolrQueryRequest req, SolrQueryResponse rsp) {
-        super(operationName, metaData);
+    public PayloadObj(String operationName, Object originalMetadata, T obj, SolrQueryRequest req, SolrQueryResponse rsp) {
+        super(operationName, originalMetadata);
         this.obj = obj;
         this.req = req;
         this.rsp = rsp;
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index f980341..df9eef9 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -88,6 +88,7 @@ import org.apache.solr.core.DirectoryFactory.DirContext;
 import org.apache.solr.core.backup.repository.BackupRepository;
 import org.apache.solr.core.backup.repository.BackupRepositoryFactory;
 import org.apache.solr.filestore.PackageStoreAPI;
+import org.apache.solr.handler.ClusterAPI;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.SnapShooter;
 import org.apache.solr.handler.admin.AutoscalingHistoryHandler;
@@ -724,6 +725,9 @@ public class CoreContainer {
     createHandler(ZK_PATH, ZookeeperInfoHandler.class.getName(), ZookeeperInfoHandler.class);
     createHandler(ZK_STATUS_PATH, ZookeeperStatusHandler.class.getName(), ZookeeperStatusHandler.class);
     collectionsHandler = createHandler(COLLECTIONS_HANDLER_PATH, cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
+    ClusterAPI clusterAPI = new ClusterAPI(collectionsHandler);
+    containerHandlers.getApiBag().registerObject(clusterAPI);
+    containerHandlers.getApiBag().registerObject(clusterAPI.commands);
     /*
      * HealthCheckHandler needs to be initialized before InfoHandler, since the later one will call CoreContainer.getHealthCheckHandler().
      * We don't register the handler here because it'll be registered inside InfoHandler
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
new file mode 100644
index 0000000..8d1fcae
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -0,0 +1,175 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+
+import org.apache.solr.api.Command;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.api.PayloadObj;
+import org.apache.solr.client.solrj.request.beans.ClusterPropInfo;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.annotation.JsonProperty;
+import org.apache.solr.common.cloud.ClusterProperties;
+import org.apache.solr.common.params.DefaultSolrParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.ReflectMapWriter;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.admin.CollectionsHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
+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.cloud.api.collections.OverseerCollectionMessageHandler.REQUESTID;
+import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDROLE;
+import static org.apache.solr.common.params.CollectionParams.CollectionAction.CLUSTERPROP;
+import static org.apache.solr.common.params.CollectionParams.CollectionAction.OVERSEERSTATUS;
+import static org.apache.solr.common.params.CollectionParams.CollectionAction.REMOVEROLE;
+import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
+import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
+
+public class ClusterAPI {
+  private final CoreContainer coreContainer;
+  private final CollectionsHandler collectionsHandler;
+
+  public  final Commands commands = new Commands();
+
+  public ClusterAPI(CollectionsHandler ch) {
+    this.collectionsHandler = ch;
+    this.coreContainer = ch.getCoreContainer();
+  }
+
+
+  @EndPoint(method = GET,
+      path = "/cluster/overseer",
+      permission = COLL_READ_PERM)
+  public void getOverseerStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    coreContainer.getCollectionsHandler().handleRequestBody(wrapParams(req, "action", OVERSEERSTATUS.toString()), rsp);
+  }
+
+  @EndPoint(method = GET,
+      path = "/cluster",
+      permission = COLL_READ_PERM)
+  public void getCluster(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    CollectionsHandler.CollectionOperation.LIST_OP.execute(req, rsp, coreContainer.getCollectionsHandler());
+  }
+
+  @EndPoint(method = DELETE,
+      path = "/cluster/command-status/{id}",
+      permission = COLL_EDIT_PERM)
+  public void deleteCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    wrapParams(req, REQUESTID, req.getPathTemplateValues().get("id"));
+    CollectionsHandler.CollectionOperation.DELETESTATUS_OP.execute(req, rsp, coreContainer.getCollectionsHandler());
+  }
+
+  @SuppressWarnings({"rawtypes"})
+  public static SolrQueryRequest wrapParams(SolrQueryRequest req, Object... def) {
+    Map m = Utils.makeMap(def);
+    return wrapParams(req, m);
+  }
+
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  public static SolrQueryRequest wrapParams(SolrQueryRequest req, Map m) {
+    ModifiableSolrParams solrParams = new ModifiableSolrParams();
+    m.forEach((k, v) -> {
+      if(v == null) return;
+      solrParams.add(k.toString(), String.valueOf(v));
+    });
+    DefaultSolrParams dsp = new DefaultSolrParams(req.getParams(),solrParams);
+    req.setParams(dsp);
+    return req;
+  }
+
+  @EndPoint(method = GET,
+      path = "/cluster/command-status",
+      permission = COLL_READ_PERM)
+  public void getCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    CollectionsHandler.CollectionOperation.REQUESTSTATUS_OP.execute(req, rsp, collectionsHandler);
+  }
+
+  @EndPoint(method = GET,
+      path = "/cluster/nodes",
+      permission = COLL_READ_PERM)
+  public void getNodes(SolrQueryRequest req, SolrQueryResponse rsp) {
+    rsp.add("nodes", coreContainer.getZkController().getClusterState().getLiveNodes());
+  }
+
+  @EndPoint(method = POST,
+      path = "/cluster",
+      permission = COLL_EDIT_PERM)
+  public class Commands {
+
+
+    @Command(name = "add-role")
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public void addRole(PayloadObj<RoleInfo> obj) throws Exception {
+      RoleInfo info = obj.get();
+      Map m = info.toMap(new HashMap<>());
+      m.put("action", ADDROLE.toString());
+      collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), m), obj.getResponse());
+    }
+
+    @Command(name = "remove-role")
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public void removeRole(PayloadObj<RoleInfo> obj) throws Exception {
+      RoleInfo info = obj.get();
+      Map m = info.toMap(new HashMap<>());
+      m.put("action", REMOVEROLE.toString());
+      collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(),m), obj.getResponse());
+
+    }
+
+    @Command(name = "set-obj-property")
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public void setObjProperty(PayloadObj<ClusterPropInfo> obj) {
+      //Not using the object directly here because the API differentiate between {name:null} and {}
+      Map m = obj.getDataMap();
+      ClusterProperties clusterProperties = new ClusterProperties(coreContainer.getZkController().getZkClient());
+      try {
+        clusterProperties.setClusterProperties(m);
+      } catch (Exception e) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in API", e);
+      }
+
+    }
+
+    @Command(name = "set-property")
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public void setProperty(PayloadObj<Map<String,String>> obj) throws Exception {
+      Map m =  obj.get();
+      m.put("action", CLUSTERPROP.toString());
+      collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(),m ), obj.getResponse());
+
+    }
+
+  }
+
+  public static class RoleInfo implements ReflectMapWriter {
+    @JsonProperty(required = true)
+    public String node;
+    @JsonProperty(required = true)
+    public String role;
+
+  }
+
+
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
index 1c0c709..9ac6207 100644
--- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
@@ -60,6 +60,7 @@ import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.CommandOperation;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.ConfigOverlay;
@@ -77,7 +78,6 @@ import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.schema.SchemaManager;
 import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.security.PermissionNameProvider;
-import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.util.RTimer;
 import org.apache.solr.util.SolrPluginUtils;
 import org.apache.solr.util.plugin.SolrCoreAware;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
index b63f7bd..fe856df 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionHandlerApi.java
@@ -17,7 +17,6 @@
 
 package org.apache.solr.handler.admin;
 
-import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.EnumMap;
@@ -28,18 +27,11 @@ import org.apache.solr.client.solrj.request.CollectionApiMapping;
 import org.apache.solr.client.solrj.request.CollectionApiMapping.CommandMeta;
 import org.apache.solr.client.solrj.request.CollectionApiMapping.Meta;
 import org.apache.solr.client.solrj.request.CollectionApiMapping.V2EndPoint;
-import org.apache.solr.common.Callable;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.cloud.ClusterProperties;
-import org.apache.solr.common.util.CommandOperation;
 import org.apache.solr.handler.admin.CollectionsHandler.CollectionOperation;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class CollectionHandlerApi extends BaseHandlerApiSupport {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   final CollectionsHandler handler;
   static Collection<ApiCommand> apiCommands = createCollMapping();
@@ -64,55 +56,10 @@ public class CollectionHandlerApi extends BaseHandlerApiSupport {
         }
       }
     }
-    //The following APIs have only V2 implementations
-    addApi(result, Meta.GET_NODES, params -> params.rsp.add("nodes", ((CollectionHandlerApi) params.apiHandler).handler.coreContainer.getZkController().getClusterState().getLiveNodes()));
-    addApi(result, Meta.SET_CLUSTER_PROPERTY_OBJ, params -> {
-      List<CommandOperation> commands = params.req.getCommands(true);
-      if (commands == null || commands.isEmpty()) throw new RuntimeException("Empty commands");
-      ClusterProperties clusterProperties = new ClusterProperties(((CollectionHandlerApi) params.apiHandler).handler.coreContainer.getZkController().getZkClient());
-
-      try {
-        clusterProperties.setClusterProperties(commands.get(0).getDataMap());
-      } catch (Exception e) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in API", e);
-      }
-    });
-
-    for (Meta meta : Meta.values()) {
-      if (result.get(meta) == null) {
-        log.error("ERROR_INIT. No corresponding API implementation for : {}", meta.commandName);
-      }
-    }
 
     return result.values();
   }
 
-  private static void addApi(Map<Meta, ApiCommand> result, Meta metaInfo, Callable<ApiParams> fun) {
-    result.put(metaInfo, new ApiCommand() {
-      @Override
-      public CommandMeta meta() {
-        return metaInfo;
-      }
-
-      @Override
-      public void invoke(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) throws Exception {
-        fun.call(new ApiParams(req, rsp, apiHandler));
-      }
-    });
-  }
-
-  static class ApiParams {
-    final SolrQueryRequest req;
-    final SolrQueryResponse rsp;
-    final BaseHandlerApiSupport apiHandler;
-
-    ApiParams(SolrQueryRequest req, SolrQueryResponse rsp, BaseHandlerApiSupport apiHandler) {
-      this.req = req;
-      this.rsp = rsp;
-      this.apiHandler = apiHandler;
-    }
-  }
-
   public CollectionHandlerApi(CollectionsHandler handler) {
     this.handler = handler;
   }
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 42df2e5..c753c59 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -228,17 +228,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
   @Override
   public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
     // Make sure the cores is enabled
-    CoreContainer cores = getCoreContainer();
-    if (cores == null) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-          "Core container instance missing");
-    }
-
-    // Make sure that the core is ZKAware
-    if (!cores.isZooKeeperAware()) {
-      throw new SolrException(ErrorCode.BAD_REQUEST,
-          "Solr instance is not running in SolrCloud mode.");
-    }
+    CoreContainer cores = checkErrors();
 
     // Pick the action
     SolrParams params = req.getParams();
@@ -261,6 +251,21 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     rsp.setHttpCaching(false);
   }
 
+  protected CoreContainer checkErrors() {
+    CoreContainer cores = getCoreContainer();
+    if (cores == null) {
+      throw new SolrException(ErrorCode.BAD_REQUEST,
+          "Core container instance missing");
+    }
+
+    // Make sure that the core is ZKAware
+    if (!cores.isZooKeeperAware()) {
+      throw new SolrException(ErrorCode.BAD_REQUEST,
+          "Solr instance is not running in SolrCloud mode.");
+    }
+    return cores;
+  }
+
   @SuppressWarnings({"unchecked"})
   void invokeAction(SolrQueryRequest req, SolrQueryResponse rsp, CoreContainer cores, CollectionAction action, CollectionOperation operation) throws Exception {
     if (!coreContainer.isZooKeeperAware()) {
diff --git a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
index 83dccd5..bd4ae7e 100644
--- a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
+++ b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
@@ -54,6 +54,7 @@ import org.apache.solr.common.MapSerializable;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.cloud.SolrClassLoader;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
@@ -62,7 +63,6 @@ import org.apache.solr.common.util.Cache;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Pair;
 import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.common.cloud.SolrClassLoader;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.core.XmlConfigFile;
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
index 78bf879..2b342ec 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
@@ -40,6 +40,7 @@ import org.apache.solr.common.util.ContentStreamBase;
 import org.apache.solr.common.util.Pair;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.ClusterAPI;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -84,6 +85,10 @@ public class TestCollectionAPIs extends SolrTestCaseJ4 {
       apiBag = new ApiBag(false);
       Collection<Api> apis = collectionsHandler.getApis();
       for (Api api : apis) apiBag.register(api, Collections.emptyMap());
+
+      ClusterAPI clusterAPI = new ClusterAPI(collectionsHandler);
+      apiBag.registerObject(clusterAPI);
+      apiBag.registerObject(clusterAPI.commands);
     }
     //test a simple create collection call
     compareOutput(apiBag, "/collections", POST,
@@ -278,6 +283,11 @@ public class TestCollectionAPIs extends SolrTestCaseJ4 {
     }
 
     @Override
+    protected CoreContainer checkErrors() {
+      return null;
+    }
+
+    @Override
     protected void copyFromClusterProp(Map<String, Object> props, String prop) {
 
     }
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
index 5df18e0..9d35e40 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
@@ -41,12 +41,7 @@ import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
 import static org.apache.solr.client.solrj.request.CollectionApiMapping.ConfigSetEndPoint.CONFIG_COMMANDS;
 import static org.apache.solr.client.solrj.request.CollectionApiMapping.ConfigSetEndPoint.CONFIG_DEL;
 import static org.apache.solr.client.solrj.request.CollectionApiMapping.ConfigSetEndPoint.LIST_CONFIG;
-import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.CLUSTER;
 import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.CLUSTER_ALIASES;
-import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.CLUSTER_CMD;
-import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.CLUSTER_CMD_STATUS;
-import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.CLUSTER_CMD_STATUS_DELETE;
-import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.CLUSTER_NODES;
 import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.COLLECTIONS;
 import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.COLLECTIONS_COMMANDS;
 import static org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.COLLECTION_STATE;
@@ -66,10 +61,6 @@ public class CollectionApiMapping {
 
   public enum Meta implements CommandMeta {
     GET_COLLECTIONS(COLLECTIONS, GET, LIST),
-    GET_CLUSTER(CLUSTER, GET, LIST, "/cluster", null),
-    GET_CLUSTER_OVERSEER(CLUSTER, GET, OVERSEERSTATUS, "/cluster/overseer", null),
-    GET_CLUSTER_STATUS_CMD(CLUSTER_CMD_STATUS, GET, REQUESTSTATUS),
-    DELETE_CLUSTER_STATUS(CLUSTER_CMD_STATUS_DELETE, DELETE, DELETESTATUS),
     GET_A_COLLECTION(COLLECTION_STATE, GET, CLUSTERSTATUS),
     LIST_ALIASES(CLUSTER_ALIASES, GET, LISTALIASES),
     CREATE_COLLECTION(COLLECTIONS_COMMANDS,
@@ -190,28 +181,23 @@ public class CollectionApiMapping {
             NAME, "collection",
             "propertyName", "name",
             "propertyValue", "value")),
-    ADD_ROLE(CLUSTER_CMD,
+/*    ADD_ROLE(CLUSTER_CMD,
         POST,
         ADDROLE,
-        "add-role",null),
-    REMOVE_ROLE(CLUSTER_CMD,
+        "add-role",null),*/
+/*    REMOVE_ROLE(CLUSTER_CMD,
         POST,
         REMOVEROLE,
-        "remove-role",null),
+        "remove-role",null),*/
 
-    SET_CLUSTER_PROPERTY(CLUSTER_CMD,
+  /*  SET_CLUSTER_PROPERTY(CLUSTER_CMD,
         POST,
         CLUSTERPROP,
-        "set-property",null),
-    SET_CLUSTER_PROPERTY_OBJ(CLUSTER_CMD,
+        "set-property",null),*/
+   /* SET_CLUSTER_PROPERTY_OBJ(CLUSTER_CMD,
         POST,
         null,
-        "set-obj-property", null),
-    UTILIZE_NODE(CLUSTER_CMD,
-        POST,
-        UTILIZENODE,
-        "utilize-node",null),
-
+        "set-obj-property", null),*/
     BACKUP_COLLECTION(COLLECTIONS_COMMANDS,
         POST,
         BACKUP,
@@ -223,7 +209,6 @@ public class CollectionApiMapping {
         "restore-collection",
         null
     ),
-    GET_NODES(CLUSTER_NODES, GET, null),
     FORCE_LEADER(PER_COLLECTION_PER_SHARD_COMMANDS, POST, CollectionAction.FORCELEADER, "force-leader", null),
     BALANCE_SHARD_UNIQUE(PER_COLLECTION, POST, BALANCESHARDUNIQUE,"balance-shard-unique" , null)
     ;
@@ -347,12 +332,7 @@ public class CollectionApiMapping {
   }
 
   public enum EndPoint implements V2EndPoint {
-    CLUSTER("cluster"),
     CLUSTER_ALIASES("cluster.aliases"),
-    CLUSTER_CMD("cluster.Commands"),
-    CLUSTER_NODES("cluster.nodes"),
-    CLUSTER_CMD_STATUS("cluster.commandstatus"),
-    CLUSTER_CMD_STATUS_DELETE("cluster.commandstatus.delete"),
     COLLECTIONS_COMMANDS("collections.Commands"),
     COLLECTIONS("collections"),
     COLLECTION_STATE("collections.collection"),
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/ClusterPropInfo.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/ClusterPropInfo.java
new file mode 100644
index 0000000..fe768be
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/ClusterPropInfo.java
@@ -0,0 +1,72 @@
+/*
+ * 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.client.solrj.request.beans;
+
+import org.apache.solr.common.annotation.JsonProperty;
+import org.apache.solr.common.util.ReflectMapWriter;
+
+public class ClusterPropInfo implements ReflectMapWriter {
+
+  @JsonProperty
+  public String urlScheme;
+
+  @JsonProperty
+  public Integer maxCoresPerNode;
+  @JsonProperty
+  public String location;
+
+  @JsonProperty
+  public DefaultsInfo defaults;
+
+  @JsonProperty
+  public CollectionDefaults collectionDefaults;
+
+  public static class CollectionDefaults implements ReflectMapWriter {
+    @JsonProperty
+    public Integer numShards;
+    @JsonProperty
+    public Integer tlogReplicas;
+    @JsonProperty
+    public Integer pullReplicas;
+    @JsonProperty
+    public Integer nrtReplicas;
+
+  }
+
+  public static class DefaultsInfo implements ReflectMapWriter {
+
+    @JsonProperty
+    public CollectionDefaults collection;
+
+    @JsonProperty
+    public CollectionDefaults cluster;
+
+  }
+
+  public static class ClusterInfo implements ReflectMapWriter {
+    @JsonProperty
+    public Boolean useLegacyReplicaAssignment;
+
+
+    @JsonProperty
+    public CollectionDefaults collection;
+
+  }
+
+
+}
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/DefaultSolrParams.java b/solr/solrj/src/java/org/apache/solr/common/params/DefaultSolrParams.java
index c8bc3b0..bec6266 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/DefaultSolrParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/DefaultSolrParams.java
@@ -27,7 +27,7 @@ public class DefaultSolrParams extends SolrParams {
   protected final SolrParams params;
   protected final SolrParams defaults;
 
-  protected DefaultSolrParams(SolrParams params, SolrParams defaults) {
+  public DefaultSolrParams(SolrParams params, SolrParams defaults) {
     assert params != null && defaults != null;
     this.params = params;
     this.defaults = defaults;
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/JsonSchemaCreator.java b/solr/solrj/src/java/org/apache/solr/common/util/JsonSchemaCreator.java
index defd33d..e6cef30 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/JsonSchemaCreator.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/JsonSchemaCreator.java
@@ -58,10 +58,16 @@ public class JsonSchemaCreator {
   private static Map<String, Object> createSchemaFromType(java.lang.reflect.Type t, Map<String, Object> map) {
     if (natives.containsKey(t)) {
       map.put("type", natives.get(t));
-    } else if (t instanceof ParameterizedType && ((ParameterizedType) t).getRawType() == List.class) {
-      Type typ = ((ParameterizedType) t).getActualTypeArguments()[0];
-      map.put("type", "array");
-      map.put("items", getSchema(typ));
+    } else if (t instanceof ParameterizedType) {
+      if (((ParameterizedType) t).getRawType() == List.class) {
+        Type typ = ((ParameterizedType) t).getActualTypeArguments()[0];
+        map.put("type", "array");
+        map.put("items", getSchema(typ));
+      } else if (((ParameterizedType) t).getRawType() == Map.class) {
+        Type typ = ((ParameterizedType) t).getActualTypeArguments()[0];
+        map.put("type", "object");
+        map.put("additionalProperties", true);
+      }
     } else {
       createObjectSchema((Class) t, map);
     }
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/Utils.java b/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
index 1437111..08e96e5 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
@@ -877,7 +877,7 @@ public class Utils {
               l.add((ew, inst) -> ew.put(fname, (float) mh.invoke(inst)));
             } else {
               MethodHandle mh = lookup.findGetter(c, field.getName(), field.getType());
-              l.add((ew, inst) -> ew.put(fname, mh.invoke(inst)));
+              l.add((ew, inst) -> ew.putIfNotNull(fname, mh.invoke(inst)));
             }
           } catch (NoSuchFieldException e) {
             //this is unlikely
diff --git a/solr/solrj/src/resources/apispec/cluster.Commands.json b/solr/solrj/src/resources/apispec/cluster.Commands.json
deleted file mode 100644
index 069cd1d..0000000
--- a/solr/solrj/src/resources/apispec/cluster.Commands.json
+++ /dev/null
@@ -1,174 +0,0 @@
-{
-  "documentation": "https://lucene.apache.org/solr/guide/cluster-node-management.html",
-  "description": "Cluster-wide commands to assign roles to nodes, remove role assignments, or add, edit or remove a cluster-wide property.",
-  "methods": [
-    "POST"
-  ],
-  "url": {
-    "paths": [
-      "/cluster"
-    ]
-  },
-  "commands": {
-    "add-role":{
-      "type":"object",
-      "documentation":"https://lucene.apache.org/solr/guide/cluster-node-management.html#addrole",
-      "description":"Assign a specific role to a node in the cluster.",
-      "properties": {
-        "role": {
-          "type": "string",
-          "description": "The name of the role. The only supported role is 'overseer'."
-
-        },
-        "node": {
-          "type": "string",
-          "description": "The name of the node. It is possible to assign a role even before that node is started."
-
-        }
-      },
-      "required": [
-        "role",
-        "node"
-      ]
-    },
-    "remove-role":{
-      "type":"object",
-      "documentation":"https://lucene.apache.org/solr/guide/cluster-node-management.html#removerole",
-      "description":"Unassign a role from a node in the cluster.",
-      "properties": {
-        "role": {
-          "type": "string",
-          "description": "The name of the role. The only supported role as of now is 'overseer'."
-
-        },
-        "node": {
-          "type": "string",
-          "description": "The name of the node where the role should be removed."
-        }
-      },
-      "required": [
-        "role",
-        "node"
-      ]
-    },
-    "set-property": {
-      "type": "object",
-      "documentation": "https://lucene.apache.org/solr/guide/cluster-node-management.html#clusterprop",
-      "description": "Add, edit, or delete a cluster-wide property.",
-      "properties": {
-        "name": {
-          "type": "string",
-          "description": "The name of the property"
-        },
-        "val": {
-          "type": ["string","boolean","null"],
-          "description": "The value of the property. If the value is empty or null, the property is unset."
-        }
-      },
-      "required": [
-        "name",
-        "val"
-      ]
-    },
-    "set-obj-property": {
-      "type": "object",
-      "documentation": "https://lucene.apache.org/solr/guide/cluster-node-management.html#clusterprop",
-      "description": "Add, edit, or delete a cluster-wide property.",
-      "properties": {
-        "legacyCloud": {
-          "type": "boolean"
-        },
-        "urlScheme": {
-          "type": "string"
-        },
-        "autoAddReplicas": {
-          "type": "boolean"
-        },
-        "maxCoresPerNode": {
-          "type": "boolean"
-        },
-        "location": {
-          "type": "string"
-        },
-        "defaults" : {
-          "type" : "object",
-          "properties": {
-            "cluster": {
-              "type" : "object",
-              "properties": {
-                "useLegacyReplicaAssignment": {
-                  "type" : "boolean",
-                  "description" : "Decides wheyher to use the deprecated legacy replica assignment strategy or not"
-                }
-              }
-            },
-            "collection": {
-              "type": "object",
-              "properties": {
-                "numShards": {
-                  "type": "integer",
-                  "description": "Default number of shards for a collection"
-                },
-                "tlogReplicas": {
-                  "type": "integer",
-                  "description": "Default number of TLOG replicas"
-                },
-                "pullReplicas": {
-                  "type": "integer",
-                  "description": "Default number of PULL replicas"
-                },
-                "nrtReplicas": {
-                  "type": "integer",
-                  "description": "Default number of NRT replicas"
-                }
-              }
-            }
-          }
-        },
-        "collectionDefaults": {
-          "type": "object",
-          "properties": {
-            "numShards": {
-              "type": "integer",
-              "description": "Default number of shards for a collection"
-            },
-            "tlogReplicas": {
-              "type": "integer",
-              "description": "Default number of TLOG replicas"
-            },
-            "pullReplicas": {
-              "type": "integer",
-              "description": "Default number of PULL replicas"
-            },
-            "nrtReplicas": {
-              "type": "integer",
-              "description": "Default number of NRT replicas"
-            }
-          }
-        }
-      }
-    },
-    "utilize-node": {
-      "type": "object",
-      "documentation": "https://lucene.apache.org/solr/guide/cluster-node-management.html#utilizenode",
-      "description": "use a replica to reduce load",
-      "properties": {
-        "node": {
-          "type": "string",
-          "description": "The name of the node"
-        },
-        "async": {
-          "type": [
-            "string",
-            "boolean",
-            "null"
-          ],
-          "description": "The value of the property. If the value is empty or null, the property is unset."
-        }
-      },
-      "required": [
-        "name"
-      ]
-    }
-  }
-}
diff --git a/solr/solrj/src/resources/apispec/cluster.commandstatus.delete.json b/solr/solrj/src/resources/apispec/cluster.commandstatus.delete.json
deleted file mode 100644
index 5576c42..0000000
--- a/solr/solrj/src/resources/apispec/cluster.commandstatus.delete.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "methods": [
-    "DELETE"
-  ],
-  "url": {
-    "paths": [
-      "/cluster/command-status/{id}"
-    ]
-  }
-}
diff --git a/solr/solrj/src/resources/apispec/cluster.commandstatus.json b/solr/solrj/src/resources/apispec/cluster.commandstatus.json
deleted file mode 100644
index a8a402b..0000000
--- a/solr/solrj/src/resources/apispec/cluster.commandstatus.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "methods": [
-    "GET"
-  ],
-  "url": {
-    "paths": [
-      "/cluster/command-status"
-    ],
-    "params": {
-      "flush": {
-        "type": "boolean",
-        "default": false
-      },
-      "id":{
-        "type":"string",
-        "description": "The command id"
-      }
-    }
-  }
-}
diff --git a/solr/solrj/src/resources/apispec/cluster.json b/solr/solrj/src/resources/apispec/cluster.json
deleted file mode 100644
index 028869d..0000000
--- a/solr/solrj/src/resources/apispec/cluster.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "documentation": "https://lucene.apache.org/solr/guide/cluster-node-management.html",
-  "description": "General information about the cluster, including defined collections (with the 'cluster' endpoint), status of the overseer (with the 'cluster/overseer' endpoint), and available nodes (with the 'cluster/nodes' endpoint).",
-  "methods": [
-    "GET"
-  ],
-  "url": {
-    "paths": [
-      "/cluster",
-      "/cluster/overseer",
-      "/cluster/nodes"
-    ]
-  }
-}
diff --git a/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java b/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java
index 404661a..b4807e3 100644
--- a/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java
+++ b/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java
@@ -40,7 +40,6 @@ public class JsonValidatorTest extends SolrTestCaseJ4  {
     checkSchema("core.config.Commands");
     checkSchema("core.SchemaEdit");
     checkSchema("cluster.configs.Commands");
-    checkSchema("cluster.Commands");
   }
 
 
@@ -173,13 +172,6 @@ public class JsonValidatorTest extends SolrTestCaseJ4  {
 
   }
 
-  public void testNullObjectValue() {
-    ValidatingJsonMap spec = Utils.getSpec("cluster.Commands").getSpec();
-    JsonSchemaValidator validator = new JsonSchemaValidator((Map) Utils.getObjectByPath(spec, false, "/commands/set-obj-property"));
-    List<String> object = validator.validateJson(Utils.fromJSONString("{collectionDefaults: null}"));
-    assertNull(object);
-  }
-
   private void checkSchema(String name) {
     ValidatingJsonMap spec = Utils.getSpec(name).getSpec();
     @SuppressWarnings({"rawtypes"})