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
This commit is contained in:
Lee Hinman 2014-04-04 10:22:29 -06:00
parent 3248359660
commit 211f740100
7 changed files with 182 additions and 14 deletions

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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.

View File

@ -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 + "]");
}
}
}
}

View File

@ -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

View File

@ -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
}
}
}