This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_10x in repository https://gitbox.apache.org/repos/asf/solr.git
commit c600489a65fc7d6ea23ee98f977736646daa4e95 Author: Prithvi S <[email protected]> AuthorDate: Fri Aug 28 17:20:08 2026 +0530 SOLR-16458: Migrate node properties API to JAX-RS (#4775) Signed-off-by: prithvi <[email protected]> Co-authored-by: Eric Pugh <[email protected]> (cherry picked from commit 340e604d0eda6dabebf572bd4656f4b9b273d27e) --- .../SOLR-16458-migrate-node-properties-api.yml | 8 ++ .../client/api/endpoint/NodePropertiesApi.java | 44 ++++++++ .../client/api/model/NodePropertiesResponse.java | 34 ++++++ .../handler/admin/PropertiesRequestHandler.java | 40 ++++--- .../solr/handler/admin/api/GetNodeProperties.java | 92 ++++++++++++++++ .../solr/handler/admin/api/NodePropertiesAPI.java | 47 -------- .../admin/PropertiesRequestHandlerTest.java | 40 ++++++- .../handler/admin/api/GetNodePropertiesTest.java | 120 +++++++++++++++++++++ .../handler/admin/api/V2NodeAPIMappingTest.java | 24 ----- .../pages/implicit-requesthandlers.adoc | 9 +- .../deployment-guide/pages/jvm-settings.adoc | 4 + .../solr/client/solrj/impl/NodeValueFetcher.java | 16 ++- 12 files changed, 378 insertions(+), 100 deletions(-) diff --git a/changelog/unreleased/SOLR-16458-migrate-node-properties-api.yml b/changelog/unreleased/SOLR-16458-migrate-node-properties-api.yml new file mode 100644 index 00000000000..0b45680bdec --- /dev/null +++ b/changelog/unreleased/SOLR-16458-migrate-node-properties-api.yml @@ -0,0 +1,8 @@ +title: "A single node system property can be fetched at GET /api/node/properties/{propertyName} (v1 still uses ?name=). SolrJ now provides NodeApi.GetNodeProperties and NodeApi.GetNodeProperty." +type: added +authors: + - name: Prithvi S + nick: iprithv +links: + - name: SOLR-16458 + url: https://issues.apache.org/jira/browse/SOLR-16458 diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodePropertiesApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodePropertiesApi.java new file mode 100644 index 00000000000..7c986e5ad82 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodePropertiesApi.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.client.api.endpoint; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.NodePropertiesResponse; + +/** V2 API definition for listing JRE system properties on a Solr node. */ +@Path("/node/properties") +public interface NodePropertiesApi { + + @GET + @Operation( + summary = "List system properties for the target Solr node.", + tags = {"node"}) + NodePropertiesResponse getNodeProperties(); + + @GET + @Path("/{propertyName}") + @Operation( + summary = "Get a single system property for the target Solr node.", + tags = {"node"}) + NodePropertiesResponse getNodeProperty( + @Parameter(description = "Name of the system property to return.") @PathParam("propertyName") + String propertyName); +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/NodePropertiesResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/NodePropertiesResponse.java new file mode 100644 index 00000000000..81ca0331fdd --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/NodePropertiesResponse.java @@ -0,0 +1,34 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.Map; + +/** + * Response body for {@code GET /api/node/properties} and {@code GET + * /api/node/properties/{propertyName}}. + */ +public class NodePropertiesResponse extends SolrJerseyResponse { + + public static final String SYSTEM_PROPERTIES = "system.properties"; + + @Schema(description = "JRE system properties for the Solr node. Secret values are redacted.") + @JsonProperty(SYSTEM_PROPERTIES) + public Map<String, String> systemProperties; +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java index 8658adf3c52..fb290bdd1a2 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java @@ -20,20 +20,22 @@ import static org.apache.solr.common.params.CommonParams.NAME; import java.io.IOException; import java.util.Collection; -import java.util.Enumeration; -import org.apache.solr.api.AnnotatedApi; +import java.util.List; import org.apache.solr.api.Api; -import org.apache.solr.common.util.NamedList; -import org.apache.solr.common.util.SimpleOrderedMap; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.model.NodePropertiesResponse; import org.apache.solr.core.CoreContainer; -import org.apache.solr.core.NodeConfig; import org.apache.solr.handler.RequestHandlerBase; -import org.apache.solr.handler.admin.api.NodePropertiesAPI; +import org.apache.solr.handler.admin.api.GetNodeProperties; +import org.apache.solr.handler.api.V2ApiUtils; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuthorizationContext; /** + * v1 implementation of {@code GET /admin/info/properties}. Business logic lives in {@link + * GetNodeProperties}. + * * @since solr 1.2 */ public class PropertiesRequestHandler extends RequestHandlerBase { @@ -51,21 +53,12 @@ public class PropertiesRequestHandler extends RequestHandlerBase { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException { - NamedList<String> props = new SimpleOrderedMap<>(); - String name = req.getParams().get(NAME); - NodeConfig nodeConfig = getCoreContainer(req).getNodeConfig(); - if (name != null) { - String property = nodeConfig.getRedactedSysPropValue(name); - props.add(name, property); - } else { - Enumeration<?> enumeration = System.getProperties().propertyNames(); - while (enumeration.hasMoreElements()) { - name = (String) enumeration.nextElement(); - props.add(name, nodeConfig.getRedactedSysPropValue(name)); - } - } - rsp.add("system.properties", props); rsp.setHttpCaching(false); + final GetNodeProperties api = new GetNodeProperties(getCoreContainer(req)); + final NodePropertiesResponse response = new NodePropertiesResponse(); + // v1 ?name= returns the key even if unset; the v2 path form 404s for unknown names. + response.systemProperties = api.collectProperties(req.getParams().get(NAME)); + V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response); } //////////////////////// SolrInfoMBeans methods ////////////////////// @@ -82,7 +75,12 @@ public class PropertiesRequestHandler extends RequestHandlerBase { @Override public Collection<Api> getApis() { - return AnnotatedApi.getApis(new NodePropertiesAPI(this)); + return List.of(); + } + + @Override + public Collection<Class<? extends JerseyResource>> getJerseyResources() { + return List.of(GetNodeProperties.class); } @Override diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeProperties.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeProperties.java new file mode 100644 index 00000000000..464ec22c687 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetNodeProperties.java @@ -0,0 +1,92 @@ +/* + * 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.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.endpoint.NodePropertiesApi; +import org.apache.solr.client.api.model.NodePropertiesResponse; +import org.apache.solr.common.SolrException; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.core.NodeConfig; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.security.PermissionNameProvider; + +/** + * V2 API for listing system properties on the receiving node. + * + * <p>GET /api/node/properties lists all properties. GET /api/node/properties/{propertyName} returns + * a single property. Both are analogous to v1 /admin/info/properties, which still uses a {@code + * name} query parameter for the single-property form. + * + * <p>The v1 {@link org.apache.solr.handler.admin.PropertiesRequestHandler} delegates to this class. + */ +public class GetNodeProperties extends JerseyResource implements NodePropertiesApi { + + private final CoreContainer coreContainer; + + @Inject + public GetNodeProperties(CoreContainer coreContainer) { + this.coreContainer = coreContainer; + } + + @Override + @PermissionName(PermissionNameProvider.Name.CONFIG_READ_PERM) + public NodePropertiesResponse getNodeProperties() { + return buildResponse(null); + } + + @Override + @PermissionName(PermissionNameProvider.Name.CONFIG_READ_PERM) + public NodePropertiesResponse getNodeProperty(String propertyName) { + final NodeConfig nodeConfig = coreContainer.getNodeConfig(); + // Hidden names always return 200 + a redacted value, even if unset, so callers cannot + // probe whether a secret is configured. + if (!System.getProperties().containsKey(propertyName) + && !nodeConfig.isSysPropHidden(propertyName)) { + throw new SolrException( + SolrException.ErrorCode.NOT_FOUND, + "No system property named '" + propertyName + "' exists on this node."); + } + return buildResponse(propertyName); + } + + private NodePropertiesResponse buildResponse(String name) { + final NodePropertiesResponse response = instantiateJerseyResponse(NodePropertiesResponse.class); + response.systemProperties = collectProperties(name); + return response; + } + + /** Collect redacted system properties, optionally limited to a single named property. */ + public Map<String, String> collectProperties(String name) { + final NodeConfig nodeConfig = coreContainer.getNodeConfig(); + final Map<String, String> props = new LinkedHashMap<>(); + if (name != null) { + props.put(name, nodeConfig.getRedactedSysPropValue(name)); + } else { + Enumeration<?> enumeration = System.getProperties().propertyNames(); + while (enumeration.hasMoreElements()) { + String propertyName = (String) enumeration.nextElement(); + props.put(propertyName, nodeConfig.getRedactedSysPropValue(propertyName)); + } + } + return props; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodePropertiesAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodePropertiesAPI.java deleted file mode 100644 index d9cf81f8a8b..00000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/NodePropertiesAPI.java +++ /dev/null @@ -1,47 +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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET; -import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM; - -import org.apache.solr.api.EndPoint; -import org.apache.solr.handler.admin.PropertiesRequestHandler; -import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.response.SolrQueryResponse; - -/** - * V2 API for listing system properties for each node. - * - * <p>This API (GET /v2/node/properties) is analogous to the v1 /admin/info/properties. - */ -public class NodePropertiesAPI { - private final PropertiesRequestHandler handler; - - public NodePropertiesAPI(PropertiesRequestHandler handler) { - this.handler = handler; - } - - @EndPoint( - path = {"/node/properties"}, - method = GET, - permission = CONFIG_READ_PERM) - public void getRequestedProperties(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - handler.handleRequestBody(req, rsp); - } -} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/PropertiesRequestHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/PropertiesRequestHandlerTest.java index f64b96ae311..7202a6b5a54 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/PropertiesRequestHandlerTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/PropertiesRequestHandlerTest.java @@ -16,11 +16,13 @@ */ package org.apache.solr.handler.admin; +import java.util.Map; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer; import org.apache.solr.client.solrj.request.GenericSolrRequest; +import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.core.NodeConfig; import org.junit.BeforeClass; @@ -45,7 +47,7 @@ public class PropertiesRequestHandlerTest extends SolrTestCaseJ4 { "some.Secret" }) { System.setProperty(propName, PASSWORD); - NamedList<Object> properties = readProperties(); + Map<String, Object> properties = readProperties(); assertEquals( "Failed to redact " + propName, @@ -54,13 +56,41 @@ public class PropertiesRequestHandlerTest extends SolrTestCaseJ4 { } } + @Test + public void testSingleProperty() throws Exception { + System.setProperty("GetNodeProperties.v1.visible", "hello"); + try { + Map<String, Object> properties = readProperties("GetNodeProperties.v1.visible"); + assertEquals(1, properties.size()); + assertEquals("hello", properties.get("GetNodeProperties.v1.visible")); + } finally { + System.clearProperty("GetNodeProperties.v1.visible"); + } + } + + @Test + public void testMissingPropertyStillReturned() throws Exception { + Map<String, Object> properties = readProperties("GetNodeProperties.v1.doesNotExist"); + assertEquals(1, properties.size()); + assertTrue(properties.containsKey("GetNodeProperties.v1.doesNotExist")); + assertNull(properties.get("GetNodeProperties.v1.doesNotExist")); + } + + private Map<String, Object> readProperties() throws Exception { + return readProperties(null); + } + @SuppressWarnings({"unchecked"}) - private NamedList<Object> readProperties() throws Exception { + private Map<String, Object> readProperties(String name) throws Exception { SolrClient client = new EmbeddedSolrServer(h.getCore()); - + ModifiableSolrParams params = new ModifiableSolrParams(); + if (name != null) { + params.set("name", name); + } NamedList<Object> properties = - client.request(new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/info/properties")); + client.request( + new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/info/properties", params)); - return (NamedList<Object>) properties.get("system.properties"); + return (Map<String, Object>) properties.get("system.properties"); } } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodePropertiesTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodePropertiesTest.java new file mode 100644 index 00000000000..c00a8822310 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetNodePropertiesTest.java @@ -0,0 +1,120 @@ +/* + * 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.util.Collections; +import org.apache.solr.SolrTestCase; +import org.apache.solr.client.api.model.NodePropertiesResponse; +import org.apache.solr.client.solrj.RemoteSolrException; +import org.apache.solr.client.solrj.request.NodeApi; +import org.apache.solr.core.NodeConfig; +import org.apache.solr.util.SolrJettyTestRule; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +/** + * HTTP tests for {@code GET /api/node/properties} and {@code GET + * /api/node/properties/{propertyName}} via the generated SolrJ client classes. + */ +public class GetNodePropertiesTest extends SolrTestCase { + + private static final String VISIBLE_PROP = "GetNodePropertiesTest.visible"; + private static final String SECRET_PROP = "GetNodePropertiesTest.password"; + private static final String PASSWORD = "secret123"; + + @ClassRule public static final SolrJettyTestRule solrTestRule = new SolrJettyTestRule(); + + @BeforeClass + public static void setupSolr() throws Exception { + solrTestRule.startSolr(createTempDir()); + } + + @After + public void clearTestProperties() { + System.clearProperty(VISIBLE_PROP); + System.clearProperty(SECRET_PROP); + } + + @Test + public void testNamedProperty() throws Exception { + var req = new NodeApi.GetNodeProperty("java.version"); + var rsp = req.process(solrTestRule.getAdminClient()); + + assertNotNull(rsp); + assertNull(rsp.error); + assertEquals(1, rsp.systemProperties.size()); + assertEquals(System.getProperty("java.version"), rsp.systemProperties.get("java.version")); + } + + @Test + public void testAllProperties() throws Exception { + System.setProperty(VISIBLE_PROP, "hello"); + + NodePropertiesResponse rsp = fetchProperties(null); + + assertEquals( + Collections.list(System.getProperties().propertyNames()).size(), + rsp.systemProperties.size()); + assertEquals(System.getProperty("java.version"), rsp.systemProperties.get("java.version")); + assertEquals("hello", rsp.systemProperties.get(VISIBLE_PROP)); + } + + @Test + public void testRedactsHiddenProperties() throws Exception { + System.setProperty(SECRET_PROP, PASSWORD); + + NodePropertiesResponse named = fetchProperties(SECRET_PROP); + assertEquals(1, named.systemProperties.size()); + assertEquals(NodeConfig.REDACTED_SYS_PROP_VALUE, named.systemProperties.get(SECRET_PROP)); + assertFalse(named.systemProperties.containsValue(PASSWORD)); + + NodePropertiesResponse all = fetchProperties(null); + assertEquals(NodeConfig.REDACTED_SYS_PROP_VALUE, all.systemProperties.get(SECRET_PROP)); + assertFalse(all.systemProperties.containsValue(PASSWORD)); + } + + @Test + public void testUnknownPropertyReturns404() { + var req = new NodeApi.GetNodeProperty("GetNodePropertiesTest.doesNotExist"); + final RemoteSolrException ex = + expectThrows(RemoteSolrException.class, () -> req.process(solrTestRule.getAdminClient())); + assertEquals(404, ex.code()); + } + + @Test + public void testUnknownHiddenPropertyDoesNotRevealExistence() throws Exception { + final String hiddenUnset = "GetNodePropertiesTest.doesNotExist.password"; + assertFalse(System.getProperties().containsKey(hiddenUnset)); + + NodePropertiesResponse rsp = fetchProperties(hiddenUnset); + assertEquals(1, rsp.systemProperties.size()); + assertEquals(NodeConfig.REDACTED_SYS_PROP_VALUE, rsp.systemProperties.get(hiddenUnset)); + } + + private NodePropertiesResponse fetchProperties(String name) throws Exception { + NodePropertiesResponse rsp = + name == null + ? new NodeApi.GetNodeProperties().process(solrTestRule.getAdminClient()) + : new NodeApi.GetNodeProperty(name).process(solrTestRule.getAdminClient()); + assertNotNull(rsp); + assertNull(rsp.error); + assertNotNull(rsp.systemProperties); + return rsp; + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/V2NodeAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/V2NodeAPIMappingTest.java index 6b3c63de45b..ac2b660bc88 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/V2NodeAPIMappingTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/V2NodeAPIMappingTest.java @@ -35,8 +35,6 @@ import org.apache.solr.common.util.ContentStreamBase; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.admin.CoreAdminHandler; import org.apache.solr.handler.admin.InfoHandler; -import org.apache.solr.handler.admin.LoggingHandler; -import org.apache.solr.handler.admin.PropertiesRequestHandler; import org.apache.solr.handler.admin.ThreadDumpHandler; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrQueryRequestBase; @@ -52,8 +50,6 @@ public class V2NodeAPIMappingTest extends SolrTestCaseJ4 { private ArgumentCaptor<SolrQueryRequest> queryRequestCaptor; private CoreAdminHandler mockCoresHandler; private InfoHandler infoHandler; - private LoggingHandler mockLoggingHandler; - private PropertiesRequestHandler mockPropertiesHandler; private ThreadDumpHandler mockThreadDumpHandler; @BeforeClass @@ -65,13 +61,9 @@ public class V2NodeAPIMappingTest extends SolrTestCaseJ4 { public void setupApiBag() { mockCoresHandler = mock(CoreAdminHandler.class); infoHandler = mock(InfoHandler.class); - mockLoggingHandler = mock(LoggingHandler.class); - mockPropertiesHandler = mock(PropertiesRequestHandler.class); mockThreadDumpHandler = mock(ThreadDumpHandler.class); queryRequestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); - when(infoHandler.getLoggingHandler()).thenReturn(mockLoggingHandler); - when(infoHandler.getPropertiesHandler()).thenReturn(mockPropertiesHandler); when(infoHandler.getThreadDumpHandler()).thenReturn(mockThreadDumpHandler); apiBag = new ApiBag(false); @@ -116,16 +108,6 @@ public class V2NodeAPIMappingTest extends SolrTestCaseJ4 { assertEquals("true", v1Params.get("rejoinAtHead")); } - @Test - public void testSystemPropsApiAllProperties() throws Exception { - final ModifiableSolrParams solrParams = new ModifiableSolrParams(); - solrParams.add("name", "specificPropertyName"); - final SolrParams v1Params = - captureConvertedPropertiesV1Params("/node/properties", "GET", solrParams); - - assertEquals("specificPropertyName", v1Params.get("name")); - } - @Test public void testThreadDumpApiAllProperties() throws Exception { final ModifiableSolrParams solrParams = new ModifiableSolrParams(); @@ -143,11 +125,6 @@ public class V2NodeAPIMappingTest extends SolrTestCaseJ4 { path, method, new ModifiableSolrParams(), v2RequestBody, mockCoresHandler); } - private SolrParams captureConvertedPropertiesV1Params( - String path, String method, SolrParams inputParams) throws Exception { - return doCaptureParams(path, method, inputParams, null, mockPropertiesHandler); - } - private SolrParams captureConvertedThreadDumpV1Params( String path, String method, SolrParams inputParams) throws Exception { return doCaptureParams(path, method, inputParams, null, mockThreadDumpHandler); @@ -188,7 +165,6 @@ public class V2NodeAPIMappingTest extends SolrTestCaseJ4 { ApiBag apiBag, CoreAdminHandler coreHandler, InfoHandler infoHandler) { apiBag.registerObject(new OverseerOperationAPI(coreHandler)); apiBag.registerObject(new RejoinLeaderElectionAPI(coreHandler)); - apiBag.registerObject(new NodePropertiesAPI(infoHandler.getPropertiesHandler())); apiBag.registerObject(new NodeThreadsAPI(infoHandler.getThreadDumpHandler())); } } diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc index 4380337752c..005742a7cec 100644 --- a/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc +++ b/solr/solr-ref-guide/modules/configuration-guide/pages/implicit-requesthandlers.adoc @@ -92,14 +92,21 @@ This handler must have a collection name in the path to the endpoint. |=== System Properties:: Return JRE system properties. +Secret values are redacted. ++ +*Documentation*: xref:deployment-guide:jvm-settings.adoc#java-properties-screen[Java Properties Screen] + [cols="3*.",frame=none,grid=cols,options="header"] |=== |API Endpoints |Class & Javadocs |Paramset |v1: `solr/admin/info/properties` -v2: `api/node/properties` |{solr-javadocs}/core/org/apache/solr/handler/admin/PropertiesRequestHandler.html[PropertiesRequestHandler] |`_ADMIN_PROPERTIES` +v2: `api/node/properties` |v1: {solr-javadocs}/core/org/apache/solr/handler/admin/PropertiesRequestHandler.html[PropertiesRequestHandler] + +v2: {solr-javadocs}/core/org/apache/solr/handler/admin/api/GetNodeProperties.html[GetNodeProperties] |`_ADMIN_PROPERTIES` |=== ++ +To fetch a single property, v1 uses a `name` query parameter (`solr/admin/info/properties?name=java.version`) and v2 uses a path segment (`api/node/properties/java.version`). Segments:: Return info on last commit generation Lucene index segments. + diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/jvm-settings.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/jvm-settings.adoc index a40cdc4073e..5c9dc071163 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/jvm-settings.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/jvm-settings.adoc @@ -72,6 +72,10 @@ Test this by running `java -help` and look for `-server` as an available option A great way to see what JVM settings your server is using, along with other useful information, is to use the `admin` request handler, `/solr/admin/info/system`. This request handler will display a wealth of server statistics and settings. +The JVM's system properties are available from `GET /solr/admin/info/properties` (v1) or `GET /api/node/properties` (v2). +A single property can be requested with v1 `?name=java.version` or v2 `GET /api/node/properties/java.version`. +Secret values are redacted. + === Java Properties Screen Many of the system environment variables include Java settings, and these can be seen on the main Dashboard of the Admin UI. diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java b/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java index abd31c5d86c..917eb59ceea 100644 --- a/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java +++ b/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java @@ -26,6 +26,7 @@ import java.nio.charset.StandardCharsets; import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -243,9 +244,10 @@ public class NodeValueFetcher { ModifiableSolrParams params = new ModifiableSolrParams(); try { SimpleSolrResponse rsp = ctx.invokeWithRetry(ctx.getNode(), "/admin/info/properties", params); - NamedList<?> systemPropsRsp = (NamedList<?>) rsp.getResponse().get("system.properties"); + Object systemPropsRsp = rsp.getResponse().get("system.properties"); for (String requestedProperty : requestedTagNames) { - Object property = systemPropsRsp.get(requestedProperty.substring(SYSPROP_PREFIX.length())); + String key = requestedProperty.substring(SYSPROP_PREFIX.length()); + Object property = getSysProp(systemPropsRsp, key); if (property != null) ctx.tags.put(requestedProperty, property.toString()); } } catch (Exception e) { @@ -253,6 +255,16 @@ public class NodeValueFetcher { } } + private static Object getSysProp(Object systemProperties, String key) { + if (systemProperties instanceof NamedList<?> namedList) { + return namedList.get(key); + } + if (systemProperties instanceof Map<?, ?> map) { + return map.get(key); + } + return null; + } + /** * Retrieve values that match metrics. Metrics names are structured like below: *
