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

gerlowskija pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 417e5c1  SOLR-15747: Convert /node v2 APIs to annotations (#433)
417e5c1 is described below

commit 417e5c187f5072def6f159e16cc27a24bd6f08f7
Author: Jason Gerlowski <[email protected]>
AuthorDate: Sun Dec 5 21:25:23 2021 -0500

    SOLR-15747: Convert /node v2 APIs to annotations (#433)
---
 .../solr/handler/admin/CoreAdminHandler.java       |  35 ++-
 .../solr/handler/admin/HealthCheckHandler.java     |  28 ++-
 .../org/apache/solr/handler/admin/InfoHandler.java |  49 ++--
 .../apache/solr/handler/admin/LoggingHandler.java  |  26 +-
 .../handler/admin/PropertiesRequestHandler.java    |  20 +-
 .../solr/handler/admin/SystemInfoHandler.java      |  44 ++--
 .../solr/handler/admin/ThreadDumpHandler.java      |  32 ++-
 .../solr/handler/admin/api/InvokeClassAPI.java     |  64 +++++
 .../solr/handler/admin/api/NodeHealthAPI.java      |  48 ++++
 .../solr/handler/admin/api/NodeLoggingAPI.java     |  49 ++++
 .../solr/handler/admin/api/NodePropertiesAPI.java  |  47 ++++
 .../solr/handler/admin/api/NodeSystemInfoAPI.java  |  49 ++++
 .../solr/handler/admin/api/NodeThreadsAPI.java     |  47 ++++
 .../handler/admin/api/OverseerOperationAPI.java    |  64 +++++
 .../handler/admin/api/RejoinLeaderElectionAPI.java |  73 ++++++
 .../org/apache/solr/handler/api/ApiRegistrar.java  |  19 +-
 .../handler/admin/api/V2NodeAPIMappingTest.java    | 272 +++++++++++++++++++++
 .../solr/client/solrj/request/CoreApiMapping.java  |  23 +-
 .../solrj/request/beans/InvokeClassPayload.java    |  29 +++
 .../request/beans/OverseerOperationPayload.java    |  29 +++
 .../request/beans/RejoinLeaderElectionPayload.java |  44 ++++
 .../solrj/src/resources/apispec/node.Commands.json |  24 --
 solr/solrj/src/resources/apispec/node.Info.json    |  12 -
 solr/solrj/src/resources/apispec/node.invoke.json  |  16 --
 .../apache/solr/common/util/JsonValidatorTest.java |   1 -
 25 files changed, 998 insertions(+), 146 deletions(-)

diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
index a738e43..5950b11 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
@@ -16,19 +16,10 @@
  */
 package org.apache.solr.handler.admin;
 
-import java.io.File;
-import java.lang.invoke.MethodHandles;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.ExecutorService;
-
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.solr.api.AnnotatedApi;
 import org.apache.solr.api.Api;
 import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.cloud.ZkController;
@@ -45,6 +36,9 @@ import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.CoreDescriptor;
 import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.admin.api.InvokeClassAPI;
+import org.apache.solr.handler.admin.api.OverseerOperationAPI;
+import org.apache.solr.handler.admin.api.RejoinLeaderElectionAPI;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricsContext;
@@ -58,6 +52,18 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
 
+import java.io.File;
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+
 import static org.apache.solr.common.params.CoreAdminParams.ACTION;
 import static 
org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.STATUS;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM;
@@ -402,7 +408,12 @@ public class CoreAdminHandler extends RequestHandlerBase 
implements PermissionNa
 
   @Override
   public Collection<Api> getApis() {
-    return coreAdminHandlerApi.getApis();
+    final List<Api> apis = Lists.newArrayList(coreAdminHandlerApi.getApis());
+    // Only some core-admin APIs use the v2 AnnotatedApi framework
+    apis.addAll(AnnotatedApi.getApis(new InvokeClassAPI(this)));
+    apis.addAll(AnnotatedApi.getApis(new RejoinLeaderElectionAPI(this)));
+    apis.addAll(AnnotatedApi.getApis(new OverseerOperationAPI(this)));
+    return apis;
   }
 
   static {
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java
index 147ad8a..5c2831d 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java
@@ -17,15 +17,9 @@
 
 package org.apache.solr.handler.admin;
 
-import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-import java.util.stream.Collectors;
-
 import org.apache.lucene.index.IndexCommit;
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
 import org.apache.solr.client.solrj.request.HealthCheckRequest;
 import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.common.SolrException;
@@ -38,14 +32,21 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.IndexFetcher;
 import org.apache.solr.handler.ReplicationHandler;
 import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.admin.api.NodeHealthAPI;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static org.apache.solr.common.params.CommonParams.FAILURE;
-import static org.apache.solr.common.params.CommonParams.OK;
-import static org.apache.solr.common.params.CommonParams.STATUS;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import static org.apache.solr.common.params.CommonParams.*;
 import static org.apache.solr.handler.ReplicationHandler.GENERATION;
 
 /**
@@ -275,4 +276,9 @@ public class HealthCheckHandler extends RequestHandlerBase {
   public Boolean registerV2() {
     return Boolean.TRUE;
   }
+
+  @Override
+  public Collection<Api> getApis() {
+    return AnnotatedApi.getApis(new NodeHealthAPI(this));
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
index 29032da..235a615 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
@@ -16,12 +16,8 @@
  */
 package org.apache.solr.handler.admin;
 
-import java.util.Collection;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.solr.api.ApiBag.ReqHandlerToApi;
+import com.google.common.collect.ImmutableList;
+import org.apache.solr.api.Api;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.CoreContainer;
@@ -29,15 +25,18 @@ import org.apache.solr.handler.RequestHandlerBase;
 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 static java.util.Collections.singletonList;
-import static org.apache.solr.common.util.Utils.getSpec;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
 import static org.apache.solr.common.params.CommonParams.PATH;
 
 public class InfoHandler extends RequestHandlerBase  {
 
   protected final CoreContainer coreContainer;
+  private Map<String, RequestHandlerBase> handlers = new ConcurrentHashMap<>();
 
   /**
    * Overloaded ctor to inject CoreContainer into the handler.
@@ -108,23 +107,27 @@ public class InfoHandler extends RequestHandlerBase  {
     return Category.ADMIN;
   }
 
-  protected PropertiesRequestHandler getPropertiesHandler() {
+  public PropertiesRequestHandler getPropertiesHandler() {
     return (PropertiesRequestHandler) handlers.get("properties");
 
   }
 
-  protected ThreadDumpHandler getThreadDumpHandler() {
+  public ThreadDumpHandler getThreadDumpHandler() {
     return (ThreadDumpHandler) handlers.get("threads");
   }
 
-  protected LoggingHandler getLoggingHandler() {
+  public LoggingHandler getLoggingHandler() {
     return (LoggingHandler) handlers.get("logging");
   }
 
-  protected SystemInfoHandler getSystemInfoHandler() {
+  public SystemInfoHandler getSystemInfoHandler() {
     return (SystemInfoHandler) handlers.get("system");
   }
 
+  public HealthCheckHandler getHealthCheckHandler() {
+    return (HealthCheckHandler) handlers.get("health");
+  }
+
   protected void setPropertiesHandler(PropertiesRequestHandler 
propertiesHandler) {
     handlers.put("properties", propertiesHandler);
   }
@@ -141,20 +144,28 @@ public class InfoHandler extends RequestHandlerBase  {
     handlers.put("system", systemInfoHandler);
   }
 
+  protected void setHealthCheckHandler(HealthCheckHandler healthCheckHandler) {
+    handlers.put("health", healthCheckHandler);
+  }
+
   @Override
   public SolrRequestHandler getSubHandler(String subPath) {
     return this;
   }
 
-  private Map<String, RequestHandlerBase> handlers = new ConcurrentHashMap<>();
-
   @Override
-  public Collection<Api> getApis() {
-    return singletonList(new ReqHandlerToApi(this, getSpec("node.Info")));
+  public Boolean registerV2() {
+    return Boolean.TRUE;
   }
 
   @Override
-  public Boolean registerV2() {
-    return Boolean.TRUE;
+  public Collection<Api> getApis() {
+    final ImmutableList.Builder<Api> list = new ImmutableList.Builder<>();
+    list.addAll(handlers.get("threads").getApis());
+    list.addAll(handlers.get("properties").getApis());
+    list.addAll(handlers.get("logging").getApis());
+    list.addAll(handlers.get("system").getApis());
+    list.addAll(handlers.get("health").getApis());
+    return list.build();
   }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/LoggingHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/LoggingHandler.java
index 3d2d764..c86a908 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/LoggingHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/LoggingHandler.java
@@ -16,12 +16,8 @@
  */
 package org.apache.solr.handler.admin;
 
-import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
@@ -30,6 +26,7 @@ import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.admin.api.NodeLoggingAPI;
 import org.apache.solr.logging.LogWatcher;
 import org.apache.solr.logging.LoggerInfo;
 import org.apache.solr.request.SolrQueryRequest;
@@ -38,6 +35,13 @@ import org.apache.solr.util.plugin.SolrCoreAware;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 
 /**
  * A request handler to show which loggers are registered and allows you to 
set them
@@ -168,4 +172,14 @@ public class LoggingHandler extends RequestHandlerBase 
implements SolrCoreAware
     return Category.ADMIN;
   }
 
+  @Override
+  public Collection<Api> getApis() {
+    return AnnotatedApi.getApis(new NodeLoggingAPI(this));
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
+
 }
\ No newline at end of file
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java
index 57a7492..ce35a35 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java
@@ -16,16 +16,20 @@
  */
 package org.apache.solr.handler.admin;
 
-import java.io.IOException;
-import java.util.Enumeration;
-
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.admin.api.NodePropertiesAPI;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.util.RedactionUtils;
 
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Enumeration;
+
 import static org.apache.solr.common.params.CommonParams.NAME;
 
 /**
@@ -75,4 +79,14 @@ public class PropertiesRequestHandler extends 
RequestHandlerBase
   public Category getCategory() {
     return Category.ADMIN;
   }
+
+  @Override
+  public Collection<Api> getApis() {
+    return AnnotatedApi.getApis(new NodePropertiesAPI(this));
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java
index 5a3c7b9..46c6670 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java
@@ -16,28 +16,16 @@
  */
 package org.apache.solr.handler.admin;
 
-import java.io.File;
-import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.lang.management.ManagementFactory;
-import java.lang.management.OperatingSystemMXBean;
-import java.lang.management.RuntimeMXBean;
-import java.net.InetAddress;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
 import com.codahale.metrics.Gauge;
 import org.apache.lucene.LucenePackage;
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
 import org.apache.solr.common.cloud.UrlScheme;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.admin.api.NodeSystemInfoAPI;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.schema.IndexSchema;
@@ -49,6 +37,22 @@ import org.apache.solr.util.stats.MetricUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
+import java.lang.management.RuntimeMXBean;
+import java.net.InetAddress;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
 import static org.apache.solr.common.params.CommonParams.NAME;
 
 
@@ -420,6 +424,16 @@ public class SystemInfoHandler extends RequestHandlerBase
     }
     return list;
   }
+
+  @Override
+  public Collection<Api> getApis() {
+    return AnnotatedApi.getApis(new NodeSystemInfoAPI(this));
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
   
 }
 
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java
index e13a0a0..5a6f3e5 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java
@@ -16,21 +16,25 @@
  */
 package org.apache.solr.handler.admin;
 
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.admin.api.NodeThreadsAPI;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
 import java.io.IOException;
+import java.lang.management.LockInfo;
 import java.lang.management.ManagementFactory;
 import java.lang.management.ThreadInfo;
-import java.lang.management.LockInfo;
 import java.lang.management.ThreadMXBean;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.handler.RequestHandlerBase;
-import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.response.SolrQueryResponse;
-
 import static org.apache.solr.common.params.CommonParams.ID;
 import static org.apache.solr.common.params.CommonParams.NAME;
 
@@ -38,8 +42,8 @@ import static org.apache.solr.common.params.CommonParams.NAME;
  * 
  * @since solr 1.2
  */
-public class ThreadDumpHandler extends RequestHandlerBase
-{
+public class ThreadDumpHandler extends RequestHandlerBase {
+
   @Override
   public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) 
throws IOException 
   {    
@@ -172,4 +176,14 @@ public class ThreadDumpHandler extends RequestHandlerBase
   public Category getCategory() {
     return Category.ADMIN;
   }
+
+  @Override
+  public Collection<Api> getApis() {
+    return AnnotatedApi.getApis(new NodeThreadsAPI(this));
+  }
+
+  @Override
+  public Boolean registerV2() {
+    return Boolean.TRUE;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/InvokeClassAPI.java 
b/solr/core/src/java/org/apache/solr/handler/admin/api/InvokeClassAPI.java
new file mode 100644
index 0000000..cdcb4a1
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/InvokeClassAPI.java
@@ -0,0 +1,64 @@
+/*
+ * 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.api;
+
+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.InvokeClassPayload;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+
+import java.util.Locale;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.common.params.CoreAdminParams.ACTION;
+import static 
org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.INVOKE;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM;
+
+/**
+ * V2 API for triggering "invocable" classes.
+ *
+ * This API (POST /v2/node {'invoke': {...}}) is analogous to the v1 
/admin/cores?action=INVOKE command.
+ */
+@EndPoint(
+        path = {"/node"},
+        method = POST,
+        permission = CORE_EDIT_PERM)
+public class InvokeClassAPI {
+    public static final String INVOKE_CMD = "invoke";
+
+    private final CoreAdminHandler coreAdminHandler;
+
+    public InvokeClassAPI(CoreAdminHandler coreAdminHandler) {
+        this.coreAdminHandler = coreAdminHandler;
+    }
+
+    @Command(name = INVOKE_CMD)
+    public void invokeClasses(PayloadObj<InvokeClassPayload> payload) throws 
Exception {
+        final InvokeClassPayload v2Body = payload.get();
+        final ModifiableSolrParams v1Params = new 
ModifiableSolrParams(payload.getRequest().getParams());
+        v1Params.add(ACTION, INVOKE.name().toLowerCase(Locale.ROOT));
+        for (String clazzStr : v2Body.classes) {
+            v1Params.add("class", clazzStr);
+        }
+
+        payload.getRequest().setParams(v1Params);
+        coreAdminHandler.handleRequestBody(payload.getRequest(), 
payload.getResponse());
+    }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealthAPI.java 
b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealthAPI.java
new file mode 100644
index 0000000..4139867
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealthAPI.java
@@ -0,0 +1,48 @@
+/*
+ * 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.api;
+
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.handler.admin.HealthCheckHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM;
+
+/**
+ * V2 API for checking the health of the receiving node.
+ *
+ * This API (GET /v2/node/health) is analogous to the v1 /admin/info/health.
+ */
+public class NodeHealthAPI {
+    private final HealthCheckHandler handler;
+
+    public NodeHealthAPI(HealthCheckHandler handler) {
+        this.handler = handler;
+    }
+
+    // TODO Update permission here once SOLR-11623 lands.
+    @EndPoint(
+            path = {"/node/health"},
+            method = GET,
+            permission = CONFIG_READ_PERM)
+    public void getSystemInformation(SolrQueryRequest req, SolrQueryResponse 
rsp) throws Exception {
+        handler.handleRequestBody(req, rsp);
+    }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeLoggingAPI.java 
b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeLoggingAPI.java
new file mode 100644
index 0000000..e891ee4
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeLoggingAPI.java
@@ -0,0 +1,49 @@
+/*
+ * 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.api;
+
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.handler.admin.LoggingHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM;
+
+/**
+ * V2 API for getting or setting log levels on an individual node.
+ *
+ * This API (GET /v2/node/logging) is analogous to the v1 /admin/info/logging.
+ */
+public class NodeLoggingAPI {
+
+    private final LoggingHandler handler;
+
+    public NodeLoggingAPI(LoggingHandler handler) {
+        this.handler = handler;
+    }
+
+    // TODO See SOLR-15823 for background on the (less than ideal) permission 
chosen here.
+    @EndPoint(
+            path = {"/node/logging"},
+            method = GET,
+            permission = CONFIG_EDIT_PERM)
+    public void getOrSetLogLevels(SolrQueryRequest req, SolrQueryResponse rsp) 
throws Exception {
+        handler.handleRequestBody(req, rsp);
+    }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/NodePropertiesAPI.java 
b/solr/core/src/java/org/apache/solr/handler/admin/api/NodePropertiesAPI.java
new file mode 100644
index 0000000..4f86883
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/NodePropertiesAPI.java
@@ -0,0 +1,47 @@
+/*
+ * 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.api;
+
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.handler.admin.PropertiesRequestHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM;
+
+/**
+ * V2 API for listing system properties for each node.
+ *
+ * This API (GET /v2/node/properties) is analogous to the v1 
/admin/info/properties.
+ */
+public class NodePropertiesAPI {
+    private final PropertiesRequestHandler handler;
+
+    public NodePropertiesAPI(PropertiesRequestHandler handler) {
+        this.handler = handler;
+    }
+
+    @EndPoint(
+            path = {"/node/properties"},
+            method = GET,
+            permission = CONFIG_READ_PERM)
+    public void getRequestedProperties(SolrQueryRequest req, SolrQueryResponse 
rsp) throws Exception {
+        handler.handleRequestBody(req, rsp);
+    }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeSystemInfoAPI.java 
b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeSystemInfoAPI.java
new file mode 100644
index 0000000..9d05fc6
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeSystemInfoAPI.java
@@ -0,0 +1,49 @@
+/*
+ * 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.api;
+
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.handler.admin.SystemInfoHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM;
+
+/**
+ * V2 API for getting "system" information from the receiving node.
+ *
+ * This includes current resource utilization, information about the 
installation (location, version, etc.), and JVM settings.
+ *
+ * This API (GET /v2/node/system) is analogous to the v1 /admin/info/system.
+ */
+public class NodeSystemInfoAPI {
+    private final SystemInfoHandler handler;
+
+    public NodeSystemInfoAPI(SystemInfoHandler handler) {
+        this.handler = handler;
+    }
+
+    @EndPoint(
+            path = {"/node/system"},
+            method = GET,
+            permission = CONFIG_READ_PERM)
+    public void getSystemInformation(SolrQueryRequest req, SolrQueryResponse 
rsp) throws Exception {
+        handler.handleRequestBody(req, rsp);
+    }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeThreadsAPI.java 
b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeThreadsAPI.java
new file mode 100644
index 0000000..64f992d
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeThreadsAPI.java
@@ -0,0 +1,47 @@
+/*
+ * 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.api;
+
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.handler.admin.ThreadDumpHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.METRICS_READ_PERM;
+
+/**
+ * V2 API for triggering a thread dump on the receiving node.
+ *
+ * This API (GET /v2/node/threads) is analogous to the v1 /admin/info/threads.
+ */
+public class NodeThreadsAPI {
+    private final ThreadDumpHandler handler;
+
+    public NodeThreadsAPI(ThreadDumpHandler handler) {
+        this.handler = handler;
+    }
+
+    @EndPoint(
+            path = {"/node/threads"},
+            method = GET,
+            permission = METRICS_READ_PERM)
+    public void triggerThreadDump(SolrQueryRequest req, SolrQueryResponse rsp) 
throws Exception {
+        handler.handleRequestBody(req, rsp);
+    }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/OverseerOperationAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/OverseerOperationAPI.java
new file mode 100644
index 0000000..b8b2e5c
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/OverseerOperationAPI.java
@@ -0,0 +1,64 @@
+/*
+ * 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.api;
+
+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.OverseerOperationPayload;
+import org.apache.solr.common.params.CoreAdminParams;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.common.params.CoreAdminParams.ACTION;
+import static org.apache.solr.handler.ClusterAPI.wrapParams;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM;
+
+/**
+ * V2 API for triggering a node to rejoin leader election for the 'overseer' 
role.
+ *
+ * This API (POST /v2/node {'overseer-op': {...}}) is analogous to the v1 
/admin/cores?action=overseerop command.
+ *
+ * @see OverseerOperationPayload
+ */
+@EndPoint(
+        path = {"/node"},
+        method = POST,
+        permission = CORE_EDIT_PERM)
+public class OverseerOperationAPI {
+
+    // TODO rename this command, this API doesn't really have anything to do 
with overseer-ops, its about leader election
+    public static final String OVERSEER_OP_CMD = "overseer-op";
+
+    private final CoreAdminHandler coreAdminHandler;
+
+    public OverseerOperationAPI(CoreAdminHandler coreAdminHandler) {
+        this.coreAdminHandler = coreAdminHandler;
+    }
+
+    @Command(name = OVERSEER_OP_CMD)
+    public void 
joinOverseerLeaderElection(PayloadObj<OverseerOperationPayload> payload) throws 
Exception {
+        final Map<String, Object> v1Params = payload.get().toMap(new 
HashMap<>());
+        v1Params.put(ACTION, 
CoreAdminParams.CoreAdminAction.OVERSEEROP.name().toLowerCase(Locale.ROOT));
+        coreAdminHandler.handleRequestBody(wrapParams(payload.getRequest(), 
v1Params), payload.getResponse());
+    }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/RejoinLeaderElectionAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/RejoinLeaderElectionAPI.java
new file mode 100644
index 0000000..fdb8207
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/RejoinLeaderElectionAPI.java
@@ -0,0 +1,73 @@
+/*
+ * 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.api;
+
+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.RejoinLeaderElectionPayload;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.common.cloud.ZkStateReader.CORE_NODE_NAME_PROP;
+import static org.apache.solr.common.cloud.ZkStateReader.ELECTION_NODE_PROP;
+import static org.apache.solr.common.params.CoreAdminParams.ACTION;
+import static 
org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.REJOINLEADERELECTION;
+import static org.apache.solr.handler.ClusterAPI.wrapParams;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM;
+
+/**
+ * V2 API for triggering a core to rejoin leader election for the shard it 
constitutes.
+ *
+ * This API (POST /v2/node {'rejoin-leader-election': {...}}) is analogous to 
the v1
+ * /admin/cores?action=REJOINLEADERELECTION command.
+ */
+@EndPoint(
+        path = {"/node"},
+        method = POST,
+        permission = CORE_EDIT_PERM)
+public class RejoinLeaderElectionAPI {
+    public static final String REJOIN_LEADER_ELECTION_CMD = 
"rejoin-leader-election";
+
+    private final CoreAdminHandler coreAdminHandler;
+
+    public RejoinLeaderElectionAPI(CoreAdminHandler coreAdminHandler) {
+        this.coreAdminHandler = coreAdminHandler;
+    }
+
+    @Command(name = REJOIN_LEADER_ELECTION_CMD)
+    public void rejoinLeaderElection(PayloadObj<RejoinLeaderElectionPayload> 
payload) throws Exception {
+        final RejoinLeaderElectionPayload v2Body = payload.get();
+        final Map<String, Object> v1Params = v2Body.toMap(new HashMap<>());
+        v1Params.put(ACTION, 
REJOINLEADERELECTION.name().toLowerCase(Locale.ROOT));
+        if (v2Body.electionNode != null) {
+            v1Params.remove("electionNode");
+            v1Params.put(ELECTION_NODE_PROP, v2Body.electionNode);
+        }
+        if (v2Body.coreNodeName != null) {
+            v1Params.remove("coreNodeName");
+            v1Params.put(CORE_NODE_NAME_PROP, v2Body.coreNodeName);
+        }
+
+            
coreAdminHandler.handleRequestBody(wrapParams(payload.getRequest(), v1Params), 
payload.getResponse());
+    }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java 
b/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java
index 25718f2..c59ae31 100644
--- a/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java
+++ b/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java
@@ -19,7 +19,24 @@ package org.apache.solr.handler.api;
 
 import org.apache.solr.api.ApiBag;
 import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.handler.admin.api.*;
+import org.apache.solr.handler.admin.api.AddReplicaAPI;
+import org.apache.solr.handler.admin.api.AddReplicaPropertyAPI;
+import org.apache.solr.handler.admin.api.BalanceShardUniqueAPI;
+import org.apache.solr.handler.admin.api.CollectionStatusAPI;
+import org.apache.solr.handler.admin.api.CreateShardAPI;
+import org.apache.solr.handler.admin.api.DeleteCollectionAPI;
+import org.apache.solr.handler.admin.api.DeleteReplicaAPI;
+import org.apache.solr.handler.admin.api.DeleteReplicaPropertyAPI;
+import org.apache.solr.handler.admin.api.DeleteShardAPI;
+import org.apache.solr.handler.admin.api.ForceLeaderAPI;
+import org.apache.solr.handler.admin.api.MigrateDocsAPI;
+import org.apache.solr.handler.admin.api.ModifyCollectionAPI;
+import org.apache.solr.handler.admin.api.MoveReplicaAPI;
+import org.apache.solr.handler.admin.api.RebalanceLeadersAPI;
+import org.apache.solr.handler.admin.api.ReloadCollectionAPI;
+import org.apache.solr.handler.admin.api.SetCollectionPropertyAPI;
+import org.apache.solr.handler.admin.api.SplitShardAPI;
+import org.apache.solr.handler.admin.api.SyncShardAPI;
 
 /**
  * Registers annotation-based V2 APIs with an {@link ApiBag}
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/V2NodeAPIMappingTest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/V2NodeAPIMappingTest.java
new file mode 100644
index 0000000..193a815
--- /dev/null
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/V2NodeAPIMappingTest.java
@@ -0,0 +1,272 @@
+/*
+ * 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.api;
+
+import com.google.common.collect.Maps;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.CommandOperation;
+import org.apache.solr.common.util.ContentStreamBase;
+import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+import org.apache.solr.handler.admin.HealthCheckHandler;
+import org.apache.solr.handler.admin.InfoHandler;
+import org.apache.solr.handler.admin.LoggingHandler;
+import org.apache.solr.handler.admin.PropertiesRequestHandler;
+import org.apache.solr.handler.admin.SystemInfoHandler;
+import org.apache.solr.handler.admin.ThreadDumpHandler;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.solr.SolrTestCaseJ4.assumeWorkingMockito;
+import static org.apache.solr.common.params.CommonParams.ACTION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+/**
+ * Unit tests for the v2 to v1 mapping for Solr's /node/ APIs
+ */
+public class V2NodeAPIMappingTest {
+    private ApiBag apiBag;
+    private ArgumentCaptor<SolrQueryRequest> queryRequestCaptor;
+    private CoreAdminHandler mockCoresHandler;
+    private InfoHandler infoHandler;
+    private SystemInfoHandler mockSystemInfoHandler;
+    private LoggingHandler mockLoggingHandler;
+    private PropertiesRequestHandler mockPropertiesHandler;
+    private HealthCheckHandler mockHealthCheckHandler;
+    private ThreadDumpHandler mockThreadDumpHandler;
+
+    @BeforeClass
+    public static void ensureWorkingMockito() {
+        assumeWorkingMockito();
+    }
+
+    @Before
+    public void setupApiBag() throws Exception {
+        mockCoresHandler = mock(CoreAdminHandler.class);
+        infoHandler = mock(InfoHandler.class);
+        mockSystemInfoHandler = mock(SystemInfoHandler.class);
+        mockLoggingHandler = mock(LoggingHandler.class);
+        mockPropertiesHandler = mock(PropertiesRequestHandler.class);
+        mockHealthCheckHandler = mock(HealthCheckHandler.class);
+        mockThreadDumpHandler = mock(ThreadDumpHandler.class);
+        queryRequestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class);
+
+        
when(infoHandler.getSystemInfoHandler()).thenReturn(mockSystemInfoHandler);
+        when(infoHandler.getLoggingHandler()).thenReturn(mockLoggingHandler);
+        
when(infoHandler.getPropertiesHandler()).thenReturn(mockPropertiesHandler);
+        
when(infoHandler.getHealthCheckHandler()).thenReturn(mockHealthCheckHandler);
+        
when(infoHandler.getThreadDumpHandler()).thenReturn(mockThreadDumpHandler);
+
+        apiBag = new ApiBag(false);
+        registerAllNodeApis(apiBag, mockCoresHandler, infoHandler);
+    }
+
+    @Test
+    public void testOverseerOpApiAllProperties() throws Exception {
+        final SolrParams v1Params = captureConvertedCoreV1Params("/node", 
"POST",
+                "{" +
+                        "\"overseer-op\": {" +
+                          "\"op\": \"asdf\", " +
+                          "\"electionNode\": \"someNodeName\"" +
+                        "}}");
+
+        assertEquals("overseerop", v1Params.get(ACTION));
+        assertEquals("asdf", v1Params.get("op"));
+        assertEquals("someNodeName", v1Params.get("electionNode"));
+    }
+
+    @Test
+    public void testRejoinLeaderElectionApiAllProperties() throws Exception {
+        final SolrParams v1Params = captureConvertedCoreV1Params("/node", 
"POST",
+                "{" +
+                        "\"rejoin-leader-election\": {" +
+                          "\"collection\": \"someCollection\", " +
+                          "\"shard\": \"someShard\"," +
+                          "\"coreNodeName\": \"someNodeName\"," +
+                          "\"core\": \"someCore\"," +
+                          "\"electionNode\": \"someElectionNode\"," +
+                          "\"rejoinAtHead\": true" +
+                        "}}");
+
+        assertEquals("rejoinleaderelection", v1Params.get(ACTION));
+        assertEquals("someCollection", v1Params.get("collection"));
+        assertEquals("someShard", v1Params.get("shard"));
+        assertEquals("someNodeName", v1Params.get("core_node_name"));
+        assertEquals("someCore", v1Params.get("core"));
+        assertEquals("someElectionNode", v1Params.get("election_node"));
+        assertEquals("true", v1Params.get("rejoinAtHead"));
+    }
+
+    @Test
+        public void testInvokeClassApiAllProperties() throws Exception {
+        final SolrParams v1Params = captureConvertedCoreV1Params("/node", 
"POST",
+                "{" +
+                        "\"invoke\": {" +
+                          "\"classes\": [\"someClassName\", 
\"someOtherClassName\"]" +
+                        "}}");
+
+        assertEquals("invoke", v1Params.get(ACTION));
+        assertEquals(2, v1Params.getParams("class").length);
+        final List<String> classes = 
Arrays.asList(v1Params.getParams("class"));
+        assertTrue(classes.contains("someClassName"));
+        assertTrue(classes.contains("someOtherClassName"));
+
+    }
+
+    @Test
+    public void testSystemPropsApiAllProperties() throws Exception {
+        final ModifiableSolrParams solrParams = new ModifiableSolrParams();
+        solrParams.add("name", "specificPropertyName");
+        final SolrParams v1Params = 
captureConvertedPropertiesV1Params("/node/properties", "GET", solrParams);
+
+        assertEquals("specificPropertyName", v1Params.get("name"));
+    }
+
+    @Test
+    public void testThreadDumpApiAllProperties() throws Exception {
+        final ModifiableSolrParams solrParams = new ModifiableSolrParams();
+        solrParams.add("anyParamName", "anyParamValue");
+        final SolrParams v1Params = 
captureConvertedThreadDumpV1Params("/node/threads", "GET", solrParams);
+
+        // All parameters are passed through to v1 API as-is
+        assertEquals("anyParamValue", v1Params.get("anyParamName"));
+    }
+
+    @Test
+    public void testLogLevelsApiAllProperties() throws Exception {
+        final ModifiableSolrParams solrParams = new ModifiableSolrParams();
+        solrParams.add("since", "12345678");
+        solrParams.add("threshold", "someThresholdValue");
+        solrParams.add("test", "someTestValue");
+        solrParams.add("set", "SomeClassName");
+        final SolrParams v1Params = 
captureConvertedLoggingV1Params("/node/logging", "GET", solrParams);
+
+        // All parameters are passed through to v1 API as-is.
+        assertEquals("12345678", v1Params.get("since"));
+        assertEquals("someThresholdValue", v1Params.get("threshold"));
+        assertEquals("someTestValue", v1Params.get("test"));
+        assertEquals("SomeClassName", v1Params.get("set"));
+    }
+
+    @Test
+    public void testSystemInfoApiAllProperties() throws Exception {
+        final ModifiableSolrParams solrParams = new ModifiableSolrParams();
+        solrParams.add("anyParamName", "anyParamValue");
+        final SolrParams v1Params = 
captureConvertedSystemV1Params("/node/system", "GET", solrParams);
+
+        // All parameters are passed through to v1 API as-is.
+        assertEquals("anyParamValue", v1Params.get("anyParamName"));
+    }
+
+    @Test
+    public void testHealthCheckApiAllProperties() throws Exception {
+        final ModifiableSolrParams solrParams = new ModifiableSolrParams();
+        solrParams.add("requireHealthyCores", "true");
+        solrParams.add("maxGenerationLag", "123");
+        final SolrParams v1Params = 
captureConvertedHealthCheckV1Params("/node/health", "GET", solrParams);
+
+        // All parameters are passed through to v1 API as-is.
+        assertEquals(true, v1Params.getBool("requireHealthyCores"));
+        assertEquals(123, v1Params.getPrimitiveInt("maxGenerationLag"));
+    }
+
+    private SolrParams captureConvertedCoreV1Params(String path, String 
method, String v2RequestBody) throws Exception {
+        return doCaptureParams(path, method, new ModifiableSolrParams(), 
v2RequestBody, mockCoresHandler);
+    }
+
+    private SolrParams captureConvertedSystemV1Params(String path, String 
method, SolrParams inputParams) throws Exception {
+        return doCaptureParams(path, method, inputParams, null, 
mockSystemInfoHandler);
+    }
+
+    private SolrParams captureConvertedLoggingV1Params(String path, String 
method, SolrParams inputParams) throws Exception {
+        return doCaptureParams(path, method, inputParams, null, 
mockLoggingHandler);
+    }
+
+    private SolrParams captureConvertedPropertiesV1Params(String path, String 
method, SolrParams inputParams) throws Exception {
+        return doCaptureParams(path, method, inputParams, null, 
mockPropertiesHandler);
+    }
+
+    private SolrParams captureConvertedHealthCheckV1Params(String path, String 
method, SolrParams inputParams) throws Exception {
+        return doCaptureParams(path, method, inputParams, null, 
mockHealthCheckHandler);
+    }
+
+    private SolrParams captureConvertedThreadDumpV1Params(String path, String 
method, SolrParams inputParams) throws Exception {
+        return doCaptureParams(path, method, inputParams, null, 
mockThreadDumpHandler);
+    }
+
+    private SolrParams doCaptureParams(String path, String method, SolrParams 
inputParams, String v2RequestBody, RequestHandlerBase mockHandler) throws 
Exception {
+        final HashMap<String, String> parts = new HashMap<>();
+        final Map<String, String[]> inputParamsMap = Maps.newHashMap();
+        inputParams.stream().forEach(e -> {
+            inputParamsMap.put(e.getKey(), e.getValue());
+        });
+        final Api api = apiBag.lookup(path, method, parts);
+        final SolrQueryResponse rsp = new SolrQueryResponse();
+        final LocalSolrQueryRequest req = new LocalSolrQueryRequest(null, 
inputParamsMap) {
+            @Override
+            public List<CommandOperation> getCommands(boolean validateInput) {
+                if (v2RequestBody == null) return Collections.emptyList();
+                return ApiBag.getCommandOperations(new 
ContentStreamBase.StringStream(v2RequestBody), api.getCommandSchema(), true);
+            }
+
+            @Override
+            public Map<String, String> getPathTemplateValues() {
+                return parts;
+            }
+
+            @Override
+            public String getHttpMethod() {
+                return method;
+            }
+        };
+
+
+        api.call(req, rsp);
+        verify(mockHandler).handleRequestBody(queryRequestCaptor.capture(), 
any());
+        return queryRequestCaptor.getValue().getParams();
+    }
+
+    private static void registerAllNodeApis(ApiBag apiBag, CoreAdminHandler 
coreHandler,
+                                            InfoHandler infoHandler) {
+        apiBag.registerObject(new OverseerOperationAPI(coreHandler));
+        apiBag.registerObject(new RejoinLeaderElectionAPI(coreHandler));
+        apiBag.registerObject(new InvokeClassAPI(coreHandler));
+        apiBag.registerObject(new 
NodePropertiesAPI(infoHandler.getPropertiesHandler()));
+        apiBag.registerObject(new 
NodeThreadsAPI(infoHandler.getThreadDumpHandler()));
+        apiBag.registerObject(new 
NodeLoggingAPI(infoHandler.getLoggingHandler()));
+        apiBag.registerObject(new 
NodeSystemInfoAPI(infoHandler.getSystemInfoHandler()));
+        apiBag.registerObject(new 
NodeHealthAPI(infoHandler.getHealthCheckHandler()));
+    }
+}
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CoreApiMapping.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CoreApiMapping.java
index 89ae691..a20a427 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CoreApiMapping.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CoreApiMapping.java
@@ -18,20 +18,16 @@
 package org.apache.solr.client.solrj.request;
 
 
-import java.util.Collections;
-import java.util.Map;
-
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.request.ApiMapping.CommandMeta;
 import org.apache.solr.common.params.CoreAdminParams.CoreAdminAction;
 
+import java.util.Collections;
+import java.util.Map;
+
 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.client.solrj.request.CoreApiMapping.EndPoint.CORES_COMMANDS;
-import static 
org.apache.solr.client.solrj.request.CoreApiMapping.EndPoint.CORES_STATUS;
-import static 
org.apache.solr.client.solrj.request.CoreApiMapping.EndPoint.NODEAPIS;
-import static 
org.apache.solr.client.solrj.request.CoreApiMapping.EndPoint.NODEINVOKE;
-import static 
org.apache.solr.client.solrj.request.CoreApiMapping.EndPoint.PER_CORE_COMMANDS;
+import static org.apache.solr.client.solrj.request.CoreApiMapping.EndPoint.*;
 
 /** stores the mapping of v1 API parameters to v2 API parameters
  * for core admin API
@@ -52,10 +48,7 @@ public class CoreApiMapping {
     REQUESTSYNCSHARD(PER_CORE_COMMANDS, POST, 
CoreAdminAction.REQUESTSYNCSHARD, "request-sync-shard", null),
     REQUESTBUFFERUPDATES(PER_CORE_COMMANDS, POST, 
CoreAdminAction.REQUESTBUFFERUPDATES, "request-buffer-updates", null),
     REQUESTAPPLYUPDATES(PER_CORE_COMMANDS, POST, 
CoreAdminAction.REQUESTAPPLYUPDATES, "request-apply-updates", null),
-    REQUESTSTATUS(PER_CORE_COMMANDS, GET, CoreAdminAction.REQUESTSTATUS, 
"request-status", null),/*TODO*/
-    OVERSEEROP(NODEAPIS, POST, CoreAdminAction.OVERSEEROP, "overseer-op", 
null),
-    REJOINLEADERELECTION(NODEAPIS, POST, CoreAdminAction.REJOINLEADERELECTION, 
"rejoin-leader-election", null),
-    INVOKE(NODEINVOKE, GET, CoreAdminAction.INVOKE,"invoke",  null);
+    REQUESTSTATUS(PER_CORE_COMMANDS, GET, CoreAdminAction.REQUESTSTATUS, 
"request-status", null)/*TODO*/;
 
     public final String commandName;
     public final EndPoint endPoint;
@@ -91,16 +84,12 @@ public class CoreApiMapping {
     public String getParamSubstitute(String param) {
       return paramstoAttr.containsKey(param) ? paramstoAttr.get(param) : param;
     }
-
-
   }
 
   public enum EndPoint implements ApiMapping.V2EndPoint {
     CORES_STATUS("cores.Status"),
     CORES_COMMANDS("cores.Commands"),
-    PER_CORE_COMMANDS("cores.core.Commands"),
-    NODEINVOKE("node.invoke"),
-    NODEAPIS("node.Commands");
+    PER_CORE_COMMANDS("cores.core.Commands");
 
     final String specName;
 
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/InvokeClassPayload.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/InvokeClassPayload.java
new file mode 100644
index 0000000..5555daa
--- /dev/null
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/InvokeClassPayload.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+import java.util.List;
+
+public class InvokeClassPayload implements ReflectMapWriter {
+
+    @JsonProperty(required = true)
+    public List<String> classes;
+}
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/OverseerOperationPayload.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/OverseerOperationPayload.java
new file mode 100644
index 0000000..ce7cee4
--- /dev/null
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/OverseerOperationPayload.java
@@ -0,0 +1,29 @@
+/*
+ * 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 OverseerOperationPayload implements ReflectMapWriter {
+    @JsonProperty
+    public String op;
+
+    @JsonProperty
+    public String electionNode;
+}
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/RejoinLeaderElectionPayload.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/RejoinLeaderElectionPayload.java
new file mode 100644
index 0000000..fac9f79
--- /dev/null
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/RejoinLeaderElectionPayload.java
@@ -0,0 +1,44 @@
+/*
+ * 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 RejoinLeaderElectionPayload implements ReflectMapWriter {
+
+    // TODO It seems like most of these properties should be required, but 
it's hard to tell which ones are meant to be
+    //  required without that being specified on the v1 API or elsewhere
+    @JsonProperty
+    public String collection;
+
+    @JsonProperty
+    public String shard;
+
+    @JsonProperty
+    public String coreNodeName;
+
+    @JsonProperty
+    public String core;
+
+    @JsonProperty
+    public String electionNode;
+
+    @JsonProperty
+    public Boolean rejoinAtHead;
+}
diff --git a/solr/solrj/src/resources/apispec/node.Commands.json 
b/solr/solrj/src/resources/apispec/node.Commands.json
deleted file mode 100644
index 11b3c89..0000000
--- a/solr/solrj/src/resources/apispec/node.Commands.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "methods": [
-    "POST"
-  ],
-  "url": {
-    "paths": [
-      "/node"
-    ]
-  },
-  "commands": {
-    "overseer-op": {
-      "type": "object",
-      "additionalProperties": true
-    },
-    "rejoin-leader-election": {
-      "type": "object",
-      "additionalProperties": true
-    },
-    "invoke":{
-      "type": "object",
-      "additionalProperties": true
-    }
-  }
-}
diff --git a/solr/solrj/src/resources/apispec/node.Info.json 
b/solr/solrj/src/resources/apispec/node.Info.json
deleted file mode 100644
index 9fa3e94..0000000
--- a/solr/solrj/src/resources/apispec/node.Info.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-   "description": "Provides information about system properties, threads, 
logging settings, system details and health (available in SolrCloud mode) for a 
node.",
-  "methods": ["GET"],
-  "url": {
-    "paths": [
-      "/node/properties",
-      "/node/threads",
-      "/node/logging",
-      "/node/system",
-      "/node/health"]
-  }
-}
diff --git a/solr/solrj/src/resources/apispec/node.invoke.json 
b/solr/solrj/src/resources/apispec/node.invoke.json
deleted file mode 100644
index c8a9f69..0000000
--- a/solr/solrj/src/resources/apispec/node.invoke.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "methods": [
-    "GET"
-  ],
-  "url": {
-    "paths": [
-      "/node/invoke"
-    ],
-    "params": {
-      "class": {
-        "type": "string",
-        "description": "Name of the class that must be invoked. "
-      }
-    }
-  }
-}
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 18bc953..2ef8699 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
@@ -30,7 +30,6 @@ public class JsonValidatorTest extends SolrTestCaseJ4  {
   public void testSchema() {
     checkSchema("cores.Commands");
     checkSchema("cores.core.Commands");
-    checkSchema("node.Commands");
     checkSchema("cluster.security.BasicAuth.Commands");
     checkSchema("cluster.security.RuleBasedAuthorization");
     checkSchema("core.config.Commands");

Reply via email to