Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelog/unreleased/PR#5110-admin-ui-node-picker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
title: Admin UI adds a node picker so Thread Dump, Java Properties, Logging, Cores, and Dashboard can target any live node in SolrCloud, with the sidebar split into Cluster and Node sections.
type: added
authors:
- name: Prithvi S.
nick: iprithv
links:
- name: PR#5110
url: https://github.com/apache/solr/pull/5110
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ interface Create {
interface GetStatus {

@GET
@Operation(summary = "Fetch status info for all cores hosted on this node.", tags = "cores")
CoreStatusResponse getAllCoreStatus(@QueryParam("indexInfo") Boolean indexInfo)
@Operation(
summary =
"Fetch status info for all cores hosted on this node, or on other live nodes when 'nodes' is set.",
tags = "cores")
CoreStatusResponse getAllCoreStatus(
@QueryParam("indexInfo") Boolean indexInfo, @QueryParam("nodes") String nodes)
throws Exception;

@GET
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ public interface NodeLoggingApis {
@GET
@Path("/levels")
@Operation(
summary = "List all log-levels for the target node.",
summary =
"List all log-levels for the target node, or for other live nodes when 'nodes' is set.",
tags = {"logging"})
ListLevelsResponse listAllLoggersAndLevels();
ListLevelsResponse listAllLoggersAndLevels(@QueryParam("nodes") String nodes);

@PUT
@Path("/levels")
Expand All @@ -48,9 +49,11 @@ public interface NodeLoggingApis {
@GET
@Path("/messages")
@Operation(
summary = "Fetch recent log messages on the targeted node.",
summary =
"Fetch recent log messages on the targeted node, or for other live nodes when 'nodes' is set.",
tags = {"logging"})
LogMessagesResponse fetchLocalLogMessages(@QueryParam("since") Long boundingTimeMillis);
LogMessagesResponse fetchLocalLogMessages(
@QueryParam("since") Long boundingTimeMillis, @QueryParam("nodes") String nodes);

@PUT
@Path("/messages/threshold")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.QueryParam;
import org.apache.solr.client.api.model.NodePropertiesResponse;

/** V2 API definition for listing JRE system properties on a Solr node. */
Expand All @@ -29,9 +30,10 @@ public interface NodePropertiesApi {

@GET
@Operation(
summary = "List system properties for the target Solr node.",
summary =
"List system properties for the target Solr node, or for other live nodes when 'nodes' is set.",
tags = {"node"})
NodePropertiesResponse getNodeProperties();
NodePropertiesResponse getNodeProperties(@QueryParam("nodes") String nodes);

@GET
@Path("/{propertyName}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.swagger.v3.oas.annotations.Operation;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import org.apache.solr.client.api.model.NodeThreadsResponse;

/** V2 API for retrieving thread information from the receiving node. */
Expand All @@ -27,7 +28,8 @@ public interface NodeThreadsApi {

@GET
@Operation(
summary = "Retrieve thread information for the receiving node.",
summary =
"Retrieve thread information for the receiving node, or for other live nodes when 'nodes' is set.",
tags = {"system"})
NodeThreadsResponse getThreadDump();
NodeThreadsResponse getThreadDump(@QueryParam("nodes") String nodes);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,28 @@
*/
package org.apache.solr.client.api.model;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

public class CoreStatusResponse extends SolrJerseyResponse {

// Object, not CoreStatusResponse, since @JsonAnySetter below also feeds this map raw values.
public Map<String, Object> remoteNodeData = new LinkedHashMap<>();

@JsonAnyGetter
public Map<String, Object> remoteNodeData() {
return remoteNodeData;
}

@JsonAnySetter
public void setRemoteNodeResponse(String field, Object value) {
remoteNodeData.put(field, value);
}

// Map values are often exceptions on the server side, but often serialized as strings.
@JsonProperty public Map<String, Object> initFailures;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,28 @@
*/
package org.apache.solr.client.api.model;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.LinkedHashMap;
import java.util.Map;

/** Generic logging response that includes the name of the log watcher (e.g. "Log4j2") */
public class LoggingResponse extends SolrJerseyResponse {

// Object, not LoggingResponse, since @JsonAnySetter below also feeds this map raw values.
public Map<String, Object> remoteNodeData = new LinkedHashMap<>();

@JsonAnyGetter
public Map<String, Object> remoteNodeData() {
return remoteNodeData;
}

@JsonAnySetter
public void setRemoteNodeResponse(String field, Object value) {
remoteNodeData.put(field, value);
}

@JsonProperty("watcher")
public String watcherName;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
*/
package org.apache.solr.client.api.model;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.LinkedHashMap;
import java.util.Map;

/**
Expand All @@ -28,6 +31,19 @@ public class NodePropertiesResponse extends SolrJerseyResponse {

public static final String SYSTEM_PROPERTIES = "system.properties";

// Object, not NodePropertiesResponse, since @JsonAnySetter below also feeds this map raw values.
public Map<String, Object> remoteNodeData = new LinkedHashMap<>();

@JsonAnyGetter
public Map<String, Object> remoteNodeData() {
return remoteNodeData;
}

@JsonAnySetter
public void setRemoteNodeResponse(String field, Object value) {
remoteNodeData.put(field, value);
}

@Schema(description = "JRE system properties for the Solr node. Secret values are redacted.")
@JsonProperty(SYSTEM_PROPERTIES)
public Map<String, String> systemProperties;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,30 @@
*/
package org.apache.solr.client.api.model;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/** Response body for the {@code /api/node/threads} endpoint. */
public class NodeThreadsResponse extends SolrJerseyResponse {

// Object, not NodeThreadsResponse, since @JsonAnySetter below also feeds this map raw values.
public Map<String, Object> remoteNodeData = new LinkedHashMap<>();

@JsonAnyGetter
public Map<String, Object> remoteNodeData() {
return remoteNodeData;
}

@JsonAnySetter
public void setRemoteNodeResponse(String field, Object value) {
remoteNodeData.put(field, value);
}

@Schema(description = "Thread information for the receiving JVM.")
@JsonProperty("system")
public SystemInfo system;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw
} catch (Exception ex) {
throw new SolrException(ErrorCode.BAD_REQUEST, "invalid timestamp: " + since);
}
squashV2Response(rsp, loggingApi.fetchLocalLogMessages(time));
squashV2Response(rsp, loggingApi.fetchLocalLogMessages(time, null));
} else {
squashV2Response(rsp, loggingApi.listAllLoggersAndLevels());
squashV2Response(rsp, loggingApi.listAllLoggersAndLevels(null));
}

rsp.setHttpCaching(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class ThreadDumpHandler extends RequestHandlerBase {

@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
final var response = new NodeThreadsAPI().getThreadDump();
final var response = new NodeThreadsAPI().getThreadDump(null);
final var system = new SimpleOrderedMap<Object>();
final var counts = new SimpleOrderedMap<Object>();
counts.add("current", Math.toIntExact(response.system.threadCount.current));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@
import java.util.Map;
import org.apache.solr.client.api.endpoint.CoreApis;
import org.apache.solr.client.api.model.CoreStatusResponse;
import org.apache.solr.client.solrj.request.CoresApi;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.CoreDescriptor;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.admin.CoreAdminHandler;
import org.apache.solr.handler.admin.LukeRequestHandler;
import org.apache.solr.handler.admin.proxy.V2SolrRequestBasedProxy;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
Expand Down Expand Up @@ -61,11 +64,40 @@ public CoreStatus(

@Override
@PermissionName(CORE_READ_PERM)
public CoreStatusResponse getAllCoreStatus(Boolean indexInfo) throws IOException {
public CoreStatusResponse getAllCoreStatus(Boolean indexInfo, String nodes) throws IOException {
if (nodes != null && !nodes.isEmpty()) {
final CoreStatusResponse response = instantiateJerseyResponse(CoreStatusResponse.class);
if (proxyToNodes(response, indexInfo, nodes)) {
return response;
}
}
final var indexInfoNeeded = indexInfo == null ? INDEX_INFO_DEFAULT_VALUE : indexInfo;
return fetchStatusInfo(coreContainer, null, indexInfoNeeded);
}

private boolean proxyToNodes(CoreStatusResponse response, Boolean indexInfo, String nodes) {
try {
if (coreContainer != null) {
final var req = new CoresApi.GetAllCoreStatus();
req.setIndexInfo(indexInfo);
req.setNodes(nodes);
final var reqProxy =
new V2SolrRequestBasedProxy<CoreStatusResponse>(coreContainer, req) {
@Override
public void processTypedProxiedResponse(
String nodeName, CoreStatusResponse proxiedResponse) {
response.remoteNodeData.put(nodeName, proxiedResponse);
}
};
return reqProxy.proxyRequest();
}
} catch (Exception e) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR, "Error occurred while proxying to other node", e);
}
return false;
}

@Override
@PermissionName(CORE_READ_PERM)
public CoreStatusResponse getCoreStatus(String coreName, Boolean indexInfo) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
import org.apache.solr.api.JerseyResource;
import org.apache.solr.client.api.endpoint.NodePropertiesApi;
import org.apache.solr.client.api.model.NodePropertiesResponse;
import org.apache.solr.client.solrj.request.NodeApi;
import org.apache.solr.common.SolrException;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.NodeConfig;
import org.apache.solr.handler.admin.proxy.V2SolrRequestBasedProxy;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.security.PermissionNameProvider;

Expand All @@ -49,8 +51,13 @@ public GetNodeProperties(CoreContainer coreContainer) {

@Override
@PermissionName(PermissionNameProvider.Name.CONFIG_READ_PERM)
public NodePropertiesResponse getNodeProperties() {
return buildResponse(null);
public NodePropertiesResponse getNodeProperties(String nodes) {
final NodePropertiesResponse response = instantiateJerseyResponse(NodePropertiesResponse.class);
if (proxyToNodes(response, nodes)) {
return response;
}
response.systemProperties = collectProperties(null);
return response;
}

@Override
Expand All @@ -65,15 +72,33 @@ public NodePropertiesResponse getNodeProperty(String propertyName) {
SolrException.ErrorCode.NOT_FOUND,
"No system property named '" + propertyName + "' exists on this node.");
}
return buildResponse(propertyName);
}

private NodePropertiesResponse buildResponse(String name) {
final NodePropertiesResponse response = instantiateJerseyResponse(NodePropertiesResponse.class);
response.systemProperties = collectProperties(name);
response.systemProperties = collectProperties(propertyName);
return response;
}

private boolean proxyToNodes(NodePropertiesResponse response, String nodes) {
try {
if (coreContainer != null) {
final var req = new NodeApi.GetNodeProperties();
req.setNodes(nodes);
final var reqProxy =
new V2SolrRequestBasedProxy<NodePropertiesResponse>(coreContainer, req) {
@Override
public void processTypedProxiedResponse(
String nodeName, NodePropertiesResponse proxiedResponse) {
response.remoteNodeData.put(nodeName, proxiedResponse);
}
};
return reqProxy.proxyRequest();
}
} catch (Exception e) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR, "Error occurred while proxying to other node", e);
}
return false;
}

/** Collect redacted system properties, optionally limited to a single named property. */
public Map<String, String> collectProperties(String name) {
final NodeConfig nodeConfig = coreContainer.getNodeConfig();
Expand Down
Loading
Loading