Browse Source

Add `getAsRatio` to Settings class, allow DiskThresholdDecider to take percentages

Adds new RatioValue class that parses ratios between 0-100% expressed in
either floating-point (0.13) or percentage (51.12%) notation.

Closes #5690
Lee Hinman 11 years ago
parent
commit
211f740100

+ 3 - 3
docs/reference/index-modules/allocation.asciidoc

@@ -37,7 +37,7 @@ curl -XPUT localhost:9200/test/_settings -d '{
 }'
 --------------------------------------------------
 
-`index.routing.allocation.require.*` can be used to 
+`index.routing.allocation.require.*` can be used to
 specify a number of rules, all of which MUST match in order for a shard
 to be allocated to a node. This is in contrast to `include` which will
 include a node if ANY rule matches.
@@ -117,14 +117,14 @@ Once enabled, Elasticsearch uses two watermarks to decide whether
 shards should be allocated or can remain on the node.
 
 `cluster.routing.allocation.disk.watermark.low` controls the low
-watermark for disk usage. It defaults to 0.70, meaning ES will not
+watermark for disk usage. It defaults to 70%, meaning ES will not
 allocate new shards to nodes once they have more than 70% disk
 used. It can also be set to an absolute byte value (like 500mb) to
 prevent ES from allocating shards if less than the configured amount
 of space is available.
 
 `cluster.routing.allocation.disk.watermark.high` controls the high
-watermark. It defaults to 0.85, meaning ES will attempt to relocate
+watermark. It defaults to 85%, meaning ES will attempt to relocate
 shards to another node if the node disk usage rises above 85%. It can
 also be set to an absolute byte value (similar to the low watermark)
 to relocate shards once less than the configured amount of space is

+ 7 - 9
src/main/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDecider.java

@@ -28,6 +28,7 @@ import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.common.unit.RatioValue;
 import org.elasticsearch.node.settings.NodeSettingsService;
 
 import java.util.Map;
@@ -109,8 +110,8 @@ public class DiskThresholdDecider extends AllocationDecider {
     @Inject
     public DiskThresholdDecider(Settings settings, NodeSettingsService nodeSettingsService) {
         super(settings);
-        String lowWatermark = settings.get(CLUSTER_ROUTING_ALLOCATION_LOW_DISK_WATERMARK, "0.7");
-        String highWatermark = settings.get(CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK, "0.85");
+        String lowWatermark = settings.get(CLUSTER_ROUTING_ALLOCATION_LOW_DISK_WATERMARK, "70%");
+        String highWatermark = settings.get(CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK, "85%");
 
         if (!validWatermarkSetting(lowWatermark)) {
             throw new ElasticsearchParseException("Unable to parse low watermark: [" + lowWatermark + "]");
@@ -307,8 +308,8 @@ public class DiskThresholdDecider extends AllocationDecider {
      */
     public double thresholdPercentageFromWatermark(String watermark) {
         try {
-            return 100.0 * Double.parseDouble(watermark);
-        } catch (NumberFormatException ex) {
+            return RatioValue.parseRatioValue(watermark).getAsPercent();
+        } catch (ElasticsearchParseException ex) {
             return 100.0;
         }
     }
@@ -331,12 +332,9 @@ public class DiskThresholdDecider extends AllocationDecider {
      */
     public boolean validWatermarkSetting(String watermark) {
         try {
-            double w = Double.parseDouble(watermark);
-            if (w < 0 || w > 1.0) {
-                return false;
-            }
+            RatioValue.parseRatioValue(watermark);
             return true;
-        } catch (NumberFormatException e) {
+        } catch (ElasticsearchParseException e) {
             try {
                 ByteSizeValue.parseBytesSizeValue(watermark);
                 return true;

+ 10 - 0
src/main/java/org/elasticsearch/common/settings/ImmutableSettings.java

@@ -388,6 +388,16 @@ public class ImmutableSettings implements Settings {
         return MemorySizeValue.parseBytesSizeValueOrHeapRatio(get(settings, defaultValue));
     }
 
+    @Override
+    public RatioValue getAsRatio(String setting, String defaultValue) throws SettingsException {
+        return RatioValue.parseRatioValue(get(setting, defaultValue));
+    }
+
+    @Override
+    public RatioValue getAsRatio(String[] settings, String defaultValue) throws SettingsException {
+        return RatioValue.parseRatioValue(get(settings, defaultValue));
+    }
+
     @Override
     public SizeValue getAsSize(String setting, SizeValue defaultValue) throws SettingsException {
         return parseSizeValue(get(setting), defaultValue);

+ 15 - 0
src/main/java/org/elasticsearch/common/settings/Settings.java

@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap;
 import org.elasticsearch.Version;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.common.unit.RatioValue;
 import org.elasticsearch.common.unit.SizeValue;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.common.xcontent.ToXContent;
@@ -212,6 +213,20 @@ public interface Settings extends ToXContent {
      */
     ByteSizeValue getAsMemory(String[] setting, String defaultValue) throws SettingsException;
 
+    /**
+     * Returns the setting value (as a RatioValue) associated with the setting key. Provided values can
+     * either be a percentage value (eg. 23%), or expressed as a floating point number (eg. 0.23). If
+     * it does not exist, parses the default value provided.
+     */
+    RatioValue getAsRatio(String setting, String defaultValue) throws SettingsException;
+
+    /**
+     * Returns the setting value (as a RatioValue) associated with the setting key. Provided values can
+     * either be a percentage value (eg. 23%), or expressed as a floating point number (eg. 0.23). If
+     * it does not exist, parses the default value provided.
+     */
+    RatioValue getAsRatio(String[] settings, String defaultValue) throws SettingsException;
+
     /**
      * Returns the setting value (as size) associated with the setting key. If it does not exists,
      * returns the default value provided.

+ 76 - 0
src/main/java/org/elasticsearch/common/unit/RatioValue.java

@@ -0,0 +1,76 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.common.unit;
+
+import org.elasticsearch.ElasticsearchParseException;
+
+/**
+ * Utility class to represent ratio and percentage values between 0 and 100
+ */
+public class RatioValue {
+    private final double percent;
+
+    public RatioValue(double percent) {
+        this.percent = percent;
+    }
+
+    public double getAsRatio() {
+        return this.percent / 100.0;
+    }
+
+    public double getAsPercent() {
+        return this.percent;
+    }
+
+    public String toString() {
+        return this.percent + "%";
+    }
+
+    /**
+     * Parses the provided string as a {@link RatioValue}, the string can
+     * either be in percentage format (eg. 73.5%), or a floating-point ratio
+     * format (eg. 0.735)
+     */
+    public static RatioValue parseRatioValue(String sValue) {
+        if (sValue.endsWith("%")) {
+            final String percentAsString = sValue.substring(0, sValue.length() - 1);
+            try {
+                final double percent = Double.parseDouble(percentAsString);
+                if (percent < 0 || percent > 100) {
+                    throw new ElasticsearchParseException("Percentage should be in [0-100], got " + percentAsString);
+                }
+                return new RatioValue(Math.abs(percent));
+            } catch (NumberFormatException e) {
+                throw new ElasticsearchParseException("Failed to parse [" + percentAsString + "] as a double", e);
+            }
+        } else {
+            try {
+                double ratio = Double.parseDouble(sValue);
+                if (ratio < 0 || ratio > 1.0) {
+                    throw new ElasticsearchParseException("Ratio should be in [0-1.0], got " + ratio);
+                }
+                return new RatioValue(100.0 * Math.abs(ratio));
+            } catch (NumberFormatException e) {
+                throw new ElasticsearchParseException("Invalid ratio or percentage: [" + sValue + "]");
+            }
+
+        }
+    }
+}

+ 2 - 2
src/test/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderTests.java

@@ -155,7 +155,7 @@ public class DiskThresholdDeciderTests extends ElasticsearchAllocationTestCase {
         // node2 now should not have new shards allocated to it, but shards can remain
         diskSettings = settingsBuilder()
                 .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED, true)
-                .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_LOW_DISK_WATERMARK, 0.6)
+                .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_LOW_DISK_WATERMARK, "60%")
                 .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK, 0.7).build();
 
         deciders = new AllocationDeciders(ImmutableSettings.EMPTY,
@@ -430,7 +430,7 @@ public class DiskThresholdDeciderTests extends ElasticsearchAllocationTestCase {
         Settings diskSettings = settingsBuilder()
                 .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED, true)
                 .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_LOW_DISK_WATERMARK, 0.7)
-                .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK, 0.71).build();
+                .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK, "71%").build();
 
         Map<String, DiskUsage> usages = new HashMap<>();
         usages.put("node1", new DiskUsage("node1", 100, 31)); // 69% used

+ 69 - 0
src/test/java/org/elasticsearch/common/unit/RatioValueTests.java

@@ -0,0 +1,69 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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.elasticsearch.common.unit;
+
+import org.elasticsearch.ElasticsearchParseException;
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for the {@link RatioValue} class
+ */
+public class RatioValueTests extends ElasticsearchTestCase {
+
+    @Test
+    public void testParsing() {
+        assertThat(RatioValue.parseRatioValue("100%").toString(), is("100.0%"));
+        assertThat(RatioValue.parseRatioValue("0%").toString(), is("0.0%"));
+        assertThat(RatioValue.parseRatioValue("-0%").toString(), is("0.0%"));
+        assertThat(RatioValue.parseRatioValue("15.1%").toString(), is("15.1%"));
+        assertThat(RatioValue.parseRatioValue("0.1%").toString(), is("0.1%"));
+        assertThat(RatioValue.parseRatioValue("1.0").toString(), is("100.0%"));
+        assertThat(RatioValue.parseRatioValue("0").toString(), is("0.0%"));
+        assertThat(RatioValue.parseRatioValue("-0").toString(), is("0.0%"));
+        assertThat(RatioValue.parseRatioValue("0.0").toString(), is("0.0%"));
+        assertThat(RatioValue.parseRatioValue("-0.0").toString(), is("0.0%"));
+        assertThat(RatioValue.parseRatioValue("0.151").toString(), is("15.1%"));
+        assertThat(RatioValue.parseRatioValue("0.001").toString(), is("0.1%"));
+    }
+
+    @Test
+    public void testNegativeCase() {
+        testInvalidRatio("100.0001%");
+        testInvalidRatio("-0.1%");
+        testInvalidRatio("1a0%");
+        testInvalidRatio("2");
+        testInvalidRatio("-0.01");
+        testInvalidRatio("0.1.0");
+        testInvalidRatio("five");
+        testInvalidRatio("1/2");
+    }
+
+    public void testInvalidRatio(String r) {
+        try {
+            RatioValue.parseRatioValue(r);
+            fail("Value: [" + r + "] should be an invalid ratio");
+        } catch (ElasticsearchParseException e) {
+            // success
+        }
+    }
+}