diff --git a/changelog/unreleased/SOLR-18400-plugins-metrics-disabled.yml b/changelog/unreleased/SOLR-18400-plugins-metrics-disabled.yml new file mode 100644 index 000000000000..812f62b29f8b --- /dev/null +++ b/changelog/unreleased/SOLR-18400-plugins-metrics-disabled.yml @@ -0,0 +1,11 @@ +title: > + The metrics endpoints now answer HTTP 510 when metrics collection is disabled in solr.xml, + on both the v1 and v2 API. The v1 API used to return HTTP 500, which made the Admin UI + Plugins / Stats screen render blank. It now explains that metrics are disabled. +type: fixed +authors: + - name: Jan Høydahl + - name: Eric Pugh +links: + - name: SOLR-18400 + url: https://issues.apache.org/jira/browse/SOLR-18400 diff --git a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java index 784676656aeb..d8063bfe9883 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java @@ -124,8 +124,8 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw public void handleRequest(SolrParams params, BiConsumer consumer) { if (!enabled) { - consumer.accept("error", "metrics collection is disabled"); - return; + throw new SolrException( + SolrException.ErrorCode.INVALID_STATE, "Metrics collection is disabled"); } Set metricNames = MetricUtils.readParamsAsSet(params, MetricUtils.METRIC_NAME_PARAM); diff --git a/solr/core/src/test/org/apache/solr/handler/admin/MetricsDisabledTest.java b/solr/core/src/test/org/apache/solr/handler/admin/MetricsDisabledTest.java new file mode 100644 index 000000000000..5f70377c444e --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/MetricsDisabledTest.java @@ -0,0 +1,65 @@ +/* + * 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 org.apache.solr.client.solrj.request.MetricsRequest; +import org.apache.solr.client.solrj.response.InputStreamResponseParser; +import org.apache.solr.cloud.SolrCloudTestCase; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.common.params.SolrParams; +import org.apache.solr.common.util.NamedList; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Both the v1 {@code /admin/metrics} and the v2 {@code /api/metrics} endpoint answer HTTP 510 when + * metrics collection is switched off in solr.xml. + */ +public class MetricsDisabledTest extends SolrCloudTestCase { + + private static final String METRICS_V2_PATH = "/metrics"; + + @BeforeClass + public static void setupCluster() throws Exception { + // MiniSolrCloudCluster's default solr.xml has + configureCluster(1).configure(); + } + + @Test + public void testV1MetricsDisabled() throws Exception { + assertMetricsDisabled(CommonParams.METRICS_PATH); + } + + @Test + public void testV2MetricsDisabled() throws Exception { + assertMetricsDisabled(METRICS_V2_PATH); + } + + private static void assertMetricsDisabled(String path) throws Exception { + var req = new MetricsRequest(path, SolrParams.of(CommonParams.WT, "prometheus")); + + NamedList resp = cluster.getSolrClient().request(req); + String body = InputStreamResponseParser.consumeResponseToString(resp); + + assertEquals( + "Expected HTTP 510 from " + path, + SolrException.ErrorCode.INVALID_STATE.code, + (int) (Integer) resp.get(InputStreamResponseParser.HTTP_STATUS_KEY)); + assertTrue(body, body.contains("Metrics collection is disabled")); + } +} diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/metrics-reporting.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/metrics-reporting.adoc index 1be5df0b61b1..880359491fd0 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/metrics-reporting.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/metrics-reporting.adoc @@ -171,6 +171,9 @@ The V2 `/metrics` endpoint is equivalent to the V1 `/admin/metrics` endpoint. Examples on this page show only the V2 endpoint. ==== +Metrics collection can be switched off for a node with `` in `solr.xml`. +Both endpoints then respond with `HTTP 510` and the message `Metrics collection is disabled`, which a Prometheus scraper reports as a target that is down. + === Prometheus See https://prometheus.io/docs/concepts/data_model/[Prometheus Data Model] documentation for more information on its data model. diff --git a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiMetricsDisabledStandaloneTest.java b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiMetricsDisabledStandaloneTest.java new file mode 100644 index 000000000000..7a97de4c6bec --- /dev/null +++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiMetricsDisabledStandaloneTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.webapp; + +import java.nio.file.Path; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openqa.selenium.By; + +/** + * SOLR-18400: the Plugins screen on a node with metrics collection switched off in solr.xml. The + * metrics endpoint answers HTTP 510 there, which the screen must turn into an explanation rather + * than a blank page or the global error banner. + */ +public class AdminUiMetricsDisabledStandaloneTest extends AdminUiStandaloneTestBase { + + private static final String CORE = "collection1"; + + @BeforeClass + public static void startStandaloneNode() throws Exception { + // the base class turned metrics on for the UI screens; this suite is about them being off. + // Must come before the node starts, and is restored after the class with the other properties + System.setProperty("metricsEnabled", "false"); + Path home = buildStandaloneHome(CORE); + standaloneJetty = startStandaloneJetty(home); + baseUrl = standaloneJetty.getBaseUrl().toString(); + assertFalse( + "fixture node should have metrics disabled", + standaloneJetty.getCoreContainer().getConfig().getMetricsConfig().isEnabled()); + } + + @AfterClass + public static void stopStandaloneNode() throws Exception { + if (standaloneJetty != null) { + standaloneJetty.stop(); + standaloneJetty = null; + } + } + + @Test + public void testPluginsScreenExplainsDisabledMetrics() { + openPage(CORE + "/plugins", By.id("plugins")); + + String message = waitForText(By.cssSelector("#plugins .message-container .message")); + assertTrue(message, message.contains("Metrics collection is disabled")); + + // no plugin categories or entries, as there is no metrics data to build them from + assertTrue(driver.findElements(By.cssSelector("#plugins #navigation a[rel]")).isEmpty()); + assertTrue(driver.findElements(By.cssSelector("#plugins #frame li.entry")).isEmpty()); + + // the 510 is handled by the screen itself; the global error banner must stay away + assertTrue( + "global error banner should not show for a disabled feature", + driver.findElements(By.id("http-exception")).isEmpty()); + + // Chrome reports the failed XHR itself at SEVERE level; everything else must be clean + assertNoSevereConsoleErrors("510"); + } +} diff --git a/solr/webapp/web/js/angular/app.js b/solr/webapp/web/js/angular/app.js index 1558918d09ea..aba910499752 100644 --- a/solr/webapp/web/js/angular/app.js +++ b/solr/webapp/web/js/angular/app.js @@ -429,6 +429,9 @@ solrAdminApp.config([ // Schema Designer and Security panels handle errors internally to provide a better user experience than the global error handler var isHandledBySchemaDesigner = rejection.config.url && rejection.config.url.startsWith("/api/schema-designer/"); var isHandledBySecurity = rejection.config.url && rejection.config.url.startsWith("/api/cluster/security/"); + // HTTP 510 means a feature is switched off in solr.xml, e.g. metrics collection. The screen + // asking for that data explains it in place, so skip the global error banner. + var isDisabledFeature = rejection.status === 510 && rejection.config.url && rejection.config.url.endsWith("admin/metrics"); if (rejection.status === 0) { $rootScope.$broadcast('connectionStatusActive'); if (!$rootScope.retryCount) $rootScope.retryCount=0; @@ -463,7 +466,7 @@ solrAdminApp.config([ } else if (isHandledBySecurity) { // Let the security panel surface the detailed error in its own dialog $rootScope.$broadcast('securityApiError', rejection); - } else { + } else if (!isDisabledFeature) { // schema designer prefers to handle errors itself if (!isHandledBySchemaDesigner) { $rootScope.exceptions[rejection.config.url] = rejection.data.error; diff --git a/solr/webapp/web/js/angular/controllers/plugins.js b/solr/webapp/web/js/angular/controllers/plugins.js index bf0dd9bcac73..157639980f03 100644 --- a/solr/webapp/web/js/angular/controllers/plugins.js +++ b/solr/webapp/web/js/angular/controllers/plugins.js @@ -35,6 +35,7 @@ solrAdminApp.controller('PluginsController', var type = $location.search().type; Metrics.raw(params, function (response) { + $scope.metricsDisabled = false; $scope.types = getPluginTypesFromMetrics(response.data, type); $scope.type = getSelectedType($scope.types, type); @@ -44,6 +45,12 @@ solrAdminApp.controller('PluginsController', } else { $scope.plugins = []; } + }, function (response) { + // Solr answers HTTP 510 when metrics collection is turned off in solr.xml + $scope.metricsDisabled = response.status === 510; + $scope.types = []; + $scope.type = null; + $scope.plugins = []; }); }; diff --git a/solr/webapp/web/partials/plugins.html b/solr/webapp/web/partials/plugins.html index 1e80b6f9b6e7..f858f4ce6ddf 100644 --- a/solr/webapp/web/partials/plugins.html +++ b/solr/webapp/web/partials/plugins.html @@ -17,6 +17,9 @@
+
+
Metrics collection is disabled in this node's solr.xml (<metrics enabled="false">), so no plugin information is available.
+