From b54126169b2c2f116b5217c3566f5df2ba206a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Wed, 26 Jun 2019 11:32:11 +0200 Subject: [PATCH] SOLR-13569: AdminUI visual indication of prod/test/dev environment --- solr/CHANGES.txt | 2 + solr/bin/solr.in.cmd | 5 + solr/bin/solr.in.sh | 5 + .../solr/handler/admin/SolrEnvironment.java | 94 +++++++++++++++++++ .../solr/handler/admin/SystemInfoHandler.java | 11 +++ .../handler/admin/SolrEnvironmentTest.java | 73 ++++++++++++++ .../src/taking-solr-to-production.adoc | 9 ++ .../solr/common/cloud/ZkStateReader.java | 3 + solr/webapp/web/css/angular/common.css | 8 +- solr/webapp/web/index.html | 4 +- solr/webapp/web/js/angular/app.js | 12 +++ 11 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/handler/admin/SolrEnvironment.java create mode 100644 solr/core/src/test/org/apache/solr/handler/admin/SolrEnvironmentTest.java diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 5cba5cbf3ef..5dcf499049d 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -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 ---------------------- diff --git a/solr/bin/solr.in.cmd b/solr/bin/solr.in.cmd index 38491953f8d..a831c55d3a7 100755 --- a/solr/bin/solr.in.cmd +++ b/solr/bin/solr.in.cmd @@ -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" diff --git a/solr/bin/solr.in.sh b/solr/bin/solr.in.sh index 832e3cbf550..cd063381ab6 100644 --- a/solr/bin/solr.in.sh +++ b/solr/bin/solr.in.sh @@ -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" diff --git a/solr/core/src/java/org/apache/solr/handler/admin/SolrEnvironment.java b/solr/core/src/java/org/apache/solr/handler/admin/SolrEnvironment.java new file mode 100644 index 00000000000..9a218cb80ca --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/SolrEnvironment.java @@ -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); + } +} 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 d8e10ab32e3..5dcf64cd863 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 @@ -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) { diff --git a/solr/core/src/test/org/apache/solr/handler/admin/SolrEnvironmentTest.java b/solr/core/src/test/org/apache/solr/handler/admin/SolrEnvironmentTest.java new file mode 100644 index 00000000000..268c05666c9 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/SolrEnvironmentTest.java @@ -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"); + } +} \ No newline at end of file diff --git a/solr/solr-ref-guide/src/taking-solr-to-production.adoc b/solr/solr-ref-guide/src/taking-solr-to-production.adoc index 41b70d4738e..873d4e29965 100644 --- a/solr/solr-ref-guide/src/taking-solr-to-production.adoc +++ b/solr/solr-ref-guide/src/taking-solr-to-production.adoc @@ -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: diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java b/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java index 88b67ff4045..816ddb770e1 100644 --- a/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java +++ b/solr/solrj/src/java/org/apache/solr/common/cloud/ZkStateReader.java @@ -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))); /** diff --git a/solr/webapp/web/css/angular/common.css b/solr/webapp/web/css/angular/common.css index 080935c772d..fecffb6d428 100644 --- a/solr/webapp/web/css/angular/common.css +++ b/solr/webapp/web/css/angular/common.css @@ -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; diff --git a/solr/webapp/web/index.html b/solr/webapp/web/index.html index fcf0f821ade..f1e6a2e8fe9 100644 --- a/solr/webapp/web/index.html +++ b/solr/webapp/web/index.html @@ -98,8 +98,6 @@ limitations under the License. Apache SOLR -

 

-
@@ -143,6 +141,8 @@ limitations under the License.