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 000000000000..55b8df3dc1a0 --- /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.NodeSystemInfoResponse; + +/** 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"}) + 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 64% 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 09bc35f08677..864021c48b34 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 @@ -20,33 +20,41 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Set; +import org.apache.solr.client.api.util.ReflectWritable; -/** Response from /node/system */ -public class NodeSystemResponse extends SolrJerseyResponse { +/** Response from /node/info/system */ +public class NodeSystemInfoResponse extends SolrJerseyResponse { - @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 String node; - @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 { @@ -54,8 +62,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 +129,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 000000000000..3f91085d20d7 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/NodeSystemInfoProvider.java @@ -0,0 +1,538 @@ +/* + * 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.NodeSystemInfoResponse; +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; +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 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; + + // 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; + 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; + params = request.getParams(); + cc = request.getCoreContainer(); + initHostname(); + } + + 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 (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) { + 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()) { + info.environment = env.getCode(); + if (env.getLabel() != null) { + info.environmentLabel = env.getLabel(); + } + if (env.getColor() != null) { + info.environmentColor = env.getColor(); + } + } + response.nodeInfo = info; + return response; + } + + /** Get system info */ + private NodeSystemInfoResponse.Core getCoreInfo(SolrCore core, IndexSchema schema) { + NodeSystemInfoResponse.Core info = new NodeSystemInfoResponse.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 + NodeSystemInfoResponse.Directory dirs = new NodeSystemInfoResponse.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 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"); + 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 + NodeSystemInfoResponse.Vendor spec = new NodeSystemInfoResponse.Vendor(); + spec.vendor = javaVendor; + spec.name = javaName; + spec.version = javaVersion; + jvm.spec = spec; + + NodeSystemInfoResponse.Vendor jre = new NodeSystemInfoResponse.Vendor(); + jre.vendor = jreVendor; + jre.version = jreVersion; + jvm.jre = jre; + + NodeSystemInfoResponse.Vendor vm = new NodeSystemInfoResponse.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)); + + NodeSystemInfoResponse.JvmMemory mem = new NodeSystemInfoResponse.JvmMemory(); + NodeSystemInfoResponse.JvmMemoryRaw raw = new NodeSystemInfoResponse.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 + NodeSystemInfoResponse.JvmJmx jmx = new NodeSystemInfoResponse.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 NodeSystemInfoResponse.Security getSecurityInfo(SolrQueryRequest req) { + NodeSystemInfoResponse.Security info = new NodeSystemInfoResponse.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 NodeSystemInfoResponse.Lucene getLuceneInfo() { + NodeSystemInfoResponse.Lucene info = new NodeSystemInfoResponse.Lucene(); + + Package p = SolrCore.class.getPackage(); + 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(); + + return info; + } + + private NodeSystemInfoResponse.GPU getGpuInfo() { + NodeSystemInfoResponse.GPU gpuInfo = new NodeSystemInfoResponse.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) { + NodeSystemInfoResponse.MemoryRaw memory = new NodeSystemInfoResponse.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 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 List getInputArgumentsRedacted(NodeConfig nodeConfig, RuntimeMXBean mx) { + List list = new ArrayList<>(); + for (String arg : mx.getInputArguments()) { + if (arg.startsWith("-D") + && arg.contains("=") + && (nodeConfig != null + && 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 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 + */ + // protected & static : ref. test in NodeSystemInfoProviderTest + protected 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; + } 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 16e78ab4268b..6d635475b692 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.NodeSystemInfoResponse; 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,21 @@ 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 { 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()); + NodeSystemInfoProvider provider = new NodeSystemInfoProvider(req); + NodeSystemInfoResponse response = provider.getNodeSystemInfo(new NodeSystemInfoResponse()); + V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response); - 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()); - } - } + return; } private CoreContainer getCoreContainer(SolrQueryRequest req) { @@ -263,203 +73,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 +85,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 Set.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 000000000000..79626a9e3718 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeSystemInfo.java @@ -0,0 +1,70 @@ +/* + * 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.NodeSystemInfoResponse; +import org.apache.solr.handler.admin.AdminHandlersProxy; +import org.apache.solr.handler.admin.NodeSystemInfoProvider; +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 NodeSystemInfoResponse getNodeSystemInfo() { + solrQueryResponse.setHttpCaching(false); + + // TODO: AdminHandlersProxy does not support V2: PRs #4057, #3991 + try { + 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); + } + + NodeSystemInfoProvider provider = new NodeSystemInfoProvider(solrQueryRequest); + 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/java/org/apache/solr/handler/admin/api/NodeSystemInfoAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeSystemInfoAPI.java index d7df2cfa0cd2..7a83498dd642 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 endpoint: /node/system. Please use /node/info/system"); handler.handleRequestBody(req, rsp); } } 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 14411a38876b..c26bc57ccf20 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 new file mode 100644 index 000000000000..374adc67f5c8 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/NodeSystemInfoProviderTest.java @@ -0,0 +1,90 @@ +/* + * 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 info = provider.getNodeSystemInfo(new NodeSystemInfoResponse()); + + Assert.assertNotNull(info.nodeInfo); + // these can be validated + 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.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/SystemInfoHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/SystemInfoHandlerTest.java deleted file mode 100644 index 363de728439a..000000000000 --- 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 000000000000..c198e5b7cb1a --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodeSystemInfoTest.java @@ -0,0 +1,204 @@ +/* + * 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.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +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.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.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 { + 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); + } + + @AfterClass + public static void afterClass() throws Exception { + jettyHttpClient.destroy(); + cluster.shutdown(); + } + + @Before + public void beforeTest() throws Exception { + // stop and start Jetty client for each test, otherwise, it seems responses get mixed! + jettyHttpClient.start(); + } + + @After + public void afterTest() throws Exception { + jettyHttpClient.stop(); + } + + /** 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.nodeInfo); + Assert.assertEquals(expectedNode, infoResponse.nodeInfo.node); + // other validations in NodeSystemInfoProviderTest + } + + /** 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()); + } + + String expectedNode = baseV2Url.getHost() + ":" + baseV2Url.getPort() + "_solr"; + Assert.assertNotNull(nlResponse.get("nodeInfo")); + Assert.assertEquals( + expectedNode, (String) ((NamedList) nlResponse.get("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.nodeInfo); + Assert.assertEquals("std", response.nodeInfo.mode); + // Standalone mode : no "node" + Assert.assertNull(response.nodeInfo.node); + // other validations in NodeSystemInfoProviderTest + } +} 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 345c06502566..8367d5cfc326 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,15 @@ 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 +82,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 a34981dc16b7..a2d6341b5d71 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 @@ -21,16 +21,18 @@ 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<>(); + // 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."); @@ -53,7 +55,7 @@ public void setResponse(NamedList response) { nodesInfo.put( node.getKey(), JacksonContentWriter.DEFAULT_MAPPER.convertValue( - node.getValue(), NodeSystemResponse.class)); + node.getValue(), NodeSystemInfoResponse.class)); } } @@ -62,7 +64,8 @@ public void setResponse(NamedList response) { if (nodesInfo.isEmpty()) { nodesInfo.put( null, - JacksonContentWriter.DEFAULT_MAPPER.convertValue(response, NodeSystemResponse.class)); + JacksonContentWriter.DEFAULT_MAPPER.convertValue( + response, NodeSystemInfoResponse.class)); } } else { @@ -70,14 +73,14 @@ public void setResponse(NamedList response) { nodesInfo.put( response.get("node").toString(), JacksonContentWriter.DEFAULT_MAPPER.convertValue( - getResponse(), NodeSystemResponse.class)); + getResponse(), NodeSystemInfoResponse.class)); } } /** 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)'."); @@ -87,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)'."); @@ -109,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)'."); @@ -131,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)'."); @@ -153,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)'."); @@ -180,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(); } @@ -189,16 +192,16 @@ 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} 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(); + return nodesInfo.values().stream().findFirst().get().nodeInfo; } else { throw new UnsupportedOperationException( "Multiple nodes system info available, use method 'getAllNodeResponses', or 'getNodeResponseForNode(String)'."); @@ -206,13 +209,13 @@ public NodeSystemResponse getNodeResponse() { } /** Get all {@code NodeSystemResponse}s */ - public Map getAllNodeResponses() { + public Map getAllNodeResponses() { return nodesInfo; } - /** Get the {@code NodeSystemResponse} for the given node name */ - public NodeSystemResponse getNodeResponseForNode(String node) { - return nodesInfo.get(node); + /** Get the {@code NodeSystemResponse.NodeSystemInfo} for the given node name */ + public NodeSystemInfoResponse.NodeSystemInfo getNodeResponseForNode(String node) { + return nodesInfo.get(node).nodeInfo; } public String getSolrImplVersion() { 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 58f60c507735..714a66c6a575 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,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}. + */ @Override public NamedList processResponse(InputStream stream, String encoding) throws IOException { // TODO SOLR-17549 for error handling, implying a significant ResponseParser API change @@ -68,4 +71,29 @@ public NamedList processResponse(InputStream stream, String encoding) th 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 b26360864c51..1878bf4f4568 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"; 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 f129d63f10e0..c188e35e18f3 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)); } }