You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ja...@apache.org on 2019/06/26 10:09:12 UTC

[lucene-solr] branch master updated: SOLR-13569: AdminUI visual indication of prod/test/dev environment

This is an automated email from the ASF dual-hosted git repository.

janhoy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/master by this push:
     new b541261  SOLR-13569: AdminUI visual indication of prod/test/dev environment
b541261 is described below

commit b54126169b2c2f116b5217c3566f5df2ba206a39
Author: Jan Høydahl <ja...@apache.org>
AuthorDate: Wed Jun 26 11:32:11 2019 +0200

    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 ++
 .../apache/solr/handler/admin/SolrEnvironment.java | 94 ++++++++++++++++++++++
 .../solr/handler/admin/SystemInfoHandler.java      | 11 +++
 .../solr/handler/admin/SolrEnvironmentTest.java    | 73 +++++++++++++++++
 .../src/taking-solr-to-production.adoc             |  9 +++
 .../apache/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(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 5cba5cb..5dcf499 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 3849195..a831c55 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 832e3cb..cd06338 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 0000000..9a218cb
--- /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 &lt;prod|stage|test|dev&gt;
+   * 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 d8e10ab..5dcf64c 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 0000000..268c056
--- /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 41b70d4..873d4e2 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 C [...]
+
+* `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 88b67ff..816ddb7 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 080935c..fecffb6 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 fcf0f82..f1e6a2e 100644
--- a/solr/webapp/web/index.html
+++ b/solr/webapp/web/index.html
@@ -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>
diff --git a/solr/webapp/web/js/angular/app.js b/solr/webapp/web/js/angular/app.js
index 9abacee..6ba0d4c 100644
--- a/solr/webapp/web/js/angular/app.js
+++ b/solr/webapp/web/js/angular/app.js
@@ -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;