SOLR-13569: AdminUI visual indication of prod/test/dev environment

This commit is contained in:
Jan Høydahl 2019-06-26 11:32:11 +02:00
parent 6751c072ab
commit b54126169b
11 changed files with 222 additions and 4 deletions

View File

@ -146,6 +146,8 @@ New Features
* SOLR-13367: Highlighting: Range queries will now highlight in hl.method=unified mode. (David Smiley)
* SOLR-13569: AdminUI visual indication of prod/test/dev environment (janhoy)
Bug Fixes
----------------------

View File

@ -168,3 +168,8 @@ REM list of hosts needs to be whitelisted or Solr will forbid the request. The w
REM or if you are using the OOTB solr.xml, can be specified using the system property "solr.shardsWhitelist". Alternatively
REM host checking can be disabled by using the system property "solr.disable.shardsWhitelist"
REM set SOLR_OPTS="%SOLR_OPTS% -Dsolr.shardsWhitelist=http://localhost:8983,http://localhost:8984"
REM For a visual indication in the Admin UI of what type of environment this cluster is, configure
REM a -Dsolr.environment property below. Valid values are prod, stage, test, dev, with an optional
REM label or color, e.g. -Dsolr.environment=test,label=Functional+test,color=brown
REM SOLR_OPTS="$SOLR_OPTS -Dsolr.environment=prod"

View File

@ -196,3 +196,8 @@ ENABLE_REMOTE_JMX_OPTS="true"
# or if you are using the OOTB solr.xml, can be specified using the system property "solr.shardsWhitelist". Alternatively
# host checking can be disabled by using the system property "solr.disable.shardsWhitelist"
#SOLR_OPTS="$SOLR_OPTS -Dsolr.shardsWhitelist=http://localhost:8983,http://localhost:8984"
# For a visual indication in the Admin UI of what type of environment this cluster is, configure
# a -Dsolr.environment property below. Valid values are prod, stage, test, dev, with an optional
# label or color, e.g. -Dsolr.environment=test,label=Functional+test,color=brown
#SOLR_OPTS="$SOLR_OPTS -Dsolr.environment=prod"

View File

@ -0,0 +1,94 @@
/*
* 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.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ZkStateReader;
/**
* It is possible to define an environment code when starting Solr, through
* -Dsolr.environment=prod|stage|test|dev or by setting the cluster property "environment".
* This class checks if any of these are defined, and parses the string, which may also
* contain custom overrides for environment name (label) and color to be shown in Admin UI
*/
public class SolrEnvironment {
private String code = "unknown";
private String label;
private String color;
private static Pattern pattern = Pattern.compile("^(prod|stage|test|dev)(,label=([\\w\\d+ _-]+))?(,color=([#\\w\\d]+))?");
public String getCode() {
return code;
}
public String getLabel() {
return label == null ? null : label.replaceAll("\\+", " ");
}
public String getColor() {
return color;
}
public boolean isDefined() {
return !"unknown".equals(code);
}
/**
* Parse an environment string of format <prod|stage|test|dev>
* with an optional label and color as arguments
* @param environmentString the raw string to parse
* @return an instance of this object
*/
public static SolrEnvironment parse(String environmentString) {
SolrEnvironment env = new SolrEnvironment();
if (environmentString == null || environmentString.equalsIgnoreCase("unknown")) {
return env;
}
Matcher m = pattern.matcher(environmentString);
if (!m.matches()) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Bad environment pattern: " + environmentString);
}
env.code = m.group(1);
if (m.group(3) != null) {
env.label = m.group(3);
}
if (m.group(5) != null) {
env.color = m.group(5);
}
return env;
}
/**
* Gets and parses the solr environment configuration string from either
* System properties "solr.environment" or from Clusterprop "environment"
* @param zkStateReader pass in the zkStateReader if in cloud mode
* @return an instance of this class
*/
public static SolrEnvironment getFromSyspropOrClusterprop(ZkStateReader zkStateReader) {
String env = "unknown";
if (System.getProperty("solr.environment") != null) {
env = System.getProperty("solr.environment");
} else if (zkStateReader != null) {
env = zkStateReader.getClusterProperty("environment", "unknown");
}
return SolrEnvironment.parse(env);
}
}

View File

@ -150,6 +150,17 @@ public class SystemInfoHandler extends RequestHandlerBase
if (solrCloudMode) {
rsp.add("node", getCoreContainer(req, core).getZkController().getNodeName());
}
SolrEnvironment env = SolrEnvironment.getFromSyspropOrClusterprop(solrCloudMode ?
getCoreContainer(req, core).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());
}
}
}
private CoreContainer getCoreContainer(SolrQueryRequest req, SolrCore core) {

View File

@ -0,0 +1,73 @@
/*
* 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.common.SolrException;
import org.junit.Test;
import static org.junit.Assert.*;
public class SolrEnvironmentTest {
@Test(expected = SolrException.class)
public void parseWrongKey() {
SolrEnvironment.parse("foo");
}
@Test
public void parsePredefined() {
assertEquals("prod", SolrEnvironment.parse("prod").getCode());
assertNull(SolrEnvironment.parse("prod").getColor());
assertNull(SolrEnvironment.parse("prod").getLabel());
assertEquals("stage", SolrEnvironment.parse("stage").getCode());
assertNull(SolrEnvironment.parse("stage").getColor());
assertNull(SolrEnvironment.parse("stage").getLabel());
assertEquals("test", SolrEnvironment.parse("test").getCode());
assertNull(SolrEnvironment.parse("test").getColor());
assertNull(SolrEnvironment.parse("test").getLabel());
assertEquals("dev", SolrEnvironment.parse("dev").getCode());
assertNull(SolrEnvironment.parse("dev").getColor());
assertNull(SolrEnvironment.parse("dev").getLabel());
}
@Test
public void parseCustom() {
assertEquals("my Label", SolrEnvironment.parse("prod,label=my+Label,color=blue").getLabel());
assertEquals("blue", SolrEnvironment.parse("prod,label=my+Label,color=blue").getColor());
assertEquals("my Label", SolrEnvironment.parse("prod,label=my+Label").getLabel());
assertEquals("blue", SolrEnvironment.parse("prod,color=blue").getColor());
}
@Test(expected = SolrException.class)
public void tryingToHackLabel() {
SolrEnvironment.parse("prod,label=alert('hacked')");
}
@Test(expected = SolrException.class)
public void tryingToHackColor() {
SolrEnvironment.parse("prod,color=alert('hacked')");
}
@Test(expected = SolrException.class)
public void illegalParam() {
SolrEnvironment.parse("prod,foo=hello");
}
}

View File

@ -239,6 +239,15 @@ SOLR_HOST=solr1.example.com
Setting the hostname of the Solr server is recommended, especially when running in SolrCloud mode, as this determines the address of the node when it registers with ZooKeeper.
=== Environment banner in Admin UI
To guard against accidentally doing changes to the wrong cluster, you may configure a visual indication in the Admin UI of whether you currently work with a production environment or not. To do this, edit your `solr.in.sh` or `solr.in.cmd` file with a `-Dsolr.environment=prod` setting, or set the cluster property named `environment`. To specify label and/or color, use a comma delimited format as below. The `+` character can be used instead of space to avoid quoting. Colors may be valid CSS colors or numeric e.g. `#ff0000` for bright red. Examples of valid environment configs:
* `prod`
* `test,label=Functional+test`
* `dev,label=MyDev,color=blue`
* `dev,color=blue`
=== Override Settings in solrconfig.xml
Solr allows configuration properties to be overridden using Java system properties passed at startup using the `-Dproperty=value` syntax. For instance, in `solrconfig.xml`, the default auto soft commit settings are set to:

View File

@ -147,6 +147,8 @@ public class ZkStateReader implements SolrCloseable {
public static final String URL_SCHEME = "urlScheme";
private static final String SOLR_ENVIRONMENT = "environment";
public static final String REPLICA_TYPE = "type";
/**
@ -277,6 +279,7 @@ public class ZkStateReader implements SolrCloseable {
DEFAULT_SHARD_PREFERENCES,
MAX_CORES_PER_NODE,
SAMPLE_PERCENTAGE,
SOLR_ENVIRONMENT,
CollectionAdminParams.DEFAULTS)));
/**

View File

@ -291,9 +291,7 @@ ul
{
background-image: url( ../../img/ico/box.png );
background-position: 5px 50%;
display: none;
font-weight: bold;
margin-top: 10px;
padding: 5px 10px;
padding-left: 26px;
}
@ -309,6 +307,12 @@ ul
color: #fff;
}
#environment.stage
{
background-color: orange;
color: #fff;
}
#environment.test
{
background-color: #f5f5b2;

View File

@ -98,8 +98,6 @@ limitations under the License.
<a href="#/" id="solr"><span>Apache SOLR</span></a>
<p id="environment">&nbsp;</p>
</div>
<div id="main" class="clearfix">
@ -143,6 +141,8 @@ limitations under the License.
<div>
<ul id="menu">
<li id="environment" ng-class="environment" ng-show="showEnvironment" ng-style="environment_color !== undefined ? {'background-color': environment_color} : ''">{{ environment_label }}</li>
<li id="login" class="global" ng-class="{active:page=='login'}" ng-show="http401 || currentUser"><p><a href="#/login">{{http401 ? "Login" : "Logout " + currentUser}}</a></p></li>
<li id="index" class="global" ng-class="{active:page=='index'}"><p><a href="#/">Dashboard</a></p></li>

View File

@ -481,6 +481,18 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
})
}
$scope.showEnvironment = data.environment !== undefined;
if (data.environment) {
$scope.environment = data.environment;
var env_labels = {'prod': 'Production', 'stage': 'Staging', 'test': 'Test', 'dev': 'Development'};
$scope.environment_label = env_labels[data.environment];
if (data.environment_label) {
$scope.environment_label = data.environment_label;
}
if (data.environment_color) {
$scope.environment_color = data.environment_color;
}
}
});
$scope.showingLogging = page.lastIndexOf("logging", 0) === 0;