Remove old Duration class in favor of DurationSpec class

patch by Ekaterina Dimitrova; reviewed by Caleb Rackliffe, David Capwell, Michael Semb Wever and Benjamin Lerer for CASSANDRA-15234
This commit is contained in:
Ekaterina Dimitrova 2022-02-05 17:51:32 -05:00
parent c51a7c66fc
commit 9f56bf4ca7
10 changed files with 30 additions and 366 deletions

View File

@ -441,7 +441,7 @@ counter_cache_save_period: 7200s
# saved_caches_directory: /var/lib/cassandra/saved_caches
# Number of seconds the server will wait for each cache (row, key, etc ...) to load while starting
# the Cassandra process. Setting this to a negative value is equivalent to disabling all cache loading on startup
# the Cassandra process. Setting this to zero is equivalent to disabling all cache loading on startup
# while still having the cache during runtime.
# cache_load_timeout: 30s
@ -1467,15 +1467,15 @@ audit_logging_options:
# default options for full query logging - these can be overridden from command line when executing
# nodetool enablefullquerylog
#full_query_logging_options:
# log_dir:
# roll_cycle: HOURLY
# block: true
# max_queue_weight: 268435456 # 256 MiB
# max_log_size: 17179869184 # 16 GiB
## archive command is "/path/to/script.sh %path" where %path is replaced with the file being rolled:
# archive_command:
# max_archive_retries: 10
# full_query_logging_options:
# log_dir:
# roll_cycle: HOURLY
# block: true
# max_queue_weight: 268435456 # 256 MiB
# max_log_size: 17179869184 # 16 GiB
## archive command is "/path/to/script.sh %path" where %path is replaced with the file being rolled:
# archive_command:
# max_archive_retries: 10
# validate tombstones on reads and compaction
# can be either "disabled", "warn" or "exception"

View File

@ -1,276 +0,0 @@
/*
* 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.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.google.common.primitives.Ints;
/**
* Represents a positive time duration.
*/
public final class Duration
{
/**
* The Regexp used to parse the duration provided as String.
*/
private static final Pattern TIME_UNITS_PATTERN = Pattern.compile(("^(\\d+)([a-zA-Z]{1,2}|µs|µS)$"));
private static final Pattern DOUBLE_TIME_UNITS_PATTERN = Pattern.compile(("^(\\d+\\.\\d+)([a-zA-Z]{1,2}|µs|µS)$"));
private final long quantity;
private final TimeUnit unit;
public Duration(String value)
{
if (value == null || value.equals("null"))
{
quantity = 0;
unit = TimeUnit.MILLISECONDS;
return;
}
//parse the string field value
Matcher matcher = TIME_UNITS_PATTERN.matcher(value);
Matcher matcherDouble = DOUBLE_TIME_UNITS_PATTERN.matcher(value);
if(matcher.find())
{
quantity = Long.parseLong(matcher.group(1));
unit = fromSymbol(matcher.group(2));
}
else if(matcherDouble.find())
{
quantity =(long) Double.parseDouble(matcherDouble.group(1));
unit = fromSymbol(matcherDouble.group(2));
}
else {
throw new IllegalArgumentException("Invalid duration: " + value);
}
}
private Duration(long quantity, TimeUnit unit)
{
if (quantity < 0)
throw new IllegalArgumentException("Duration must be positive");
this.quantity = quantity;
this.unit = unit;
}
private Duration(double quantity, TimeUnit unit)
{
if (quantity < 0)
throw new IllegalArgumentException("Duration must be positive");
this.quantity = (long) quantity;
this.unit = unit;
}
/**
* Creates a {@code Duration} of the specified amount of milliseconds.
*
* @param milliseconds the amount of milliseconds
* @return a duration
*/
public static Duration inMilliseconds(long milliseconds)
{
return new Duration(milliseconds, TimeUnit.MILLISECONDS);
}
public static Duration inDoubleMilliseconds(double milliseconds)
{
return new Duration(milliseconds, TimeUnit.MILLISECONDS);
}
/**
* Creates a {@code Duration} of the specified amount of seconds.
*
* @param seconds the amount of seconds
* @return a duration
*/
public static Duration inSeconds(long seconds)
{
return new Duration(seconds, TimeUnit.SECONDS);
}
/**
* Creates a {@code Duration} of the specified amount of minutes.
*
* @param minutes the amount of minutes
* @return a duration
*/
public static Duration inMinutes(long minutes)
{
return new Duration(minutes, TimeUnit.MINUTES);
}
/**
* Returns the time unit associated to the specified symbol
*
* @param symbol the time unit symbol
* @return the time unit associated to the specified symbol
*/
private 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 IllegalArgumentException(String.format("Unsupported time unit: %s. Supported units are: %s",
symbol, Arrays.stream(TimeUnit.values())
.map(Duration::getSymbol)
.collect(Collectors.joining(", "))));
}
/**
* Returns this duration in the specified time unit
*
* @param targetUnit the time unit
* @return this duration in the specified time unit
*/
public long to(TimeUnit targetUnit)
{
return targetUnit.convert(quantity, unit);
}
/**
* Returns this duration in number of minutes
*
* @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());
}
/**
* Returns this duration in number of seconds
*
* @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());
}
/**
* Returns this duration in number of milliseconds
*
* @return this duration in number of milliseconds
*/
public long toMilliseconds()
{
return unit.toMillis(quantity);
}
/**
* 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 Duration))
return false;
Duration other = (Duration) 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 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 + getSymbol(unit);
}
/**
* Returns the symbol associated to the specified unit
*
* @param unit the time unit
* @return the time unit symbol
*/
private 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();
}
}

View File

@ -52,7 +52,6 @@ import org.apache.cassandra.utils.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.Duration;
import org.apache.cassandra.cache.*;
import org.apache.cassandra.concurrent.*;
import org.apache.cassandra.config.*;
@ -1870,7 +1869,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
/**
* @param ephemeral If this flag is set to true, the snapshot will be cleaned during next startup
*/
public TableSnapshot snapshotWithoutFlush(String snapshotName, Predicate<SSTableReader> predicate, boolean ephemeral, Duration ttl, RateLimiter rateLimiter, Instant creationTime)
public TableSnapshot snapshotWithoutFlush(String snapshotName, Predicate<SSTableReader> predicate, boolean ephemeral, DurationSpec ttl, RateLimiter rateLimiter, Instant creationTime)
{
if (ephemeral && ttl != null)
{
@ -1900,7 +1899,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
return createSnapshot(snapshotName, ephemeral, ttl, snapshottedSSTables, creationTime);
}
protected TableSnapshot createSnapshot(String tag, boolean ephemeral, Duration ttl, Set<SSTableReader> sstables, Instant creationTime) {
protected TableSnapshot createSnapshot(String tag, boolean ephemeral, DurationSpec ttl, Set<SSTableReader> sstables, Instant creationTime) {
Set<File> snapshotDirs = sstables.stream()
.map(s -> Directories.getSnapshotDirectory(s.descriptor, tag).toAbsolute())
.filter(dir -> !Directories.isSecondaryIndexFolder(dir)) // Remove secondary index subdirectory
@ -2064,7 +2063,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
* @param rateLimiter Rate limiter for hardlinks-per-second
* @param creationTime time when this snapshot was taken
*/
public TableSnapshot snapshot(String snapshotName, boolean skipFlush, Duration ttl, RateLimiter rateLimiter, Instant creationTime)
public TableSnapshot snapshot(String snapshotName, boolean skipFlush, DurationSpec ttl, RateLimiter rateLimiter, Instant creationTime)
{
return snapshot(snapshotName, null, false, skipFlush, ttl, rateLimiter, creationTime);
}
@ -2086,7 +2085,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
* @param rateLimiter Rate limiter for hardlinks-per-second
* @param creationTime time when this snapshot was taken
*/
public TableSnapshot snapshot(String snapshotName, Predicate<SSTableReader> predicate, boolean ephemeral, boolean skipFlush, Duration ttl, RateLimiter rateLimiter, Instant creationTime)
public TableSnapshot snapshot(String snapshotName, Predicate<SSTableReader> predicate, boolean ephemeral, boolean skipFlush, DurationSpec ttl, RateLimiter rateLimiter, Instant creationTime)
{
if (!skipFlush)
{

View File

@ -39,9 +39,9 @@ import com.google.common.util.concurrent.RateLimiter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.Duration;
import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.repair.CassandraKeyspaceRepairManager;
@ -214,7 +214,7 @@ public class Keyspace
* @param rateLimiter Rate limiter for hardlinks-per-second
* @throws IOException if the column family doesn't exist
*/
public void snapshot(String snapshotName, String columnFamilyName, boolean skipFlush, Duration ttl, RateLimiter rateLimiter, Instant creationTime) throws IOException
public void snapshot(String snapshotName, String columnFamilyName, boolean skipFlush, DurationSpec ttl, RateLimiter rateLimiter, Instant creationTime) throws IOException
{
assert snapshotName != null;
boolean tookSnapShot = false;

View File

@ -72,8 +72,8 @@ import org.apache.cassandra.batchlog.BatchlogManager;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.Duration;
import org.apache.cassandra.concurrent.*;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.commitlog.CommitLog;
@ -3847,7 +3847,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
@Override
public void takeSnapshot(String tag, Map<String, String> options, String... entities) throws IOException
{
Duration ttl = options.containsKey("ttl") ? new Duration(options.get("ttl")) : null;
DurationSpec ttl = options.containsKey("ttl") ? new DurationSpec(options.get("ttl")) : null;
if (ttl != null)
{
int minAllowedTtlSecs = CassandraRelevantProperties.SNAPSHOT_MIN_ALLOWED_TTL_SECONDS.getInt();
@ -3925,7 +3925,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
* @param skipFlush Skip blocking flush of memtable
* @param keyspaceNames the names of the keyspaces to snapshot; empty means "all."
*/
private void takeSnapshot(String tag, boolean skipFlush, Duration ttl, String... keyspaceNames) throws IOException
private void takeSnapshot(String tag, boolean skipFlush, DurationSpec ttl, String... keyspaceNames) throws IOException
{
if (operationMode == Mode.JOINING)
throw new IOException("Cannot snapshot until bootstrap completes");
@ -3971,7 +3971,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
* @param tableList
* list of tables from different keyspace in the form of ks1.cf1 ks2.cf2
*/
private void takeMultipleTableSnapshot(String tag, boolean skipFlush, Duration ttl, String... tableList)
private void takeMultipleTableSnapshot(String tag, boolean skipFlush, DurationSpec ttl, String... tableList)
throws IOException
{
Map<Keyspace, List<String>> keyspaceColumnfamily = new HashMap<Keyspace, List<String>>();

View File

@ -26,9 +26,9 @@ import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.cassandra.config.Duration;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.DeserializationFeature;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileInputStreamPlus;
import org.apache.cassandra.io.util.FileOutputStreamPlus;
@ -64,7 +64,7 @@ public class SnapshotManifest
this.expiresAt = null;
}
public SnapshotManifest(List<String> files, Duration ttl, Instant creationTime)
public SnapshotManifest(List<String> files, DurationSpec ttl, Instant creationTime)
{
this.files = files;
this.createdAt = creationTime;

View File

@ -31,9 +31,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import org.apache.cassandra.config.Duration;
@Command(name = "snapshot", description = "Take a snapshot of specified keyspaces or a snapshot of the specified table")
public class Snapshot extends NodeToolCmd
@ -69,7 +69,7 @@ public class Snapshot extends NodeToolCmd
Map<String, String> options = new HashMap<String,String>();
options.put("skipFlush", Boolean.toString(skipFlush));
if (null != ttl) {
Duration d = new Duration(ttl);
DurationSpec d = new DurationSpec(ttl);
options.put("ttl", d.toString());
}

View File

@ -1,60 +0,0 @@
/*
* 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 static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.*;
public class DurationTest
{
@Test
public void testConversions()
{
assertEquals(10, new Duration("10s").toSeconds());
assertEquals(10000, new Duration("10s").toMilliseconds());
assertEquals(0, new Duration("10s").toMinutes());
assertEquals(10, new Duration("10m").toMinutes());
assertEquals(600000, new Duration("10m").toMilliseconds());
assertEquals(600, new Duration("10m").toSeconds());
}
@Test
public void testInvalidInputs()
{
assertThatThrownBy(() -> new Duration("10")).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Invalid duration: 10");
assertThatThrownBy(() -> new Duration("-10s")).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Invalid duration: -10s");
assertThatThrownBy(() -> new Duration("10xd")).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Unsupported time unit: xd. Supported units are: ns, us, ms, s, m, h, d");
}
@Test
public void testEquals()
{
assertEquals(new Duration("10s"), new Duration("10s"));
assertEquals(new Duration("10s"), new Duration("10000ms"));
assertEquals(new Duration("10000ms"), new Duration("10s"));
assertEquals(Duration.inMinutes(Long.MAX_VALUE), Duration.inMinutes(Long.MAX_VALUE));
assertNotEquals(Duration.inMinutes(Long.MAX_VALUE), Duration.inMilliseconds(Long.MAX_VALUE));
assertNotEquals(new Duration("0m"), new Duration("10ms"));
}
}

View File

@ -27,6 +27,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import com.google.common.collect.Sets;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileOutputStreamPlus;
import org.apache.commons.lang3.StringUtils;
@ -36,7 +38,6 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.config.Duration;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.schema.Indexes;
import org.apache.cassandra.schema.SchemaConstants;
@ -186,7 +187,7 @@ public class DirectoriesTest
if (createManifest)
{
File manifestFile = Directories.getSnapshotManifestFile(snapshotDir);
manifest = new SnapshotManifest(Collections.singletonList(sstableDesc.filenameFor(Component.DATA)), new Duration("1m"), Instant.now());
manifest = new SnapshotManifest(Collections.singletonList(sstableDesc.filenameFor(Component.DATA)), new DurationSpec("1m"), Instant.now());
manifest.serializeToJsonFile(manifestFile);
}
@ -311,7 +312,7 @@ public class DirectoriesTest
File manifestFile = directories.getSnapshotManifestFile(tag);
SnapshotManifest manifest = new SnapshotManifest(files, new Duration("1m"), Instant.now());
SnapshotManifest manifest = new SnapshotManifest(files, new DurationSpec("1m"), Instant.now());
manifest.serializeToJsonFile(manifestFile);
Set<File> dirs = new HashSet<>();

View File

@ -33,8 +33,8 @@ import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.assertj.core.api.Assertions.assertThatIOException;
import static org.assertj.core.api.Assertions.assertThat;
import org.apache.cassandra.config.Duration;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileOutputStreamPlus;
@ -108,7 +108,7 @@ public class SnapshotManifestTest
@Test
public void testSerializeAndDeserialize() throws Exception {
SnapshotManifest manifest = new SnapshotManifest(Arrays.asList("db1", "db2", "db3"), new Duration("2m"), Instant.ofEpochMilli(currentTimeMillis()));
SnapshotManifest manifest = new SnapshotManifest(Arrays.asList("db1", "db2", "db3"), new DurationSpec("2m"), Instant.ofEpochMilli(currentTimeMillis()));
File manifestFile = new File(tempFolder.newFile("manifest.json"));
manifest.serializeToJsonFile(manifestFile);