From 24b1e696e50ccafe77765a913e4200a73fc86e0d Mon Sep 17 00:00:00 2001 From: Isabelle Giguere Date: Thu, 15 Jan 2026 10:17:34 -0500 Subject: [PATCH 1/9] SOLR-16458: convert api/node/system to JAX-RS Add NodeSystemIfoApi (notice the lower-case last chars), to (eventually) replace Endpoint NodeSystemIfoAPI. Lo warning about deprecation. Add GetNodeSystemInfo, move the info-gathering logic to a separate class NodeSystemInfoProvider, adjust SystemInfoHandler. Known Issue: NodeSystemInfoResponse does not support responses from multiple nodes. More testing and clean-up to do. --- .../api/endpoint/NodeSystemInfoApi.java | 33 ++ .../client/api/model/NodeSystemResponse.java | 26 +- .../handler/admin/NodeSystemInfoProvider.java | 519 ++++++++++++++++++ .../solr/handler/admin/SystemInfoHandler.java | 495 +---------------- .../handler/admin/api/GetNodeSystemInfo.java | 67 +++ .../handler/admin/api/NodeSystemInfoAPI.java | 10 + 6 files changed, 664 insertions(+), 486 deletions(-) create mode 100644 solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java create mode 100644 solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java create mode 100644 solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java new file mode 100644 index 00000000000..49bab74cfb8 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java @@ -0,0 +1,33 @@ +/* + * 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.api.endpoint; + +import io.swagger.v3.oas.annotations.Operation; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import org.apache.solr.client.api.model.NodeSystemResponse; + +/** V2 API definition to fetch node system info, analogous to the v1 /admin/info/system. */ +@Path("/node/info/system") +public interface NodeSystemInfoApi { + + @GET + @Operation( + summary = "Retrieve node system info.", + tags = {"system"}) + NodeSystemResponse getNodeSystemInfo(); +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemResponse.java index 09bc35f0867..5f727df3fda 100644 --- a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemResponse.java +++ b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemResponse.java @@ -20,8 +20,9 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Set; -/** Response from /node/system */ +/** Response from /node/info/system */ public class NodeSystemResponse extends SolrJerseyResponse { @JsonProperty public String mode; @@ -42,6 +43,7 @@ public class NodeSystemResponse extends SolrJerseyResponse { public String environmentColor; @JsonProperty public String node; + @JsonProperty public Core core; @JsonProperty public Lucene lucene; @JsonProperty public JVM jvm; @JsonProperty public Security security; @@ -54,8 +56,8 @@ public static class Security { @JsonProperty public String authenticationPlugin; @JsonProperty public String authorizationPlugin; @JsonProperty public String username; - @JsonProperty public List roles; - @JsonProperty public List permissions; + @JsonProperty public Set roles; + @JsonProperty public Set permissions; } /** /node/system/lucene */ @@ -121,6 +123,22 @@ public static class GPU { @JsonProperty public boolean available; @JsonProperty public long count; @JsonProperty public MemoryRaw memory; - @JsonProperty Map devices; + @JsonProperty public Map devices; + } + + public static class Core { + @JsonProperty public String schema; + @JsonProperty public String host; + @JsonProperty public Date now; + @JsonProperty public Date start; + @JsonProperty public Directory directory; + } + + public static class Directory { + @JsonProperty public String cwd; + @JsonProperty public String instance; + @JsonProperty public String data; + @JsonProperty public String dirimpl; + @JsonProperty public String index; } } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java new file mode 100644 index 00000000000..b16856532ba --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java @@ -0,0 +1,519 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.handler.admin; + +import static org.apache.solr.common.params.CommonParams.NAME; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.lang.management.PlatformManagedObject; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.nio.file.Path; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.lucene.util.Version; +import org.apache.solr.client.api.model.NodeSystemResponse; +import org.apache.solr.common.cloud.ZkStateReader; +import org.apache.solr.common.util.EnvUtils; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.core.NodeConfig; +import org.apache.solr.core.SolrCore; +import org.apache.solr.metrics.GpuMetricsProvider; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.schema.IndexSchema; +import org.apache.solr.security.AuthorizationPlugin; +import org.apache.solr.security.PKIAuthenticationPlugin; +import org.apache.solr.security.RuleBasedAuthorizationPluginBase; +import org.apache.solr.util.RTimer; +import org.apache.solr.util.stats.MetricUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Used by SystemInfoHandler and NodeSystemInfo */ +public class NodeSystemInfoProvider { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private SolrQueryRequest req; + private CoreContainer cc; + private String hostname = null; + + private static final long ONE_KB = 1024; + private static final long ONE_MB = ONE_KB * ONE_KB; + private static final long ONE_GB = ONE_KB * ONE_MB; + + /** + * Undocumented expert level system property to prevent doing a reverse lookup of our hostname. + * This property will be logged as a suggested workaround if any problems are noticed when doing + * reverse lookup. + * + *

TODO: should we refactor this (and the associated logic) into a helper method for any other + * places where DNS is used? + * + * @see #initHostname + */ + private static final String REVERSE_DNS_OF_LOCALHOST_SYSPROP = + "solr.admin.handler.systeminfo.dns.reverse.lookup.enabled"; + + /** + * Local cache for BeanInfo instances that are created to scan for system metrics. List of + * properties is not supposed to change for the JVM lifespan, so we can keep already create + * BeanInfo instance for future calls. + */ + private static final ConcurrentMap, BeanInfo> beanInfos = new ConcurrentHashMap<>(); + + public NodeSystemInfoProvider(SolrQueryRequest request) { + req = request; + cc = request.getCoreContainer(); + initHostname(); + } + + public NodeSystemResponse getNodeSystemInfo() { + NodeSystemResponse resp = new NodeSystemResponse(); + SolrCore core = req.getCore(); + if (core != null) resp.core = getCoreInfo(core, req.getSchema()); + boolean solrCloudMode = cc.isZooKeeperAware(); + resp.mode = solrCloudMode ? "solrcloud" : "std"; + if (solrCloudMode) { + resp.zkHost = cc.getZkController().getZkServerAddress(); + } + if (cc != null) { + resp.solrHome = cc.getSolrHome().toString(); + resp.coreRoot = cc.getCoreRootDirectory().toString(); + } + + resp.lucene = getLuceneInfo(); + NodeConfig nodeConfig = cc.getNodeConfig(); + resp.jvm = getJvmInfo(nodeConfig); + resp.security = getSecurityInfo(req); + resp.system = getSystemInfo(); + + resp.gpu = getGpuInfo(); + if (solrCloudMode) { + resp.node = cc.getZkController().getNodeName(); + } + SolrEnvironment env = + SolrEnvironment.getFromSyspropOrClusterprop( + solrCloudMode ? cc.getZkController().zkStateReader : null); + if (env.isDefined()) { + resp.environment = env.getCode(); + if (env.getLabel() != null) { + resp.environmentLabel = env.getLabel(); + } + if (env.getColor() != null) { + resp.environmentColor = env.getColor(); + } + } + return resp; + } + + /** Get system info */ + private NodeSystemResponse.Core getCoreInfo(SolrCore core, IndexSchema schema) { + NodeSystemResponse.Core info = new NodeSystemResponse.Core(); + + info.schema = schema != null ? schema.getSchemaName() : "no schema!"; + + // Host + info.host = hostname; + + // Now + info.now = new Date(); + + // Start Time + info.start = core.getStartTimeStamp(); + + // Solr Home + NodeSystemResponse.Directory dirs = new NodeSystemResponse.Directory(); + dirs.cwd = Path.of(System.getProperty("user.dir")).toAbsolutePath().toString(); + dirs.instance = core.getInstancePath().toString(); + try { + dirs.data = core.getDirectoryFactory().normalize(core.getDataDir()); + } catch (IOException e) { + log.warn("Problem getting the normalized data directory path", e); + dirs.data = "N/A"; + } + dirs.dirimpl = core.getDirectoryFactory().getClass().getName(); + try { + dirs.index = core.getDirectoryFactory().normalize(core.getIndexDir()); + } catch (IOException e) { + log.warn("Problem getting the normalized index directory path", e); + dirs.index = "N/A"; + } + info.directory = dirs; + return info; + } + + /** Get system info */ + private Map getSystemInfo() { + Map info = new HashMap<>(); + + OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); + info.put(NAME, os.getName()); // add at least this one + + // add remaining ones dynamically using Java Beans API + // also those from JVM implementation-specific classes + forEachGetterValue( + os, + MetricUtils.OS_MXBEAN_CLASSES, + (name, value) -> { + if (info.get(name) == null) { + info.put(name, String.valueOf(value)); + } + }); + + return info; + } + + /** Get JVM Info - including memory info */ + private NodeSystemResponse.JVM getJvmInfo(NodeConfig nodeConfig) { + NodeSystemResponse.JVM jvm = new NodeSystemResponse.JVM(); + + final String javaVersion = System.getProperty("java.specification.version", "unknown"); + final String javaVendor = System.getProperty("java.specification.vendor", "unknown"); + final String javaName = System.getProperty("java.specification.name", "unknown"); + final String jreVersion = System.getProperty("java.version", "unknown"); + final String jreVendor = System.getProperty("java.vendor", "unknown"); + final String vmVersion = System.getProperty("java.vm.version", "unknown"); + final String vmVendor = System.getProperty("java.vm.vendor", "unknown"); + final String vmName = System.getProperty("java.vm.name", "unknown"); + + // Summary Info + jvm.version = jreVersion + " " + vmVersion; + jvm.name = jreVendor + " " + vmName; + + // details + NodeSystemResponse.Vendor spec = new NodeSystemResponse.Vendor(); + spec.vendor = javaVendor; + spec.name = javaName; + spec.version = javaVersion; + jvm.spec = spec; + + NodeSystemResponse.Vendor jre = new NodeSystemResponse.Vendor(); + jre.vendor = jreVendor; + jre.version = jreVersion; + jvm.jre = jre; + + NodeSystemResponse.Vendor vm = new NodeSystemResponse.Vendor(); + vm.vendor = vmVendor; + vm.name = vmName; + vm.version = vmVersion; + jvm.vm = vm; + + Runtime runtime = Runtime.getRuntime(); + jvm.processors = runtime.availableProcessors(); + + // not thread safe, but could be thread local + DecimalFormat df = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.ROOT)); + + NodeSystemResponse.JvmMemory mem = new NodeSystemResponse.JvmMemory(); + NodeSystemResponse.JvmMemoryRaw raw = new NodeSystemResponse.JvmMemoryRaw(); + long free = runtime.freeMemory(); + long max = runtime.maxMemory(); + long total = runtime.totalMemory(); + long used = total - free; + double percentUsed = ((double) (used) / (double) max) * 100; + raw.free = free; + mem.free = humanReadableUnits(free, df); + raw.total = total; + mem.total = humanReadableUnits(total, df); + raw.max = max; + mem.max = humanReadableUnits(max, df); + raw.used = used; + mem.used = humanReadableUnits(used, df) + " (%" + df.format(percentUsed) + ")"; + raw.usedPercent = percentUsed; + + mem.raw = raw; + jvm.memory = mem; + + // JMX properties + NodeSystemResponse.JvmJmx jmx = new NodeSystemResponse.JvmJmx(); + try { + RuntimeMXBean mx = ManagementFactory.getRuntimeMXBean(); + if (mx.isBootClassPathSupported()) { + jmx.classpath = mx.getBootClassPath(); + } + jmx.classpath = mx.getClassPath(); + + // the input arguments passed to the Java virtual machine + // which does not include the arguments to the main method. + jmx.commandLineArgs = getInputArgumentsRedacted(nodeConfig, mx); + + jmx.startTime = new Date(mx.getStartTime()); + jmx.upTimeMS = mx.getUptime(); + + } catch (Exception e) { + log.warn("Error getting JMX properties", e); + } + jvm.jmx = jmx; + return jvm; + } + + /** Get Security Info */ + private NodeSystemResponse.Security getSecurityInfo(SolrQueryRequest req) { + NodeSystemResponse.Security info = new NodeSystemResponse.Security(); + + if (cc != null) { + if (cc.getAuthenticationPlugin() != null) { + info.authenticationPlugin = cc.getAuthenticationPlugin().getName(); + } + if (cc.getAuthorizationPlugin() != null) { + info.authorizationPlugin = cc.getAuthorizationPlugin().getClass().getName(); + } + } + + if (req.getUserPrincipal() != null + && req.getUserPrincipal() != PKIAuthenticationPlugin.CLUSTER_MEMBER_NODE) { + // User principal + info.username = req.getUserPrincipal().getName(); + + // Mapped roles for this principal + @SuppressWarnings("resource") + AuthorizationPlugin auth = cc == null ? null : cc.getAuthorizationPlugin(); + if (auth instanceof RuleBasedAuthorizationPluginBase rbap) { + Set roles = rbap.getUserRoles(req.getUserPrincipal()); + info.roles = roles; + if (roles == null) { + info.permissions = Set.of(); + } else { + info.permissions = + rbap.getPermissionNamesForRoles( + Stream.concat(roles.stream(), Stream.of("*", null)).collect(Collectors.toSet())); + } + } + } + + if (cc != null && cc.getZkController() != null) { + String urlScheme = + cc.getZkController().zkStateReader.getClusterProperty(ZkStateReader.URL_SCHEME, "http"); + info.tls = ZkStateReader.HTTPS.equals(urlScheme); + } + + return info; + } + + private NodeSystemResponse.Lucene getLuceneInfo() { + NodeSystemResponse.Lucene info = new NodeSystemResponse.Lucene(); + + Package p = SolrCore.class.getPackage(); + + info.solrSpecVersion = p.getSpecificationVersion(); + info.solrImplVersion = p.getImplementationVersion(); + + info.luceneSpecVersion = Version.LATEST.toString(); + info.luceneImplVersion = Version.getPackageImplementationVersion(); + + return info; + } + + private NodeSystemResponse.GPU getGpuInfo() { + NodeSystemResponse.GPU gpuInfo = new NodeSystemResponse.GPU(); + gpuInfo.available = false; // set below if available + + try { + GpuMetricsProvider provider = cc.getGpuMetricsProvider(); + + if (provider == null) { + return gpuInfo; + } + + long gpuCount = provider.getGpuCount(); + if (gpuCount > 0) { + gpuInfo.available = true; + gpuInfo.count = gpuCount; + + long gpuMemoryTotal = provider.getGpuMemoryTotal(); + long gpuMemoryUsed = provider.getGpuMemoryUsed(); + long gpuMemoryFree = provider.getGpuMemoryFree(); + + if (gpuMemoryTotal > 0) { + NodeSystemResponse.MemoryRaw memory = new NodeSystemResponse.MemoryRaw(); + memory.total = gpuMemoryTotal; + memory.used = gpuMemoryUsed; + memory.free = gpuMemoryFree; + gpuInfo.memory = memory; + } + + var devices = provider.getGpuDevices(); + if (devices != null && devices.size() > 0) { + gpuInfo.devices = devices; + } + } + + } catch (Exception e) { + log.warn("Failed to get GPU information", e); + } + + return gpuInfo; + } + + /** Return good default units based on byte size. */ + private static String humanReadableUnits(long bytes, DecimalFormat df) { + String newSizeAndUnits; + + if (bytes / ONE_GB > 0) { + newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_GB)) + " GB"; + } else if (bytes / ONE_MB > 0) { + newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_MB)) + " MB"; + } else if (bytes / ONE_KB > 0) { + newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_KB)) + " KB"; + } else { + newSizeAndUnits = String.valueOf(bytes) + " bytes"; + } + + return newSizeAndUnits; + } + + private static List getInputArgumentsRedacted(NodeConfig nodeConfig, RuntimeMXBean mx) { + List list = new ArrayList<>(); + for (String arg : mx.getInputArguments()) { + if (arg.startsWith("-D") + && arg.contains("=") + && nodeConfig.isSysPropHidden(arg.substring(2, arg.indexOf('=')))) { + list.add( + String.format( + Locale.ROOT, + "%s=%s", + arg.substring(0, arg.indexOf('=')), + NodeConfig.REDACTED_SYS_PROP_VALUE)); + } else { + list.add(arg); + } + } + return list; + } + + /** + * Iterates over properties of the given MXBean and invokes the provided consumer with each + * property name and its current value. + * + * @param obj an instance of MXBean + * @param interfaces interfaces that it may implement. Each interface will be tried in turn, and + * only if it exists and if it contains unique properties then they will be added as metrics. + * @param consumer consumer for each property name and value + * @param formal type + */ + private static void forEachGetterValue( + T obj, String[] interfaces, BiConsumer consumer) { + for (String clazz : interfaces) { + try { + final Class intf = + Class.forName(clazz).asSubclass(PlatformManagedObject.class); + forEachGetterValue(obj, intf, consumer); + } catch (ClassNotFoundException e) { + // ignore + } + } + } + + /** + * Iterates over properties of the given MXBean and invokes the provided consumer with each + * property name and its current value. + * + * @param obj an instance of MXBean + * @param intf MXBean interface, one of {@link PlatformManagedObject}-s + * @param consumer consumer for each property name and value + * @param formal type + */ + private static void forEachGetterValue( + T obj, Class intf, BiConsumer consumer) { + if (intf.isInstance(obj)) { + BeanInfo beanInfo = + beanInfos.computeIfAbsent( + intf, + clazz -> { + try { + return Introspector.getBeanInfo( + clazz, clazz.getSuperclass(), Introspector.IGNORE_ALL_BEANINFO); + + } catch (IntrospectionException e) { + log.warn("Unable to fetch properties of MXBean {}", obj.getClass().getName()); + return null; + } + }); + + // if BeanInfo retrieval failed, return early + if (beanInfo == null) { + return; + } + for (final PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) { + try { + Method readMethod = desc.getReadMethod(); + if (readMethod == null) { + continue; // skip properties without a read method + } + + final String name = desc.getName(); + Object value = readMethod.invoke(obj); + consumer.accept(name, value); + } catch (Exception e) { + // didn't work, skip it... + } + } + } + } + + private void initHostname() { + if (!EnvUtils.getPropertyAsBool(REVERSE_DNS_OF_LOCALHOST_SYSPROP, true)) { + log.info( + "Resolving canonical hostname for local host prevented due to '{}' sysprop", + REVERSE_DNS_OF_LOCALHOST_SYSPROP); + hostname = null; + return; + } + + RTimer timer = new RTimer(); + try { + InetAddress addr = InetAddress.getLocalHost(); + hostname = addr.getCanonicalHostName(); + } catch (Exception e) { + log.warn( + "Unable to resolve canonical hostname for local host, possible DNS misconfiguration. Set the '{}' sysprop to false on startup to prevent future lookups if DNS can not be fixed.", + REVERSE_DNS_OF_LOCALHOST_SYSPROP, + e); + hostname = null; + return; + } + timer.stop(); + + if (15000D < timer.getTime()) { + String readableTime = String.format(Locale.ROOT, "%.3f", (timer.getTime() / 1000)); + log.warn( + "Resolving canonical hostname for local host took {} seconds, possible DNS misconfiguration. Set the '{}' sysprop to false on startup to prevent future lookups if DNS can not be fixed.", + readableTime, + REVERSE_DNS_OF_LOCALHOST_SYSPROP); + } + } +} 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 16e78ab4268..8a5c0f510ea 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,55 +16,21 @@ */ package org.apache.solr.handler.admin; -import static org.apache.solr.common.params.CommonParams.NAME; - -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.io.IOException; import java.lang.invoke.MethodHandles; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; -import java.lang.management.PlatformManagedObject; -import java.lang.management.RuntimeMXBean; -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.nio.file.Path; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.lucene.util.Version; import org.apache.solr.api.AnnotatedApi; import org.apache.solr.api.Api; -import org.apache.solr.common.cloud.ZkStateReader; -import org.apache.solr.common.util.EnvUtils; -import org.apache.solr.common.util.SimpleOrderedMap; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.model.NodeSystemResponse; import org.apache.solr.core.CoreContainer; -import org.apache.solr.core.NodeConfig; -import org.apache.solr.core.SolrCore; import org.apache.solr.handler.RequestHandlerBase; +import org.apache.solr.handler.admin.api.GetNodeSystemInfo; import org.apache.solr.handler.admin.api.NodeSystemInfoAPI; -import org.apache.solr.metrics.GpuMetricsProvider; +import org.apache.solr.handler.api.V2ApiUtils; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; -import org.apache.solr.schema.IndexSchema; import org.apache.solr.security.AuthorizationContext; -import org.apache.solr.security.AuthorizationPlugin; -import org.apache.solr.security.PKIAuthenticationPlugin; -import org.apache.solr.security.RuleBasedAuthorizationPluginBase; -import org.apache.solr.util.RTimer; -import org.apache.solr.util.stats.MetricUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,32 +42,6 @@ public class SystemInfoHandler extends RequestHandlerBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - /** - * Undocumented expert level system property to prevent doing a reverse lookup of our hostname. - * This property will be logged as a suggested workaround if any problems are noticed when doing - * reverse lookup. - * - *

TODO: should we refactor this (and the associated logic) into a helper method for any other - * places where DNS is used? - * - * @see #initHostname - */ - private static final String REVERSE_DNS_OF_LOCALHOST_SYSPROP = - "solr.admin.handler.systeminfo.dns.reverse.lookup.enabled"; - - /** - * Local cache for BeanInfo instances that are created to scan for system metrics. List of - * properties is not supposed to change for the JVM lifespan, so we can keep already create - * BeanInfo instance for future calls. - */ - private static final ConcurrentMap, BeanInfo> beanInfos = new ConcurrentHashMap<>(); - - // on some platforms, resolving canonical hostname can cause the thread - // to block for several seconds if nameservices aren't available - // so resolve this once per handler instance - // (ie: not static, so core reload will refresh) - private String hostname = null; - private CoreContainer cc; public SystemInfoHandler() { @@ -111,151 +51,18 @@ public SystemInfoHandler() { public SystemInfoHandler(CoreContainer cc) { super(); this.cc = cc; - initHostname(); - } - - /** - * Iterates over properties of the given MXBean and invokes the provided consumer with each - * property name and its current value. - * - * @param obj an instance of MXBean - * @param interfaces interfaces that it may implement. Each interface will be tried in turn, and - * only if it exists and if it contains unique properties then they will be added as metrics. - * @param consumer consumer for each property name and value - * @param formal type - */ - public static void forEachGetterValue( - T obj, String[] interfaces, BiConsumer consumer) { - for (String clazz : interfaces) { - try { - final Class intf = - Class.forName(clazz).asSubclass(PlatformManagedObject.class); - forEachGetterValue(obj, intf, consumer); - } catch (ClassNotFoundException e) { - // ignore - } - } - } - - /** - * Iterates over properties of the given MXBean and invokes the provided consumer with each - * property name and its current value. - * - * @param obj an instance of MXBean - * @param intf MXBean interface, one of {@link PlatformManagedObject}-s - * @param consumer consumer for each property name and value - * @param formal type - */ - public static void forEachGetterValue( - T obj, Class intf, BiConsumer consumer) { - if (intf.isInstance(obj)) { - BeanInfo beanInfo = - beanInfos.computeIfAbsent( - intf, - clazz -> { - try { - return Introspector.getBeanInfo( - clazz, clazz.getSuperclass(), Introspector.IGNORE_ALL_BEANINFO); - - } catch (IntrospectionException e) { - log.warn("Unable to fetch properties of MXBean {}", obj.getClass().getName()); - return null; - } - }); - - // if BeanInfo retrieval failed, return early - if (beanInfo == null) { - return; - } - for (final PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) { - try { - Method readMethod = desc.getReadMethod(); - if (readMethod == null) { - continue; // skip properties without a read method - } - - final String name = desc.getName(); - Object value = readMethod.invoke(obj); - consumer.accept(name, value); - } catch (Exception e) { - // didn't work, skip it... - } - } - } - } - - private void initHostname() { - if (!EnvUtils.getPropertyAsBool(REVERSE_DNS_OF_LOCALHOST_SYSPROP, true)) { - log.info( - "Resolving canonical hostname for local host prevented due to '{}' sysprop", - REVERSE_DNS_OF_LOCALHOST_SYSPROP); - hostname = null; - return; - } - - RTimer timer = new RTimer(); - try { - InetAddress addr = InetAddress.getLocalHost(); - hostname = addr.getCanonicalHostName(); - } catch (Exception e) { - log.warn( - "Unable to resolve canonical hostname for local host, possible DNS misconfiguration. Set the '{}' sysprop to false on startup to prevent future lookups if DNS can not be fixed.", - REVERSE_DNS_OF_LOCALHOST_SYSPROP, - e); - hostname = null; - return; - } - timer.stop(); - - if (15000D < timer.getTime()) { - String readableTime = String.format(Locale.ROOT, "%.3f", (timer.getTime() / 1000)); - log.warn( - "Resolving canonical hostname for local host took {} seconds, possible DNS misconfiguration. Set the '{}' sysprop to false on startup to prevent future lookups if DNS can not be fixed.", - readableTime, - REVERSE_DNS_OF_LOCALHOST_SYSPROP); - } } @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { + log.info("handleRequestBody: {}", req.getPath()); rsp.setHttpCaching(false); - SolrCore core = req.getCore(); if (AdminHandlersProxy.maybeProxyToNodes(req, rsp, getCoreContainer(req))) { return; // Request was proxied to other node } - if (core != null) rsp.add("core", getCoreInfo(core, req.getSchema())); - boolean solrCloudMode = getCoreContainer(req).isZooKeeperAware(); - rsp.add("mode", solrCloudMode ? "solrcloud" : "std"); - if (solrCloudMode) { - rsp.add("zkHost", getCoreContainer(req).getZkController().getZkServerAddress()); - } - if (cc != null) { - rsp.add("solr_home", cc.getSolrHome()); - rsp.add("core_root", cc.getCoreRootDirectory()); - } - - rsp.add("lucene", getLuceneInfo()); - NodeConfig nodeConfig = getCoreContainer(req).getNodeConfig(); - rsp.add("jvm", getJvmInfo(nodeConfig)); - rsp.add("security", getSecurityInfo(req)); - rsp.add("system", getSystemInfo()); - - rsp.add("gpu", getGpuInfo(req)); - if (solrCloudMode) { - rsp.add("node", getCoreContainer(req).getZkController().getNodeName()); - } - SolrEnvironment env = - SolrEnvironment.getFromSyspropOrClusterprop( - solrCloudMode ? getCoreContainer(req).getZkController().zkStateReader : null); - if (env.isDefined()) { - rsp.add("environment", env.getCode()); - if (env.getLabel() != null) { - rsp.add("environment_label", env.getLabel()); - } - if (env.getColor() != null) { - rsp.add("environment_color", env.getColor()); - } - } + NodeSystemInfoProvider provider = new NodeSystemInfoProvider(req); + NodeSystemResponse response = provider.getNodeSystemInfo(); + V2ApiUtils.squashIntoSolrResponseWithHeader(rsp, response); } private CoreContainer getCoreContainer(SolrQueryRequest req) { @@ -263,203 +70,6 @@ private CoreContainer getCoreContainer(SolrQueryRequest req) { return coreContainer == null ? cc : coreContainer; } - /** Get system info */ - private SimpleOrderedMap getCoreInfo(SolrCore core, IndexSchema schema) { - SimpleOrderedMap info = new SimpleOrderedMap<>(); - - info.add("schema", schema != null ? schema.getSchemaName() : "no schema!"); - - // Host - info.add("host", hostname); - - // Now - info.add("now", new Date()); - - // Start Time - info.add("start", core.getStartTimeStamp()); - - // Solr Home - SimpleOrderedMap dirs = new SimpleOrderedMap<>(); - dirs.add("cwd", Path.of(System.getProperty("user.dir")).toAbsolutePath().toString()); - dirs.add("instance", core.getInstancePath().toString()); - try { - dirs.add("data", core.getDirectoryFactory().normalize(core.getDataDir())); - } catch (IOException e) { - log.warn("Problem getting the normalized data directory path", e); - dirs.add("data", "N/A"); - } - dirs.add("dirimpl", core.getDirectoryFactory().getClass().getName()); - try { - dirs.add("index", core.getDirectoryFactory().normalize(core.getIndexDir())); - } catch (IOException e) { - log.warn("Problem getting the normalized index directory path", e); - dirs.add("index", "N/A"); - } - info.add("directory", dirs); - return info; - } - - /** Get system info */ - public static SimpleOrderedMap getSystemInfo() { - SimpleOrderedMap info = new SimpleOrderedMap<>(); - - OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); - info.add(NAME, os.getName()); // add at least this one - - // add remaining ones dynamically using Java Beans API - // also those from JVM implementation-specific classes - forEachGetterValue( - os, - MetricUtils.OS_MXBEAN_CLASSES, - (name, value) -> { - if (info.get(name) == null) { - info.add(name, value); - } - }); - - return info; - } - - /** Get JVM Info - including memory info */ - public static SimpleOrderedMap getJvmInfo(NodeConfig nodeConfig) { - SimpleOrderedMap jvm = new SimpleOrderedMap<>(); - - final String javaVersion = System.getProperty("java.specification.version", "unknown"); - final String javaVendor = System.getProperty("java.specification.vendor", "unknown"); - final String javaName = System.getProperty("java.specification.name", "unknown"); - final String jreVersion = System.getProperty("java.version", "unknown"); - final String jreVendor = System.getProperty("java.vendor", "unknown"); - final String vmVersion = System.getProperty("java.vm.version", "unknown"); - final String vmVendor = System.getProperty("java.vm.vendor", "unknown"); - final String vmName = System.getProperty("java.vm.name", "unknown"); - - // Summary Info - jvm.add("version", jreVersion + " " + vmVersion); - jvm.add(NAME, jreVendor + " " + vmName); - - // details - SimpleOrderedMap java = new SimpleOrderedMap<>(); - java.add("vendor", javaVendor); - java.add(NAME, javaName); - java.add("version", javaVersion); - jvm.add("spec", java); - SimpleOrderedMap jre = new SimpleOrderedMap<>(); - jre.add("vendor", jreVendor); - jre.add("version", jreVersion); - jvm.add("jre", jre); - SimpleOrderedMap vm = new SimpleOrderedMap<>(); - vm.add("vendor", vmVendor); - vm.add(NAME, vmName); - vm.add("version", vmVersion); - jvm.add("vm", vm); - - Runtime runtime = Runtime.getRuntime(); - jvm.add("processors", runtime.availableProcessors()); - - // not thread safe, but could be thread local - DecimalFormat df = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.ROOT)); - - SimpleOrderedMap mem = new SimpleOrderedMap<>(); - SimpleOrderedMap raw = new SimpleOrderedMap<>(); - long free = runtime.freeMemory(); - long max = runtime.maxMemory(); - long total = runtime.totalMemory(); - long used = total - free; - double percentUsed = ((double) (used) / (double) max) * 100; - raw.add("free", free); - mem.add("free", humanReadableUnits(free, df)); - raw.add("total", total); - mem.add("total", humanReadableUnits(total, df)); - raw.add("max", max); - mem.add("max", humanReadableUnits(max, df)); - raw.add("used", used); - mem.add("used", humanReadableUnits(used, df) + " (%" + df.format(percentUsed) + ")"); - raw.add("used%", percentUsed); - - mem.add("raw", raw); - jvm.add("memory", mem); - - // JMX properties -- probably should be moved to a different handler - SimpleOrderedMap jmx = new SimpleOrderedMap<>(); - try { - RuntimeMXBean mx = ManagementFactory.getRuntimeMXBean(); - if (mx.isBootClassPathSupported()) { - jmx.add("bootclasspath", mx.getBootClassPath()); - } - jmx.add("classpath", mx.getClassPath()); - - // the input arguments passed to the Java virtual machine - // which does not include the arguments to the main method. - jmx.add("commandLineArgs", getInputArgumentsRedacted(nodeConfig, mx)); - - jmx.add("startTime", new Date(mx.getStartTime())); - jmx.add("upTimeMS", mx.getUptime()); - - } catch (Exception e) { - log.warn("Error getting JMX properties", e); - } - jvm.add("jmx", jmx); - return jvm; - } - - /** Get Security Info */ - public SimpleOrderedMap getSecurityInfo(SolrQueryRequest req) { - SimpleOrderedMap info = new SimpleOrderedMap<>(); - - if (cc != null) { - if (cc.getAuthenticationPlugin() != null) { - info.add("authenticationPlugin", cc.getAuthenticationPlugin().getName()); - } - if (cc.getAuthorizationPlugin() != null) { - info.add("authorizationPlugin", cc.getAuthorizationPlugin().getClass().getName()); - } - } - - if (req.getUserPrincipal() != null - && req.getUserPrincipal() != PKIAuthenticationPlugin.CLUSTER_MEMBER_NODE) { - // User principal - info.add("username", req.getUserPrincipal().getName()); - - // Mapped roles for this principal - @SuppressWarnings("resource") - AuthorizationPlugin auth = cc == null ? null : cc.getAuthorizationPlugin(); - if (auth instanceof RuleBasedAuthorizationPluginBase rbap) { - Set roles = rbap.getUserRoles(req.getUserPrincipal()); - info.add("roles", roles); - if (roles == null) { - info.add("permissions", Set.of()); - } else { - info.add( - "permissions", - rbap.getPermissionNamesForRoles( - Stream.concat(roles.stream(), Stream.of("*", null)).collect(Collectors.toSet()))); - } - } - } - - if (cc != null && cc.getZkController() != null) { - String urlScheme = - cc.getZkController().zkStateReader.getClusterProperty(ZkStateReader.URL_SCHEME, "http"); - info.add("tls", ZkStateReader.HTTPS.equals(urlScheme)); - } - - return info; - } - - private static SimpleOrderedMap getLuceneInfo() { - SimpleOrderedMap info = new SimpleOrderedMap<>(); - - Package p = SolrCore.class.getPackage(); - - info.add("solr-spec-version", p.getSpecificationVersion()); - info.add("solr-impl-version", p.getImplementationVersion()); - - info.add("lucene-spec-version", Version.LATEST.toString()); - info.add("lucene-impl-version", Version.getPackageImplementationVersion()); - - return info; - } - //////////////////////// SolrInfoMBeans methods ////////////////////// @Override @@ -472,98 +82,19 @@ public Category getCategory() { return Category.ADMIN; } - private static final long ONE_KB = 1024; - private static final long ONE_MB = ONE_KB * ONE_KB; - private static final long ONE_GB = ONE_KB * ONE_MB; - - /** Return good default units based on byte size. */ - private static String humanReadableUnits(long bytes, DecimalFormat df) { - String newSizeAndUnits; - - if (bytes / ONE_GB > 0) { - newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_GB)) + " GB"; - } else if (bytes / ONE_MB > 0) { - newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_MB)) + " MB"; - } else if (bytes / ONE_KB > 0) { - newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_KB)) + " KB"; - } else { - newSizeAndUnits = String.valueOf(bytes) + " bytes"; - } - - return newSizeAndUnits; - } - - private static List getInputArgumentsRedacted(NodeConfig nodeConfig, RuntimeMXBean mx) { - List list = new ArrayList<>(); - for (String arg : mx.getInputArguments()) { - if (arg.startsWith("-D") - && arg.contains("=") - && nodeConfig.isSysPropHidden(arg.substring(2, arg.indexOf('=')))) { - list.add( - String.format( - Locale.ROOT, - "%s=%s", - arg.substring(0, arg.indexOf('=')), - NodeConfig.REDACTED_SYS_PROP_VALUE)); - } else { - list.add(arg); - } - } - return list; - } - @Override public Collection getApis() { return AnnotatedApi.getApis(new NodeSystemInfoAPI(this)); } @Override - public Boolean registerV2() { - return Boolean.TRUE; + public Collection> getJerseyResources() { + return List.of(GetNodeSystemInfo.class); } - private SimpleOrderedMap getGpuInfo(SolrQueryRequest req) { - SimpleOrderedMap gpuInfo = new SimpleOrderedMap<>(); - - try { - GpuMetricsProvider provider = getCoreContainer(req).getGpuMetricsProvider(); - - if (provider == null) { - gpuInfo.add("available", false); - return gpuInfo; - } - - long gpuCount = provider.getGpuCount(); - if (gpuCount > 0) { - gpuInfo.add("available", true); - gpuInfo.add("count", gpuCount); - - long gpuMemoryTotal = provider.getGpuMemoryTotal(); - long gpuMemoryUsed = provider.getGpuMemoryUsed(); - long gpuMemoryFree = provider.getGpuMemoryFree(); - - if (gpuMemoryTotal > 0) { - SimpleOrderedMap memory = new SimpleOrderedMap<>(); - memory.add("total", gpuMemoryTotal); - memory.add("used", gpuMemoryUsed); - memory.add("free", gpuMemoryFree); - gpuInfo.add("memory", memory); - } - - var devices = provider.getGpuDevices(); - if (devices != null && devices.size() > 0) { - gpuInfo.add("devices", devices); - } - } else { - gpuInfo.add("available", false); - } - - } catch (Exception e) { - log.warn("Failed to get GPU information", e); - gpuInfo.add("available", false); - } - - return gpuInfo; + @Override + public Boolean registerV2() { + return Boolean.TRUE; } @Override diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java new file mode 100644 index 00000000000..82fc7b3fcae --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java @@ -0,0 +1,67 @@ +/* + * 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 jakarta.inject.Inject; +import java.lang.invoke.MethodHandles; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.endpoint.NodeSystemInfoApi; +import org.apache.solr.client.api.model.NodeSystemResponse; +import org.apache.solr.handler.admin.AdminHandlersProxy; +import org.apache.solr.handler.admin.NodeSystemInfoProvider; +import org.apache.solr.handler.api.V2ApiUtils; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; +import org.apache.solr.security.PermissionNameProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Implementation of the V2 JerseyResource /node/info/system */ +public class GetNodeSystemInfo extends JerseyResource implements NodeSystemInfoApi { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final SolrQueryRequest solrQueryRequest; + private final SolrQueryResponse solrQueryResponse; + + @Inject + public GetNodeSystemInfo(SolrQueryRequest solrQueryRequest, SolrQueryResponse solrQueryResponse) { + this.solrQueryRequest = solrQueryRequest; + this.solrQueryResponse = solrQueryResponse; + } + + @Override + @PermissionName(PermissionNameProvider.Name.CONFIG_READ_PERM) + public NodeSystemResponse getNodeSystemInfo() { + solrQueryResponse.setHttpCaching(false); + // TODO: AdminHandlersProxy is only V1 or also V2? + try { + if (solrQueryRequest.getCoreContainer() != null + && AdminHandlersProxy.maybeProxyToNodes( + solrQueryRequest, solrQueryResponse, solrQueryRequest.getCoreContainer())) { + return null; + } + } catch (Exception e) { + log.warn("Exception proxying to other node", e); + } + + NodeSystemInfoProvider provider = new NodeSystemInfoProvider(solrQueryRequest); + NodeSystemResponse response = provider.getNodeSystemInfo(); + V2ApiUtils.squashIntoSolrResponseWithHeader(solrQueryResponse, response); + return provider.getNodeSystemInfo(); + } +} 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 index d7df2cfa0cd..a3254ba3655 100644 --- 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 @@ -20,10 +20,13 @@ import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET; import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM; +import java.lang.invoke.MethodHandles; 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * V2 API for getting "system" information from the receiving node. @@ -32,8 +35,14 @@ * version, etc.), and JVM settings. * *

This API (GET /v2/node/system) is analogous to the v1 /admin/info/system. + * + * @deprecated Use the JAX-RS API: NodeSystemInfo (/node/info/system), implementing NodeSystemApi, + * and returning NodeSystemResponse. */ +@Deprecated public class NodeSystemInfoAPI { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private final SystemInfoHandler handler; public NodeSystemInfoAPI(SystemInfoHandler handler) { @@ -45,6 +54,7 @@ public NodeSystemInfoAPI(SystemInfoHandler handler) { method = GET, permission = CONFIG_READ_PERM) public void getSystemInformation(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { + log.warn("Request to deprecated solr.api.Endpoint: /node/system. Please use /node/info/system"); handler.handleRequestBody(req, rsp); } } From af9757a6612223d9535a53de675d1b74fd6ce57c Mon Sep 17 00:00:00 2001 From: Isabelle Giguere Date: Sun, 18 Jan 2026 15:51:39 -0500 Subject: [PATCH 2/9] SOLR-16397: Replace V2 Endpoint for system info by JerseyResource impl Fix NodeSystemResponse to contain a map of node name to node info. Fix responses for Json and XML. TODO : nodes=all is ignored --- .../api/endpoint/NodeSystemInfoApi.java | 7 +- ...ponse.java => NodeSystemInfoResponse.java} | 61 ++++--- .../handler/admin/NodeSystemInfoProvider.java | 156 ++++++++++-------- .../solr/handler/admin/SystemInfoHandler.java | 57 ++++++- .../handler/admin/api/GetNodeSystemInfo.java | 25 ++- .../handler/admin/api/NodeSystemInfoAPI.java | 2 +- .../admin/NodeSystemInfoProviderTest.java | 91 ++++++++++ .../handler/admin/SystemInfoHandlerTest.java | 46 ------ .../admin/api/GetNodeSystemInfoTest.java | 97 +++++++++++ .../solrj/request/SystemInfoRequest.java | 10 +- .../solrj/response/SystemInfoResponse.java | 77 ++++----- .../json/JacksonDataBindResponseParser.java | 28 +++- .../solr/common/params/CommonParams.java | 2 +- 13 files changed, 460 insertions(+), 199 deletions(-) rename solr/api/src/java/org/apache/solr/client/api/model/{NodeSystemResponse.java => NodeSystemInfoResponse.java} (79%) create mode 100644 solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java delete mode 100644 solr/core/src/test/org/apache/solr/handler/admin/SystemInfoHandlerTest.java create mode 100644 solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java index 49bab74cfb8..14d2d4fe3ef 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java @@ -19,7 +19,10 @@ import io.swagger.v3.oas.annotations.Operation; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import org.apache.solr.client.api.model.NodeSystemResponse; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import org.apache.solr.client.api.model.NodeSystemInfoResponse; /** V2 API definition to fetch node system info, analogous to the v1 /admin/info/system. */ @Path("/node/info/system") @@ -29,5 +32,5 @@ public interface NodeSystemInfoApi { @Operation( summary = "Retrieve node system info.", tags = {"system"}) - NodeSystemResponse getNodeSystemInfo(); + NodeSystemInfoResponse getNodeSystemInfo(); } diff --git a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java similarity index 79% rename from solr/api/src/java/org/apache/solr/client/api/model/NodeSystemResponse.java rename to solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java index 5f727df3fda..5b9a01ba568 100644 --- a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemResponse.java +++ b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java @@ -17,38 +17,47 @@ package org.apache.solr.client.api.model; import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -/** Response from /node/info/system */ -public class NodeSystemResponse extends SolrJerseyResponse { - - @JsonProperty public String mode; - @JsonProperty public String zkHost; - - @JsonProperty("solr_home") - public String solrHome; - - @JsonProperty("core_root") - public String coreRoot; +import org.apache.solr.client.api.util.ReflectWritable; - @JsonProperty public String environment; - - @JsonProperty(value = "environment_label") - public String environmentLabel; - - @JsonProperty(value = "environment_color") - public String environmentColor; - - @JsonProperty public String node; - @JsonProperty public Core core; - @JsonProperty public Lucene lucene; - @JsonProperty public JVM jvm; - @JsonProperty public Security security; - @JsonProperty public GPU gpu; - @JsonProperty public Map system; +/** Response from /node/info/system */ +public class NodeSystemInfoResponse extends SolrJerseyResponse { + + @JsonProperty public Map nodesInfo; + + public static class NodeSystemInfo { + + @JsonProperty public String node; + @JsonProperty public String mode; + @JsonProperty public String zkHost; + + @JsonProperty("solr_home") + public String solrHome; + + @JsonProperty("core_root") + public String coreRoot; + + @JsonProperty public String environment; + + @JsonProperty(value = "environment_label") + public String environmentLabel; + + @JsonProperty(value = "environment_color") + public String environmentColor; + + @JsonProperty public Core core; + @JsonProperty public Lucene lucene; + @JsonProperty public JVM jvm; + @JsonProperty public Security security; + @JsonProperty public GPU gpu; + @JsonProperty public Map system; + } /** /node/system/security */ public static class Security { diff --git a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java index b16856532ba..30b1324c0b8 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java @@ -46,8 +46,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.lucene.util.Version; -import org.apache.solr.client.api.model.NodeSystemResponse; +import org.apache.solr.client.api.model.NodeSystemInfoResponse; +import org.apache.solr.client.api.model.NodeSystemInfoResponse.NodeSystemInfo; +import org.apache.solr.client.api.util.SolrVersion; import org.apache.solr.common.cloud.ZkStateReader; +import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.EnvUtils; import org.apache.solr.core.CoreContainer; import org.apache.solr.core.NodeConfig; @@ -63,13 +66,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** Used by SystemInfoHandler and NodeSystemInfo */ +/** Used by GetNodeSystemInfo, and indirectly by SystemInfoHandler */ public class NodeSystemInfoProvider { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private SolrQueryRequest req; + private SolrParams params; private CoreContainer cc; - private String hostname = null; + + // on some platforms, resolving canonical hostname can cause the thread + // to block for several seconds if nameservices aren't available + // so resolve this once per provider instance + // (ie: not static, so core reload will refresh) + private String hostname; private static final long ONE_KB = 1024; private static final long ONE_MB = ONE_KB * ONE_KB; @@ -94,55 +103,68 @@ public class NodeSystemInfoProvider { * BeanInfo instance for future calls. */ private static final ConcurrentMap, BeanInfo> beanInfos = new ConcurrentHashMap<>(); - + public NodeSystemInfoProvider(SolrQueryRequest request) { req = request; + params = request.getParams(); cc = request.getCoreContainer(); initHostname(); } + + public void getNodeSystemInfo(NodeSystemInfoResponse response) { + Map nodes = new HashMap<>(); + NodeSystemInfo info = getNodeInfo(); + String key = info.node != null ? info.node : hostname; // should allow null key ? + nodes.put(key, info); + response.nodesInfo = nodes; + } + + private NodeSystemInfoResponse.NodeSystemInfo getNodeInfo() { + NodeSystemInfo info = new NodeSystemInfo(); - public NodeSystemResponse getNodeSystemInfo() { - NodeSystemResponse resp = new NodeSystemResponse(); SolrCore core = req.getCore(); - if (core != null) resp.core = getCoreInfo(core, req.getSchema()); - boolean solrCloudMode = cc.isZooKeeperAware(); - resp.mode = solrCloudMode ? "solrcloud" : "std"; - if (solrCloudMode) { - resp.zkHost = cc.getZkController().getZkServerAddress(); - } + if (core != null) info.core = getCoreInfo(core, req.getSchema()); + if (cc != null) { - resp.solrHome = cc.getSolrHome().toString(); - resp.coreRoot = cc.getCoreRootDirectory().toString(); + info.solrHome = cc.getSolrHome().toString(); + info.coreRoot = cc.getCoreRootDirectory().toString(); } - - resp.lucene = getLuceneInfo(); - NodeConfig nodeConfig = cc.getNodeConfig(); - resp.jvm = getJvmInfo(nodeConfig); - resp.security = getSecurityInfo(req); - resp.system = getSystemInfo(); - - resp.gpu = getGpuInfo(); + + boolean solrCloudMode = cc != null && cc.isZooKeeperAware(); + info.mode = solrCloudMode ? "solrcloud" : "std"; if (solrCloudMode) { - resp.node = cc.getZkController().getNodeName(); + info.zkHost = cc.getZkController().getZkServerAddress(); + info.node = cc.getZkController().getNodeName(); } + + info.lucene = getLuceneInfo(); + + NodeConfig nodeConfig = cc != null ? cc.getNodeConfig() : null; + info.jvm = getJvmInfo(nodeConfig); + + info.security = getSecurityInfo(req); + info.system = getSystemInfo(); + info.gpu = getGpuInfo(); + SolrEnvironment env = SolrEnvironment.getFromSyspropOrClusterprop( solrCloudMode ? cc.getZkController().zkStateReader : null); if (env.isDefined()) { - resp.environment = env.getCode(); + info.environment = env.getCode(); if (env.getLabel() != null) { - resp.environmentLabel = env.getLabel(); + info.environmentLabel = env.getLabel(); } if (env.getColor() != null) { - resp.environmentColor = env.getColor(); + info.environmentColor = env.getColor(); } } - return resp; + + return info; } /** Get system info */ - private NodeSystemResponse.Core getCoreInfo(SolrCore core, IndexSchema schema) { - NodeSystemResponse.Core info = new NodeSystemResponse.Core(); + private NodeSystemInfoResponse.Core getCoreInfo(SolrCore core, IndexSchema schema) { + NodeSystemInfoResponse.Core info = new NodeSystemInfoResponse.Core(); info.schema = schema != null ? schema.getSchemaName() : "no schema!"; @@ -156,7 +178,7 @@ private NodeSystemResponse.Core getCoreInfo(SolrCore core, IndexSchema schema) { info.start = core.getStartTimeStamp(); // Solr Home - NodeSystemResponse.Directory dirs = new NodeSystemResponse.Directory(); + NodeSystemInfoResponse.Directory dirs = new NodeSystemInfoResponse.Directory(); dirs.cwd = Path.of(System.getProperty("user.dir")).toAbsolutePath().toString(); dirs.instance = core.getInstancePath().toString(); try { @@ -198,8 +220,8 @@ private Map getSystemInfo() { } /** Get JVM Info - including memory info */ - private NodeSystemResponse.JVM getJvmInfo(NodeConfig nodeConfig) { - NodeSystemResponse.JVM jvm = new NodeSystemResponse.JVM(); + private NodeSystemInfoResponse.JVM getJvmInfo(NodeConfig nodeConfig) { + NodeSystemInfoResponse.JVM jvm = new NodeSystemInfoResponse.JVM(); final String javaVersion = System.getProperty("java.specification.version", "unknown"); final String javaVendor = System.getProperty("java.specification.vendor", "unknown"); @@ -215,18 +237,18 @@ private NodeSystemResponse.JVM getJvmInfo(NodeConfig nodeConfig) { jvm.name = jreVendor + " " + vmName; // details - NodeSystemResponse.Vendor spec = new NodeSystemResponse.Vendor(); + NodeSystemInfoResponse.Vendor spec = new NodeSystemInfoResponse.Vendor(); spec.vendor = javaVendor; spec.name = javaName; spec.version = javaVersion; jvm.spec = spec; - NodeSystemResponse.Vendor jre = new NodeSystemResponse.Vendor(); + NodeSystemInfoResponse.Vendor jre = new NodeSystemInfoResponse.Vendor(); jre.vendor = jreVendor; jre.version = jreVersion; jvm.jre = jre; - NodeSystemResponse.Vendor vm = new NodeSystemResponse.Vendor(); + NodeSystemInfoResponse.Vendor vm = new NodeSystemInfoResponse.Vendor(); vm.vendor = vmVendor; vm.name = vmName; vm.version = vmVersion; @@ -238,8 +260,8 @@ private NodeSystemResponse.JVM getJvmInfo(NodeConfig nodeConfig) { // not thread safe, but could be thread local DecimalFormat df = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.ROOT)); - NodeSystemResponse.JvmMemory mem = new NodeSystemResponse.JvmMemory(); - NodeSystemResponse.JvmMemoryRaw raw = new NodeSystemResponse.JvmMemoryRaw(); + NodeSystemInfoResponse.JvmMemory mem = new NodeSystemInfoResponse.JvmMemory(); + NodeSystemInfoResponse.JvmMemoryRaw raw = new NodeSystemInfoResponse.JvmMemoryRaw(); long free = runtime.freeMemory(); long max = runtime.maxMemory(); long total = runtime.totalMemory(); @@ -259,7 +281,7 @@ private NodeSystemResponse.JVM getJvmInfo(NodeConfig nodeConfig) { jvm.memory = mem; // JMX properties - NodeSystemResponse.JvmJmx jmx = new NodeSystemResponse.JvmJmx(); + NodeSystemInfoResponse.JvmJmx jmx = new NodeSystemInfoResponse.JvmJmx(); try { RuntimeMXBean mx = ManagementFactory.getRuntimeMXBean(); if (mx.isBootClassPathSupported()) { @@ -282,8 +304,8 @@ private NodeSystemResponse.JVM getJvmInfo(NodeConfig nodeConfig) { } /** Get Security Info */ - private NodeSystemResponse.Security getSecurityInfo(SolrQueryRequest req) { - NodeSystemResponse.Security info = new NodeSystemResponse.Security(); + private NodeSystemInfoResponse.Security getSecurityInfo(SolrQueryRequest req) { + NodeSystemInfoResponse.Security info = new NodeSystemInfoResponse.Security(); if (cc != null) { if (cc.getAuthenticationPlugin() != null) { @@ -300,7 +322,7 @@ private NodeSystemResponse.Security getSecurityInfo(SolrQueryRequest req) { info.username = req.getUserPrincipal().getName(); // Mapped roles for this principal - @SuppressWarnings("resource") + //@SuppressWarnings("resource") AuthorizationPlugin auth = cc == null ? null : cc.getAuthorizationPlugin(); if (auth instanceof RuleBasedAuthorizationPluginBase rbap) { Set roles = rbap.getUserRoles(req.getUserPrincipal()); @@ -324,13 +346,15 @@ private NodeSystemResponse.Security getSecurityInfo(SolrQueryRequest req) { return info; } - private NodeSystemResponse.Lucene getLuceneInfo() { - NodeSystemResponse.Lucene info = new NodeSystemResponse.Lucene(); + private NodeSystemInfoResponse.Lucene getLuceneInfo() { + NodeSystemInfoResponse.Lucene info = new NodeSystemInfoResponse.Lucene(); Package p = SolrCore.class.getPackage(); - - info.solrSpecVersion = p.getSpecificationVersion(); - info.solrImplVersion = p.getImplementationVersion(); + String specVersion = p.getSpecificationVersion(); + String implVersion = p.getImplementationVersion(); + // non-null mostly for testing + info.solrSpecVersion = specVersion== null ? SolrVersion.LATEST_STRING : specVersion; + info.solrImplVersion = implVersion == null ? SolrVersion.LATEST.getPrereleaseVersion() : implVersion; info.luceneSpecVersion = Version.LATEST.toString(); info.luceneImplVersion = Version.getPackageImplementationVersion(); @@ -338,8 +362,8 @@ private NodeSystemResponse.Lucene getLuceneInfo() { return info; } - private NodeSystemResponse.GPU getGpuInfo() { - NodeSystemResponse.GPU gpuInfo = new NodeSystemResponse.GPU(); + private NodeSystemInfoResponse.GPU getGpuInfo() { + NodeSystemInfoResponse.GPU gpuInfo = new NodeSystemInfoResponse.GPU(); gpuInfo.available = false; // set below if available try { @@ -359,7 +383,7 @@ private NodeSystemResponse.GPU getGpuInfo() { long gpuMemoryFree = provider.getGpuMemoryFree(); if (gpuMemoryTotal > 0) { - NodeSystemResponse.MemoryRaw memory = new NodeSystemResponse.MemoryRaw(); + NodeSystemInfoResponse.MemoryRaw memory = new NodeSystemInfoResponse.MemoryRaw(); memory.total = gpuMemoryTotal; memory.used = gpuMemoryUsed; memory.free = gpuMemoryFree; @@ -380,7 +404,7 @@ private NodeSystemResponse.GPU getGpuInfo() { } /** Return good default units based on byte size. */ - private static String humanReadableUnits(long bytes, DecimalFormat df) { + private String humanReadableUnits(long bytes, DecimalFormat df) { String newSizeAndUnits; if (bytes / ONE_GB > 0) { @@ -396,12 +420,12 @@ private static String humanReadableUnits(long bytes, DecimalFormat df) { return newSizeAndUnits; } - private static List getInputArgumentsRedacted(NodeConfig nodeConfig, RuntimeMXBean mx) { + private List getInputArgumentsRedacted(NodeConfig nodeConfig, RuntimeMXBean mx) { List list = new ArrayList<>(); for (String arg : mx.getInputArguments()) { if (arg.startsWith("-D") && arg.contains("=") - && nodeConfig.isSysPropHidden(arg.substring(2, arg.indexOf('=')))) { + && (nodeConfig != null && nodeConfig.isSysPropHidden(arg.substring(2, arg.indexOf('='))))) { list.add( String.format( Locale.ROOT, @@ -425,7 +449,7 @@ private static List getInputArgumentsRedacted(NodeConfig nodeConfig, Run * @param consumer consumer for each property name and value * @param formal type */ - private static void forEachGetterValue( + private void forEachGetterValue( T obj, String[] interfaces, BiConsumer consumer) { for (String clazz : interfaces) { try { @@ -447,7 +471,8 @@ private static void forEachGetterValue( * @param consumer consumer for each property name and value * @param formal type */ - private static void forEachGetterValue( + // protected & static : ref. test in NodeSystemInfoProviderTest + protected static void forEachGetterValue( T obj, Class intf, BiConsumer consumer) { if (intf.isInstance(obj)) { BeanInfo beanInfo = @@ -497,23 +522,24 @@ private void initHostname() { RTimer timer = new RTimer(); try { InetAddress addr = InetAddress.getLocalHost(); - hostname = addr.getCanonicalHostName(); + hostname = addr.getCanonicalHostName(); } catch (Exception e) { log.warn( "Unable to resolve canonical hostname for local host, possible DNS misconfiguration. Set the '{}' sysprop to false on startup to prevent future lookups if DNS can not be fixed.", REVERSE_DNS_OF_LOCALHOST_SYSPROP, e); hostname = null; - return; - } - timer.stop(); - - if (15000D < timer.getTime()) { - String readableTime = String.format(Locale.ROOT, "%.3f", (timer.getTime() / 1000)); - log.warn( - "Resolving canonical hostname for local host took {} seconds, possible DNS misconfiguration. Set the '{}' sysprop to false on startup to prevent future lookups if DNS can not be fixed.", - readableTime, - REVERSE_DNS_OF_LOCALHOST_SYSPROP); + return ; + } finally { + timer.stop(); + + if (15000D < timer.getTime()) { + String readableTime = String.format(Locale.ROOT, "%.3f", (timer.getTime() / 1000)); + log.warn( + "Resolving canonical hostname for local host took {} seconds, possible DNS misconfiguration. Set the '{}' sysprop to false on startup to prevent future lookups if DNS can not be fixed.", + readableTime, + REVERSE_DNS_OF_LOCALHOST_SYSPROP); + } } } } 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 8a5c0f510ea..3c8d5983a7b 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,21 +16,59 @@ */ package org.apache.solr.handler.admin; +import static org.apache.solr.common.params.CommonParams.NAME; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.IOException; import java.lang.invoke.MethodHandles; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.lang.management.PlatformManagedObject; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.nio.file.Path; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.lucene.util.Version; import org.apache.solr.api.AnnotatedApi; import org.apache.solr.api.Api; import org.apache.solr.api.JerseyResource; -import org.apache.solr.client.api.model.NodeSystemResponse; +import org.apache.solr.client.api.model.NodeSystemInfoResponse; +import org.apache.solr.common.cloud.ZkStateReader; +import org.apache.solr.common.util.EnvUtils; +import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.core.CoreContainer; +import org.apache.solr.core.NodeConfig; +import org.apache.solr.core.SolrCore; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.admin.api.GetNodeSystemInfo; import org.apache.solr.handler.admin.api.NodeSystemInfoAPI; import org.apache.solr.handler.api.V2ApiUtils; +import org.apache.solr.metrics.GpuMetricsProvider; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; +import org.apache.solr.schema.IndexSchema; import org.apache.solr.security.AuthorizationContext; +import org.apache.solr.security.AuthorizationPlugin; +import org.apache.solr.security.PKIAuthenticationPlugin; +import org.apache.solr.security.RuleBasedAuthorizationPluginBase; +import org.apache.solr.util.RTimer; +import org.apache.solr.util.stats.MetricUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,21 +93,24 @@ public SystemInfoHandler(CoreContainer cc) { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - log.info("handleRequestBody: {}", req.getPath()); rsp.setHttpCaching(false); + if (AdminHandlersProxy.maybeProxyToNodes(req, rsp, getCoreContainer(req))) { return; // Request was proxied to other node } - NodeSystemInfoProvider provider = new NodeSystemInfoProvider(req); - NodeSystemResponse response = provider.getNodeSystemInfo(); - V2ApiUtils.squashIntoSolrResponseWithHeader(rsp, response); + + GetNodeSystemInfo getter = new GetNodeSystemInfo(req, rsp); + NodeSystemInfoResponse response = getter.getNodeSystemInfo(); + V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response); + + return; } private CoreContainer getCoreContainer(SolrQueryRequest req) { CoreContainer coreContainer = req.getCoreContainer(); return coreContainer == null ? cc : coreContainer; } - + //////////////////////// SolrInfoMBeans methods ////////////////////// @Override @@ -86,10 +127,10 @@ public Category getCategory() { public Collection getApis() { return AnnotatedApi.getApis(new NodeSystemInfoAPI(this)); } - + @Override public Collection> getJerseyResources() { - return List.of(GetNodeSystemInfo.class); + return Set.of(GetNodeSystemInfo.class); } @Override diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java index 82fc7b3fcae..10213ae0e54 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java @@ -20,7 +20,10 @@ import java.lang.invoke.MethodHandles; import org.apache.solr.api.JerseyResource; import org.apache.solr.client.api.endpoint.NodeSystemInfoApi; -import org.apache.solr.client.api.model.NodeSystemResponse; +import org.apache.solr.client.api.model.NodeSystemInfoResponse; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.client.api.model.SolrJerseyResponse.ResponseHeader; +import org.apache.solr.core.CoreContainer; import org.apache.solr.handler.admin.AdminHandlersProxy; import org.apache.solr.handler.admin.NodeSystemInfoProvider; import org.apache.solr.handler.api.V2ApiUtils; @@ -46,22 +49,26 @@ public GetNodeSystemInfo(SolrQueryRequest solrQueryRequest, SolrQueryResponse so @Override @PermissionName(PermissionNameProvider.Name.CONFIG_READ_PERM) - public NodeSystemResponse getNodeSystemInfo() { + public NodeSystemInfoResponse getNodeSystemInfo() { solrQueryResponse.setHttpCaching(false); - // TODO: AdminHandlersProxy is only V1 or also V2? + + // TODO? nodes=all is ignored try { - if (solrQueryRequest.getCoreContainer() != null - && AdminHandlersProxy.maybeProxyToNodes( + if (AdminHandlersProxy.maybeProxyToNodes( solrQueryRequest, solrQueryResponse, solrQueryRequest.getCoreContainer())) { return null; } } catch (Exception e) { - log.warn("Exception proxying to other node", e); + log.warn("Error occurred while proxying to other node", e); } NodeSystemInfoProvider provider = new NodeSystemInfoProvider(solrQueryRequest); - NodeSystemResponse response = provider.getNodeSystemInfo(); - V2ApiUtils.squashIntoSolrResponseWithHeader(solrQueryResponse, response); - return provider.getNodeSystemInfo(); + NodeSystemInfoResponse response = instantiateJerseyResponse(NodeSystemInfoResponse.class); + provider.getNodeSystemInfo(response); + log.info("Found {} nodes.", response == null ? "NO" : response.nodesInfo.size()); + if(response != null) { + response.nodesInfo.entrySet().forEach(e -> log.info("Node {}, core root: {}", e.getKey(), e.getValue().coreRoot)); + } + return response; } } 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 index a3254ba3655..7a83498dd64 100644 --- 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 @@ -54,7 +54,7 @@ public NodeSystemInfoAPI(SystemInfoHandler handler) { method = GET, permission = CONFIG_READ_PERM) public void getSystemInformation(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - log.warn("Request to deprecated solr.api.Endpoint: /node/system. Please use /node/info/system"); + log.warn("Request to deprecated endpoint: /node/system. Please use /node/info/system"); handler.handleRequestBody(req, rsp); } } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java b/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java new file mode 100644 index 00000000000..451b2de27a7 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.handler.admin; + +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.util.Arrays; + +import org.apache.lucene.util.Version; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.NodeSystemInfoResponse; +import org.apache.solr.client.api.util.SolrVersion; +import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.util.SimpleOrderedMap; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.junit.Assert; +import org.junit.BeforeClass; + +public class NodeSystemInfoProviderTest extends SolrTestCaseJ4 { + + @BeforeClass + public static void beforeClass() throws Exception { + initCore("solrconfig-minimal.xml", "schema.xml"); + } + + public void testMagickGetter() { + OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); + + // make one directly + SimpleOrderedMap info = new SimpleOrderedMap<>(); + info.add("name", os.getName()); + info.add("version", os.getVersion()); + info.add("arch", os.getArch()); + + // make another using MetricUtils.addMXBeanMetrics() + SimpleOrderedMap info2 = new SimpleOrderedMap<>(); + NodeSystemInfoProvider.forEachGetterValue(os, OperatingSystemMXBean.class, info2::add); + + // make sure they got the same thing + for (String p : Arrays.asList("name", "version", "arch")) { + assertEquals(info.get(p), info2.get(p)); + } + } + + public void testGetNodeSystemInfo() { + SolrQueryRequest req = new SolrQueryRequestBase(h.getCore(), new ModifiableSolrParams()) {}; + NodeSystemInfoProvider provider = new NodeSystemInfoProvider(req); + NodeSystemInfoResponse response = new NodeSystemInfoResponse(); + provider.getNodeSystemInfo(response); + + Assert.assertNotNull(response.nodesInfo); + Assert.assertEquals(1, response.nodesInfo.size()); + + NodeSystemInfoResponse.NodeSystemInfo info = response.nodesInfo.values().stream().findFirst().get(); + // these can be validated + Assert.assertEquals(h.getCoreContainer().getSolrHome().toString(), info.solrHome); + Assert.assertEquals(h.getCoreContainer().getCoreRootDirectory().toString(), info.coreRoot); + Assert.assertNotNull(info.core); + Assert.assertNotNull(info.core.directory); + Assert.assertEquals(h.getCore().getInstancePath().toString(), info.core.directory.instance); + Assert.assertNotNull(info.lucene); + Assert.assertNotNull(info.lucene.solrImplVersion); + Assert.assertEquals(info.lucene.solrImplVersion, SolrVersion.LATEST.getPrereleaseVersion()); + Assert.assertNotNull(info.lucene.solrSpecVersion); + Assert.assertEquals(info.lucene.solrSpecVersion, SolrVersion.LATEST_STRING); + Assert.assertNotNull(info.lucene.luceneImplVersion); + Assert.assertEquals(info.lucene.luceneImplVersion, Version.getPackageImplementationVersion()); + Assert.assertNotNull(info.lucene.luceneSpecVersion); + Assert.assertEquals(info.lucene.luceneSpecVersion, Version.LATEST.toString()); + // these should be set + Assert.assertNotNull(info.mode); + Assert.assertNotNull(info.jvm); + Assert.assertNotNull(info.security); + Assert.assertNotNull(info.system); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/SystemInfoHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/SystemInfoHandlerTest.java deleted file mode 100644 index 363de728439..00000000000 --- a/solr/core/src/test/org/apache/solr/handler/admin/SystemInfoHandlerTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.solr.handler.admin; - -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; -import java.util.Arrays; -import org.apache.solr.SolrTestCase; -import org.apache.solr.common.util.SimpleOrderedMap; - -public class SystemInfoHandlerTest extends SolrTestCase { - - public void testMagickGetter() { - - OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); - - // make one directly - SimpleOrderedMap info = new SimpleOrderedMap<>(); - info.add("name", os.getName()); - info.add("version", os.getVersion()); - info.add("arch", os.getArch()); - - // make another using MetricUtils.addMXBeanMetrics() - SimpleOrderedMap info2 = new SimpleOrderedMap<>(); - SystemInfoHandler.forEachGetterValue(os, OperatingSystemMXBean.class, info2::add); - - // make sure they got the same thing - for (String p : Arrays.asList("name", "version", "arch")) { - assertEquals(info.get(p), info2.get(p)); - } - } -} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java new file mode 100644 index 00000000000..2ee8e75e8df --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java @@ -0,0 +1,97 @@ +/* + * 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 java.io.InputStream; +import java.lang.invoke.MethodHandles; +import java.net.URL; +import java.util.Map; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.solr.client.api.model.NodeSystemInfoResponse; +import org.apache.solr.client.solrj.request.CollectionAdminRequest; +import org.apache.solr.client.solrj.response.InputStreamResponseParser; +import org.apache.solr.cloud.SolrCloudTestCase; +import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.util.NamedList; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Ignore; + +/** Test {@link GetNodeSystemInfo}. */ +public class GetNodeSystemInfoTest extends SolrCloudTestCase { + + @BeforeClass + public static void beforeClass() throws Exception { + initCore("solrconfig-minimal.xml", "schema-minimal.xml"); + } + + public void testGetNodeInfo() throws Exception { + SolrQueryRequest req = new SolrQueryRequestBase(h.getCore(), new ModifiableSolrParams()) {}; + SolrQueryResponse resp = new SolrQueryResponse(); + + GetNodeSystemInfo getter = new GetNodeSystemInfo(req, resp); + + NodeSystemInfoResponse response = getter.getNodeSystemInfo(); + Assert.assertNotNull(response.nodesInfo); + Assert.assertEquals(1, response.nodesInfo.size()); + + NodeSystemInfoResponse.NodeSystemInfo info = response.nodesInfo.values().stream().findFirst().orElseThrow(); + Assert.assertTrue(info.coreRoot != null); + Assert.assertEquals(h.getCoreContainer().getCoreRootDirectory().toString(), info.coreRoot); + // other validations in NodeSystemInfoProviderTest + } + + + @Ignore + public void testGetAllNodesInfo() throws Exception { + // SystemInfoRequestTest ?? SolrJ + // beforeClass + configureCluster(2).addConfig("conf", configset("cloud-minimal")).configure(); + CollectionAdminRequest.createCollection(DEFAULT_TEST_COLLECTION_NAME, "conf", 2, 1) + .process(cluster.getSolrClient()); + URL baseUrl = cluster.getJettySolrRunner(0).getBaseURLV2(); + // test + HttpGet get = new HttpGet(baseUrl.toString()+ "/node/info/system"); + try(CloseableHttpClient client = HttpClientBuilder.create().build(); + CloseableHttpResponse response = client.execute(get)) { + try(InputStream in = response.getEntity().getContent()){ + NamedList nl = InputStreamResponseParser.createInputStreamNamedList(response.getStatusLine().getStatusCode(), in); + } + } + + SolrQueryRequest req = new SolrQueryRequestBase(h.getCore(), new ModifiableSolrParams(Map.of("nodes", new String[] {"all"}))) {}; + SolrQueryResponse resp = new SolrQueryResponse(); + GetNodeSystemInfo getter = new GetNodeSystemInfo(req, resp); + + NodeSystemInfoResponse response = getter.getNodeSystemInfo(); + Assert.assertNotNull(response.nodesInfo); + Assert.assertEquals(2, response.nodesInfo.size()); + + response.nodesInfo.entrySet().forEach(e -> { + String key = e.getKey(); + NodeSystemInfoResponse.NodeSystemInfo info = e.getValue(); + Assert.assertEquals(key, info.node); + }); + } +} diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java index 345c0650256..1c0e05e7967 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java @@ -18,6 +18,7 @@ import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.response.SystemInfoResponse; +import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; @@ -38,7 +39,7 @@ public SystemInfoRequest() { /** * @param path the HTTP path to use for this request. Supports V1 "/admin/info/system" (default) - * or V2 "/node/system" + * or V2 "/node/info/system" */ public SystemInfoRequest(String path) { this(path, new ModifiableSolrParams()); @@ -53,11 +54,14 @@ public SystemInfoRequest(SolrParams params) { /** * @param path the HTTP path to use for this request. Supports V1 "/admin/info/system" (default) - * or V2 "/node/system" + * or V2 "/node/info/system" * @param params query parameter names and values for making this request. */ public SystemInfoRequest(String path, SolrParams params) { super(METHOD.GET, path, SolrRequestType.ADMIN); + if (!path.endsWith("/system")) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unsupported request path: " + path); + } this.params = params; } @@ -77,7 +81,7 @@ public ApiVersion getApiVersion() { // (/solr) /admin/info/system return ApiVersion.V1; } - // Ref. org.apache.solr.handler.admin.api.NodeSystemInfoAPI : /node/system + // Ref. org.apache.solr.client.api.endpoint.NodeSystemInfoApi : /node/info/system return ApiVersion.V2; } } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java index a34981dc16b..b6b867d7080 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java @@ -19,18 +19,17 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; -import org.apache.solr.client.api.model.NodeSystemResponse; +import org.apache.solr.client.api.model.NodeSystemInfoResponse; import org.apache.solr.client.solrj.request.json.JacksonContentWriter; import org.apache.solr.common.util.NamedList; -/** This class holds the response from V1 "/admin/info/system" or V2 "/node/system" */ +/** This class holds the response from V1 "/admin/info/system" or V2 "/node/info/system" */ public class SystemInfoResponse extends SolrResponseBase { private static final long serialVersionUID = 1L; - private final Map nodesInfo = new HashMap<>(); + private final Map nodesInfo = new HashMap<>(); public SystemInfoResponse(NamedList namedList) { if (namedList == null) throw new IllegalArgumentException("Null NamedList is not allowed."); @@ -45,33 +44,37 @@ public void setResponse(NamedList response) { assert response.equals(getResponse()); return; } - - if (getResponse().get("node") == null) { - // multi-nodes response, NamedList of "host:port_solr"->NodeSystemResponse - for (Entry node : response) { - if (node.getKey().endsWith("_solr")) { - nodesInfo.put( - node.getKey(), - JacksonContentWriter.DEFAULT_MAPPER.convertValue( - node.getValue(), NodeSystemResponse.class)); - } - } - - // If no node was found, that's very likely Solr runs in standalone mode. - // Add a single node info instance with null key (no node name is available). - if (nodesInfo.isEmpty()) { - nodesInfo.put( - null, - JacksonContentWriter.DEFAULT_MAPPER.convertValue(response, NodeSystemResponse.class)); - } - - } else { - // single-node response - nodesInfo.put( - response.get("node").toString(), - JacksonContentWriter.DEFAULT_MAPPER.convertValue( - getResponse(), NodeSystemResponse.class)); - } + + NodeSystemInfoResponse fullResp = JacksonContentWriter.DEFAULT_MAPPER.convertValue( + getResponse(), NodeSystemInfoResponse.class); + nodesInfo.putAll(fullResp.nodesInfo); + +// if (getResponse().get("node") == null) { +// // multi-nodes response, NamedList of "host:port_solr"->NodeSystemResponse +// for (Entry node : response) { +// if (node.getKey().endsWith("_solr")) { +// nodesInfo.put( +// node.getKey(), +// JacksonContentWriter.DEFAULT_MAPPER.convertValue( +// node.getValue(), NodeSystemInfoResponse.NodeSystemInfo.class)); +// } +// } +// +// // If no node was found, that's very likely Solr runs in standalone mode. +// // Add a single node info instance with null key (no node name is available). +// if (nodesInfo.isEmpty()) { +// nodesInfo.put( +// null, +// JacksonContentWriter.DEFAULT_MAPPER.convertValue(response, NodeSystemInfoResponse.NodeSystemInfo.class)); +// } +// +// } else { +// // single-node response +// nodesInfo.put( +// response.get("node").toString(), +// JacksonContentWriter.DEFAULT_MAPPER.convertValue( +// getResponse(), NodeSystemInfoResponse.NodeSystemInfo.class)); +// } } /** Get the mode from a single node system info */ @@ -195,8 +198,8 @@ public String getNodeForCoreRoot(String coreRoot) { .get(); } - /** Get the {@code NodeSystemResponse} for a single node */ - public NodeSystemResponse getNodeResponse() { + /** Get the {@code NodeSystemResponse.NodeSystemInfo} for a single node */ + public NodeSystemInfoResponse.NodeSystemInfo getNodeResponse() { if (nodesInfo.size() == 1) { return nodesInfo.values().stream().findFirst().get(); } else { @@ -205,13 +208,13 @@ public NodeSystemResponse getNodeResponse() { } } - /** Get all {@code NodeSystemResponse}s */ - public Map getAllNodeResponses() { + /** Get all {@code NodeSystemResponse.NodeSystemInfo}s */ + public Map getAllNodeResponses() { return nodesInfo; } - /** Get the {@code NodeSystemResponse} for the given node name */ - public NodeSystemResponse getNodeResponseForNode(String node) { + /** Get the {@code NodeSystemResponse.NodeSystemInfo} for the given node name */ + public NodeSystemInfoResponse.NodeSystemInfo getNodeResponseForNode(String node) { return nodesInfo.get(node); } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/json/JacksonDataBindResponseParser.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/json/JacksonDataBindResponseParser.java index 58f60c50773..ddfb0cf4ab8 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/json/JacksonDataBindResponseParser.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/json/JacksonDataBindResponseParser.java @@ -53,6 +53,7 @@ public Collection getContentTypes() { // TODO it'd be nice if the ResponseParser could receive the mime type so it can parse // accordingly, maybe json, cbor, smile + /** Parse the Json {@code stream} to the expected Java type, then, converts to a {@link NamedList}. */ @Override public NamedList processResponse(InputStream stream, String encoding) throws IOException { // TODO SOLR-17549 for error handling, implying a significant ResponseParser API change @@ -65,7 +66,32 @@ public NamedList processResponse(InputStream stream, String encoding) th } else { parsedVal = mapper.readValue(new InputStreamReader(stream, encoding), typeParam); } - + return SimpleOrderedMap.of("response", parsedVal); } + + /** Parse the Json {@code stream} to the expected Java type. */ + public T processToType(InputStream stream, String encoding) throws IOException { + // TODO SOLR-17549 for error handling, implying a significant ResponseParser API change + // TODO generalize to CBOR, Smile, ... + var mapper = JacksonContentWriter.DEFAULT_MAPPER; + + T parsedVal; + try { + if (encoding == null) { + parsedVal = mapper.readValue(stream, typeParam); + } else { + parsedVal = mapper.readValue(new InputStreamReader(stream, encoding), typeParam); + } + } catch (Exception e) { + + if (encoding == null) { + parsedVal = mapper.convertValue(stream, typeParam); + } else { + parsedVal = mapper.convertValue(new InputStreamReader(stream, encoding), typeParam); + } + } + + return parsedVal; + } } diff --git a/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java b/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java index b26360864c5..1878bf4f456 100644 --- a/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java +++ b/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java @@ -205,7 +205,7 @@ public interface CommonParams { String ZK_STATUS_PATH = "/admin/zookeeper/status"; String SYSTEM_INFO_PATH = "/admin/info/system"; String METRICS_PATH = "/admin/metrics"; - String V2_SYSTEM_INFO_PATH = "/node/system"; + String V2_SYSTEM_INFO_PATH = "/node/info/system"; String STATUS = "status"; From 9654a32f57fe0fb39721802d708dce8757b400a6 Mon Sep 17 00:00:00 2001 From: Isabelle Giguere Date: Tue, 20 Jan 2026 08:20:08 -0500 Subject: [PATCH 3/9] SOLR-16397: V2 Node systme info - clean up Clean-up unused imports --- .../client/api/endpoint/NodeSystemInfoApi.java | 3 --- .../api/model/NodeSystemInfoResponse.java | 18 +++++++----------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java index 14d2d4fe3ef..55b8df3dc1a 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java @@ -19,9 +19,6 @@ import io.swagger.v3.oas.annotations.Operation; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; - import org.apache.solr.client.api.model.NodeSystemInfoResponse; /** V2 API definition to fetch node system info, analogous to the v1 /admin/info/system. */ diff --git a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java index 5b9a01ba568..86468388913 100644 --- a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java +++ b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java @@ -17,40 +17,36 @@ package org.apache.solr.client.api.model; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.solr.client.api.util.ReflectWritable; - /** Response from /node/info/system */ public class NodeSystemInfoResponse extends SolrJerseyResponse { @JsonProperty public Map nodesInfo; - + public static class NodeSystemInfo { @JsonProperty public String node; @JsonProperty public String mode; @JsonProperty public String zkHost; - + @JsonProperty("solr_home") public String solrHome; - + @JsonProperty("core_root") public String coreRoot; - + @JsonProperty public String environment; - + @JsonProperty(value = "environment_label") public String environmentLabel; - + @JsonProperty(value = "environment_color") public String environmentColor; - + @JsonProperty public Core core; @JsonProperty public Lucene lucene; @JsonProperty public JVM jvm; From fdfab36789effa37df7313aeba2ec7372362023c Mon Sep 17 00:00:00 2001 From: Isabelle Giguere Date: Tue, 20 Jan 2026 08:21:52 -0500 Subject: [PATCH 4/9] SOLR-16397: files changed by tidy forgot those in the previous commit --- .../handler/admin/NodeSystemInfoProvider.java | 36 +++++------ .../solr/handler/admin/SystemInfoHandler.java | 46 ++------------ .../handler/admin/api/GetNodeSystemInfo.java | 13 ++-- .../admin/NodeSystemInfoProviderTest.java | 12 ++-- .../admin/api/GetNodeSystemInfoTest.java | 52 +++++++++------- .../solrj/request/SystemInfoRequest.java | 3 +- .../solrj/response/SystemInfoResponse.java | 60 ++++++++++--------- .../json/JacksonDataBindResponseParser.java | 10 ++-- 8 files changed, 103 insertions(+), 129 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java index 30b1324c0b8..1c7c98267ed 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java @@ -103,33 +103,33 @@ public class NodeSystemInfoProvider { * BeanInfo instance for future calls. */ private static final ConcurrentMap, BeanInfo> beanInfos = new ConcurrentHashMap<>(); - + public NodeSystemInfoProvider(SolrQueryRequest request) { req = request; params = request.getParams(); cc = request.getCoreContainer(); initHostname(); } - + public void getNodeSystemInfo(NodeSystemInfoResponse response) { Map nodes = new HashMap<>(); - NodeSystemInfo info = getNodeInfo(); + NodeSystemInfo info = getNodeInfo(); String key = info.node != null ? info.node : hostname; // should allow null key ? nodes.put(key, info); response.nodesInfo = nodes; } - private NodeSystemInfoResponse.NodeSystemInfo getNodeInfo() { + private NodeSystemInfoResponse.NodeSystemInfo getNodeInfo() { NodeSystemInfo info = new NodeSystemInfo(); SolrCore core = req.getCore(); if (core != null) info.core = getCoreInfo(core, req.getSchema()); - + if (cc != null) { info.solrHome = cc.getSolrHome().toString(); info.coreRoot = cc.getCoreRootDirectory().toString(); } - + boolean solrCloudMode = cc != null && cc.isZooKeeperAware(); info.mode = solrCloudMode ? "solrcloud" : "std"; if (solrCloudMode) { @@ -138,10 +138,10 @@ private NodeSystemInfoResponse.NodeSystemInfo getNodeInfo() { } info.lucene = getLuceneInfo(); - - NodeConfig nodeConfig = cc != null ? cc.getNodeConfig() : null; + + NodeConfig nodeConfig = cc != null ? cc.getNodeConfig() : null; info.jvm = getJvmInfo(nodeConfig); - + info.security = getSecurityInfo(req); info.system = getSystemInfo(); info.gpu = getGpuInfo(); @@ -158,7 +158,7 @@ private NodeSystemInfoResponse.NodeSystemInfo getNodeInfo() { info.environmentColor = env.getColor(); } } - + return info; } @@ -322,7 +322,7 @@ private NodeSystemInfoResponse.Security getSecurityInfo(SolrQueryRequest req) { info.username = req.getUserPrincipal().getName(); // Mapped roles for this principal - //@SuppressWarnings("resource") + // @SuppressWarnings("resource") AuthorizationPlugin auth = cc == null ? null : cc.getAuthorizationPlugin(); if (auth instanceof RuleBasedAuthorizationPluginBase rbap) { Set roles = rbap.getUserRoles(req.getUserPrincipal()); @@ -353,8 +353,9 @@ private NodeSystemInfoResponse.Lucene getLuceneInfo() { String specVersion = p.getSpecificationVersion(); String implVersion = p.getImplementationVersion(); // non-null mostly for testing - info.solrSpecVersion = specVersion== null ? SolrVersion.LATEST_STRING : specVersion; - info.solrImplVersion = implVersion == null ? SolrVersion.LATEST.getPrereleaseVersion() : implVersion; + info.solrSpecVersion = specVersion == null ? SolrVersion.LATEST_STRING : specVersion; + info.solrImplVersion = + implVersion == null ? SolrVersion.LATEST.getPrereleaseVersion() : implVersion; info.luceneSpecVersion = Version.LATEST.toString(); info.luceneImplVersion = Version.getPackageImplementationVersion(); @@ -425,7 +426,8 @@ private List getInputArgumentsRedacted(NodeConfig nodeConfig, RuntimeMXB for (String arg : mx.getInputArguments()) { if (arg.startsWith("-D") && arg.contains("=") - && (nodeConfig != null && nodeConfig.isSysPropHidden(arg.substring(2, arg.indexOf('='))))) { + && (nodeConfig != null + && nodeConfig.isSysPropHidden(arg.substring(2, arg.indexOf('='))))) { list.add( String.format( Locale.ROOT, @@ -522,17 +524,17 @@ private void initHostname() { RTimer timer = new RTimer(); try { InetAddress addr = InetAddress.getLocalHost(); - hostname = addr.getCanonicalHostName(); + hostname = addr.getCanonicalHostName(); } catch (Exception e) { log.warn( "Unable to resolve canonical hostname for local host, possible DNS misconfiguration. Set the '{}' sysprop to false on startup to prevent future lookups if DNS can not be fixed.", REVERSE_DNS_OF_LOCALHOST_SYSPROP, e); hostname = null; - return ; + return; } finally { timer.stop(); - + if (15000D < timer.getTime()) { String readableTime = String.format(Locale.ROOT, "%.3f", (timer.getTime() / 1000)); log.warn( 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 3c8d5983a7b..c6daa01b3a3 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,59 +16,21 @@ */ package org.apache.solr.handler.admin; -import static org.apache.solr.common.params.CommonParams.NAME; - -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.io.IOException; import java.lang.invoke.MethodHandles; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; -import java.lang.management.PlatformManagedObject; -import java.lang.management.RuntimeMXBean; -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.nio.file.Path; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.ArrayList; import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Locale; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.apache.lucene.util.Version; import org.apache.solr.api.AnnotatedApi; import org.apache.solr.api.Api; import org.apache.solr.api.JerseyResource; import org.apache.solr.client.api.model.NodeSystemInfoResponse; -import org.apache.solr.common.cloud.ZkStateReader; -import org.apache.solr.common.util.EnvUtils; -import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.core.CoreContainer; -import org.apache.solr.core.NodeConfig; -import org.apache.solr.core.SolrCore; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.admin.api.GetNodeSystemInfo; import org.apache.solr.handler.admin.api.NodeSystemInfoAPI; import org.apache.solr.handler.api.V2ApiUtils; -import org.apache.solr.metrics.GpuMetricsProvider; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; -import org.apache.solr.schema.IndexSchema; import org.apache.solr.security.AuthorizationContext; -import org.apache.solr.security.AuthorizationPlugin; -import org.apache.solr.security.PKIAuthenticationPlugin; -import org.apache.solr.security.RuleBasedAuthorizationPluginBase; -import org.apache.solr.util.RTimer; -import org.apache.solr.util.stats.MetricUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -98,11 +60,11 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw if (AdminHandlersProxy.maybeProxyToNodes(req, rsp, getCoreContainer(req))) { return; // Request was proxied to other node } - + GetNodeSystemInfo getter = new GetNodeSystemInfo(req, rsp); NodeSystemInfoResponse response = getter.getNodeSystemInfo(); V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response); - + return; } @@ -110,7 +72,7 @@ private CoreContainer getCoreContainer(SolrQueryRequest req) { CoreContainer coreContainer = req.getCoreContainer(); return coreContainer == null ? cc : coreContainer; } - + //////////////////////// SolrInfoMBeans methods ////////////////////// @Override @@ -127,7 +89,7 @@ public Category getCategory() { public Collection getApis() { return AnnotatedApi.getApis(new NodeSystemInfoAPI(this)); } - + @Override public Collection> getJerseyResources() { return Set.of(GetNodeSystemInfo.class); diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java index 10213ae0e54..5762ccb6646 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java @@ -21,12 +21,8 @@ import org.apache.solr.api.JerseyResource; import org.apache.solr.client.api.endpoint.NodeSystemInfoApi; import org.apache.solr.client.api.model.NodeSystemInfoResponse; -import org.apache.solr.client.api.model.SolrJerseyResponse; -import org.apache.solr.client.api.model.SolrJerseyResponse.ResponseHeader; -import org.apache.solr.core.CoreContainer; import org.apache.solr.handler.admin.AdminHandlersProxy; import org.apache.solr.handler.admin.NodeSystemInfoProvider; -import org.apache.solr.handler.api.V2ApiUtils; import org.apache.solr.jersey.PermissionName; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; @@ -55,7 +51,7 @@ public NodeSystemInfoResponse getNodeSystemInfo() { // TODO? nodes=all is ignored try { if (AdminHandlersProxy.maybeProxyToNodes( - solrQueryRequest, solrQueryResponse, solrQueryRequest.getCoreContainer())) { + solrQueryRequest, solrQueryResponse, solrQueryRequest.getCoreContainer())) { return null; } } catch (Exception e) { @@ -66,8 +62,11 @@ public NodeSystemInfoResponse getNodeSystemInfo() { NodeSystemInfoResponse response = instantiateJerseyResponse(NodeSystemInfoResponse.class); provider.getNodeSystemInfo(response); log.info("Found {} nodes.", response == null ? "NO" : response.nodesInfo.size()); - if(response != null) { - response.nodesInfo.entrySet().forEach(e -> log.info("Node {}, core root: {}", e.getKey(), e.getValue().coreRoot)); + if (response != null) { + response + .nodesInfo + .entrySet() + .forEach(e -> log.info("Node {}, core root: {}", e.getKey(), e.getValue().coreRoot)); } return response; } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java b/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java index 451b2de27a7..1fb024319d8 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java @@ -19,7 +19,6 @@ import java.lang.management.ManagementFactory; import java.lang.management.OperatingSystemMXBean; import java.util.Arrays; - import org.apache.lucene.util.Version; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.client.api.model.NodeSystemInfoResponse; @@ -32,7 +31,7 @@ import org.junit.BeforeClass; public class NodeSystemInfoProviderTest extends SolrTestCaseJ4 { - + @BeforeClass public static void beforeClass() throws Exception { initCore("solrconfig-minimal.xml", "schema.xml"); @@ -56,17 +55,18 @@ public void testMagickGetter() { assertEquals(info.get(p), info2.get(p)); } } - + public void testGetNodeSystemInfo() { SolrQueryRequest req = new SolrQueryRequestBase(h.getCore(), new ModifiableSolrParams()) {}; NodeSystemInfoProvider provider = new NodeSystemInfoProvider(req); NodeSystemInfoResponse response = new NodeSystemInfoResponse(); provider.getNodeSystemInfo(response); - + Assert.assertNotNull(response.nodesInfo); Assert.assertEquals(1, response.nodesInfo.size()); - - NodeSystemInfoResponse.NodeSystemInfo info = response.nodesInfo.values().stream().findFirst().get(); + + NodeSystemInfoResponse.NodeSystemInfo info = + response.nodesInfo.values().stream().findFirst().get(); // these can be validated Assert.assertEquals(h.getCoreContainer().getSolrHome().toString(), info.solrHome); Assert.assertEquals(h.getCoreContainer().getCoreRootDirectory().toString(), info.coreRoot); diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java index 2ee8e75e8df..57884a3d5da 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java @@ -17,10 +17,8 @@ package org.apache.solr.handler.admin.api; import java.io.InputStream; -import java.lang.invoke.MethodHandles; import java.net.URL; import java.util.Map; - import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; @@ -40,29 +38,29 @@ /** Test {@link GetNodeSystemInfo}. */ public class GetNodeSystemInfoTest extends SolrCloudTestCase { - + @BeforeClass public static void beforeClass() throws Exception { initCore("solrconfig-minimal.xml", "schema-minimal.xml"); } - + public void testGetNodeInfo() throws Exception { SolrQueryRequest req = new SolrQueryRequestBase(h.getCore(), new ModifiableSolrParams()) {}; SolrQueryResponse resp = new SolrQueryResponse(); - + GetNodeSystemInfo getter = new GetNodeSystemInfo(req, resp); - + NodeSystemInfoResponse response = getter.getNodeSystemInfo(); Assert.assertNotNull(response.nodesInfo); Assert.assertEquals(1, response.nodesInfo.size()); - - NodeSystemInfoResponse.NodeSystemInfo info = response.nodesInfo.values().stream().findFirst().orElseThrow(); + + NodeSystemInfoResponse.NodeSystemInfo info = + response.nodesInfo.values().stream().findFirst().orElseThrow(); Assert.assertTrue(info.coreRoot != null); Assert.assertEquals(h.getCoreContainer().getCoreRootDirectory().toString(), info.coreRoot); // other validations in NodeSystemInfoProviderTest } - - + @Ignore public void testGetAllNodesInfo() throws Exception { // SystemInfoRequestTest ?? SolrJ @@ -72,26 +70,34 @@ public void testGetAllNodesInfo() throws Exception { .process(cluster.getSolrClient()); URL baseUrl = cluster.getJettySolrRunner(0).getBaseURLV2(); // test - HttpGet get = new HttpGet(baseUrl.toString()+ "/node/info/system"); - try(CloseableHttpClient client = HttpClientBuilder.create().build(); + HttpGet get = new HttpGet(baseUrl.toString() + "/node/info/system"); + try (CloseableHttpClient client = HttpClientBuilder.create().build(); CloseableHttpResponse response = client.execute(get)) { - try(InputStream in = response.getEntity().getContent()){ - NamedList nl = InputStreamResponseParser.createInputStreamNamedList(response.getStatusLine().getStatusCode(), in); + try (InputStream in = response.getEntity().getContent()) { + NamedList nl = + InputStreamResponseParser.createInputStreamNamedList( + response.getStatusLine().getStatusCode(), in); } } - - SolrQueryRequest req = new SolrQueryRequestBase(h.getCore(), new ModifiableSolrParams(Map.of("nodes", new String[] {"all"}))) {}; + + SolrQueryRequest req = + new SolrQueryRequestBase( + h.getCore(), new ModifiableSolrParams(Map.of("nodes", new String[] {"all"}))) {}; SolrQueryResponse resp = new SolrQueryResponse(); GetNodeSystemInfo getter = new GetNodeSystemInfo(req, resp); - + NodeSystemInfoResponse response = getter.getNodeSystemInfo(); Assert.assertNotNull(response.nodesInfo); Assert.assertEquals(2, response.nodesInfo.size()); - - response.nodesInfo.entrySet().forEach(e -> { - String key = e.getKey(); - NodeSystemInfoResponse.NodeSystemInfo info = e.getValue(); - Assert.assertEquals(key, info.node); - }); + + response + .nodesInfo + .entrySet() + .forEach( + e -> { + String key = e.getKey(); + NodeSystemInfoResponse.NodeSystemInfo info = e.getValue(); + Assert.assertEquals(key, info.node); + }); } } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java index 1c0e05e7967..8367d5cfc32 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java @@ -60,7 +60,8 @@ public SystemInfoRequest(SolrParams params) { public SystemInfoRequest(String path, SolrParams params) { super(METHOD.GET, path, SolrRequestType.ADMIN); if (!path.endsWith("/system")) { - throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unsupported request path: " + path); + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, "Unsupported request path: " + path); } this.params = params; } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java index b6b867d7080..ed3182863b7 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java @@ -44,37 +44,39 @@ public void setResponse(NamedList response) { assert response.equals(getResponse()); return; } - - NodeSystemInfoResponse fullResp = JacksonContentWriter.DEFAULT_MAPPER.convertValue( - getResponse(), NodeSystemInfoResponse.class); + + NodeSystemInfoResponse fullResp = + JacksonContentWriter.DEFAULT_MAPPER.convertValue( + getResponse(), NodeSystemInfoResponse.class); nodesInfo.putAll(fullResp.nodesInfo); -// if (getResponse().get("node") == null) { -// // multi-nodes response, NamedList of "host:port_solr"->NodeSystemResponse -// for (Entry node : response) { -// if (node.getKey().endsWith("_solr")) { -// nodesInfo.put( -// node.getKey(), -// JacksonContentWriter.DEFAULT_MAPPER.convertValue( -// node.getValue(), NodeSystemInfoResponse.NodeSystemInfo.class)); -// } -// } -// -// // If no node was found, that's very likely Solr runs in standalone mode. -// // Add a single node info instance with null key (no node name is available). -// if (nodesInfo.isEmpty()) { -// nodesInfo.put( -// null, -// JacksonContentWriter.DEFAULT_MAPPER.convertValue(response, NodeSystemInfoResponse.NodeSystemInfo.class)); -// } -// -// } else { -// // single-node response -// nodesInfo.put( -// response.get("node").toString(), -// JacksonContentWriter.DEFAULT_MAPPER.convertValue( -// getResponse(), NodeSystemInfoResponse.NodeSystemInfo.class)); -// } + // if (getResponse().get("node") == null) { + // // multi-nodes response, NamedList of "host:port_solr"->NodeSystemResponse + // for (Entry node : response) { + // if (node.getKey().endsWith("_solr")) { + // nodesInfo.put( + // node.getKey(), + // JacksonContentWriter.DEFAULT_MAPPER.convertValue( + // node.getValue(), NodeSystemInfoResponse.NodeSystemInfo.class)); + // } + // } + // + // // If no node was found, that's very likely Solr runs in standalone mode. + // // Add a single node info instance with null key (no node name is available). + // if (nodesInfo.isEmpty()) { + // nodesInfo.put( + // null, + // JacksonContentWriter.DEFAULT_MAPPER.convertValue(response, + // NodeSystemInfoResponse.NodeSystemInfo.class)); + // } + // + // } else { + // // single-node response + // nodesInfo.put( + // response.get("node").toString(), + // JacksonContentWriter.DEFAULT_MAPPER.convertValue( + // getResponse(), NodeSystemInfoResponse.NodeSystemInfo.class)); + // } } /** Get the mode from a single node system info */ diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/json/JacksonDataBindResponseParser.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/json/JacksonDataBindResponseParser.java index ddfb0cf4ab8..714a66c6a57 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/json/JacksonDataBindResponseParser.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/json/JacksonDataBindResponseParser.java @@ -53,7 +53,9 @@ public Collection getContentTypes() { // TODO it'd be nice if the ResponseParser could receive the mime type so it can parse // accordingly, maybe json, cbor, smile - /** Parse the Json {@code stream} to the expected Java type, then, converts to a {@link NamedList}. */ + /** + * Parse the Json {@code stream} to the expected Java type, then, converts to a {@link NamedList}. + */ @Override public NamedList processResponse(InputStream stream, String encoding) throws IOException { // TODO SOLR-17549 for error handling, implying a significant ResponseParser API change @@ -66,10 +68,10 @@ public NamedList processResponse(InputStream stream, String encoding) th } else { parsedVal = mapper.readValue(new InputStreamReader(stream, encoding), typeParam); } - + return SimpleOrderedMap.of("response", parsedVal); } - + /** Parse the Json {@code stream} to the expected Java type. */ public T processToType(InputStream stream, String encoding) throws IOException { // TODO SOLR-17549 for error handling, implying a significant ResponseParser API change @@ -91,7 +93,7 @@ public T processToType(InputStream stream, String encoding) throws IOException { parsedVal = mapper.convertValue(new InputStreamReader(stream, encoding), typeParam); } } - + return parsedVal; } } From b3dd6c7b1c4e73a97c90b431c5c832154bcd77e1 Mon Sep 17 00:00:00 2001 From: Isabelle Giguere Date: Mon, 26 Jan 2026 18:32:53 -0500 Subject: [PATCH 5/9] SOLR-16458: System Info V2 API Fix log calls. Add unit tests for HTTP call. --- .../handler/admin/api/GetNodeSystemInfo.java | 21 +- .../admin/api/GetNodeSystemInfoTest.java | 214 +++++++++++++----- 2 files changed, 178 insertions(+), 57 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java index 5762ccb6646..4698ff36d76 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java @@ -48,10 +48,11 @@ public GetNodeSystemInfo(SolrQueryRequest solrQueryRequest, SolrQueryResponse so public NodeSystemInfoResponse getNodeSystemInfo() { solrQueryResponse.setHttpCaching(false); - // TODO? nodes=all is ignored + // TODO: AdminHandlersProxy does not support V2 try { - if (AdminHandlersProxy.maybeProxyToNodes( - solrQueryRequest, solrQueryResponse, solrQueryRequest.getCoreContainer())) { + if (solrQueryRequest.getCoreContainer() != null + && AdminHandlersProxy.maybeProxyToNodes( + solrQueryRequest, solrQueryResponse, solrQueryRequest.getCoreContainer())) { return null; } } catch (Exception e) { @@ -61,12 +62,20 @@ public NodeSystemInfoResponse getNodeSystemInfo() { NodeSystemInfoProvider provider = new NodeSystemInfoProvider(solrQueryRequest); NodeSystemInfoResponse response = instantiateJerseyResponse(NodeSystemInfoResponse.class); provider.getNodeSystemInfo(response); - log.info("Found {} nodes.", response == null ? "NO" : response.nodesInfo.size()); - if (response != null) { + if (log.isDebugEnabled()) { + log.debug("Found {} nodes.", response == null ? "NO" : response.nodesInfo.size()); + } + if (response != null && log.isTraceEnabled()) { response .nodesInfo .entrySet() - .forEach(e -> log.info("Node {}, core root: {}", e.getKey(), e.getValue().coreRoot)); + .forEach( + e -> { + // yep, need to validate the log settings again. + if (log.isTraceEnabled()) { + log.trace("Node {}, core root: {}", e.getKey(), e.getValue().coreRoot); + } + }); } return response; } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java index 57884a3d5da..f842b8dcb95 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java @@ -16,88 +16,200 @@ */ package org.apache.solr.handler.admin.api; +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URL; -import java.util.Map; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; import org.apache.solr.client.api.model.NodeSystemInfoResponse; -import org.apache.solr.client.solrj.request.CollectionAdminRequest; -import org.apache.solr.client.solrj.response.InputStreamResponseParser; +import org.apache.solr.client.api.model.NodeSystemInfoResponse.NodeSystemInfo; +import org.apache.solr.client.solrj.response.XMLResponseParser; +import org.apache.solr.client.solrj.response.json.JacksonDataBindResponseParser; +import org.apache.solr.cloud.MiniSolrCloudCluster; import org.apache.solr.cloud.SolrCloudTestCase; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.util.NamedList; +import org.apache.solr.embedded.JettySolrRunner; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrQueryRequestBase; import org.apache.solr.response.SolrQueryResponse; +import org.apache.solr.util.SSLTestConfig; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpFields.Mutable; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.After; +import org.junit.AfterClass; import org.junit.Assert; +import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Ignore; +import org.junit.Test; /** Test {@link GetNodeSystemInfo}. */ public class GetNodeSystemInfoTest extends SolrCloudTestCase { + private static final int NUM_NODES = 2; + + private static final int TIMEOUT = 15000; + + private static MiniSolrCloudCluster cluster; + private static HttpClient jettyHttpClient; + + private static JettySolrRunner jettyRunner; + private static URL baseV2Url; + private static String systemInfoV2Url; + @BeforeClass public static void beforeClass() throws Exception { - initCore("solrconfig-minimal.xml", "schema-minimal.xml"); + cluster = configureCluster(NUM_NODES).configure(); + cluster.waitForAllNodes(TIMEOUT / 1000); + jettyRunner = cluster.getRandomJetty(random()); + baseV2Url = jettyRunner.getBaseURLV2(); + systemInfoV2Url = baseV2Url.toString().concat("/node/info/system"); + + // useSsl = true, clientAuth = false + SSLTestConfig sslConfig = new SSLTestConfig(true, false); + // trustAll = true + SslContextFactory.Client factory = new SslContextFactory.Client(true); + try { + factory.setSslContext(sslConfig.buildClientSSLContext()); + } catch (KeyManagementException + | UnrecoverableKeyException + | NoSuchAlgorithmException + | KeyStoreException e) { + throw new IllegalStateException( + "Unable to setup https scheme for HTTPClient to test SSL.", e); + } + + jettyHttpClient = new HttpClient(); + jettyHttpClient.setConnectTimeout(TIMEOUT); + jettyHttpClient.setSslContextFactory(factory); + jettyHttpClient.setMaxConnectionsPerDestination(1); + jettyHttpClient.setMaxRequestsQueuedPerDestination(1); } - public void testGetNodeInfo() throws Exception { - SolrQueryRequest req = new SolrQueryRequestBase(h.getCore(), new ModifiableSolrParams()) {}; - SolrQueryResponse resp = new SolrQueryResponse(); + @AfterClass + public static void afterClass() throws Exception { + jettyHttpClient.destroy(); + cluster.shutdown(); + } - GetNodeSystemInfo getter = new GetNodeSystemInfo(req, resp); + @Before + public void beforeTest() throws Exception { + // stop and start Jetty client for each test, otherwise, it seems responses get mixed! + jettyHttpClient.start(); + } - NodeSystemInfoResponse response = getter.getNodeSystemInfo(); - Assert.assertNotNull(response.nodesInfo); - Assert.assertEquals(1, response.nodesInfo.size()); + @After + public void afterTest() throws Exception { + jettyHttpClient.stop(); + } - NodeSystemInfoResponse.NodeSystemInfo info = - response.nodesInfo.values().stream().findFirst().orElseThrow(); - Assert.assertTrue(info.coreRoot != null); - Assert.assertEquals(h.getCoreContainer().getCoreRootDirectory().toString(), info.coreRoot); + /** test through HTTP request: default Json */ + @Test + public void testGetNodeInfoHttpJson() throws Exception { + ContentResponse response = null; + try { + response = + jettyHttpClient + .newRequest(systemInfoV2Url) + .timeout(TIMEOUT, TimeUnit.MILLISECONDS) + .method(HttpMethod.GET) + .send(); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Assert.fail("Should not throw exception: " + e.getClass() + ". message: " + e.getMessage()); + return; + } + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals("application/json", response.getMediaType()); + + NodeSystemInfoResponse infoResponse; + JacksonDataBindResponseParser parser = + new JacksonDataBindResponseParser<>(NodeSystemInfoResponse.class); + try (InputStream in = new ByteArrayInputStream(response.getContent())) { + infoResponse = parser.processToType(in, StandardCharsets.UTF_8.toString()); + } + Assert.assertEquals(0, infoResponse.responseHeader.status); + + String expectedNode = baseV2Url.getHost() + ":" + baseV2Url.getPort() + "_solr"; + Assert.assertNotNull(infoResponse.nodesInfo); + Assert.assertNotNull(infoResponse.nodesInfo.get(expectedNode)); + + NodeSystemInfo nodeInfo = infoResponse.nodesInfo.get(expectedNode); + Assert.assertEquals(expectedNode, nodeInfo.node); // other validations in NodeSystemInfoProviderTest } - @Ignore - public void testGetAllNodesInfo() throws Exception { - // SystemInfoRequestTest ?? SolrJ - // beforeClass - configureCluster(2).addConfig("conf", configset("cloud-minimal")).configure(); - CollectionAdminRequest.createCollection(DEFAULT_TEST_COLLECTION_NAME, "conf", 2, 1) - .process(cluster.getSolrClient()); - URL baseUrl = cluster.getJettySolrRunner(0).getBaseURLV2(); - // test - HttpGet get = new HttpGet(baseUrl.toString() + "/node/info/system"); - try (CloseableHttpClient client = HttpClientBuilder.create().build(); - CloseableHttpResponse response = client.execute(get)) { - try (InputStream in = response.getEntity().getContent()) { - NamedList nl = - InputStreamResponseParser.createInputStreamNamedList( - response.getStatusLine().getStatusCode(), in); - } + /** test through HTTP request: Xml */ + @SuppressWarnings("unchecked") + @Test + public void testGetNodeInfoHttpXml() throws Exception { + ContentResponse response = null; + try { + response = + jettyHttpClient + .newRequest(systemInfoV2Url) + .timeout(TIMEOUT, TimeUnit.MILLISECONDS) + .method(HttpMethod.GET) + .headers( + new Consumer() { + + @Override + public void accept(Mutable arg0) { + arg0.add(HttpHeader.ACCEPT, "application/xml"); + } + }) + .send(); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Assert.fail("Should not throw exception: " + e.getClass() + ". message: " + e.getMessage()); + return; + } + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals("application/xml", response.getMediaType()); + + NamedList nlResponse; + XMLResponseParser parser = new XMLResponseParser(); + try (InputStream in = new ByteArrayInputStream(response.getContent())) { + nlResponse = parser.processResponse(in, StandardCharsets.UTF_8.toString()); } - SolrQueryRequest req = - new SolrQueryRequestBase( - h.getCore(), new ModifiableSolrParams(Map.of("nodes", new String[] {"all"}))) {}; + String expectedNode = baseV2Url.getHost() + ":" + baseV2Url.getPort() + "_solr"; + Assert.assertNotNull(nlResponse.get("nodesInfo")); + NamedList nlNodesInfo = (NamedList) nlResponse.get("nodesInfo"); + Assert.assertNotNull(nlNodesInfo.get(expectedNode)); + NamedList nodeInfo = (NamedList) nlNodesInfo.get(expectedNode); + Assert.assertEquals(expectedNode, (String) nodeInfo.get("node")); + // other validations in NodeSystemInfoProviderTest + } + + /** test GetNodeSystemInfo directly */ + @Test + public void testGetNodeInfo() throws Exception { + // Getting System info should not require a SolrCore: null + SolrQueryRequest req = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; SolrQueryResponse resp = new SolrQueryResponse(); + GetNodeSystemInfo getter = new GetNodeSystemInfo(req, resp); NodeSystemInfoResponse response = getter.getNodeSystemInfo(); Assert.assertNotNull(response.nodesInfo); - Assert.assertEquals(2, response.nodesInfo.size()); - - response - .nodesInfo - .entrySet() - .forEach( - e -> { - String key = e.getKey(); - NodeSystemInfoResponse.NodeSystemInfo info = e.getValue(); - Assert.assertEquals(key, info.node); - }); + Assert.assertEquals(1, response.nodesInfo.size()); + + NodeSystemInfoResponse.NodeSystemInfo info = + response.nodesInfo.values().stream().findFirst().orElseThrow(); + Assert.assertNull(info.coreRoot); + Assert.assertEquals("std", info.mode); + // Standalone mode : no "node" + Assert.assertNull(info.node); + // other validations in NodeSystemInfoProviderTest } } From 2b7aba0f171d52a597d66e7804b14843948fd2d9 Mon Sep 17 00:00:00 2001 From: Isabelle Giguere Date: Tue, 27 Jan 2026 15:31:14 -0500 Subject: [PATCH 6/9] SOLR-16458: Node system info V2 Jersey resource Remove the Map from NodeSystemInfoResponse: the AdminHandlersProxy wraps all nodes responses into a map, it should not belong to the response class. --- .../api/model/NodeSystemInfoResponse.java | 41 +++++------ .../handler/admin/NodeSystemInfoProvider.java | 13 +--- .../handler/admin/api/GetNodeSystemInfo.java | 19 +---- .../admin/NodeSystemInfoProviderTest.java | 9 +-- .../admin/api/GetNodeSystemInfoTest.java | 25 ++----- .../solrj/response/SystemInfoResponse.java | 70 +++++++++---------- 6 files changed, 65 insertions(+), 112 deletions(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java index 86468388913..97fbaa907b0 100644 --- a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java +++ b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java @@ -25,35 +25,30 @@ /** Response from /node/info/system */ public class NodeSystemInfoResponse extends SolrJerseyResponse { - @JsonProperty public Map nodesInfo; + @JsonProperty public String node; + @JsonProperty public String mode; + @JsonProperty public String zkHost; - public static class NodeSystemInfo { + @JsonProperty("solr_home") + public String solrHome; - @JsonProperty public String node; - @JsonProperty public String mode; - @JsonProperty public String zkHost; + @JsonProperty("core_root") + public String coreRoot; - @JsonProperty("solr_home") - public String solrHome; + @JsonProperty public String environment; - @JsonProperty("core_root") - public String coreRoot; + @JsonProperty(value = "environment_label") + public String environmentLabel; - @JsonProperty public String environment; + @JsonProperty(value = "environment_color") + public String environmentColor; - @JsonProperty(value = "environment_label") - public String environmentLabel; - - @JsonProperty(value = "environment_color") - public String environmentColor; - - @JsonProperty public Core core; - @JsonProperty public Lucene lucene; - @JsonProperty public JVM jvm; - @JsonProperty public Security security; - @JsonProperty public GPU gpu; - @JsonProperty public Map system; - } + @JsonProperty public Core core; + @JsonProperty public Lucene lucene; + @JsonProperty public JVM jvm; + @JsonProperty public Security security; + @JsonProperty public GPU gpu; + @JsonProperty public Map system; /** /node/system/security */ public static class Security { diff --git a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java index 1c7c98267ed..13fbcb6aa29 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java @@ -47,7 +47,6 @@ import java.util.stream.Stream; import org.apache.lucene.util.Version; import org.apache.solr.client.api.model.NodeSystemInfoResponse; -import org.apache.solr.client.api.model.NodeSystemInfoResponse.NodeSystemInfo; import org.apache.solr.client.api.util.SolrVersion; import org.apache.solr.common.cloud.ZkStateReader; import org.apache.solr.common.params.SolrParams; @@ -111,16 +110,8 @@ public NodeSystemInfoProvider(SolrQueryRequest request) { initHostname(); } - public void getNodeSystemInfo(NodeSystemInfoResponse response) { - Map nodes = new HashMap<>(); - NodeSystemInfo info = getNodeInfo(); - String key = info.node != null ? info.node : hostname; // should allow null key ? - nodes.put(key, info); - response.nodesInfo = nodes; - } - - private NodeSystemInfoResponse.NodeSystemInfo getNodeInfo() { - NodeSystemInfo info = new NodeSystemInfo(); + public NodeSystemInfoResponse getNodeSystemInfo() { + NodeSystemInfoResponse info = new NodeSystemInfoResponse(); SolrCore core = req.getCore(); if (core != null) info.core = getCoreInfo(core, req.getSchema()); diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java index 4698ff36d76..742575a7ff5 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java @@ -48,7 +48,7 @@ public GetNodeSystemInfo(SolrQueryRequest solrQueryRequest, SolrQueryResponse so public NodeSystemInfoResponse getNodeSystemInfo() { solrQueryResponse.setHttpCaching(false); - // TODO: AdminHandlersProxy does not support V2 + // TODO: AdminHandlersProxy does not support V2: PRs #4057, #3991 try { if (solrQueryRequest.getCoreContainer() != null && AdminHandlersProxy.maybeProxyToNodes( @@ -60,22 +60,9 @@ public NodeSystemInfoResponse getNodeSystemInfo() { } NodeSystemInfoProvider provider = new NodeSystemInfoProvider(solrQueryRequest); - NodeSystemInfoResponse response = instantiateJerseyResponse(NodeSystemInfoResponse.class); - provider.getNodeSystemInfo(response); - if (log.isDebugEnabled()) { - log.debug("Found {} nodes.", response == null ? "NO" : response.nodesInfo.size()); - } + NodeSystemInfoResponse response = provider.getNodeSystemInfo(); if (response != null && log.isTraceEnabled()) { - response - .nodesInfo - .entrySet() - .forEach( - e -> { - // yep, need to validate the log settings again. - if (log.isTraceEnabled()) { - log.trace("Node {}, core root: {}", e.getKey(), e.getValue().coreRoot); - } - }); + log.trace("Node {}, core root: {}", response.node, response.coreRoot); } return response; } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java b/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java index 1fb024319d8..a64e8368321 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java @@ -59,14 +59,9 @@ public void testMagickGetter() { public void testGetNodeSystemInfo() { SolrQueryRequest req = new SolrQueryRequestBase(h.getCore(), new ModifiableSolrParams()) {}; NodeSystemInfoProvider provider = new NodeSystemInfoProvider(req); - NodeSystemInfoResponse response = new NodeSystemInfoResponse(); - provider.getNodeSystemInfo(response); + NodeSystemInfoResponse info = provider.getNodeSystemInfo(); - Assert.assertNotNull(response.nodesInfo); - Assert.assertEquals(1, response.nodesInfo.size()); - - NodeSystemInfoResponse.NodeSystemInfo info = - response.nodesInfo.values().stream().findFirst().get(); + Assert.assertNotNull(info); // these can be validated Assert.assertEquals(h.getCoreContainer().getSolrHome().toString(), info.solrHome); Assert.assertEquals(h.getCoreContainer().getCoreRootDirectory().toString(), info.coreRoot); diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java index f842b8dcb95..4948dd0f796 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java @@ -29,7 +29,6 @@ import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import org.apache.solr.client.api.model.NodeSystemInfoResponse; -import org.apache.solr.client.api.model.NodeSystemInfoResponse.NodeSystemInfo; import org.apache.solr.client.solrj.response.XMLResponseParser; import org.apache.solr.client.solrj.response.json.JacksonDataBindResponseParser; import org.apache.solr.cloud.MiniSolrCloudCluster; @@ -141,11 +140,8 @@ public void testGetNodeInfoHttpJson() throws Exception { Assert.assertEquals(0, infoResponse.responseHeader.status); String expectedNode = baseV2Url.getHost() + ":" + baseV2Url.getPort() + "_solr"; - Assert.assertNotNull(infoResponse.nodesInfo); - Assert.assertNotNull(infoResponse.nodesInfo.get(expectedNode)); - - NodeSystemInfo nodeInfo = infoResponse.nodesInfo.get(expectedNode); - Assert.assertEquals(expectedNode, nodeInfo.node); + Assert.assertNotNull(infoResponse); + Assert.assertEquals(expectedNode, infoResponse.node); // other validations in NodeSystemInfoProviderTest } @@ -183,11 +179,7 @@ public void accept(Mutable arg0) { } String expectedNode = baseV2Url.getHost() + ":" + baseV2Url.getPort() + "_solr"; - Assert.assertNotNull(nlResponse.get("nodesInfo")); - NamedList nlNodesInfo = (NamedList) nlResponse.get("nodesInfo"); - Assert.assertNotNull(nlNodesInfo.get(expectedNode)); - NamedList nodeInfo = (NamedList) nlNodesInfo.get(expectedNode); - Assert.assertEquals(expectedNode, (String) nodeInfo.get("node")); + Assert.assertEquals(expectedNode, (String) nlResponse.get("node")); // other validations in NodeSystemInfoProviderTest } @@ -201,15 +193,10 @@ public void testGetNodeInfo() throws Exception { GetNodeSystemInfo getter = new GetNodeSystemInfo(req, resp); NodeSystemInfoResponse response = getter.getNodeSystemInfo(); - Assert.assertNotNull(response.nodesInfo); - Assert.assertEquals(1, response.nodesInfo.size()); - - NodeSystemInfoResponse.NodeSystemInfo info = - response.nodesInfo.values().stream().findFirst().orElseThrow(); - Assert.assertNull(info.coreRoot); - Assert.assertEquals("std", info.mode); + Assert.assertNotNull(response); + Assert.assertEquals("std", response.mode); // Standalone mode : no "node" - Assert.assertNull(info.node); + Assert.assertNull(response.node); // other validations in NodeSystemInfoProviderTest } } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java index ed3182863b7..775d715e12e 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java @@ -19,6 +19,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.apache.solr.client.api.model.NodeSystemInfoResponse; import org.apache.solr.client.solrj.request.json.JacksonContentWriter; @@ -29,7 +30,9 @@ public class SystemInfoResponse extends SolrResponseBase { private static final long serialVersionUID = 1L; - private final Map nodesInfo = new HashMap<>(); + // AdminHandlersProxy wraps nodes responses in a map. + // Mimic that here, even if the response might be just a single node. + private final Map nodesInfo = new HashMap<>(); public SystemInfoResponse(NamedList namedList) { if (namedList == null) throw new IllegalArgumentException("Null NamedList is not allowed."); @@ -45,38 +48,33 @@ public void setResponse(NamedList response) { return; } - NodeSystemInfoResponse fullResp = - JacksonContentWriter.DEFAULT_MAPPER.convertValue( - getResponse(), NodeSystemInfoResponse.class); - nodesInfo.putAll(fullResp.nodesInfo); - - // if (getResponse().get("node") == null) { - // // multi-nodes response, NamedList of "host:port_solr"->NodeSystemResponse - // for (Entry node : response) { - // if (node.getKey().endsWith("_solr")) { - // nodesInfo.put( - // node.getKey(), - // JacksonContentWriter.DEFAULT_MAPPER.convertValue( - // node.getValue(), NodeSystemInfoResponse.NodeSystemInfo.class)); - // } - // } - // - // // If no node was found, that's very likely Solr runs in standalone mode. - // // Add a single node info instance with null key (no node name is available). - // if (nodesInfo.isEmpty()) { - // nodesInfo.put( - // null, - // JacksonContentWriter.DEFAULT_MAPPER.convertValue(response, - // NodeSystemInfoResponse.NodeSystemInfo.class)); - // } - // - // } else { - // // single-node response - // nodesInfo.put( - // response.get("node").toString(), - // JacksonContentWriter.DEFAULT_MAPPER.convertValue( - // getResponse(), NodeSystemInfoResponse.NodeSystemInfo.class)); - // } + if (getResponse().get("node") == null) { + // multi-nodes response, NamedList of "host:port_solr"->NodeSystemResponse + for (Entry node : response) { + if (node.getKey().endsWith("_solr")) { + nodesInfo.put( + node.getKey(), + JacksonContentWriter.DEFAULT_MAPPER.convertValue( + node.getValue(), NodeSystemInfoResponse.class)); + } + } + + // If no node was found, that's very likely Solr runs in standalone mode. + // Add a single node info instance with null key (no node name is available). + if (nodesInfo.isEmpty()) { + nodesInfo.put( + null, + JacksonContentWriter.DEFAULT_MAPPER.convertValue( + response, NodeSystemInfoResponse.class)); + } + + } else { + // single-node response + nodesInfo.put( + response.get("node").toString(), + JacksonContentWriter.DEFAULT_MAPPER.convertValue( + getResponse(), NodeSystemInfoResponse.class)); + } } /** Get the mode from a single node system info */ @@ -201,7 +199,7 @@ public String getNodeForCoreRoot(String coreRoot) { } /** Get the {@code NodeSystemResponse.NodeSystemInfo} for a single node */ - public NodeSystemInfoResponse.NodeSystemInfo getNodeResponse() { + public NodeSystemInfoResponse getNodeResponse() { if (nodesInfo.size() == 1) { return nodesInfo.values().stream().findFirst().get(); } else { @@ -211,12 +209,12 @@ public NodeSystemInfoResponse.NodeSystemInfo getNodeResponse() { } /** Get all {@code NodeSystemResponse.NodeSystemInfo}s */ - public Map getAllNodeResponses() { + public Map getAllNodeResponses() { return nodesInfo; } /** Get the {@code NodeSystemResponse.NodeSystemInfo} for the given node name */ - public NodeSystemInfoResponse.NodeSystemInfo getNodeResponseForNode(String node) { + public NodeSystemInfoResponse getNodeResponseForNode(String node) { return nodesInfo.get(node); } From 3257bb7df089b4ff98ea3c32c07faa19a0a29f40 Mon Sep 17 00:00:00 2001 From: Isabelle Giguere Date: Tue, 27 Jan 2026 17:01:44 -0500 Subject: [PATCH 7/9] SOLR-16458: Node System V2 jersey resource Replace the Map wrapper by just a single object wrapper: NodeSystemInfo. It provides some separation between the response header and the actual node info. --- .../api/model/NodeSystemInfoResponse.java | 42 ++++++++++-------- .../handler/admin/NodeSystemInfoProvider.java | 8 ++-- .../solr/handler/admin/SystemInfoHandler.java | 4 +- .../handler/admin/api/GetNodeSystemInfo.java | 7 +-- .../handler/admin/AdminHandlersProxyTest.java | 11 ++++- .../admin/NodeSystemInfoProviderTest.java | 44 ++++++++++--------- .../admin/api/GetNodeSystemInfoTest.java | 14 +++--- .../solrj/response/SystemInfoResponse.java | 44 +++++++++---------- .../response/SystemInfoResponseTest.java | 24 +++++++--- 9 files changed, 114 insertions(+), 84 deletions(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java index 97fbaa907b0..864021c48b3 100644 --- a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java +++ b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java @@ -21,34 +21,40 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.solr.client.api.util.ReflectWritable; /** Response from /node/info/system */ public class NodeSystemInfoResponse extends SolrJerseyResponse { - @JsonProperty public String node; - @JsonProperty public String mode; - @JsonProperty public String zkHost; + @JsonProperty public NodeSystemInfo nodeInfo; - @JsonProperty("solr_home") - public String solrHome; + /** wrapper around the node info */ + public static class NodeSystemInfo implements ReflectWritable { + @JsonProperty public String node; + @JsonProperty public String mode; + @JsonProperty public String zkHost; - @JsonProperty("core_root") - public String coreRoot; + @JsonProperty("solr_home") + public String solrHome; - @JsonProperty public String environment; + @JsonProperty("core_root") + public String coreRoot; - @JsonProperty(value = "environment_label") - public String environmentLabel; + @JsonProperty public String environment; - @JsonProperty(value = "environment_color") - public String environmentColor; + @JsonProperty(value = "environment_label") + public String environmentLabel; - @JsonProperty public Core core; - @JsonProperty public Lucene lucene; - @JsonProperty public JVM jvm; - @JsonProperty public Security security; - @JsonProperty public GPU gpu; - @JsonProperty public Map system; + @JsonProperty(value = "environment_color") + public String environmentColor; + + @JsonProperty public Core core; + @JsonProperty public Lucene lucene; + @JsonProperty public JVM jvm; + @JsonProperty public Security security; + @JsonProperty public GPU gpu; + @JsonProperty public Map system; + } /** /node/system/security */ public static class Security { diff --git a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java index 13fbcb6aa29..3f91085d20d 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java @@ -110,8 +110,8 @@ public NodeSystemInfoProvider(SolrQueryRequest request) { initHostname(); } - public NodeSystemInfoResponse getNodeSystemInfo() { - NodeSystemInfoResponse info = new NodeSystemInfoResponse(); + public NodeSystemInfoResponse getNodeSystemInfo(NodeSystemInfoResponse response) { + NodeSystemInfoResponse.NodeSystemInfo info = new NodeSystemInfoResponse.NodeSystemInfo(); SolrCore core = req.getCore(); if (core != null) info.core = getCoreInfo(core, req.getSchema()); @@ -149,8 +149,8 @@ public NodeSystemInfoResponse getNodeSystemInfo() { info.environmentColor = env.getColor(); } } - - return info; + response.nodeInfo = info; + return response; } /** Get system info */ 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 c6daa01b3a3..6d635475b69 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 @@ -61,8 +61,8 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw return; // Request was proxied to other node } - GetNodeSystemInfo getter = new GetNodeSystemInfo(req, rsp); - NodeSystemInfoResponse response = getter.getNodeSystemInfo(); + NodeSystemInfoProvider provider = new NodeSystemInfoProvider(req); + NodeSystemInfoResponse response = provider.getNodeSystemInfo(new NodeSystemInfoResponse()); V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response); return; diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java index 742575a7ff5..79626a9e371 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java @@ -60,9 +60,10 @@ public NodeSystemInfoResponse getNodeSystemInfo() { } NodeSystemInfoProvider provider = new NodeSystemInfoProvider(solrQueryRequest); - NodeSystemInfoResponse response = provider.getNodeSystemInfo(); - if (response != null && log.isTraceEnabled()) { - log.trace("Node {}, core root: {}", response.node, response.coreRoot); + NodeSystemInfoResponse response = instantiateJerseyResponse(NodeSystemInfoResponse.class); + provider.getNodeSystemInfo(response); + if (response.nodeInfo != null && log.isTraceEnabled()) { + log.trace("Node {}, core root: {}", response.nodeInfo.node, response.nodeInfo.coreRoot); } return response; } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/AdminHandlersProxyTest.java b/solr/core/src/test/org/apache/solr/handler/admin/AdminHandlersProxyTest.java index 14411a38876..c26bc57ccf2 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/AdminHandlersProxyTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/AdminHandlersProxyTest.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.Collections; +import java.util.Map; import java.util.Set; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.CloudSolrClient; @@ -49,6 +50,7 @@ public void setUp() throws Exception { } @Test + @SuppressWarnings("unchecked") public void proxySystemInfoHandlerAllNodes() throws IOException, SolrServerException { MapSolrParams params = new MapSolrParams(Collections.singletonMap("nodes", "all")); @@ -58,8 +60,13 @@ public void proxySystemInfoHandlerAllNodes() throws IOException, SolrServerExcep assertEquals(3, nl.size()); assertTrue(nl.getName(1).endsWith("_solr")); assertTrue(nl.getName(2).endsWith("_solr")); - assertEquals("solrcloud", ((NamedList) nl.get(nl.getName(1))).get("mode")); - assertEquals(nl.getName(2), ((NamedList) nl.get(nl.getName(2))).get("node")); + String node1name = nl.getName(1); + String node2name = nl.getName(2); + NamedList node1 = (NamedList) nl.get(node1name); + // mmm??? nodeInfo is not translated to NamedList? + assertEquals("solrcloud", ((Map) node1.get("nodeInfo")).get("mode")); + NamedList node2 = (NamedList) nl.get(node2name); + assertEquals(node2name, ((Map) node2.get("nodeInfo")).get("node")); } @Test(expected = SolrException.class) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java b/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java index a64e8368321..374adc67f5c 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java @@ -59,28 +59,32 @@ public void testMagickGetter() { public void testGetNodeSystemInfo() { SolrQueryRequest req = new SolrQueryRequestBase(h.getCore(), new ModifiableSolrParams()) {}; NodeSystemInfoProvider provider = new NodeSystemInfoProvider(req); - NodeSystemInfoResponse info = provider.getNodeSystemInfo(); + NodeSystemInfoResponse info = provider.getNodeSystemInfo(new NodeSystemInfoResponse()); - Assert.assertNotNull(info); + Assert.assertNotNull(info.nodeInfo); // these can be validated - Assert.assertEquals(h.getCoreContainer().getSolrHome().toString(), info.solrHome); - Assert.assertEquals(h.getCoreContainer().getCoreRootDirectory().toString(), info.coreRoot); - Assert.assertNotNull(info.core); - Assert.assertNotNull(info.core.directory); - Assert.assertEquals(h.getCore().getInstancePath().toString(), info.core.directory.instance); - Assert.assertNotNull(info.lucene); - Assert.assertNotNull(info.lucene.solrImplVersion); - Assert.assertEquals(info.lucene.solrImplVersion, SolrVersion.LATEST.getPrereleaseVersion()); - Assert.assertNotNull(info.lucene.solrSpecVersion); - Assert.assertEquals(info.lucene.solrSpecVersion, SolrVersion.LATEST_STRING); - Assert.assertNotNull(info.lucene.luceneImplVersion); - Assert.assertEquals(info.lucene.luceneImplVersion, Version.getPackageImplementationVersion()); - Assert.assertNotNull(info.lucene.luceneSpecVersion); - Assert.assertEquals(info.lucene.luceneSpecVersion, Version.LATEST.toString()); + Assert.assertEquals(h.getCoreContainer().getSolrHome().toString(), info.nodeInfo.solrHome); + Assert.assertEquals( + h.getCoreContainer().getCoreRootDirectory().toString(), info.nodeInfo.coreRoot); + Assert.assertNotNull(info.nodeInfo.core); + Assert.assertNotNull(info.nodeInfo.core.directory); + Assert.assertEquals( + h.getCore().getInstancePath().toString(), info.nodeInfo.core.directory.instance); + Assert.assertNotNull(info.nodeInfo.lucene); + Assert.assertNotNull(info.nodeInfo.lucene.solrImplVersion); + Assert.assertEquals( + info.nodeInfo.lucene.solrImplVersion, SolrVersion.LATEST.getPrereleaseVersion()); + Assert.assertNotNull(info.nodeInfo.lucene.solrSpecVersion); + Assert.assertEquals(info.nodeInfo.lucene.solrSpecVersion, SolrVersion.LATEST_STRING); + Assert.assertNotNull(info.nodeInfo.lucene.luceneImplVersion); + Assert.assertEquals( + info.nodeInfo.lucene.luceneImplVersion, Version.getPackageImplementationVersion()); + Assert.assertNotNull(info.nodeInfo.lucene.luceneSpecVersion); + Assert.assertEquals(info.nodeInfo.lucene.luceneSpecVersion, Version.LATEST.toString()); // these should be set - Assert.assertNotNull(info.mode); - Assert.assertNotNull(info.jvm); - Assert.assertNotNull(info.security); - Assert.assertNotNull(info.system); + Assert.assertNotNull(info.nodeInfo.mode); + Assert.assertNotNull(info.nodeInfo.jvm); + Assert.assertNotNull(info.nodeInfo.security); + Assert.assertNotNull(info.nodeInfo.system); } } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java index 4948dd0f796..c198e5b7cb1 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java @@ -140,8 +140,8 @@ public void testGetNodeInfoHttpJson() throws Exception { Assert.assertEquals(0, infoResponse.responseHeader.status); String expectedNode = baseV2Url.getHost() + ":" + baseV2Url.getPort() + "_solr"; - Assert.assertNotNull(infoResponse); - Assert.assertEquals(expectedNode, infoResponse.node); + Assert.assertNotNull(infoResponse.nodeInfo); + Assert.assertEquals(expectedNode, infoResponse.nodeInfo.node); // other validations in NodeSystemInfoProviderTest } @@ -179,7 +179,9 @@ public void accept(Mutable arg0) { } String expectedNode = baseV2Url.getHost() + ":" + baseV2Url.getPort() + "_solr"; - Assert.assertEquals(expectedNode, (String) nlResponse.get("node")); + Assert.assertNotNull(nlResponse.get("nodeInfo")); + Assert.assertEquals( + expectedNode, (String) ((NamedList) nlResponse.get("nodeInfo")).get("node")); // other validations in NodeSystemInfoProviderTest } @@ -193,10 +195,10 @@ public void testGetNodeInfo() throws Exception { GetNodeSystemInfo getter = new GetNodeSystemInfo(req, resp); NodeSystemInfoResponse response = getter.getNodeSystemInfo(); - Assert.assertNotNull(response); - Assert.assertEquals("std", response.mode); + Assert.assertNotNull(response.nodeInfo); + Assert.assertEquals("std", response.nodeInfo.mode); // Standalone mode : no "node" - Assert.assertNull(response.node); + Assert.assertNull(response.nodeInfo.node); // other validations in NodeSystemInfoProviderTest } } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java index 775d715e12e..a2d6341b5d7 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java @@ -80,7 +80,7 @@ public void setResponse(NamedList response) { /** Get the mode from a single node system info */ public String getMode() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().orElseThrow().mode; + return nodesInfo.values().stream().findFirst().orElseThrow().nodeInfo.mode; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllModes', or 'getModeForNode(String)'."); @@ -90,19 +90,19 @@ public String getMode() { /** Get all modes, per node */ public Map getAllModes() { Map allModes = new HashMap<>(); - nodesInfo.forEach((key, value) -> allModes.put(key, value.mode)); + nodesInfo.forEach((key, value) -> allModes.put(key, value.nodeInfo.mode)); return allModes; } /** Get the mode for the given node name */ public String getModeForNode(String node) { - return nodesInfo.get(node).mode; + return nodesInfo.get(node).nodeInfo.mode; } /** Get the ZK host from a single node system info */ public String getZkHost() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().orElseThrow().zkHost; + return nodesInfo.values().stream().findFirst().orElseThrow().nodeInfo.zkHost; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllZkHosts', or 'getZkHostForNode(String)'."); @@ -112,19 +112,19 @@ public String getZkHost() { /** Get all ZK hosts, per node */ public Map getAllZkHosts() { Map allModes = new HashMap<>(); - nodesInfo.forEach((key, value) -> allModes.put(key, value.zkHost)); + nodesInfo.forEach((key, value) -> allModes.put(key, value.nodeInfo.zkHost)); return allModes; } /** Get the ZK host for the given node name */ public String getZkHostForNode(String node) { - return nodesInfo.get(node).zkHost; + return nodesInfo.get(node).nodeInfo.zkHost; } /** Get the Solr home from a single node system info */ public String getSolrHome() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().orElseThrow().solrHome; + return nodesInfo.values().stream().findFirst().orElseThrow().nodeInfo.solrHome; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllSolrHomes', or 'getSolrHomeForNode(String)'."); @@ -134,19 +134,19 @@ public String getSolrHome() { /** Get all Solr homes, per node */ public Map getAllSolrHomes() { Map allModes = new HashMap<>(); - nodesInfo.forEach((key, value) -> allModes.put(key, value.solrHome)); + nodesInfo.forEach((key, value) -> allModes.put(key, value.nodeInfo.solrHome)); return allModes; } /** Get the Solr home for the given node name */ public String getSolrHomeForNode(String node) { - return nodesInfo.get(node).solrHome; + return nodesInfo.get(node).nodeInfo.solrHome; } /** Get the core root from a single node system info */ public String getCoreRoot() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().orElseThrow().coreRoot; + return nodesInfo.values().stream().findFirst().orElseThrow().nodeInfo.coreRoot; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllCoreRoots', or 'getCoreRootForNode(String)'."); @@ -156,19 +156,19 @@ public String getCoreRoot() { /** Get all core roots, per node */ public Map getAllCoreRoots() { Map allModes = new HashMap<>(); - nodesInfo.forEach((key, value) -> allModes.put(key, value.coreRoot)); + nodesInfo.forEach((key, value) -> allModes.put(key, value.nodeInfo.coreRoot)); return allModes; } /** Get the core root for the given node name */ public String getCoreRootForNode(String node) { - return nodesInfo.get(node).coreRoot; + return nodesInfo.get(node).nodeInfo.coreRoot; } /** Get the node name from a single node system info */ public String getNode() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().orElseThrow().node; + return nodesInfo.values().stream().findFirst().orElseThrow().nodeInfo.node; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllNodes', or 'getNodeForSolrHome(String)', or 'getNodeForCoreRoot(String)'."); @@ -183,8 +183,8 @@ public Set getAllNodes() { /** Get the node name for the given Solr home */ public String getNodeForSolrHome(String solrHome) { return nodesInfo.values().stream() - .filter(v -> solrHome.equals(v.solrHome)) - .map(v -> v.node) + .filter(v -> solrHome.equals(v.nodeInfo.solrHome)) + .map(v -> v.nodeInfo.node) .findFirst() .get(); } @@ -192,30 +192,30 @@ public String getNodeForSolrHome(String solrHome) { /** Get the node name for the given core root */ public String getNodeForCoreRoot(String coreRoot) { return nodesInfo.values().stream() - .filter(v -> coreRoot.equals(v.coreRoot)) - .map(v -> v.node) + .filter(v -> coreRoot.equals(v.nodeInfo.coreRoot)) + .map(v -> v.nodeInfo.node) .findFirst() .get(); } /** Get the {@code NodeSystemResponse.NodeSystemInfo} for a single node */ - public NodeSystemInfoResponse getNodeResponse() { + public NodeSystemInfoResponse.NodeSystemInfo getNodeResponse() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().get(); + return nodesInfo.values().stream().findFirst().get().nodeInfo; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllNodeResponses', or 'getNodeResponseForNode(String)'."); } } - /** Get all {@code NodeSystemResponse.NodeSystemInfo}s */ + /** Get all {@code NodeSystemResponse}s */ public Map getAllNodeResponses() { return nodesInfo; } /** Get the {@code NodeSystemResponse.NodeSystemInfo} for the given node name */ - public NodeSystemInfoResponse getNodeResponseForNode(String node) { - return nodesInfo.get(node); + public NodeSystemInfoResponse.NodeSystemInfo getNodeResponseForNode(String node) { + return nodesInfo.get(node).nodeInfo; } public String getSolrImplVersion() { diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoResponseTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoResponseTest.java index f129d63f10e..c188e35e18f 100644 --- a/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoResponseTest.java +++ b/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoResponseTest.java @@ -30,6 +30,7 @@ public class SystemInfoResponseTest extends SolrCloudTestCase { + // private static MiniSolrCloudCluster cluster; private CloudSolrClient solrClient; @BeforeClass @@ -61,20 +62,29 @@ public void testAllNodesResponse() throws SolrServerException, IOException { Assert.assertEquals(2, rsp.getAllNodeResponses().size()); Assert.assertEquals(2, rsp.getAllCoreRoots().size()); Assert.assertEquals(2, rsp.getAllModes().size()); + + for (String node : rsp.getAllNodes()) { + String coreRoot = rsp.getCoreRootForNode(node); + Assert.assertEquals(node, rsp.getNodeForCoreRoot(coreRoot)); + String solrHome = rsp.getCoreRootForNode(node); + Assert.assertEquals(node, rsp.getNodeForSolrHome(solrHome)); + } } @Test public void testResponseForGivenNode() throws SolrServerException, IOException { - MapSolrParams params = new MapSolrParams(Map.of("nodes", "all")); + String queryNode = cluster.getJettySolrRunner(0).getNodeName(); + MapSolrParams params = new MapSolrParams(Map.of("nodes", queryNode)); SystemInfoRequest req = new SystemInfoRequest(params); SystemInfoResponse rsp = req.process(solrClient); - for (String node : rsp.getAllNodes()) { - String coreRoot = rsp.getCoreRootForNode(node); - Assert.assertEquals(node, rsp.getNodeForCoreRoot(coreRoot)); - String solrHome = rsp.getCoreRootForNode(node); - Assert.assertEquals(node, rsp.getNodeForSolrHome(solrHome)); - } + Assert.assertEquals(1, rsp.getAllNodeResponses().size()); + Assert.assertEquals(1, rsp.getAllCoreRoots().size()); + Assert.assertEquals(1, rsp.getAllModes().size()); + String coreRoot = rsp.getCoreRootForNode(queryNode); + Assert.assertEquals(queryNode, rsp.getNodeForCoreRoot(coreRoot)); + String solrHome = rsp.getCoreRootForNode(queryNode); + Assert.assertEquals(queryNode, rsp.getNodeForSolrHome(solrHome)); } } From 65d747947f03cfa38cfa902a2657bf010739b731 Mon Sep 17 00:00:00 2001 From: Isabelle Giguere Date: Fri, 30 Jan 2026 18:12:42 -0500 Subject: [PATCH 8/9] SOLR-16458: NodeSystemInfoApi JerseyResource review comments Ensure back-compatible response from V1 path (and from old V2 path) --- .../api/endpoint/NodeSystemInfoApi.java | 3 +- .../api/model/NodeSystemInfoResponse.java | 3 +- .../solr/client/api/util/Constants.java | 2 + .../solr/handler/admin/SystemInfoHandler.java | 3 +- .../handler/admin/AdminHandlersProxyTest.java | 11 +- .../admin/api/GetNodeSystemInfoTest.java | 3 +- .../solrj/request/SystemInfoRequest.java | 9 +- .../solrj/request/SystemInfoV2Request.java | 74 +++++++++++++ .../solrj/response/SystemInfoResponse.java | 70 +++++++----- .../solrj/response/SystemInfoV2Response.java | 50 +++++++++ .../solr/common/params/CommonParams.java | 2 +- .../response/SystemInfoResponseTest.java | 10 ++ .../response/SystemInfoV2ResponseTest.java | 103 ++++++++++++++++++ 13 files changed, 297 insertions(+), 46 deletions(-) create mode 100644 solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoV2Request.java create mode 100644 solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoV2Response.java create mode 100644 solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoV2ResponseTest.java diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java index 55b8df3dc1a..bb69a775f98 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java @@ -20,9 +20,10 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import org.apache.solr.client.api.model.NodeSystemInfoResponse; +import org.apache.solr.client.api.util.Constants; /** V2 API definition to fetch node system info, analogous to the v1 /admin/info/system. */ -@Path("/node/info/system") +@Path(Constants.NODE_INFO_SYSTEM_PATH) public interface NodeSystemInfoApi { @GET diff --git a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java index 864021c48b3..2a253be8c18 100644 --- a/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java +++ b/solr/api/src/java/org/apache/solr/client/api/model/NodeSystemInfoResponse.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.solr.client.api.util.ReflectWritable; /** Response from /node/info/system */ public class NodeSystemInfoResponse extends SolrJerseyResponse { @@ -29,7 +28,7 @@ public class NodeSystemInfoResponse extends SolrJerseyResponse { @JsonProperty public NodeSystemInfo nodeInfo; /** wrapper around the node info */ - public static class NodeSystemInfo implements ReflectWritable { + public static class NodeSystemInfo { @JsonProperty public String node; @JsonProperty public String mode; @JsonProperty public String zkHost; diff --git a/solr/api/src/java/org/apache/solr/client/api/util/Constants.java b/solr/api/src/java/org/apache/solr/client/api/util/Constants.java index 3e3c7443c18..053d88b6854 100644 --- a/solr/api/src/java/org/apache/solr/client/api/util/Constants.java +++ b/solr/api/src/java/org/apache/solr/client/api/util/Constants.java @@ -47,4 +47,6 @@ private Constants() { public static final String ADDTL_FIELDS_PROPERTY = "hasAdditionalFields"; public static final String JAVABIN_CONTENT_TYPE_V2 = "application/vnd.apache.solr.javabin"; + + public static final String NODE_INFO_SYSTEM_PATH = "/node/info/system"; } 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 6d635475b69..5f9667bc348 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 @@ -63,7 +63,8 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw NodeSystemInfoProvider provider = new NodeSystemInfoProvider(req); NodeSystemInfoResponse response = provider.getNodeSystemInfo(new NodeSystemInfoResponse()); - V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response); + // V1 does not wrap the system info into "nodeInfo" field + V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response.nodeInfo); return; } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/AdminHandlersProxyTest.java b/solr/core/src/test/org/apache/solr/handler/admin/AdminHandlersProxyTest.java index c26bc57ccf2..14411a38876 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/AdminHandlersProxyTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/AdminHandlersProxyTest.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.util.Collections; -import java.util.Map; import java.util.Set; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.CloudSolrClient; @@ -50,7 +49,6 @@ public void setUp() throws Exception { } @Test - @SuppressWarnings("unchecked") public void proxySystemInfoHandlerAllNodes() throws IOException, SolrServerException { MapSolrParams params = new MapSolrParams(Collections.singletonMap("nodes", "all")); @@ -60,13 +58,8 @@ public void proxySystemInfoHandlerAllNodes() throws IOException, SolrServerExcep assertEquals(3, nl.size()); assertTrue(nl.getName(1).endsWith("_solr")); assertTrue(nl.getName(2).endsWith("_solr")); - String node1name = nl.getName(1); - String node2name = nl.getName(2); - NamedList node1 = (NamedList) nl.get(node1name); - // mmm??? nodeInfo is not translated to NamedList? - assertEquals("solrcloud", ((Map) node1.get("nodeInfo")).get("mode")); - NamedList node2 = (NamedList) nl.get(node2name); - assertEquals(node2name, ((Map) node2.get("nodeInfo")).get("node")); + assertEquals("solrcloud", ((NamedList) nl.get(nl.getName(1))).get("mode")); + assertEquals(nl.getName(2), ((NamedList) nl.get(nl.getName(2))).get("node")); } @Test(expected = SolrException.class) diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java index c198e5b7cb1..1caae729049 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java @@ -29,6 +29,7 @@ import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import org.apache.solr.client.api.model.NodeSystemInfoResponse; +import org.apache.solr.client.api.util.Constants; import org.apache.solr.client.solrj.response.XMLResponseParser; import org.apache.solr.client.solrj.response.json.JacksonDataBindResponseParser; import org.apache.solr.cloud.MiniSolrCloudCluster; @@ -73,7 +74,7 @@ public static void beforeClass() throws Exception { cluster.waitForAllNodes(TIMEOUT / 1000); jettyRunner = cluster.getRandomJetty(random()); baseV2Url = jettyRunner.getBaseURLV2(); - systemInfoV2Url = baseV2Url.toString().concat("/node/info/system"); + systemInfoV2Url = baseV2Url.toString().concat(Constants.NODE_INFO_SYSTEM_PATH); // useSsl = true, clientAuth = false SSLTestConfig sslConfig = new SSLTestConfig(true, false); diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java index 8367d5cfc32..766510a1979 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java @@ -39,7 +39,7 @@ public SystemInfoRequest() { /** * @param path the HTTP path to use for this request. Supports V1 "/admin/info/system" (default) - * or V2 "/node/info/system" + * or old V2 "/node/system" */ public SystemInfoRequest(String path) { this(path, new ModifiableSolrParams()); @@ -54,12 +54,13 @@ public SystemInfoRequest(SolrParams params) { /** * @param path the HTTP path to use for this request. Supports V1 "/admin/info/system" (default) - * or V2 "/node/info/system" + * or old V2 "/node/system" * @param params query parameter names and values for making this request. */ public SystemInfoRequest(String path, SolrParams params) { super(METHOD.GET, path, SolrRequestType.ADMIN); - if (!path.endsWith("/system")) { + if (!path.equals(CommonParams.SYSTEM_INFO_PATH) + && !path.equals(CommonParams.V2_SYSTEM_INFO_PATH)) { throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Unsupported request path: " + path); } @@ -82,7 +83,7 @@ public ApiVersion getApiVersion() { // (/solr) /admin/info/system return ApiVersion.V1; } - // Ref. org.apache.solr.client.api.endpoint.NodeSystemInfoApi : /node/info/system + // Ref. org.apache.solr.handler.admin.api.NodeSystemInfoAPI : /node/system return ApiVersion.V2; } } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoV2Request.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoV2Request.java new file mode 100644 index 00000000000..7808a4ba5bd --- /dev/null +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoV2Request.java @@ -0,0 +1,74 @@ +/* + * 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; + +import org.apache.solr.client.api.util.Constants; +import org.apache.solr.client.solrj.SolrRequest; +import org.apache.solr.client.solrj.response.SystemInfoV2Response; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.params.SolrParams; +import org.apache.solr.common.util.NamedList; + +/** Class to get a system info response. */ +public class SystemInfoV2Request extends SolrRequest { + + private static final long serialVersionUID = 1L; + + private final SolrParams params; + + /** Request to "/node/info/system" by default, without params. */ + public SystemInfoV2Request() { + // TODO: support V2 by default. Requires refactoring throughout the CLI tools, at least + this(Constants.NODE_INFO_SYSTEM_PATH, new ModifiableSolrParams()); + } + + /** + * @param params the Solr parameters to use for this request. + */ + public SystemInfoV2Request(SolrParams params) { + this(Constants.NODE_INFO_SYSTEM_PATH, params); + } + + /** + * @param path the HTTP path to use for this request. Supports V2 "/node/info/system" + * @param params query parameter names and values for making this request. + */ + public SystemInfoV2Request(String path, SolrParams params) { + super(METHOD.GET, path, SolrRequestType.ADMIN); + if (!path.equals(Constants.NODE_INFO_SYSTEM_PATH)) { + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, "Unsupported request path: " + path); + } + this.params = params; + } + + @Override + public SolrParams getParams() { + return params; + } + + @Override + protected SystemInfoV2Response createResponse(NamedList namedList) { + return new SystemInfoV2Response(namedList); + } + + @Override + public ApiVersion getApiVersion() { + return ApiVersion.V2; + } +} diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java index a2d6341b5d7..46946f3f57f 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java @@ -16,6 +16,7 @@ */ package org.apache.solr.client.solrj.response; +import java.lang.invoke.MethodHandles; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -25,14 +26,18 @@ import org.apache.solr.client.solrj.request.json.JacksonContentWriter; import org.apache.solr.common.util.NamedList; -/** This class holds the response from V1 "/admin/info/system" or V2 "/node/info/system" */ +/** This class holds the response from V1 "/admin/info/system" */ public class SystemInfoResponse extends SolrResponseBase { private static final long serialVersionUID = 1L; // AdminHandlersProxy wraps nodes responses in a map. // Mimic that here, even if the response might be just a single node. - private final Map nodesInfo = new HashMap<>(); + protected final Map nodesInfo = new HashMap<>(); + + protected SystemInfoResponse() { + // allow a NamedList constructor in the V2 extension + } public SystemInfoResponse(NamedList namedList) { if (namedList == null) throw new IllegalArgumentException("Null NamedList is not allowed."); @@ -43,19 +48,25 @@ public SystemInfoResponse(NamedList namedList) { public void setResponse(NamedList response) { if (getResponse() == null) { super.setResponse(response); + parseResponse(response); } else { assert response.equals(getResponse()); return; } + } - if (getResponse().get("node") == null) { - // multi-nodes response, NamedList of "host:port_solr"->NodeSystemResponse + /** Parse the V1 response */ + @SuppressWarnings("unchecked") + protected void parseResponse(NamedList response) { + if (response.get("node") == null) { + // multi-nodes response, NamedList of "host:port_solr"-> NodeSystemInfoResponse for (Entry node : response) { if (node.getKey().endsWith("_solr")) { nodesInfo.put( node.getKey(), JacksonContentWriter.DEFAULT_MAPPER.convertValue( - node.getValue(), NodeSystemInfoResponse.class)); + removeHeader((NamedList) node.getValue()), + NodeSystemInfoResponse.NodeSystemInfo.class)); } } @@ -65,7 +76,7 @@ public void setResponse(NamedList response) { nodesInfo.put( null, JacksonContentWriter.DEFAULT_MAPPER.convertValue( - response, NodeSystemInfoResponse.class)); + removeHeader(response), NodeSystemInfoResponse.NodeSystemInfo.class)); } } else { @@ -73,14 +84,19 @@ public void setResponse(NamedList response) { nodesInfo.put( response.get("node").toString(), JacksonContentWriter.DEFAULT_MAPPER.convertValue( - getResponse(), NodeSystemInfoResponse.class)); + removeHeader(response), NodeSystemInfoResponse.NodeSystemInfo.class)); } } + private NamedList removeHeader(NamedList value) { + value.remove("responseHeader"); + return value; + } + /** Get the mode from a single node system info */ public String getMode() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().orElseThrow().nodeInfo.mode; + return nodesInfo.values().stream().findFirst().orElseThrow().mode; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllModes', or 'getModeForNode(String)'."); @@ -90,19 +106,19 @@ public String getMode() { /** Get all modes, per node */ public Map getAllModes() { Map allModes = new HashMap<>(); - nodesInfo.forEach((key, value) -> allModes.put(key, value.nodeInfo.mode)); + nodesInfo.forEach((key, value) -> allModes.put(key, value.mode)); return allModes; } /** Get the mode for the given node name */ public String getModeForNode(String node) { - return nodesInfo.get(node).nodeInfo.mode; + return nodesInfo.get(node).mode; } /** Get the ZK host from a single node system info */ public String getZkHost() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().orElseThrow().nodeInfo.zkHost; + return nodesInfo.values().stream().findFirst().orElseThrow().zkHost; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllZkHosts', or 'getZkHostForNode(String)'."); @@ -112,19 +128,19 @@ public String getZkHost() { /** Get all ZK hosts, per node */ public Map getAllZkHosts() { Map allModes = new HashMap<>(); - nodesInfo.forEach((key, value) -> allModes.put(key, value.nodeInfo.zkHost)); + nodesInfo.forEach((key, value) -> allModes.put(key, value.zkHost)); return allModes; } /** Get the ZK host for the given node name */ public String getZkHostForNode(String node) { - return nodesInfo.get(node).nodeInfo.zkHost; + return nodesInfo.get(node).zkHost; } /** Get the Solr home from a single node system info */ public String getSolrHome() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().orElseThrow().nodeInfo.solrHome; + return nodesInfo.values().stream().findFirst().orElseThrow().solrHome; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllSolrHomes', or 'getSolrHomeForNode(String)'."); @@ -134,19 +150,19 @@ public String getSolrHome() { /** Get all Solr homes, per node */ public Map getAllSolrHomes() { Map allModes = new HashMap<>(); - nodesInfo.forEach((key, value) -> allModes.put(key, value.nodeInfo.solrHome)); + nodesInfo.forEach((key, value) -> allModes.put(key, value.solrHome)); return allModes; } /** Get the Solr home for the given node name */ public String getSolrHomeForNode(String node) { - return nodesInfo.get(node).nodeInfo.solrHome; + return nodesInfo.get(node).solrHome; } /** Get the core root from a single node system info */ public String getCoreRoot() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().orElseThrow().nodeInfo.coreRoot; + return nodesInfo.values().stream().findFirst().orElseThrow().coreRoot; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllCoreRoots', or 'getCoreRootForNode(String)'."); @@ -156,19 +172,19 @@ public String getCoreRoot() { /** Get all core roots, per node */ public Map getAllCoreRoots() { Map allModes = new HashMap<>(); - nodesInfo.forEach((key, value) -> allModes.put(key, value.nodeInfo.coreRoot)); + nodesInfo.forEach((key, value) -> allModes.put(key, value.coreRoot)); return allModes; } /** Get the core root for the given node name */ public String getCoreRootForNode(String node) { - return nodesInfo.get(node).nodeInfo.coreRoot; + return nodesInfo.get(node).coreRoot; } /** Get the node name from a single node system info */ public String getNode() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().orElseThrow().nodeInfo.node; + return nodesInfo.values().stream().findFirst().orElseThrow().node; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllNodes', or 'getNodeForSolrHome(String)', or 'getNodeForCoreRoot(String)'."); @@ -183,8 +199,8 @@ public Set getAllNodes() { /** Get the node name for the given Solr home */ public String getNodeForSolrHome(String solrHome) { return nodesInfo.values().stream() - .filter(v -> solrHome.equals(v.nodeInfo.solrHome)) - .map(v -> v.nodeInfo.node) + .filter(v -> solrHome.equals(v.solrHome)) + .map(v -> v.node) .findFirst() .get(); } @@ -192,8 +208,8 @@ public String getNodeForSolrHome(String solrHome) { /** Get the node name for the given core root */ public String getNodeForCoreRoot(String coreRoot) { return nodesInfo.values().stream() - .filter(v -> coreRoot.equals(v.nodeInfo.coreRoot)) - .map(v -> v.nodeInfo.node) + .filter(v -> coreRoot.equals(v.coreRoot)) + .map(v -> v.node) .findFirst() .get(); } @@ -201,7 +217,7 @@ public String getNodeForCoreRoot(String coreRoot) { /** Get the {@code NodeSystemResponse.NodeSystemInfo} for a single node */ public NodeSystemInfoResponse.NodeSystemInfo getNodeResponse() { if (nodesInfo.size() == 1) { - return nodesInfo.values().stream().findFirst().get().nodeInfo; + return nodesInfo.values().stream().findFirst().get(); } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllNodeResponses', or 'getNodeResponseForNode(String)'."); @@ -209,13 +225,13 @@ public NodeSystemInfoResponse.NodeSystemInfo getNodeResponse() { } /** Get all {@code NodeSystemResponse}s */ - public Map getAllNodeResponses() { + public Map getAllNodeResponses() { return nodesInfo; } /** Get the {@code NodeSystemResponse.NodeSystemInfo} for the given node name */ public NodeSystemInfoResponse.NodeSystemInfo getNodeResponseForNode(String node) { - return nodesInfo.get(node).nodeInfo; + return nodesInfo.get(node); } public String getSolrImplVersion() { diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoV2Response.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoV2Response.java new file mode 100644 index 00000000000..429181aa30f --- /dev/null +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoV2Response.java @@ -0,0 +1,50 @@ +/* + * 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.response; + +import java.lang.invoke.MethodHandles; +import java.util.Map; +import org.apache.solr.client.api.model.NodeSystemInfoResponse; +import org.apache.solr.client.solrj.request.json.JacksonContentWriter; +import org.apache.solr.common.util.NamedList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class holds the response from V2 "/node/info/system" */ +public class SystemInfoV2Response extends SystemInfoResponse { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final long serialVersionUID = 1L; + + public SystemInfoV2Response(NamedList namedList) { + if (namedList == null) throw new IllegalArgumentException("Null NamedList is not allowed."); + setResponse(namedList); + } + + /** Parse the V2 response, with "nodeInfo" wrapper */ + @SuppressWarnings("unchecked") + @Override + protected void parseResponse(NamedList response) { + log.info("V2 response: {}", response); + Map info = (Map) response.get("nodeInfo"); + String nodeName = (String) info.get("node"); + nodesInfo.put( + nodeName, + JacksonContentWriter.DEFAULT_MAPPER.convertValue( + info, NodeSystemInfoResponse.NodeSystemInfo.class)); + } +} diff --git a/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java b/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java index 1878bf4f456..b26360864c5 100644 --- a/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java +++ b/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java @@ -205,7 +205,7 @@ public interface CommonParams { String ZK_STATUS_PATH = "/admin/zookeeper/status"; String SYSTEM_INFO_PATH = "/admin/info/system"; String METRICS_PATH = "/admin/metrics"; - String V2_SYSTEM_INFO_PATH = "/node/info/system"; + String V2_SYSTEM_INFO_PATH = "/node/system"; String STATUS = "status"; diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoResponseTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoResponseTest.java index c188e35e18f..05e8d319370 100644 --- a/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoResponseTest.java +++ b/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoResponseTest.java @@ -45,6 +45,16 @@ public void setUp() throws Exception { solrClient = cluster.getSolrClient(); } + @Test + public void testDefaultResponse() throws SolrServerException, IOException { + SystemInfoRequest req = new SystemInfoRequest(); + SystemInfoResponse rsp = req.process(solrClient); + + Assert.assertEquals(1, rsp.getAllNodeResponses().size()); + Assert.assertEquals(1, rsp.getAllCoreRoots().size()); + Assert.assertEquals(1, rsp.getAllModes().size()); + } + @Test public void testAllNodesResponse() throws SolrServerException, IOException { MapSolrParams params = new MapSolrParams(Map.of("nodes", "all")); diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoV2ResponseTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoV2ResponseTest.java new file mode 100644 index 00000000000..7c9a205e5be --- /dev/null +++ b/solr/solrj/src/test/org/apache/solr/client/solrj/response/SystemInfoV2ResponseTest.java @@ -0,0 +1,103 @@ +/* + * 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.response; + +import java.io.IOException; +import java.util.Map; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.CloudSolrClient; +import org.apache.solr.client.solrj.request.SystemInfoV2Request; +import org.apache.solr.cloud.SolrCloudTestCase; +import org.apache.solr.common.params.MapSolrParams; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +public class SystemInfoV2ResponseTest extends SolrCloudTestCase { + + // private static MiniSolrCloudCluster cluster; + private CloudSolrClient solrClient; + + @BeforeClass + public static void setupCluster() throws Exception { + configureCluster(2).addConfig("config", getFile("solrj/solr/collection1/conf")).configure(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + solrClient = cluster.getSolrClient(); + } + + @Test + public void testDefaultResponse() throws SolrServerException, IOException { + SystemInfoV2Request req = new SystemInfoV2Request(); + SystemInfoV2Response rsp = req.process(solrClient); + + Assert.assertEquals(1, rsp.getAllNodeResponses().size()); + Assert.assertEquals(1, rsp.getAllCoreRoots().size()); + Assert.assertEquals(1, rsp.getAllModes().size()); + } + + @Test + @Ignore("AdminHandlersProxy does not support V2.") + public void testAllNodesResponse() throws SolrServerException, IOException { + MapSolrParams params = new MapSolrParams(Map.of("nodes", "all")); + + SystemInfoV2Request req = new SystemInfoV2Request(params); + SystemInfoV2Response rsp = req.process(solrClient); + + try { + rsp.getNodeResponse(); + Assert.fail("Should throw UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + Assert.assertTrue(e.getMessage().startsWith("Multiple nodes system info available")); + } + + Assert.assertEquals(2, rsp.getAllNodeResponses().size()); + Assert.assertEquals(2, rsp.getAllCoreRoots().size()); + Assert.assertEquals(2, rsp.getAllModes().size()); + + for (String node : rsp.getAllNodes()) { + String coreRoot = rsp.getCoreRootForNode(node); + Assert.assertEquals(node, rsp.getNodeForCoreRoot(coreRoot)); + String solrHome = rsp.getCoreRootForNode(node); + Assert.assertEquals(node, rsp.getNodeForSolrHome(solrHome)); + } + } + + @Test + @Ignore("AdminHandlersProxy does not support V2.") + public void testResponseForGivenNode() throws SolrServerException, IOException { + String queryNode = cluster.getJettySolrRunner(0).getNodeName(); + MapSolrParams params = new MapSolrParams(Map.of("nodes", queryNode)); + + SystemInfoV2Request req = new SystemInfoV2Request(params); + SystemInfoV2Response rsp = req.process(solrClient); + + Assert.assertEquals(1, rsp.getAllNodeResponses().size()); + Assert.assertEquals(1, rsp.getAllCoreRoots().size()); + Assert.assertEquals(1, rsp.getAllModes().size()); + String coreRoot = rsp.getCoreRootForNode(queryNode); + Assert.assertEquals(queryNode, rsp.getNodeForCoreRoot(coreRoot)); + String solrHome = rsp.getCoreRootForNode(queryNode); + Assert.assertEquals(queryNode, rsp.getNodeForSolrHome(solrHome)); + } +} From d7b86386c423f9b84c685478b64b1001c067a605 Mon Sep 17 00:00:00 2001 From: Isabelle Giguere Date: Fri, 30 Jan 2026 19:14:22 -0500 Subject: [PATCH 9/9] SOLR-16458: NodeSystemInfoApi JerseyResource - path param Add method getSpecificNodeSystemInfo, with path parameter, to get only selected info. Add query param "nodes": not used, because AdminHandlersProxy does not support V2 yet. --- .../api/endpoint/NodeSystemInfoApi.java | 15 +++++- .../handler/admin/NodeSystemInfoProvider.java | 34 ++++++------- .../handler/admin/api/GetNodeSystemInfo.java | 50 ++++++++++++++++++- .../admin/api/GetNodeSystemInfoTest.java | 39 ++++++++++++++- .../solrj/response/SystemInfoResponse.java | 1 - 5 files changed, 116 insertions(+), 23 deletions(-) diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java index bb69a775f98..8635bb58275 100644 --- a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java @@ -19,6 +19,8 @@ import io.swagger.v3.oas.annotations.Operation; 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.NodeSystemInfoResponse; import org.apache.solr.client.api.util.Constants; @@ -28,7 +30,16 @@ public interface NodeSystemInfoApi { @GET @Operation( - summary = "Retrieve node system info.", + summary = "Retrieve all node system info.", tags = {"system"}) - NodeSystemInfoResponse getNodeSystemInfo(); + NodeSystemInfoResponse getNodeSystemInfo(@QueryParam(value = "nodes") String nodes); + + @GET + @Operation( + summary = "Retrieve specific node system info.", + tags = {"system"}) + @Path("/{requestedInfo}") + NodeSystemInfoResponse getSpecificNodeSystemInfo( + @PathParam(value = "requestedInfo") String requestedInfo, + @QueryParam(value = "nodes") String nodes); } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java index 3f91085d20d..c6545255bb7 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java @@ -110,11 +110,12 @@ public NodeSystemInfoProvider(SolrQueryRequest request) { initHostname(); } + /** Fill-out the provided response with all system info. */ public NodeSystemInfoResponse getNodeSystemInfo(NodeSystemInfoResponse response) { NodeSystemInfoResponse.NodeSystemInfo info = new NodeSystemInfoResponse.NodeSystemInfo(); SolrCore core = req.getCore(); - if (core != null) info.core = getCoreInfo(core, req.getSchema()); + if (core != null) info.core = getCoreInfo(); if (cc != null) { info.solrHome = cc.getSolrHome().toString(); @@ -130,10 +131,9 @@ public NodeSystemInfoResponse getNodeSystemInfo(NodeSystemInfoResponse response) info.lucene = getLuceneInfo(); - NodeConfig nodeConfig = cc != null ? cc.getNodeConfig() : null; - info.jvm = getJvmInfo(nodeConfig); + info.jvm = getJvmInfo(); - info.security = getSecurityInfo(req); + info.security = getSecurityInfo(); info.system = getSystemInfo(); info.gpu = getGpuInfo(); @@ -153,19 +153,16 @@ public NodeSystemInfoResponse getNodeSystemInfo(NodeSystemInfoResponse response) return response; } - /** Get system info */ - private NodeSystemInfoResponse.Core getCoreInfo(SolrCore core, IndexSchema schema) { + /** Get core info */ + public NodeSystemInfoResponse.Core getCoreInfo() { NodeSystemInfoResponse.Core info = new NodeSystemInfoResponse.Core(); - info.schema = schema != null ? schema.getSchemaName() : "no schema!"; + SolrCore core = req.getCore(); + IndexSchema schema = req.getSchema(); - // Host + info.schema = schema != null ? schema.getSchemaName() : "no schema!"; info.host = hostname; - - // Now info.now = new Date(); - - // Start Time info.start = core.getStartTimeStamp(); // Solr Home @@ -190,7 +187,7 @@ private NodeSystemInfoResponse.Core getCoreInfo(SolrCore core, IndexSchema schem } /** Get system info */ - private Map getSystemInfo() { + public Map getSystemInfo() { Map info = new HashMap<>(); OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); @@ -211,7 +208,7 @@ private Map getSystemInfo() { } /** Get JVM Info - including memory info */ - private NodeSystemInfoResponse.JVM getJvmInfo(NodeConfig nodeConfig) { + public NodeSystemInfoResponse.JVM getJvmInfo() { NodeSystemInfoResponse.JVM jvm = new NodeSystemInfoResponse.JVM(); final String javaVersion = System.getProperty("java.specification.version", "unknown"); @@ -282,6 +279,7 @@ private NodeSystemInfoResponse.JVM getJvmInfo(NodeConfig nodeConfig) { // the input arguments passed to the Java virtual machine // which does not include the arguments to the main method. + NodeConfig nodeConfig = cc != null ? cc.getNodeConfig() : null; jmx.commandLineArgs = getInputArgumentsRedacted(nodeConfig, mx); jmx.startTime = new Date(mx.getStartTime()); @@ -295,7 +293,7 @@ private NodeSystemInfoResponse.JVM getJvmInfo(NodeConfig nodeConfig) { } /** Get Security Info */ - private NodeSystemInfoResponse.Security getSecurityInfo(SolrQueryRequest req) { + public NodeSystemInfoResponse.Security getSecurityInfo() { NodeSystemInfoResponse.Security info = new NodeSystemInfoResponse.Security(); if (cc != null) { @@ -337,7 +335,8 @@ private NodeSystemInfoResponse.Security getSecurityInfo(SolrQueryRequest req) { return info; } - private NodeSystemInfoResponse.Lucene getLuceneInfo() { + /** Get Lucene and Solr versions */ + public NodeSystemInfoResponse.Lucene getLuceneInfo() { NodeSystemInfoResponse.Lucene info = new NodeSystemInfoResponse.Lucene(); Package p = SolrCore.class.getPackage(); @@ -354,7 +353,8 @@ private NodeSystemInfoResponse.Lucene getLuceneInfo() { return info; } - private NodeSystemInfoResponse.GPU getGpuInfo() { + /** Get GPU info */ + public NodeSystemInfoResponse.GPU getGpuInfo() { NodeSystemInfoResponse.GPU gpuInfo = new NodeSystemInfoResponse.GPU(); gpuInfo.available = false; // set below if available diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java index 79626a9e371..ecdf0b1198b 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java @@ -21,6 +21,7 @@ import org.apache.solr.api.JerseyResource; import org.apache.solr.client.api.endpoint.NodeSystemInfoApi; import org.apache.solr.client.api.model.NodeSystemInfoResponse; +import org.apache.solr.common.SolrException; import org.apache.solr.handler.admin.AdminHandlersProxy; import org.apache.solr.handler.admin.NodeSystemInfoProvider; import org.apache.solr.jersey.PermissionName; @@ -45,18 +46,20 @@ public GetNodeSystemInfo(SolrQueryRequest solrQueryRequest, SolrQueryResponse so @Override @PermissionName(PermissionNameProvider.Name.CONFIG_READ_PERM) - public NodeSystemInfoResponse getNodeSystemInfo() { + public NodeSystemInfoResponse getNodeSystemInfo(String nodes) { solrQueryResponse.setHttpCaching(false); // TODO: AdminHandlersProxy does not support V2: PRs #4057, #3991 try { + // TODO: Should use the "nodes" param if (solrQueryRequest.getCoreContainer() != null && AdminHandlersProxy.maybeProxyToNodes( solrQueryRequest, solrQueryResponse, solrQueryRequest.getCoreContainer())) { return null; } } catch (Exception e) { - log.warn("Error occurred while proxying to other node", e); + throw new SolrException( + SolrException.ErrorCode.SERVER_ERROR, "Error occurred while proxying to other node", e); } NodeSystemInfoProvider provider = new NodeSystemInfoProvider(solrQueryRequest); @@ -67,4 +70,47 @@ public NodeSystemInfoResponse getNodeSystemInfo() { } return response; } + + @Override + public NodeSystemInfoResponse getSpecificNodeSystemInfo(String requestedInfo, String nodes) { + solrQueryResponse.setHttpCaching(false); + + // TODO: AdminHandlersProxy does not support V2: PRs #4057, #3991 + try { + // TODO: Should use the "nodes" param + if (solrQueryRequest.getCoreContainer() != null + && AdminHandlersProxy.maybeProxyToNodes( + solrQueryRequest, solrQueryResponse, solrQueryRequest.getCoreContainer())) { + return null; + } + } catch (Exception e) { + throw new SolrException( + SolrException.ErrorCode.SERVER_ERROR, "Error occurred while proxying to other node", e); + } + + NodeSystemInfoResponse response = instantiateJerseyResponse(NodeSystemInfoResponse.class); + response.nodeInfo = new NodeSystemInfoResponse.NodeSystemInfo(); + NodeSystemInfoProvider provider = new NodeSystemInfoProvider(solrQueryRequest); + switch (requestedInfo) { + case "core": + response.nodeInfo.core = provider.getCoreInfo(); + break; + case "gpu": + response.nodeInfo.gpu = provider.getGpuInfo(); + break; + case "jvm": + response.nodeInfo.jvm = provider.getJvmInfo(); + break; + case "lucene": + response.nodeInfo.lucene = provider.getLuceneInfo(); + break; + case "security": + response.nodeInfo.security = provider.getSecurityInfo(); + break; + default: + throw new SolrException( + SolrException.ErrorCode.BAD_REQUEST, "Unknown parameter: " + requestedInfo); + } + return response; + } } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java index 1caae729049..de5127826b1 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java @@ -30,6 +30,7 @@ import java.util.function.Consumer; import org.apache.solr.client.api.model.NodeSystemInfoResponse; import org.apache.solr.client.api.util.Constants; +import org.apache.solr.client.api.util.SolrVersion; import org.apache.solr.client.solrj.response.XMLResponseParser; import org.apache.solr.client.solrj.response.json.JacksonDataBindResponseParser; import org.apache.solr.cloud.MiniSolrCloudCluster; @@ -195,11 +196,47 @@ public void testGetNodeInfo() throws Exception { GetNodeSystemInfo getter = new GetNodeSystemInfo(req, resp); - NodeSystemInfoResponse response = getter.getNodeSystemInfo(); + NodeSystemInfoResponse response = getter.getNodeSystemInfo(null); Assert.assertNotNull(response.nodeInfo); Assert.assertEquals("std", response.nodeInfo.mode); // Standalone mode : no "node" Assert.assertNull(response.nodeInfo.node); // other validations in NodeSystemInfoProviderTest } + + /** Test getting specific information */ + @Test + public void testGetSpecificNodeSystemInfo() throws Exception { + ContentResponse response = null; + try { + response = + jettyHttpClient + .newRequest(systemInfoV2Url.concat("/lucene")) + .timeout(TIMEOUT, TimeUnit.MILLISECONDS) + .method(HttpMethod.GET) + .send(); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Assert.fail("Should not throw exception: " + e.getClass() + ". message: " + e.getMessage()); + return; + } + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals("application/json", response.getMediaType()); + + NodeSystemInfoResponse infoResponse; + JacksonDataBindResponseParser parser = + new JacksonDataBindResponseParser<>(NodeSystemInfoResponse.class); + try (InputStream in = new ByteArrayInputStream(response.getContent())) { + infoResponse = parser.processToType(in, StandardCharsets.UTF_8.toString()); + } + Assert.assertNotNull(infoResponse.nodeInfo); + Assert.assertNotNull(infoResponse.nodeInfo.lucene); + Assert.assertEquals( + 0, + SolrVersion.compareVersions( + infoResponse.nodeInfo.lucene.solrSpecVersion, SolrVersion.LATEST_STRING)); + Assert.assertNull(infoResponse.nodeInfo.gpu); + Assert.assertNull(infoResponse.nodeInfo.jvm); + Assert.assertNull(infoResponse.nodeInfo.security); + Assert.assertNull(infoResponse.nodeInfo.core); + } } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java index 46946f3f57f..7337887dcdf 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/SystemInfoResponse.java @@ -16,7 +16,6 @@ */ package org.apache.solr.client.solrj.response; -import java.lang.invoke.MethodHandles; import java.util.Date; import java.util.HashMap; import java.util.Map;