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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions changelog/unreleased/SOLR-18400-plugins-metrics-disabled.yml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw

public void handleRequest(SolrParams params, BiConsumer<String, Object> consumer) {
if (!enabled) {
consumer.accept("error", "metrics collection is disabled");
return;
throw new SolrException(
SolrException.ErrorCode.INVALID_STATE, "Metrics collection is disabled");
}

Set<String> metricNames = MetricUtils.readParamsAsSet(params, MetricUtils.METRIC_NAME_PARAM);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <metrics enabled="false">
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<Object> 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"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<metrics enabled="false"/>` 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
}
}
5 changes: 4 additions & 1 deletion solr/webapp/web/js/angular/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions solr/webapp/web/js/angular/controllers/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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 = [];
Comment thread
janhoy marked this conversation as resolved.
});
};

Expand Down
3 changes: 3 additions & 0 deletions solr/webapp/web/partials/plugins.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
<div id="plugins" class="clearfix">

<div id="frame">
<div class="message-container" ng-show="metricsDisabled">
<div class="message">Metrics collection is disabled in this node's solr.xml (&lt;metrics enabled="false"&gt;), so no plugin information is available.</div>
</div>
<ul>
<li class="entry" ng-class="{changed: plugin.changed}" ng-repeat="plugin in type.plugins">
<a ng-click="selectPlugin(plugin)">
Expand Down
Loading