You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ed...@apache.org on 2022/02/06 03:44:14 UTC
[cassandra] 01/13: Add new custom types and unit tests for configuration patch by Ekaterina Dimitrova; reviewed by Caleb Rackliffe, David Capwell, Michael Semb Wever and Benjamin Lerer for CASSANDRA-15234
This is an automated email from the ASF dual-hosted git repository.
edimitrova pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
commit db9f7a67ec4b03413c10034956e2cf18739ca4b1
Author: Ekaterina Dimitrova <ek...@datastax.com>
AuthorDate: Tue Dec 14 23:00:56 2021 -0500
Add new custom types and unit tests for configuration
patch by Ekaterina Dimitrova; reviewed by Caleb Rackliffe, David Capwell, Michael Semb Wever and Benjamin Lerer for CASSANDRA-15234
---
CHANGES.txt | 1 +
NEWS.txt | 6 +
.../org/apache/cassandra/config/DataRateSpec.java | 378 ++++++++++++++++++++
.../apache/cassandra/config/DataStorageSpec.java | 397 +++++++++++++++++++++
.../org/apache/cassandra/config/DurationSpec.java | 342 ++++++++++++++++++
.../apache/cassandra/config/DataRateSpecTest.java | 136 +++++++
.../cassandra/config/DataStorageSpecTest.java | 141 ++++++++
.../apache/cassandra/config/DurationSpecTest.java | 160 +++++++++
8 files changed, 1561 insertions(+)
diff --git a/CHANGES.txt b/CHANGES.txt
index 4304aa7..66aae18 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
4.1
+ * Standardize storage configuration parameters' names. Support unit suffixes. (CASSANDRA-15234)
* Remove support for Windows (CASSANDRA-16956)
* Runtime-configurable YAML option to prohibit USE statements (CASSANDRA-17318)
* When streaming sees a ClosedChannelException this triggers the disk failure policy (CASSANDRA-17116)
diff --git a/NEWS.txt b/NEWS.txt
index ff166df..966a729 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -81,6 +81,12 @@ New features
Upgrading
---------
+ - There is a new cassandra.yaml version 2. Units suffixes should be provided for all rates(B/s|MiB/s|KiB/s|MiB/s),
+ memory (KiB|MiB|GiB|B) and duration(d|h|s|ms|us|µs|ns|m)
+ parameters. (CASSANDRA-15234)
+ Backward compatibility with the old cassandra.yaml file will be in place until at least the next major version.
+ - Many cassandra.yaml parameters' names have been changed. Full list can be found on ...... (ADD LINK LATER WHEN PAGE
+ IS CREATED) (CASSANDRA-15234)
- Before you upgrade, if you are using `cassandra.auth_bcrypt_gensalt_log2_rounds` property,
confirm it is set to value lower than 31 otherwise Cassandra will fail to start. See CASSANDRA-9384
for further details. You also need to regenerate passwords for users for who the password
diff --git a/src/java/org/apache/cassandra/config/DataRateSpec.java b/src/java/org/apache/cassandra/config/DataRateSpec.java
new file mode 100644
index 0000000..3512513
--- /dev/null
+++ b/src/java/org/apache/cassandra/config/DataRateSpec.java
@@ -0,0 +1,378 @@
+/*
+ * 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.cassandra.config;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import com.google.common.primitives.Ints;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+
+/**
+ * Represents a data rate type used for cassandra configuration. It supports the opportunity for the users to be able to
+ * add units to the confiuration parameter value. (CASSANDRA-15234)
+ */
+public final class DataRateSpec
+{
+ /**
+ * The Regexp used to parse the rate provided as String in cassandra.yaml.
+ */
+ private static final Pattern BIT_RATE_UNITS_PATTERN = Pattern.compile("^(\\d+)(MiB/s|KiB/s|B/s)$");
+
+ private final double quantity;
+
+ private final DataRateUnit unit;
+
+ public DataRateSpec(String value)
+ {
+ //parse the string field value
+ Matcher matcher = BIT_RATE_UNITS_PATTERN.matcher(value);
+
+ if (!matcher.find())
+ throw new ConfigurationException("Invalid bit rate: " + value + " Accepted units: MiB/s, KiB/s, B/s where " +
+ "case matters and " + "only non-negative values are valid");
+
+ quantity = Long.parseLong(matcher.group(1));
+ unit = DataRateUnit.fromSymbol(matcher.group(2));
+ }
+
+ DataRateSpec(double quantity, DataRateUnit unit)
+ {
+ if (quantity < 0)
+ throw new ConfigurationException("Invalid bit rare: value must be non-negative");
+
+ if (quantity > Long.MAX_VALUE)
+ throw new NumberFormatException("Invalid bit rate: value must be between 0 and Long.MAX_VALUE = 9223372036854775807");
+
+ this.quantity = quantity;
+ this.unit = unit;
+ }
+
+ /**
+ * Creates a {@code DataRateSpec} of the specified amount of bits per second.
+ *
+ * @param bytesPerSecond the amount of bytes per second
+ * @return a {@code DataRateSpec}
+ */
+ public static DataRateSpec inBytesPerSecond(long bytesPerSecond)
+ {
+ return new DataRateSpec(bytesPerSecond, DataRateUnit.BYTES_PER_SECOND);
+ }
+
+ /**
+ * Creates a {@code DataRateSpec} of the specified amount of kibibytes per second.
+ *
+ * @param kibibytesPerSecond the amount of kibibytes per second
+ * @return a {@code DataRateSpec}
+ */
+ public static DataRateSpec inKibibytesPerSecond(long kibibytesPerSecond)
+ {
+ return new DataRateSpec(kibibytesPerSecond, DataRateUnit.KIBIBYTES_PER_SECOND);
+ }
+
+ /**
+ * Creates a {@code DataRateSpec} of the specified amount of mebibytes per second.
+ *
+ * @param mebibytesPerSecond the amount of mebibytes per second
+ * @return a {@code DataRateSpec}
+ */
+ public static DataRateSpec inMebibytesPerSecond(long mebibytesPerSecond)
+ {
+ return new DataRateSpec(mebibytesPerSecond, DataRateUnit.MEBIBYTES_PER_SECOND);
+ }
+
+ /**
+ * Creates a {@code DataRateSpec} of the specified amount of mebibytes per second.
+ *
+ * @param megabitsPerSecond the amount of megabits per second
+ * @return a {@code DataRateSpec}
+ */
+ public static DataRateSpec megabitsPerSecondInMebibytesPerSecond(long megabitsPerSecond)
+ {
+ final double MEBIBYTES_PER_MEGABIT = 0.119209289550781;
+ double mebibytesPerSecond = (double)megabitsPerSecond * MEBIBYTES_PER_MEGABIT;
+
+ return new DataRateSpec(mebibytesPerSecond, DataRateUnit.MEBIBYTES_PER_SECOND);
+ }
+
+ /**
+ * @return the data rate unit assigned.
+ */
+ public DataRateUnit getUnit()
+ {
+ return unit;
+ }
+
+ /**
+ * @return the data rate in bytes per second
+ */
+ public double toBytesPerSecond()
+ {
+ return unit.toBytesPerSecond(quantity);
+ }
+
+ /**
+ * Returns the data rate in bytes per second as an {@code int}
+ *
+ * @return the data rate in bytes per second or {@code Integer.MAX_VALUE} if the rate is too large.
+ */
+ public int toBytesPerSecondAsInt()
+ {
+ return Ints.saturatedCast(Math.round(toBytesPerSecond()));
+ }
+
+ /**
+ * @return the data rate in kibibytes per second
+ */
+ public double toKibibytesPerSecond()
+ {
+ return unit.toKibibytesPerSecond(quantity);
+ }
+
+ /**
+ * Returns the data rate in kibibytes per second as an {@code int}
+ *
+ * @return the data rate in kibibytes per second or {@code Integer.MAX_VALUE} if the number of kibibytes is too large.
+ */
+ public int toKibibytesPerSecondAsInt()
+ {
+ return Ints.saturatedCast(Math.round(toKibibytesPerSecond()));
+ }
+
+ /**
+ * @return the data rate in mebibytes per second
+ */
+ public double toMebibytesPerSecond()
+ {
+ return unit.toMebibytesPerSecond(quantity);
+ }
+
+ /**
+ * Returns the data rate in mebibytes per second as an {@code int}
+ *
+ * @return the data rate in mebibytes per second or {@code Integer.MAX_VALUE} if the number of mebibytes is too large.
+ */
+ public int toMebibytesPerSecondAsInt()
+ {
+ return Ints.saturatedCast(Math.round(toMebibytesPerSecond()));
+ }
+
+ /**
+ * This method is required in order to support backward compatibility with the old unit used for a few Data Rate
+ * parameters before CASSANDRA-15234
+ *
+ * @return the data rate in megabits per second.
+ */
+ public double toMegabitsPerSecond()
+ {
+ return unit.toMegabitsPerSecond(quantity);
+ }
+
+ /**
+ * Returns the data rate in megabits per second as an {@code int}. This method is required in order to support
+ * backward compatibility with the old unit used for a few Data Rate parameters before CASSANDRA-15234
+ *
+ * @return the data rate in mebibytes per second or {@code Integer.MAX_VALUE} if the number of mebibytes is too large.
+ */
+ public int toMegabitsPerSecondAsInt()
+ {
+ return Ints.saturatedCast(Math.round(toMegabitsPerSecond()));
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(unit.toKibibytesPerSecond(quantity));
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+
+ if (!(obj instanceof DataRateSpec))
+ return false;
+
+ DataRateSpec other = (DataRateSpec) obj;
+ if (unit == other.unit)
+ return quantity == other.quantity;
+
+ // Due to overflows we can only guarantee that the 2 data rates are equal if we get the same results
+ // doing the conversion in both directions.
+ return unit.convert(other.quantity, other.unit) == quantity && other.unit.convert(quantity, unit) == other.quantity;
+ }
+
+ @Override
+ public String toString()
+ {
+ return Math.round(quantity) + unit.symbol;
+ }
+
+ public enum DataRateUnit
+ {
+ BYTES_PER_SECOND("B/s")
+ {
+ public double toBytesPerSecond(double d)
+ {
+ return d;
+ }
+
+ public double toKibibytesPerSecond(double d)
+ {
+ return d / 1024.0;
+ }
+
+ public double toMebibytesPerSecond(double d)
+ {
+ return d / (1024.0 * 1024.0);
+ }
+
+ public double toMegabitsPerSecond(double d) { return (d / 125000.0); }
+
+ public double convert(double source, DataRateUnit sourceUnit)
+ {
+ return sourceUnit.toBytesPerSecond(source);
+ }
+ },
+ KIBIBYTES_PER_SECOND("KiB/s")
+ {
+ public double toBytesPerSecond(double d)
+ {
+ return x(d, 1024.0, (MAX / 1024.0));
+ }
+
+ public double toKibibytesPerSecond(double d)
+ {
+ return d;
+ }
+
+ public double toMebibytesPerSecond(double d)
+ {
+ return d / 1024.0;
+ }
+
+ public double toMegabitsPerSecond(double d)
+ {
+ return d / 122.0;
+ }
+
+ public double convert(double source, DataRateUnit sourceUnit)
+ {
+ return sourceUnit.toKibibytesPerSecond(source);
+ }
+ },
+ MEBIBYTES_PER_SECOND("MiB/s")
+ {
+ public double toBytesPerSecond(double d)
+ {
+ return x(d, (1024.0 * 1024.0), (MAX / (1024.0 * 1024.0)));
+ }
+
+ public double toKibibytesPerSecond(double d)
+ {
+ return x(d, 1024.0, (MAX / 1024.0));
+ }
+
+ public double toMebibytesPerSecond(double d)
+ {
+ return d;
+ }
+
+ public double toMegabitsPerSecond(double d)
+ {
+ if (d > MAX / (MEGABITS_PER_MEBIBYTE))
+ return MAX;
+ return Math.round(d * MEGABITS_PER_MEBIBYTE);
+ }
+
+ public double convert(double source, DataRateUnit sourceUnit)
+ {
+ return sourceUnit.toMebibytesPerSecond(source);
+ }
+ };
+
+ static final double MAX = Long.MAX_VALUE;
+ static final double MEGABITS_PER_MEBIBYTE = 8.388608;
+
+ /**
+ * Scale d by m, checking for overflow. This has a short name to make above code more readable.
+ */
+ static double x(double d, double m, double over)
+ {
+ assert (over > 0.0) && (over < (MAX - 1)) && (over == (MAX / m));
+
+ if (d > over)
+ return MAX;
+ return d * m;
+ }
+
+ /**
+ * @param symbol the unit symbol
+ * @return the rate unit corresponding to the given symbol
+ */
+ public static DataRateUnit fromSymbol(String symbol)
+ {
+ for (DataRateUnit value : values())
+ {
+ if (value.symbol.equalsIgnoreCase(symbol))
+ return value;
+ }
+ throw new ConfigurationException(String.format("Unsupported data rate unit: %s. Supported units are: %s",
+ symbol, Arrays.stream(values())
+ .map(u -> u.symbol)
+ .collect(Collectors.joining(", "))));
+ }
+
+ /**
+ * The unit symbol
+ */
+ private final String symbol;
+
+ DataRateUnit(String symbol)
+ {
+ this.symbol = symbol;
+ }
+
+ public double toBytesPerSecond(double d)
+ {
+ throw new AbstractMethodError();
+ }
+
+ public double toKibibytesPerSecond(double d)
+ {
+ throw new AbstractMethodError();
+ }
+
+ public double toMebibytesPerSecond(double d)
+ {
+ throw new AbstractMethodError();
+ }
+
+ public double toMegabitsPerSecond(double d) { throw new AbstractMethodError(); }
+
+ public double convert(double source, DataRateUnit sourceUnit)
+ {
+ throw new AbstractMethodError();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/config/DataStorageSpec.java b/src/java/org/apache/cassandra/config/DataStorageSpec.java
new file mode 100644
index 0000000..72ed2ba
--- /dev/null
+++ b/src/java/org/apache/cassandra/config/DataStorageSpec.java
@@ -0,0 +1,397 @@
+/*
+ * 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.cassandra.config;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import com.google.common.primitives.Ints;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+
+/**
+ * Represents an amount of data storage. Wrapper class for Cassandra configuration parameters, providing to the
+ * users the opportunity to be able to provide config with a unit of their choice in cassandra.yaml as per the available
+ * options. (CASSANDRA-15234)
+ */
+public final class DataStorageSpec
+{
+ /**
+ * The Regexp used to parse the storage provided as String.
+ */
+ private static final Pattern STORAGE_UNITS_PATTERN = Pattern.compile("^(\\d+)(GiB|MiB|KiB|B)$");
+
+ private final long quantity;
+
+ private final DataStorageUnit unit;
+
+ public DataStorageSpec(String value)
+ {
+ if (value == null || value.equals("null"))
+ {
+ quantity = 0;
+ unit = DataStorageUnit.MEBIBYTES; // the unit doesn't really matter as 0 is 0 in all units
+ return;
+ }
+
+ //parse the string field value
+ Matcher matcher = STORAGE_UNITS_PATTERN.matcher(value);
+
+ if (!matcher.find())
+ {
+ throw new ConfigurationException("Invalid data storage: " + value + " Accepted units: B, KiB, MiB, GiB" +
+ " where case matters and only non-negative values are accepted");
+ }
+
+ quantity = Long.parseLong(matcher.group(1));
+ unit = DataStorageUnit.fromSymbol(matcher.group(2));
+ }
+
+ DataStorageSpec(long quantity, DataStorageUnit unit)
+ {
+ if (quantity < 0)
+ throw new ConfigurationException("Invalid data storage: value must be positive, but was " + quantity);
+
+ this.quantity = quantity;
+ this.unit = unit;
+ }
+
+ /**
+ * Creates a {@code DataStorageSpec} of the specified amount of bytes.
+ *
+ * @param bytes the amount of bytes
+ * @return a {@code DataStorageSpec}
+ */
+ public static DataStorageSpec inBytes(long bytes)
+ {
+ return new DataStorageSpec(bytes, DataStorageUnit.BYTES);
+ }
+
+ /**
+ * Creates a {@code DataStorageSpec} of the specified amount of kibibytes.
+ *
+ * @param kibibytes the amount of kibibytes
+ * @return a {@code DataStorageSpec}
+ */
+ public static DataStorageSpec inKibibytes(long kibibytes)
+ {
+ return new DataStorageSpec(kibibytes, DataStorageUnit.KIBIBYTES);
+ }
+
+ /**
+ * Creates a {@code DataStorageSpec} of the specified amount of mebibytes.
+ *
+ * @param mebibytes the amount of mebibytes
+ * @return a {@code DataStorageSpec}
+ */
+ public static DataStorageSpec inMebibytes(long mebibytes)
+ {
+ return new DataStorageSpec(mebibytes, DataStorageUnit.MEBIBYTES);
+ }
+
+ /**
+ * @return the data storage unit.
+ */
+ public DataStorageUnit getUnit()
+ {
+ return unit;
+ }
+
+ /**
+ * @return the amount of data storage in bytes
+ */
+ public long toBytes()
+ {
+ return unit.toBytes(quantity);
+ }
+
+ /**
+ * Returns the amount of data storage in bytes as an {@code int}
+ *
+ * @return the amount of data storage in bytes or {@code Integer.MAX_VALUE} if the number of bytes is too large.
+ */
+ public int toBytesAsInt()
+ {
+ return Ints.saturatedCast(toBytes());
+ }
+
+ /**
+ * @return the amount of data storage in kibibytes
+ */
+ public long toKibibytes()
+ {
+ return unit.toKibibytes(quantity);
+ }
+
+ /**
+ * Returns the amount of data storage in kibibytes as an {@code int}
+ *
+ * @return the amount of data storage in kibibytes or {@code Integer.MAX_VALUE} if the number of kibibytes is too large.
+ */
+ public int toKibibytesAsInt()
+ {
+ return Ints.saturatedCast(toKibibytes());
+ }
+
+ /**
+ * @return the amount of data storage in mebibytes
+ */
+ public long toMebibytes()
+ {
+ return unit.toMebibytes(quantity);
+ }
+
+ /**
+ * Returns the amount of data storage in mebibytes as an {@code int}
+ *
+ * @return the amount of data storage in mebibytes or {@code Integer.MAX_VALUE} if the number of mebibytes is too large.
+ */
+ public int toMebibytesAsInt()
+ {
+ return Ints.saturatedCast(toMebibytes());
+ }
+
+ /**
+ * @return the amount of data storage in gibibytes
+ */
+ public long toGibibytes()
+ {
+ return unit.toGibibytes(quantity);
+ }
+
+ /**
+ * Returns the amount of data storage in gibibytes as an {@code int}
+ *
+ * @return the amount of data storage in gibibytes or {@code Integer.MAX_VALUE} if the number of gibibytes is too large.
+ */
+ public int toGibibytesAsInt()
+ {
+ return Ints.saturatedCast(toGibibytes());
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(unit.toKibibytes(quantity));
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+
+ if (!(obj instanceof DataStorageSpec))
+ return false;
+
+ DataStorageSpec other = (DataStorageSpec) obj;
+ if (unit == other.unit)
+ return quantity == other.quantity;
+
+ // Due to overflows we can only guarantee that the 2 storages are equal if we get the same results
+ // doing the convertion in both directions.
+ return unit.convert(other.quantity, other.unit) == quantity && other.unit.convert(quantity, unit) == other.quantity;
+ }
+
+ @Override
+ public String toString()
+ {
+ return quantity + unit.symbol;
+ }
+
+ public enum DataStorageUnit
+ {
+ BYTES("B")
+ {
+ public long toBytes(long d)
+ {
+ return d;
+ }
+
+ public long toKibibytes(long d)
+ {
+ return (d / 1024L);
+ }
+
+ public long toMebibytes(long d)
+ {
+ return (d / (1024L * 1024));
+ }
+
+ public long toGibibytes(long d)
+ {
+ return (d / (1024L * 1024 * 1024));
+ }
+
+ public long convert(long source, DataStorageUnit sourceUnit)
+ {
+ return sourceUnit.toBytes(source);
+ }
+ },
+ KIBIBYTES("KiB")
+ {
+ public long toBytes(long d)
+ {
+ return x(d, 1024L, (MAX / 1024L));
+ }
+
+ public long toKibibytes(long d)
+ {
+ return d;
+ }
+
+ public long toMebibytes(long d)
+ {
+ return (d / 1024L);
+ }
+
+ public long toGibibytes(long d)
+ {
+ return (d / (1024L * 1024));
+ }
+
+ public long convert(long source, DataStorageUnit sourceUnit)
+ {
+ return sourceUnit.toKibibytes(source);
+ }
+ },
+ MEBIBYTES("MiB")
+ {
+ public long toBytes(long d)
+ {
+ return x(d, (1024L * 1024), MAX / (1024L * 1024));
+ }
+
+ public long toKibibytes(long d)
+ {
+ return x(d, 1024L, (MAX / 1024L));
+ }
+
+ public long toMebibytes(long d)
+ {
+ return d;
+ }
+
+ public long toGibibytes(long d)
+ {
+ return (d / 1024L);
+ }
+
+ public long convert(long source, DataStorageUnit sourceUnit)
+ {
+ return sourceUnit.toMebibytes(source);
+ }
+ },
+ GIBIBYTES("GiB")
+ {
+ public long toBytes(long d)
+ {
+ return x(d, (1024L * 1024 * 1024), MAX / (1024L * 1024 * 1024));
+ }
+
+ public long toKibibytes(long d)
+ {
+ return x(d, (1024L * 1024), MAX / (1024L * 1024));
+ }
+
+ public long toMebibytes(long d)
+ {
+ return x(d, 1024L, (MAX / 1024L));
+ }
+
+ public long toGibibytes(long d)
+ {
+ return d;
+ }
+
+ public long convert(long source, DataStorageUnit sourceUnit)
+ {
+ return sourceUnit.toGibibytes(source);
+ }
+ };
+
+ /**
+ * Scale d by m, checking for overflow. This has a short name to make above code more readable.
+ */
+ static long x(long d, long m, long over)
+ {
+ assert (over > 0) && (over < (MAX-1L)) && (over == (MAX / m));
+
+ if (d > over)
+ return Long.MAX_VALUE;
+ return Math.multiplyExact(d, m);
+ }
+
+ /**
+ * @param symbol the unit symbol
+ * @return the memory unit corresponding to the given symbol
+ */
+ public static DataStorageUnit fromSymbol(String symbol)
+ {
+ for (DataStorageUnit value : values())
+ {
+ if (value.symbol.equalsIgnoreCase(symbol))
+ return value;
+ }
+ throw new ConfigurationException(String.format("Unsupported data storage unit: %s. Supported units are: %s",
+ symbol, Arrays.stream(values())
+ .map(u -> u.symbol)
+ .collect(Collectors.joining(", "))));
+ }
+
+ static final long MAX = Long.MAX_VALUE;
+
+ /**
+ * The unit symbol
+ */
+ private final String symbol;
+
+ DataStorageUnit(String symbol)
+ {
+ this.symbol = symbol;
+ }
+
+ public long toBytes(long d)
+ {
+ throw new AbstractMethodError();
+ }
+
+ public long toKibibytes(long d)
+ {
+ throw new AbstractMethodError();
+ }
+
+ public long toMebibytes(long d)
+ {
+ throw new AbstractMethodError();
+ }
+
+ public long toGibibytes(long d)
+ {
+ throw new AbstractMethodError();
+ }
+
+ public long convert(long source, DataStorageUnit sourceUnit)
+ {
+ throw new AbstractMethodError();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/config/DurationSpec.java b/src/java/org/apache/cassandra/config/DurationSpec.java
new file mode 100644
index 0000000..796fde6
--- /dev/null
+++ b/src/java/org/apache/cassandra/config/DurationSpec.java
@@ -0,0 +1,342 @@
+/*
+ * 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.cassandra.config;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import com.google.common.primitives.Ints;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+
+/**
+ * Represents a positive time duration. Wrapper class for Cassandra duration configuration parameters, providing to the
+ * users the opportunity to be able to provide config with a unit of their choice in cassandra.yaml as per the available
+ * options. (CASSANDRA-15234)
+ */
+public final class DurationSpec
+{
+ /**
+ * The Regexp used to parse the duration provided as String.
+ */
+ private static final Pattern TIME_UNITS_PATTERN = Pattern.compile(("^(\\d+)(d|h|s|ms|us|µs|ns|m)"));
+ private static final Pattern VALUES_PATTERN = Pattern.compile(("\\d+"));
+
+ public final long quantity;
+
+ private final TimeUnit unit;
+
+ public DurationSpec(String value)
+ {
+ if (value == null || value.equals("null") || value.toLowerCase(Locale.ROOT).equals("nan"))
+ {
+ quantity = 0;
+ unit = TimeUnit.MILLISECONDS;
+ return;
+ }
+
+ //parse the string field value
+ Matcher matcher = TIME_UNITS_PATTERN.matcher(value);
+
+ if(matcher.find())
+ {
+ quantity = Long.parseLong(matcher.group(1));
+ unit = fromSymbol(matcher.group(2));
+ }
+ else
+ {
+ throw new ConfigurationException("Invalid duration: " + value + " Accepted units: d, h, m, s, ms, us, µs," +
+ " ns where case matters and " + "only non-negative values");
+ }
+ }
+
+ DurationSpec(long quantity, TimeUnit unit)
+ {
+ if (quantity < 0)
+ throw new ConfigurationException("Invalid duration: value must be positive");
+
+ this.quantity = quantity;
+ this.unit = unit;
+ }
+
+ private DurationSpec(double quantity, TimeUnit unit)
+ {
+ this(Math.round(quantity), unit);
+ }
+
+ /**
+ * Creates a {@code DurationSpec} of the specified amount of milliseconds.
+ *
+ * @param milliseconds the amount of milliseconds
+ * @return a duration
+ */
+ public static DurationSpec inMilliseconds(long milliseconds)
+ {
+ return new DurationSpec(milliseconds, TimeUnit.MILLISECONDS);
+ }
+
+ public static DurationSpec inDoubleMilliseconds(double milliseconds)
+ {
+ return new DurationSpec(milliseconds, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Creates a {@code DurationSpec} of the specified amount of seconds.
+ *
+ * @param seconds the amount of seconds
+ * @return a duration
+ */
+ public static DurationSpec inSeconds(long seconds)
+ {
+ return new DurationSpec(seconds, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Creates a {@code DurationSpec} of the specified amount of minutes.
+ *
+ * @param minutes the amount of minutes
+ * @return a duration
+ */
+ public static DurationSpec inMinutes(long minutes)
+ {
+ return new DurationSpec(minutes, TimeUnit.MINUTES);
+ }
+
+ /**
+ * Creates a {@code DurationSpec} of the specified amount of hours.
+ *
+ * @param hours the amount of hours
+ * @return a duration
+ */
+ public static DurationSpec inHours(long hours)
+ {
+ return new DurationSpec(hours, TimeUnit.HOURS);
+ }
+
+ /**
+ * Creates a {@code DurationSpec} of the specified amount of seconds. Custom method for special cases.
+ *
+ * @param value which can be in the old form only presenting the quantity or the post CASSANDRA-15234 form - a
+ * value consisting of quantity and unit. This method is necessary for three parameters which didn't change their
+ * names but only their value format. (key_cache_save_period, row_cache_save_period, counter_cache_save_period)
+ * @return a duration
+ */
+ public static DurationSpec inSecondsString(String value)
+ {
+ //parse the string field value
+ Matcher matcher = VALUES_PATTERN.matcher(value);
+
+ long seconds;
+ //if the provided string value is just a number, then we create a Duration Spec value in seconds
+ if (matcher.matches())
+ {
+ seconds = Long.parseLong(value);
+ return new DurationSpec(seconds, TimeUnit.SECONDS);
+ }
+
+ //otherwise we just use the standard constructors
+ return new DurationSpec(value);
+ }
+
+ /**
+ * @param symbol the time unit symbol
+ * @return the time unit associated to the specified symbol
+ */
+ static TimeUnit fromSymbol(String symbol)
+ {
+ switch (symbol.toLowerCase())
+ {
+ case "d": return TimeUnit.DAYS;
+ case "h": return TimeUnit.HOURS;
+ case "m": return TimeUnit.MINUTES;
+ case "s": return TimeUnit.SECONDS;
+ case "ms": return TimeUnit.MILLISECONDS;
+ case "us":
+ case "µs": return TimeUnit.MICROSECONDS;
+ case "ns": return TimeUnit.NANOSECONDS;
+ }
+ throw new ConfigurationException(String.format("Unsupported time unit: %s. Supported units are: %s",
+ symbol, Arrays.stream(TimeUnit.values())
+ .map(DurationSpec::getSymbol)
+ .collect(Collectors.joining(", "))));
+ }
+
+ /**
+ * @param targetUnit the time unit
+ * @return this duration in the specified time unit
+ */
+ public long to(TimeUnit targetUnit)
+ {
+ return targetUnit.convert(quantity, unit);
+ }
+
+ /**
+ * @return this duration in number of hours
+ */
+ public long toHours()
+ {
+ return unit.toHours(quantity);
+ }
+
+ /**
+ * Returns this duration in number of minutes as an {@code int}
+ *
+ * @return this duration in number of minutes or {@code Integer.MAX_VALUE} if the number of minutes is too large.
+ */
+ public int toHoursAsInt()
+ {
+ return Ints.saturatedCast(toHours());
+ }
+
+ /**
+ * @return this duration in number of minutes
+ */
+ public long toMinutes()
+ {
+ return unit.toMinutes(quantity);
+ }
+
+ /**
+ * Returns this duration in number of minutes as an {@code int}
+ *
+ * @return this duration in number of minutes or {@code Integer.MAX_VALUE} if the number of minutes is too large.
+ */
+ public int toMinutesAsInt()
+ {
+ return Ints.saturatedCast(toMinutes());
+ }
+
+ /**
+ * @return this duration in number of seconds
+ */
+ public long toSeconds()
+ {
+ return unit.toSeconds(quantity);
+ }
+
+ /**
+ * Returns this duration in number of seconds as an {@code int}
+ *
+ * @return this duration in number of seconds or {@code Integer.MAX_VALUE} if the number of seconds is too large.
+ */
+ public int toSecondsAsInt()
+ {
+ return Ints.saturatedCast(toSeconds());
+ }
+
+ /**
+ * @return this duration in number of nanoseconds
+ */
+ public long toNanoseconds()
+ {
+ return unit.toNanos(quantity);
+ }
+
+ /**
+ * Returns this duration in number of nanoseconds as an {@code int}
+ *
+ * @return this duration in number of nanoseconds or {@code Integer.MAX_VALUE} if the number of nanoseconds is too large.
+ */
+ public int toNanosecondsAsInt()
+ {
+ return Ints.saturatedCast(toNanoseconds());
+ }
+
+ /**
+ * @return this duration in number of milliseconds
+ */
+ public long toMilliseconds()
+ {
+ return unit.toMillis(quantity);
+ }
+
+ /**
+ * @return the duration value in milliseconds
+ */
+ public static long toMilliseconds(DurationSpec quantity)
+ {
+ return quantity.toMilliseconds();
+ }
+
+ /**
+ * Returns this duration in number of milliseconds as an {@code int}
+ *
+ * @return this duration in number of milliseconds or {@code Integer.MAX_VALUE} if the number of milliseconds is too large.
+ */
+ public int toMillisecondsAsInt()
+ {
+ return Ints.saturatedCast(toMilliseconds());
+ }
+
+ @Override
+ public int hashCode()
+ {
+ // Milliseconds seems to be a reasonable tradeoff
+ return Objects.hash(unit.toMillis(quantity));
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+
+ if (!(obj instanceof DurationSpec))
+ return false;
+
+ DurationSpec other = (DurationSpec) obj;
+ if (unit == other.unit)
+ return quantity == other.quantity;
+
+ // Due to overflows we can only guarantee that the 2 durations are equal if we get the same results
+ // doing the conversion in both directions.
+ return unit.convert(other.quantity, other.unit) == quantity && other.unit.convert(quantity, unit) == other.quantity;
+ }
+
+ @Override
+ public String toString()
+ {
+ return quantity + getSymbol(unit);
+ }
+
+ /**
+ * Returns the symbol associated to the specified unit
+ *
+ * @param unit the time unit
+ * @return the time unit symbol
+ */
+ static String getSymbol(TimeUnit unit)
+ {
+ switch (unit)
+ {
+ case DAYS: return "d";
+ case HOURS: return "h";
+ case MINUTES: return "m";
+ case SECONDS: return "s";
+ case MILLISECONDS: return "ms";
+ case MICROSECONDS: return "us";
+ case NANOSECONDS: return "ns";
+ }
+ throw new AssertionError();
+ }
+}
diff --git a/test/unit/org/apache/cassandra/config/DataRateSpecTest.java b/test/unit/org/apache/cassandra/config/DataRateSpecTest.java
new file mode 100644
index 0000000..73625fb
--- /dev/null
+++ b/test/unit/org/apache/cassandra/config/DataRateSpecTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.cassandra.config;
+
+import org.junit.Test;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.quicktheories.core.Gen;
+import org.quicktheories.generators.SourceDSL;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.*;
+import static org.quicktheories.QuickTheory.qt;
+
+public class DataRateSpecTest
+{
+ @Test
+ public void testConversions()
+ {
+ assertEquals(10, new DataRateSpec("10B/s").toBytesPerSecond(), 0);
+ assertEquals(10240, new DataRateSpec("10KiB/s").toBytesPerSecond(), 0);
+ assertEquals(0, new DataRateSpec("10KiB/s").toMebibytesPerSecond(), 0.1);
+ assertEquals(10240, new DataRateSpec("10MiB/s").toKibibytesPerSecond(), 0);
+ assertEquals(10485760, new DataRateSpec("10MiB/s").toBytesPerSecond(), 0);
+ assertEquals(10485760, new DataRateSpec("10MiB/s").toBytesPerSecond(), 0);
+ assertEquals(new DataRateSpec("24MiB/s").toString(), DataRateSpec.megabitsPerSecondInMebibytesPerSecond(200L).toString());
+ }
+
+ @Test
+ public void testOverflowingDuringConversion()
+ {
+ assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807B/s").toBytesPerSecond(), 0);
+ assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807B/s").toBytesPerSecondAsInt(), 0);
+ assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807KiB/s").toBytesPerSecond(), 0);
+ assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807KiB/s").toBytesPerSecondAsInt(), 0);
+ assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toBytesPerSecond(), 0);
+ assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toBytesPerSecondAsInt(), 0);
+ assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toBytesPerSecond(), 0);
+ assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toBytesPerSecondAsInt(), 0);
+
+ assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toMegabitsPerSecond(), 0);
+ assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toMegabitsPerSecondAsInt());
+
+ assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807KiB/s").toKibibytesPerSecond(), 0);
+ assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807KiB/s").toKibibytesPerSecondAsInt());
+ assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toKibibytesPerSecond(), 0);
+ assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toKibibytesPerSecondAsInt());
+
+ assertEquals(Long.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toMebibytesPerSecond(), 0);
+ assertEquals(Integer.MAX_VALUE, new DataRateSpec("9223372036854775807MiB/s").toMebibytesPerSecondAsInt());
+ }
+
+ @Test
+ public void testFromSymbol()
+ {
+ assertEquals(DataRateSpec.DataRateUnit.fromSymbol("B/s"), DataRateSpec.DataRateUnit.BYTES_PER_SECOND);
+ assertEquals(DataRateSpec.DataRateUnit.fromSymbol("KiB/s"), DataRateSpec.DataRateUnit.KIBIBYTES_PER_SECOND);
+ assertEquals(DataRateSpec.DataRateUnit.fromSymbol("MiB/s"), DataRateSpec.DataRateUnit.MEBIBYTES_PER_SECOND);
+ assertThatThrownBy(() -> DataRateSpec.DataRateUnit.fromSymbol("n"))
+ .isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Unsupported data rate unit: n");
+ }
+
+ @Test
+ public void testInvalidInputs()
+ {
+ assertThatThrownBy(() -> new DataRateSpec("10")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid bit rate: 10");
+ assertThatThrownBy(() -> new DataRateSpec("-10b/s")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid bit rate: -10b/s");
+ assertThatThrownBy(() -> new DataRateSpec("10xb/s")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid bit rate: 10xb/s");
+ assertThatThrownBy(() -> new DataRateSpec("9223372036854775809B/s")
+ .toBytesPerSecond()).isInstanceOf(NumberFormatException.class)
+ .hasMessageContaining("For input string: \"9223372036854775809\"");
+ }
+
+ @Test
+ public void testEquals()
+ {
+ assertEquals(new DataRateSpec("10B/s"), new DataRateSpec("10B/s"));
+ assertEquals(new DataRateSpec("10KiB/s"), new DataRateSpec("10240B/s"));
+ assertEquals(new DataRateSpec("10240B/s"), new DataRateSpec("10KiB/s"));
+ assertEquals(DataRateSpec.inMebibytesPerSecond(Long.MAX_VALUE), DataRateSpec.inMebibytesPerSecond(Long.MAX_VALUE));
+ assertNotEquals(DataRateSpec.inMebibytesPerSecond(Long.MAX_VALUE), DataRateSpec.inBytesPerSecond(Long.MAX_VALUE));
+ assertNotEquals(new DataRateSpec("0KiB/s"), new DataRateSpec("10MiB/s"));
+ }
+
+ @Test
+ public void thereAndBack()
+ {
+ Gen<DataRateSpec.DataRateUnit> unitGen = SourceDSL.arbitrary().enumValues(DataRateSpec.DataRateUnit.class);
+ Gen<Long> valueGen = SourceDSL.longs().between(0, Long.MAX_VALUE);
+ qt().forAll(valueGen, unitGen).check((value, unit) -> {
+ DataRateSpec there = new DataRateSpec(value, unit);
+ DataRateSpec back = new DataRateSpec(there.toString());
+ DataRateSpec BACK = new DataRateSpec(there.toString());
+ return there.equals(back) && there.equals(BACK);
+ });
+ }
+
+ @Test
+ public void eq()
+ {
+ qt().forAll(gen(), gen()).check((a, b) -> a.equals(b) == b.equals(a));
+ }
+
+ @Test
+ public void eqAndHash()
+ {
+ qt().forAll(gen(), gen()).check((a, b) -> !a.equals(b) || a.hashCode() == b.hashCode());
+ }
+
+ private static Gen<DataRateSpec> gen()
+ {
+ Gen<DataRateSpec.DataRateUnit> unitGen = SourceDSL.arbitrary().enumValues(DataRateSpec.DataRateUnit.class);
+ Gen<Long> valueGen = SourceDSL.longs().between(0, Long.MAX_VALUE);
+ Gen<DataRateSpec> gen = rs -> new DataRateSpec(valueGen.generate(rs), unitGen.generate(rs));
+ return gen.describedAs(DataRateSpec::toString);
+ }
+}
\ No newline at end of file
diff --git a/test/unit/org/apache/cassandra/config/DataStorageSpecTest.java b/test/unit/org/apache/cassandra/config/DataStorageSpecTest.java
new file mode 100644
index 0000000..66c6444
--- /dev/null
+++ b/test/unit/org/apache/cassandra/config/DataStorageSpecTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.cassandra.config;
+
+import java.util.Locale;
+
+import org.junit.Test;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.quicktheories.core.Gen;
+import org.quicktheories.generators.SourceDSL;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.*;
+import static org.quicktheories.QuickTheory.qt;
+
+public class DataStorageSpecTest
+{
+ @Test
+ public void testConversions()
+ {
+ assertEquals(10, new DataStorageSpec("10B").toBytes());
+ assertEquals(10240, new DataStorageSpec("10KiB").toBytes());
+ assertEquals(0, new DataStorageSpec("10KiB").toMebibytes());
+ assertEquals(10240, new DataStorageSpec("10MiB").toKibibytes());
+ assertEquals(10485760, new DataStorageSpec("10MiB").toBytes());
+ }
+
+ @Test
+ public void testOverflowingDuringConversion()
+ {
+ assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807B").toBytes());
+ assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807B").toBytesAsInt());
+ assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807KiB").toBytes());
+ assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807KiB").toBytesAsInt());
+ assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toBytes());
+ assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toBytesAsInt());
+ assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toBytes());
+ assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toBytesAsInt());
+
+ assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807KiB").toKibibytes());
+ assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807KiB").toKibibytesAsInt());
+ assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toKibibytes());
+ assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toKibibytesAsInt());
+ assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toKibibytes());
+ assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toKibibytesAsInt());
+
+ assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toMebibytes());
+ assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807MiB").toMebibytesAsInt());
+ assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toMebibytes());
+ assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toMebibytesAsInt());
+
+ assertEquals(Long.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toGibibytes());
+ assertEquals(Integer.MAX_VALUE, new DataStorageSpec("9223372036854775807GiB").toGibibytesAsInt());
+ }
+
+ @Test
+ public void testFromSymbol()
+ {
+ assertEquals(DataStorageSpec.DataStorageUnit.fromSymbol("B"), DataStorageSpec.DataStorageUnit.BYTES);
+ assertEquals(DataStorageSpec.DataStorageUnit.fromSymbol("KiB"), DataStorageSpec.DataStorageUnit.KIBIBYTES);
+ assertEquals(DataStorageSpec.DataStorageUnit.fromSymbol("MiB"), DataStorageSpec.DataStorageUnit.MEBIBYTES);
+ assertEquals(DataStorageSpec.DataStorageUnit.fromSymbol("GiB"), DataStorageSpec.DataStorageUnit.GIBIBYTES);
+ assertThatThrownBy(() -> DataStorageSpec.DataStorageUnit.fromSymbol("n"))
+ .isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Unsupported data storage unit: n");
+ }
+
+ @Test
+ public void testInvalidInputs()
+ {
+ assertThatThrownBy(() -> new DataStorageSpec("10")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid data storage: 10");
+ assertThatThrownBy(() -> new DataStorageSpec("-10bps")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid data storage: -10bps");
+ assertThatThrownBy(() -> new DataStorageSpec("-10b")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid data storage: -10b");
+ assertThatThrownBy(() -> new DataStorageSpec("10HG")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid data storage: 10HG");
+ assertThatThrownBy(() -> new DataStorageSpec("9223372036854775809B")
+ .toBytes()).isInstanceOf(NumberFormatException.class)
+ .hasMessageContaining("For input string: \"9223372036854775809\"");
+ }
+
+ @Test
+ public void testEquals()
+ {
+ assertEquals(new DataStorageSpec("10B"), new DataStorageSpec("10B"));
+ assertEquals(new DataStorageSpec("10KiB"), new DataStorageSpec("10240B"));
+ assertEquals(new DataStorageSpec("10240B"), new DataStorageSpec("10KiB"));
+ assertEquals(DataStorageSpec.inMebibytes(Long.MAX_VALUE), DataStorageSpec.inMebibytes(Long.MAX_VALUE));
+ assertNotEquals(DataStorageSpec.inMebibytes(Long.MAX_VALUE), DataStorageSpec.inKibibytes(Long.MAX_VALUE));
+ assertNotEquals(DataStorageSpec.inMebibytes(Long.MAX_VALUE), DataStorageSpec.inBytes(Long.MAX_VALUE));
+ assertNotEquals(new DataStorageSpec("0MiB"), new DataStorageSpec("10KiB"));
+ }
+
+ @Test
+ public void thereAndBack()
+ {
+ qt().forAll(gen()).check(there -> {
+ DataStorageSpec back = new DataStorageSpec(there.toString());
+ DataStorageSpec BACK = new DataStorageSpec(there.toString().toUpperCase(Locale.ROOT).replace("I", "i"));
+ return there.equals(back) && there.equals(BACK);
+ });
+ }
+
+ @Test
+ public void eq()
+ {
+ qt().forAll(gen(), gen()).check((a, b) -> a.equals(b) == b.equals(a));
+ }
+
+ @Test
+ public void eqAndHash()
+ {
+ qt().forAll(gen(), gen()).check((a, b) -> !a.equals(b) || a.hashCode() == b.hashCode());
+ }
+
+ private static Gen<DataStorageSpec> gen()
+ {
+ Gen<DataStorageSpec.DataStorageUnit> unitGen = SourceDSL.arbitrary().enumValues(DataStorageSpec.DataStorageUnit.class);
+ Gen<Long> valueGen = SourceDSL.longs().between(0, Long.MAX_VALUE);
+ Gen<DataStorageSpec> gen = rs -> new DataStorageSpec(valueGen.generate(rs), unitGen.generate(rs));
+ return gen.describedAs(DataStorageSpec::toString);
+ }
+}
\ No newline at end of file
diff --git a/test/unit/org/apache/cassandra/config/DurationSpecTest.java b/test/unit/org/apache/cassandra/config/DurationSpecTest.java
new file mode 100644
index 0000000..84733d8
--- /dev/null
+++ b/test/unit/org/apache/cassandra/config/DurationSpecTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.cassandra.config;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.quicktheories.core.Gen;
+import org.quicktheories.generators.SourceDSL;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.*;
+import static org.quicktheories.QuickTheory.qt;
+
+public class DurationSpecTest
+{
+ @Test
+ public void testConversions()
+ {
+ assertEquals(10L, new DurationSpec("10s").toSeconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toSecondsAsInt());
+ assertEquals(10000, new DurationSpec("10s").toMilliseconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toMillisecondsAsInt());
+ assertEquals(0, new DurationSpec("10s").toMinutes());
+ assertEquals(10, new DurationSpec("10m").toMinutes());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toMinutesAsInt());
+ assertEquals(600000, new DurationSpec("10m").toMilliseconds());
+ assertEquals(600, new DurationSpec("10m").toSeconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toSecondsAsInt());
+ assertEquals(DurationSpec.inDoubleMilliseconds(0.7), new DurationSpec("1ms"));
+ assertEquals(DurationSpec.inDoubleMilliseconds(0.33), new DurationSpec("0ms"));
+ assertEquals(DurationSpec.inDoubleMilliseconds(0.333), new DurationSpec("0ms"));
+ }
+
+ @Test
+ public void testFromSymbol()
+ {
+ assertEquals(DurationSpec.fromSymbol("ms"), TimeUnit.MILLISECONDS);
+ assertEquals(DurationSpec.fromSymbol("d"), TimeUnit.DAYS);
+ assertEquals(DurationSpec.fromSymbol("h"), TimeUnit.HOURS);
+ assertEquals(DurationSpec.fromSymbol("m"), TimeUnit.MINUTES);
+ assertEquals(DurationSpec.fromSymbol("s"), TimeUnit.SECONDS);
+ assertEquals(DurationSpec.fromSymbol("us"), TimeUnit.MICROSECONDS);
+ assertEquals(DurationSpec.fromSymbol("µs"), TimeUnit.MICROSECONDS);
+ assertEquals(DurationSpec.fromSymbol("ns"), TimeUnit.NANOSECONDS);
+ assertThatThrownBy(() -> DurationSpec.fromSymbol("n")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Unsupported time unit: n");
+ }
+
+ @Test
+ public void testGetSymbol()
+ {
+ assertEquals(DurationSpec.getSymbol(TimeUnit.MILLISECONDS), "ms");
+ assertEquals(DurationSpec.getSymbol(TimeUnit.DAYS), "d");
+ assertEquals(DurationSpec.getSymbol(TimeUnit.HOURS), "h");
+ assertEquals(DurationSpec.getSymbol(TimeUnit.MINUTES), "m");
+ assertEquals(DurationSpec.getSymbol(TimeUnit.SECONDS), "s");
+ assertEquals(DurationSpec.getSymbol(TimeUnit.MICROSECONDS), "us");
+ assertEquals(DurationSpec.getSymbol(TimeUnit.NANOSECONDS), "ns");
+ }
+
+ @Test
+ public void testInvalidInputs()
+ {
+ assertThatThrownBy(() -> new DurationSpec("10")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid duration: 10");
+ assertThatThrownBy(() -> new DurationSpec("-10s")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid duration: -10s");
+ assertThatThrownBy(() -> new DurationSpec("10xd")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid duration: 10xd");
+ assertThatThrownBy(() -> new DurationSpec("0.333555555ms")).isInstanceOf(ConfigurationException.class)
+ .hasMessageContaining("Invalid duration: 0.333555555ms");
+ }
+
+ @Test
+ public void testEquals()
+ {
+ assertEquals(new DurationSpec("10s"), new DurationSpec("10s"));
+ assertEquals(new DurationSpec("10s"), new DurationSpec("10000ms"));
+ assertEquals(new DurationSpec("10000ms"), new DurationSpec("10s"));
+ assertEquals(DurationSpec.inMinutes(Long.MAX_VALUE), DurationSpec.inMinutes(Long.MAX_VALUE));
+ assertEquals(new DurationSpec("4h"), new DurationSpec("14400s"));
+ assertEquals(DurationSpec.inSecondsString("14400"), new DurationSpec("14400s"));
+ assertEquals(DurationSpec.inSecondsString("4h"), new DurationSpec("14400s"));
+ assertEquals(DurationSpec.inSecondsString("14400s"), new DurationSpec("14400s"));
+ assertEquals(DurationSpec.inHours(Long.MAX_VALUE),DurationSpec.inHours(Long.MAX_VALUE));
+ assertNotEquals(DurationSpec.inMinutes(Long.MAX_VALUE), DurationSpec.inMilliseconds(Long.MAX_VALUE));
+ assertNotEquals(new DurationSpec("0m"), new DurationSpec("10ms"));
+ }
+
+ @Test
+ public void thereAndBack()
+ {
+ Gen<TimeUnit> unitGen = SourceDSL.arbitrary().enumValues(TimeUnit.class);
+ Gen<Long> valueGen = SourceDSL.longs().between(0, Long.MAX_VALUE);
+ qt().forAll(valueGen, unitGen).check((value, unit) -> {
+ DurationSpec there = new DurationSpec(value, unit);
+ DurationSpec back = new DurationSpec(there.toString());
+ return there.equals(back);
+ });
+ }
+
+ @Test
+ public void testOverflowingDuringConversion()
+ {
+ // we are heavily dependent on the Java TimeUnit for our configuration of type duration. We want to be sure
+ // that any regression in handlining overflow will be caught quickly on our end
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807ns").toNanoseconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807ns").toNanosecondsAsInt());
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807ms").toNanoseconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807ms").toNanosecondsAsInt());
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807s").toNanoseconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toNanosecondsAsInt());
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807m").toNanoseconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807m").toNanosecondsAsInt());
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807h").toNanoseconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807h").toNanosecondsAsInt());
+
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807ms").toMilliseconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807ms").toMillisecondsAsInt());
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807s").toMilliseconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toMillisecondsAsInt());
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807m").toMilliseconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807m").toMillisecondsAsInt());
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807h").toMilliseconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807h").toMillisecondsAsInt());
+
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807s").toSeconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807s").toSecondsAsInt());
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807m").toSeconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807m").toSecondsAsInt());
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807h").toSeconds());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807h").toSecondsAsInt());
+
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807m").toMinutes());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807m").toMinutesAsInt());
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807h").toMinutes());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807h").toMinutesAsInt());
+
+ assertEquals(Long.MAX_VALUE, new DurationSpec("9223372036854775807h").toHours());
+ assertEquals(Integer.MAX_VALUE, new DurationSpec("9223372036854775807h").toHoursAsInt());
+ }
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org