From 0c017cda6f49aa61ee13dbecbadc5221cd420b32 Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Wed, 11 Oct 2023 16:25:26 +0200 Subject: [PATCH 01/14] Make lock refresh + failure cache configurable#543 Make lock refresh and lock failure cache configurable, the defaults should be as they're today, but preferably dynamic based on the TTL of ecchronos.lock table. Today these values are hardcoded (assuming TTL of 10 minutes): Lock refresh 1 min (retry 9 times) Lock failure cache, 30 secs --- application/src/main/resources/ecc.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/application/src/main/resources/ecc.yml b/application/src/main/resources/ecc.yml index 5a1bb0249..ab1338a67 100644 --- a/application/src/main/resources/ecc.yml +++ b/application/src/main/resources/ecc.yml @@ -237,7 +237,14 @@ lock_factory: ## The keyspace used for the CAS lock factory tables. ## keyspace: ecchronos - + ## + ## resource lock time in second to calculate failed lock retry attempts + ## + lock_time_in_seconds: 600 + ## + ## resource lock update time in second to calculate failed lock retry attempts + ## + lock_update_time_in_seconds: 60 run_policy: time_based: ## From f1e8d099e7a3d8b761d74299b103a2888e4c3495 Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Wed, 11 Oct 2023 16:25:26 +0200 Subject: [PATCH 02/14] Make lock refresh + failure cache configurable#543 Make lock refresh and lock failure cache configurable, the defaults should be as they're today, but preferably dynamic based on the TTL of ecchronos.lock table. Today these values are hardcoded (assuming TTL of 10 minutes): Lock refresh 1 min (retry 9 times) Lock failure cache, 30 secs --- .../application/ECChronosInternals.java | 6 +++- .../lockfactory/CasLockFactoryConfig.java | 30 +++++++++++++++++-- .../ecchronos/core/CASLockFactory.java | 30 ++++++++++++------- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java index b2a93c306..8940eed86 100644 --- a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java +++ b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.metadata.Node; import com.ericsson.bss.cassandra.ecchronos.application.config.Config; +import com.ericsson.bss.cassandra.ecchronos.application.config.lockfactory.CasLockFactoryConfig; import com.ericsson.bss.cassandra.ecchronos.connection.JmxConnectionProvider; import com.ericsson.bss.cassandra.ecchronos.connection.NativeConnectionProvider; import com.ericsson.bss.cassandra.ecchronos.connection.StatementDecorator; @@ -75,11 +76,14 @@ public ECChronosInternals(final Config configuration, .withJmxProxyFactory(myJmxProxyFactory) .build(); + CasLockFactoryConfig casLockFactoryConfig = configuration.getLockFactory().getCasLockFactoryConfig(); myLockFactory = CASLockFactory.builder() .withNativeConnectionProvider(nativeConnectionProvider) .withHostStates(myHostStatesImpl) .withStatementDecorator(statementDecorator) - .withKeyspaceName(configuration.getLockFactory().getCasLockFactoryConfig().getKeyspaceName()) + .withKeyspaceName(casLockFactoryConfig.getKeyspaceName()) + .withLockTimeInSeconds(casLockFactoryConfig.getLockTimeInSeconds()) + .withLockUpdateTimeInSeconds(casLockFactoryConfig.getLockUpdateTimeInSeconds()) .build(); Node node = nativeConnectionProvider.getLocalNode(); diff --git a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java index 10353d6dd..4e6b802cc 100644 --- a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java +++ b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java @@ -19,14 +19,40 @@ public class CasLockFactoryConfig { private String myKeyspaceName = "ecchronos"; + private long myLockTimeInSeconds = 600L; + private long myLockUpdateTimeInSeconds = 60L; - @JsonProperty("keyspace") + @JsonProperty ("lock_time_in_seconds") + public final long getLockTimeInSeconds() + { + return myLockTimeInSeconds; + } + + @JsonProperty ("lock_time_in_seconds") + public final void setLockTimeInSeconds(final long lockTimeInSeconds) + { + myLockTimeInSeconds = lockTimeInSeconds; + } + + @JsonProperty ("lock_update_time_in_seconds") + public final long getLockUpdateTimeInSeconds() + { + return myLockUpdateTimeInSeconds; + } + + @JsonProperty ("lock_update_time_in_seconds") + public final void setLockUpdateTimeInSeconds(final long lockUpdateTimeInSeconds) + { + myLockTimeInSeconds = lockUpdateTimeInSeconds; + } + + @JsonProperty ("keyspace") public final String getKeyspaceName() { return myKeyspaceName; } - @JsonProperty("keyspace") + @JsonProperty ("keyspace") public final void setKeyspaceName(final String keyspaceName) { myKeyspaceName = keyspaceName; diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java index 4de046053..ea2ab4c8d 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java @@ -89,11 +89,6 @@ public final class CASLockFactory implements LockFactory, Closeable private static final String COLUMN_METADATA = "metadata"; private static final String COLUMN_PRIORITY = "priority"; - private static final int LOCK_TIME_IN_SECONDS = 600; - private static final long LOCK_UPDATE_TIME_IN_SECONDS = 60; - private static final int FAILED_LOCK_RETRY_ATTEMPTS = - (int) (LOCK_TIME_IN_SECONDS / LOCK_UPDATE_TIME_IN_SECONDS) - 1; - private static final String TABLE_LOCK = "lock"; private static final String TABLE_LOCK_PRIORITY = "lock_priority"; @@ -115,13 +110,15 @@ public final class CASLockFactory implements LockFactory, Closeable private final PreparedStatement myUpdateLockStatement; private final PreparedStatement myRemoveLockPriorityStatement; private final LockCache myLockCache; - + private final long myLockUpdateTimeInSeconds; + private final int myFailedLockRetryAttempts; private CASLockFactory(final Builder builder) { myStatementDecorator = builder.myStatementDecorator; myHostStates = builder.myHostStates; myKeyspaceName = builder.myKeyspaceName; - + myLockUpdateTimeInSeconds = builder.myLockUpdateTimeInSeconds; + myFailedLockRetryAttempts = (int) (builder.myLockTimeInSeconds / myLockUpdateTimeInSeconds) - 1; myExecutor = Executors.newSingleThreadScheduledExecutor( new ThreadFactoryBuilder().setNameFormat("LockRefresher-%d").build()); @@ -299,6 +296,9 @@ public static class Builder private StatementDecorator myStatementDecorator; private String myKeyspaceName = DEFAULT_KEYSPACE_NAME; + private long myLockTimeInSeconds = 600L; + private long myLockUpdateTimeInSeconds = 60L; + public final Builder withNativeConnectionProvider(final NativeConnectionProvider nativeConnectionProvider) { myNativeConnectionProvider = nativeConnectionProvider; @@ -322,6 +322,16 @@ public final Builder withKeyspaceName(final String keyspaceName) myKeyspaceName = keyspaceName; return this; } + public final Builder withLockTimeInSeconds(final long lockTimeInSeconds) + { + myLockTimeInSeconds = lockTimeInSeconds; + return this; + } + public final Builder withLockUpdateTimeInSeconds (final long lockUpdateTimeInSeconds ) + { + myLockUpdateTimeInSeconds = lockUpdateTimeInSeconds; + return this; + } public final CASLockFactory build() { @@ -497,8 +507,8 @@ public boolean lock() if (tryLock()) { LOG.trace("Lock for resource {} acquired", myResource); - ScheduledFuture future = myExecutor.scheduleAtFixedRate(this, LOCK_UPDATE_TIME_IN_SECONDS, - LOCK_UPDATE_TIME_IN_SECONDS, TimeUnit.SECONDS); + ScheduledFuture future = myExecutor.scheduleAtFixedRate(this, myLockUpdateTimeInSeconds, + myLockUpdateTimeInSeconds, TimeUnit.SECONDS); myUpdateFuture.set(future); return true; @@ -520,7 +530,7 @@ public void run() { int failedAttempts = myFailedUpdateAttempts.incrementAndGet(); - if (failedAttempts >= FAILED_LOCK_RETRY_ATTEMPTS) + if (failedAttempts >= myFailedLockRetryAttempts) { LOG.error("Unable to re-lock resource '{}' after {} failed attempts", myResource, failedAttempts); } From 35ebee43962720cda36c8c5205b2058b7ecca60a Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Wed, 11 Oct 2023 16:25:26 +0200 Subject: [PATCH 03/14] Make lock refresh + failure cache configurable#543 Make lock refresh and lock failure cache configurable, the defaults should be as they're today, but preferably dynamic based on the TTL of ecchronos.lock table. Today these values are hardcoded (assuming TTL of 10 minutes): Lock refresh 1 min (retry 9 times) Lock failure cache, 30 secs --- .../application/ECChronosInternals.java | 1 + .../lockfactory/CasLockFactoryConfig.java | 11 +++++++++-- application/src/main/resources/ecc.yml | 9 +++++++-- .../core/osgi/CASLockFactoryService.java | 12 ++++++++++++ .../ecchronos/core/CASLockFactory.java | 17 ++++++++++++----- .../bss/cassandra/ecchronos/core/LockCache.java | 6 ++---- .../cassandra/ecchronos/core/TestLockCache.java | 2 +- 7 files changed, 44 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java index 8940eed86..b86911281 100644 --- a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java +++ b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java @@ -84,6 +84,7 @@ public ECChronosInternals(final Config configuration, .withKeyspaceName(casLockFactoryConfig.getKeyspaceName()) .withLockTimeInSeconds(casLockFactoryConfig.getLockTimeInSeconds()) .withLockUpdateTimeInSeconds(casLockFactoryConfig.getLockUpdateTimeInSeconds()) + .withCacheExpiryInSeconds(casLockFactoryConfig.getExpiryTimeInSeconds()) .build(); Node node = nativeConnectionProvider.getLocalNode(); diff --git a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java index 4e6b802cc..360176af0 100644 --- a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java +++ b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java @@ -21,7 +21,7 @@ public class CasLockFactoryConfig private String myKeyspaceName = "ecchronos"; private long myLockTimeInSeconds = 600L; private long myLockUpdateTimeInSeconds = 60L; - + private long myExpiryTimeInSeconds = 30L; @JsonProperty ("lock_time_in_seconds") public final long getLockTimeInSeconds() { @@ -45,7 +45,14 @@ public final void setLockUpdateTimeInSeconds(final long lockUpdateTimeInSeconds) { myLockTimeInSeconds = lockUpdateTimeInSeconds; } - + @JsonProperty ("cache_expiry_time_in_second") + public long getExpiryTimeInSeconds() { + return myExpiryTimeInSeconds; + } + @JsonProperty ("cache_expiry_time_in_second") + public void setExpiryTimeInSeconds(long expiryTimeInSeconds) { + myExpiryTimeInSeconds = expiryTimeInSeconds; + } @JsonProperty ("keyspace") public final String getKeyspaceName() { diff --git a/application/src/main/resources/ecc.yml b/application/src/main/resources/ecc.yml index ab1338a67..6da196c24 100644 --- a/application/src/main/resources/ecc.yml +++ b/application/src/main/resources/ecc.yml @@ -238,13 +238,18 @@ lock_factory: ## keyspace: ecchronos ## - ## resource lock time in second to calculate failed lock retry attempts + ## The duration for which the lock is held, in seconds ## lock_time_in_seconds: 600 ## - ## resource lock update time in second to calculate failed lock retry attempts + ## The interval at which the lock is updated, in seconds ## lock_update_time_in_seconds: 60 + ## + ## The time after which the lock cache expires, in seconds", in seconds + ## + cache_expiry_time_in_second: 30 + run_policy: time_based: ## diff --git a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java index 0655e5ff6..92a4b04fc 100644 --- a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java +++ b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java @@ -38,6 +38,8 @@ public class CASLockFactoryService implements LockFactory { private static final String DEFAULT_KEYSPACE_NAME = "ecchronos"; + private static final long DEFAULT_LOCK_TIME_IN_SECONDS = 600L; + private static final long DEFAULT_UPDATE_TIME_IN_SECONDS = 60L; @Reference(service = NativeConnectionProvider.class, cardinality = ReferenceCardinality.MANDATORY, @@ -64,6 +66,8 @@ public final synchronized void activate(final Configuration configuration) .withHostStates(myHostStates) .withStatementDecorator(myStatementDecorator) .withKeyspaceName(configuration.keyspaceName()) + .withLockTimeInSeconds(configuration.lockTimeInSeconds()) + .withLockUpdateTimeInSeconds(configuration.lockUpdateTimeInSeconds()) .build(); } @@ -101,5 +105,13 @@ public final boolean sufficientNodesForLocking(final String dataCenter, final St @AttributeDefinition(name = "The lock factory keyspace to use", description = "The name of the keyspace containing the lock factory tables") String keyspaceName() default DEFAULT_KEYSPACE_NAME; + @AttributeDefinition(name = "Lock Time", + description = "The duration for which the lock is held, in seconds") + long lockTimeInSeconds() default DEFAULT_LOCK_TIME_IN_SECONDS; + + @AttributeDefinition(name = "Lock Update Time", + description = "The interval at which the lock is updated, in seconds") + long lockUpdateTimeInSeconds() default DEFAULT_UPDATE_TIME_IN_SECONDS; + } } diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java index ea2ab4c8d..7fa82bbb6 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java @@ -88,7 +88,6 @@ public final class CASLockFactory implements LockFactory, Closeable private static final String COLUMN_NODE = "node"; private static final String COLUMN_METADATA = "metadata"; private static final String COLUMN_PRIORITY = "priority"; - private static final String TABLE_LOCK = "lock"; private static final String TABLE_LOCK_PRIORITY = "lock_priority"; @@ -196,7 +195,7 @@ private CASLockFactory(final Builder builder) myUuid = hostId; - myLockCache = new LockCache(this::doTryLock); + myLockCache = new LockCache(this::doTryLock, builder.myCacheExpiryTimeInSeconds); } @Override @@ -290,14 +289,17 @@ public static Builder builder() public static class Builder { private static final String DEFAULT_KEYSPACE_NAME = "ecchronos"; + private static final long DEFAULT_LOCK_TIME_IN_SECONDS = 600L; + private static final long DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS = 60L; + private static final long DEFAULT_EXPIRY_TIME_IN_SECONDS = 30L; private NativeConnectionProvider myNativeConnectionProvider; private HostStates myHostStates; private StatementDecorator myStatementDecorator; private String myKeyspaceName = DEFAULT_KEYSPACE_NAME; - - private long myLockTimeInSeconds = 600L; - private long myLockUpdateTimeInSeconds = 60L; + private long myLockTimeInSeconds = DEFAULT_LOCK_TIME_IN_SECONDS; + private long myLockUpdateTimeInSeconds = DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS; + private long myCacheExpiryTimeInSeconds = DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS; public final Builder withNativeConnectionProvider(final NativeConnectionProvider nativeConnectionProvider) { @@ -332,6 +334,11 @@ public final Builder withLockUpdateTimeInSeconds (final long lockUpdateTimeInSec myLockUpdateTimeInSeconds = lockUpdateTimeInSeconds; return this; } + public final Builder withCacheExpiryInSeconds (final long cacheExpiryInSeconds ) + { + myCacheExpiryTimeInSeconds = cacheExpiryInSeconds; + return this; + } public final CASLockFactory build() { diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/LockCache.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/LockCache.java index 3ea1fbbcb..b6aa605f4 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/LockCache.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/LockCache.java @@ -32,14 +32,12 @@ public final class LockCache { private static final Logger LOG = LoggerFactory.getLogger(LockCache.class); - private static final long DEFAULT_EXPIRE_TIME_IN_SECONDS = 30; - private final Cache myFailureCache; private final LockSupplier myLockSupplier; - public LockCache(final LockSupplier lockSupplier) + public LockCache(final LockSupplier lockSupplier, final long expireTimeInSeconds) { - this(lockSupplier, DEFAULT_EXPIRE_TIME_IN_SECONDS, TimeUnit.SECONDS); + this(lockSupplier, expireTimeInSeconds, TimeUnit.SECONDS); } LockCache(final LockSupplier lockSupplier, final long expireTime, final TimeUnit expireTimeUnit) diff --git a/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestLockCache.java b/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestLockCache.java index 637c537b6..efb14fc16 100644 --- a/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestLockCache.java +++ b/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestLockCache.java @@ -49,7 +49,7 @@ public class TestLockCache @Before public void setup() { - myLockCache = new LockCache(mockedLockSupplier); + myLockCache = new LockCache(mockedLockSupplier, 30L); } @Test From b6490e475c9e2f662852ed6c2e9e30d6f03912f5 Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Wed, 11 Oct 2023 16:25:26 +0200 Subject: [PATCH 04/14] Make lock refresh and lock failure cache configurable #543 Make lock refresh and lock failure cache configurable, the defaults should be as they're today. Now values would be picked from ecc.yml Today these values are hardcoded (assuming TTL of 10 minutes): Lock refresh 1 min (retry 9 times) Lock failure cache, 30 secs --- .../application/ECChronosInternals.java | 12 ++-- .../lockfactory/CasLockFactoryConfig.java | 28 ++++++---- application/src/main/resources/ecc.yml | 5 +- .../config/TestCasLockFactoryConfig.java | 55 +++++++++++++++++++ application/src/test/resources/all_set.yml | 4 ++ .../ecchronos/core/CASLockFactory.java | 7 ++- 6 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java diff --git a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java index b86911281..f13b25785 100644 --- a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java +++ b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java @@ -76,7 +76,9 @@ public ECChronosInternals(final Config configuration, .withJmxProxyFactory(myJmxProxyFactory) .build(); - CasLockFactoryConfig casLockFactoryConfig = configuration.getLockFactory().getCasLockFactoryConfig(); + CasLockFactoryConfig casLockFactoryConfig = configuration.getLockFactory() + .getCasLockFactoryConfig(); + myLockFactory = CASLockFactory.builder() .withNativeConnectionProvider(nativeConnectionProvider) .withHostStates(myHostStatesImpl) @@ -84,7 +86,7 @@ public ECChronosInternals(final Config configuration, .withKeyspaceName(casLockFactoryConfig.getKeyspaceName()) .withLockTimeInSeconds(casLockFactoryConfig.getLockTimeInSeconds()) .withLockUpdateTimeInSeconds(casLockFactoryConfig.getLockUpdateTimeInSeconds()) - .withCacheExpiryInSeconds(casLockFactoryConfig.getExpiryTimeInSeconds()) + .withCacheExpiryInSeconds(casLockFactoryConfig.getFailureCacheExpiryTimeInSeconds()) .build(); Node node = nativeConnectionProvider.getLocalNode(); @@ -226,9 +228,9 @@ public void remainingRepairTime(final TableReference tableReference, final long @Override public void repairSession(final TableReference tableReference, - final long timeTaken, - final TimeUnit timeUnit, - final boolean successful) + final long timeTaken, + final TimeUnit timeUnit, + final boolean successful) { if (LOG.isTraceEnabled()) { diff --git a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java index 360176af0..910023dd1 100644 --- a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java +++ b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java @@ -18,11 +18,15 @@ public class CasLockFactoryConfig { - private String myKeyspaceName = "ecchronos"; - private long myLockTimeInSeconds = 600L; - private long myLockUpdateTimeInSeconds = 60L; - private long myExpiryTimeInSeconds = 30L; - @JsonProperty ("lock_time_in_seconds") + private static final long DEFAULT_LOCK_TIME_IN_SECONDS = 600L; + private static final long DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS = 60L; + private static final long DEFAULT_EXPIRY_TIME_IN_SECONDS = 30L; + private static final String DEFAULT_KEY_SPACE_NAME = "ecchronos"; + private String myKeyspaceName = DEFAULT_KEY_SPACE_NAME; + private long myLockTimeInSeconds = DEFAULT_LOCK_TIME_IN_SECONDS; + private long myLockUpdateTimeInSeconds = DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS; + private long myExpiryTimeInSeconds = DEFAULT_EXPIRY_TIME_IN_SECONDS; + public final long getLockTimeInSeconds() { return myLockTimeInSeconds; @@ -34,7 +38,6 @@ public final void setLockTimeInSeconds(final long lockTimeInSeconds) myLockTimeInSeconds = lockTimeInSeconds; } - @JsonProperty ("lock_update_time_in_seconds") public final long getLockUpdateTimeInSeconds() { return myLockUpdateTimeInSeconds; @@ -43,17 +46,20 @@ public final long getLockUpdateTimeInSeconds() @JsonProperty ("lock_update_time_in_seconds") public final void setLockUpdateTimeInSeconds(final long lockUpdateTimeInSeconds) { - myLockTimeInSeconds = lockUpdateTimeInSeconds; + myLockUpdateTimeInSeconds = lockUpdateTimeInSeconds; } - @JsonProperty ("cache_expiry_time_in_second") - public long getExpiryTimeInSeconds() { + + public final long getFailureCacheExpiryTimeInSeconds() + { return myExpiryTimeInSeconds; } + @JsonProperty ("cache_expiry_time_in_second") - public void setExpiryTimeInSeconds(long expiryTimeInSeconds) { + public final void setFailureCacheExpiryTimeInSeconds(final long expiryTimeInSeconds) + { myExpiryTimeInSeconds = expiryTimeInSeconds; } - @JsonProperty ("keyspace") + public final String getKeyspaceName() { return myKeyspaceName; diff --git a/application/src/main/resources/ecc.yml b/application/src/main/resources/ecc.yml index 6da196c24..62f73002f 100644 --- a/application/src/main/resources/ecc.yml +++ b/application/src/main/resources/ecc.yml @@ -246,7 +246,10 @@ lock_factory: ## lock_update_time_in_seconds: 60 ## - ## The time after which the lock cache expires, in seconds", in seconds + ## The number of seconds until the lock failure cache expires. + ## If an attempt to secure a lock is unsuccessful, + ## all subsequent attempts will be failed until + ## the cache expiration time is reached. ## cache_expiry_time_in_second: 30 diff --git a/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java b/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java new file mode 100644 index 000000000..2d20aacfb --- /dev/null +++ b/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java @@ -0,0 +1,55 @@ +package com.ericsson.bss.cassandra.ecchronos.application.config; + +import com.ericsson.bss.cassandra.ecchronos.application.config.lockfactory.CasLockFactoryConfig; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestCasLockFactoryConfig +{ + private static CasLockFactoryConfig casLockFactoryConfig; + + @Before + public void setup() throws IOException + { + if (casLockFactoryConfig == null) + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + File file = new File(classLoader.getResource("all_set.yml").getFile()); + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + Config config = mapper.readValue(file, Config.class); + casLockFactoryConfig = config.getLockFactory().getCasLockFactoryConfig(); + + } + } + + @Test + public void testKeyspaceNameFromYaml() + { + assertThat(casLockFactoryConfig.getKeyspaceName()).isEqualTo("ecc"); + } + + @Test + public void testLockTimeInSecondsFromYaml() + { + assertThat(casLockFactoryConfig.getLockTimeInSeconds()).isEqualTo(800L); + } + + @Test + public void testLockUpdateTimeInSecondsFromYaml() + { + assertThat(casLockFactoryConfig.getLockUpdateTimeInSeconds()).isEqualTo(80L); + } + + @Test + public void testExpiryTimeInSecondsFromYaml() + { + assertThat(casLockFactoryConfig.getFailureCacheExpiryTimeInSeconds()).isEqualTo(100L); + } +} diff --git a/application/src/test/resources/all_set.yml b/application/src/test/resources/all_set.yml index 4345cbc4d..558003345 100644 --- a/application/src/test/resources/all_set.yml +++ b/application/src/test/resources/all_set.yml @@ -79,6 +79,10 @@ statistics: lock_factory: cas: keyspace: ecc + lock_time_in_seconds: 800 + lock_update_time_in_seconds: 80 + cache_expiry_time_in_second: 100 + run_policy: time_based: diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java index 7fa82bbb6..b1b932aa3 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java @@ -111,6 +111,7 @@ public final class CASLockFactory implements LockFactory, Closeable private final LockCache myLockCache; private final long myLockUpdateTimeInSeconds; private final int myFailedLockRetryAttempts; + private CASLockFactory(final Builder builder) { myStatementDecorator = builder.myStatementDecorator; @@ -299,7 +300,7 @@ public static class Builder private String myKeyspaceName = DEFAULT_KEYSPACE_NAME; private long myLockTimeInSeconds = DEFAULT_LOCK_TIME_IN_SECONDS; private long myLockUpdateTimeInSeconds = DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS; - private long myCacheExpiryTimeInSeconds = DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS; + private long myCacheExpiryTimeInSeconds = DEFAULT_EXPIRY_TIME_IN_SECONDS; public final Builder withNativeConnectionProvider(final NativeConnectionProvider nativeConnectionProvider) { @@ -329,12 +330,12 @@ public final Builder withLockTimeInSeconds(final long lockTimeInSeconds) myLockTimeInSeconds = lockTimeInSeconds; return this; } - public final Builder withLockUpdateTimeInSeconds (final long lockUpdateTimeInSeconds ) + public final Builder withLockUpdateTimeInSeconds(final long lockUpdateTimeInSeconds) { myLockUpdateTimeInSeconds = lockUpdateTimeInSeconds; return this; } - public final Builder withCacheExpiryInSeconds (final long cacheExpiryInSeconds ) + public final Builder withCacheExpiryInSeconds(final long cacheExpiryInSeconds) { myCacheExpiryTimeInSeconds = cacheExpiryInSeconds; return this; From 7ab2286ed8a641436e4f9106560b0a073e744801 Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Wed, 11 Oct 2023 16:25:26 +0200 Subject: [PATCH 05/14] Make lock refresh and lock failure cache configurable #543 Make lock refresh and lock failure cache configurable, the defaults should be as they're today. Now values would be picked from ecc.yml --- .../config/TestCasLockFactoryConfig.java | 14 +++ .../ecchronos/core/CASLockFactory.java | 77 +++++++++++------ .../utils/CASLockFactoryCacheContext.java | 85 +++++++++++++++++++ 3 files changed, 149 insertions(+), 27 deletions(-) create mode 100644 core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/utils/CASLockFactoryCacheContext.java diff --git a/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java b/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java index 2d20aacfb..791c9ebe5 100644 --- a/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java +++ b/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java @@ -1,3 +1,17 @@ +/* + * Copyright 2018 Telefonaktiebolaget LM Ericsson + * + * Licensed 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 com.ericsson.bss.cassandra.ecchronos.application.config; import com.ericsson.bss.cassandra.ecchronos.application.config.lockfactory.CasLockFactoryConfig; diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java index b1b932aa3..e6d643d0d 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java @@ -32,6 +32,7 @@ import com.ericsson.bss.cassandra.ecchronos.connection.StatementDecorator; import com.ericsson.bss.cassandra.ecchronos.core.exceptions.LockException; import com.ericsson.bss.cassandra.ecchronos.core.scheduling.LockFactory; +import com.ericsson.bss.cassandra.ecchronos.core.utils.CASLockFactoryCacheContext; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.slf4j.Logger; @@ -108,17 +109,17 @@ public final class CASLockFactory implements LockFactory, Closeable private final PreparedStatement myRemoveLockStatement; private final PreparedStatement myUpdateLockStatement; private final PreparedStatement myRemoveLockPriorityStatement; - private final LockCache myLockCache; - private final long myLockUpdateTimeInSeconds; - private final int myFailedLockRetryAttempts; + private final CASLockFactoryCacheContext myCasLockFactoryContext; private CASLockFactory(final Builder builder) { + int myFailedLockRetryAttempts = (int) (builder.myLockTimeInSeconds / builder.myLockUpdateTimeInSeconds) - 1; + CASLockFactoryCacheContext.Builder casLockFactoryContextBuilder = CASLockFactoryCacheContext.newBuilder() + .withLockUpdateTimeInSeconds(builder.myLockUpdateTimeInSeconds) + .withFailedLockRetryAttempts(myFailedLockRetryAttempts); myStatementDecorator = builder.myStatementDecorator; myHostStates = builder.myHostStates; myKeyspaceName = builder.myKeyspaceName; - myLockUpdateTimeInSeconds = builder.myLockUpdateTimeInSeconds; - myFailedLockRetryAttempts = (int) (builder.myLockTimeInSeconds / myLockUpdateTimeInSeconds) - 1; myExecutor = Executors.newSingleThreadScheduledExecutor( new ThreadFactoryBuilder().setNameFormat("LockRefresher-%d").build()); @@ -141,23 +142,30 @@ private CASLockFactory(final Builder builder) SimpleStatement getLockMetadataStatement = QueryBuilder.selectFrom(myKeyspaceName, TABLE_LOCK) .column(COLUMN_METADATA) - .whereColumn(COLUMN_RESOURCE).isEqualTo(bindMarker()) + .whereColumn(COLUMN_RESOURCE) + .isEqualTo(bindMarker()) .build() .setSerialConsistencyLevel(serialConsistencyLevel); SimpleStatement removeLockStatement = QueryBuilder.deleteFrom(myKeyspaceName, TABLE_LOCK) - .whereColumn(COLUMN_RESOURCE).isEqualTo(bindMarker()) - .ifColumn(COLUMN_NODE).isEqualTo(bindMarker()) + .whereColumn(COLUMN_RESOURCE) + .isEqualTo(bindMarker()) + .ifColumn(COLUMN_NODE) + .isEqualTo(bindMarker()) .build() - .setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM).setSerialConsistencyLevel(serialConsistencyLevel); + .setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM) + .setSerialConsistencyLevel(serialConsistencyLevel); SimpleStatement updateLockStatement = QueryBuilder.update(myKeyspaceName, TABLE_LOCK) .setColumn(COLUMN_NODE, bindMarker()) .setColumn(COLUMN_METADATA, bindMarker()) - .whereColumn(COLUMN_RESOURCE).isEqualTo(bindMarker()) - .ifColumn(COLUMN_NODE).isEqualTo(bindMarker()) + .whereColumn(COLUMN_RESOURCE) + .isEqualTo(bindMarker()) + .ifColumn(COLUMN_NODE) + .isEqualTo(bindMarker()) .build() - .setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM).setSerialConsistencyLevel(serialConsistencyLevel); + .setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM) + .setSerialConsistencyLevel(serialConsistencyLevel); SimpleStatement competeStatement = QueryBuilder.insertInto(myKeyspaceName, TABLE_LOCK_PRIORITY) .value(COLUMN_RESOURCE, bindMarker()) @@ -168,13 +176,16 @@ private CASLockFactory(final Builder builder) SimpleStatement getPriorityStatement = QueryBuilder.selectFrom(myKeyspaceName, TABLE_LOCK_PRIORITY) .columns(COLUMN_PRIORITY, COLUMN_NODE) - .whereColumn(COLUMN_RESOURCE).isEqualTo(bindMarker()) + .whereColumn(COLUMN_RESOURCE) + .isEqualTo(bindMarker()) .build() .setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM); SimpleStatement removeLockPriorityStatement = QueryBuilder.deleteFrom(myKeyspaceName, TABLE_LOCK_PRIORITY) - .whereColumn(COLUMN_RESOURCE).isEqualTo(bindMarker()) - .whereColumn(COLUMN_NODE).isEqualTo(bindMarker()) + .whereColumn(COLUMN_RESOURCE) + .isEqualTo(bindMarker()) + .whereColumn(COLUMN_NODE) + .isEqualTo(bindMarker()) .build() .setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM); @@ -196,7 +207,8 @@ private CASLockFactory(final Builder builder) myUuid = hostId; - myLockCache = new LockCache(this::doTryLock, builder.myCacheExpiryTimeInSeconds); + casLockFactoryContextBuilder.withLockCache(new LockCache(this::doTryLock, builder.myCacheExpiryTimeInSeconds)); + myCasLockFactoryContext = casLockFactoryContextBuilder.build(); } @Override @@ -204,9 +216,10 @@ public DistributedLock tryLock(final String dataCenter, final String resource, final int priority, final Map metadata) - throws LockException + throws LockException { - return myLockCache.getLock(dataCenter, resource, priority, metadata); + return myCasLockFactoryContext.getLockCache() + .getLock(dataCenter, resource, priority, metadata); } @Override @@ -256,7 +269,7 @@ public boolean sufficientNodesForLocking(final String dataCenter, final String r @Override public Optional getCachedFailure(final String dataCenter, final String resource) { - return myLockCache.getCachedFailure(dataCenter, resource); + return myCasLockFactoryContext.getLockCache().getCachedFailure(dataCenter, resource); } @Override @@ -325,16 +338,19 @@ public final Builder withKeyspaceName(final String keyspaceName) myKeyspaceName = keyspaceName; return this; } + public final Builder withLockTimeInSeconds(final long lockTimeInSeconds) { myLockTimeInSeconds = lockTimeInSeconds; return this; } + public final Builder withLockUpdateTimeInSeconds(final long lockUpdateTimeInSeconds) { myLockUpdateTimeInSeconds = lockUpdateTimeInSeconds; return this; } + public final Builder withCacheExpiryInSeconds(final long cacheExpiryInSeconds) { myCacheExpiryTimeInSeconds = cacheExpiryInSeconds; @@ -366,7 +382,7 @@ private DistributedLock doTryLock(final String dataCenter, final String resource, final int priority, final Map metadata) - throws LockException + throws LockException { LOG.trace("Trying lock for {} - {}", dataCenter, resource); @@ -501,10 +517,16 @@ class CASLock implements DistributedLock, Runnable List nodePriorities = computePriorities(); - myLocallyHighestPriority = nodePriorities.stream().filter(n -> n.getUuid().equals(myUuid)) - .map(NodePriority::getPriority).findFirst().orElse(myPriority); - globalHighPriority = nodePriorities.stream().filter(n -> !n.getUuid().equals(myUuid)) - .map(NodePriority::getPriority).max(Integer::compare).orElse(myPriority); + myLocallyHighestPriority = nodePriorities.stream() + .filter(n -> n.getUuid().equals(myUuid)) + .map(NodePriority::getPriority) + .findFirst() + .orElse(myPriority); + globalHighPriority = nodePriorities.stream() + .filter(n -> !n.getUuid().equals(myUuid)) + .map(NodePriority::getPriority) + .max(Integer::compare) + .orElse(myPriority); } public boolean lock() @@ -515,8 +537,9 @@ public boolean lock() if (tryLock()) { LOG.trace("Lock for resource {} acquired", myResource); - ScheduledFuture future = myExecutor.scheduleAtFixedRate(this, myLockUpdateTimeInSeconds, - myLockUpdateTimeInSeconds, TimeUnit.SECONDS); + long lockUpdateTimeInSeconds = myCasLockFactoryContext.getLockUpdateTimeInSeconds(); + ScheduledFuture future = myExecutor.scheduleAtFixedRate(this, lockUpdateTimeInSeconds, + lockUpdateTimeInSeconds, TimeUnit.SECONDS); myUpdateFuture.set(future); return true; @@ -538,7 +561,7 @@ public void run() { int failedAttempts = myFailedUpdateAttempts.incrementAndGet(); - if (failedAttempts >= myFailedLockRetryAttempts) + if (failedAttempts >= myCasLockFactoryContext.getFailedLockRetryAttempts()) { LOG.error("Unable to re-lock resource '{}' after {} failed attempts", myResource, failedAttempts); } diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/utils/CASLockFactoryCacheContext.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/utils/CASLockFactoryCacheContext.java new file mode 100644 index 000000000..16586b71e --- /dev/null +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/utils/CASLockFactoryCacheContext.java @@ -0,0 +1,85 @@ +/* + * Copyright 2018 Telefonaktiebolaget LM Ericsson + * + * Licensed 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 com.ericsson.bss.cassandra.ecchronos.core.utils; + +import com.ericsson.bss.cassandra.ecchronos.core.LockCache; + +/** + * Represents a container for cache-related configurations and state for the CASLockFactory. + * This class is used to decouple cache-related fields from CASLockFactory to avoid excessive field count. + */ +public final class CASLockFactoryCacheContext +{ + private final LockCache myLockCache; + private final long myLockUpdateTimeInSeconds; + private final int myFailedLockRetryAttempts; + + public CASLockFactoryCacheContext(final Builder builder) + { + myLockCache = builder.myLockCache; + myLockUpdateTimeInSeconds = builder.myLockUpdateTimeInSeconds; + myFailedLockRetryAttempts = builder.myFailedLockRetryAttempts; + } + + public LockCache getLockCache() + { + return myLockCache; + } + + public long getLockUpdateTimeInSeconds() + { + return myLockUpdateTimeInSeconds; + } + + public int getFailedLockRetryAttempts() + { + return myFailedLockRetryAttempts; + } + + public static Builder newBuilder() + { + return new Builder(); + } + + public static class Builder + { + private LockCache myLockCache; + private long myLockUpdateTimeInSeconds; + private int myFailedLockRetryAttempts; + + public final Builder withLockUpdateTimeInSeconds(final long lockTimeInSeconds) + { + myLockUpdateTimeInSeconds = lockTimeInSeconds; + return this; + } + + public final Builder withFailedLockRetryAttempts(final int failedLockRetryAttempts) + { + myFailedLockRetryAttempts = failedLockRetryAttempts; + return this; + } + + public final Builder withLockCache(final LockCache lockCache) + { + myLockCache = lockCache; + return this; + } + + public final CASLockFactoryCacheContext build() + { + return new CASLockFactoryCacheContext(this); + } + } +} From ff20cc8538567409cc2d228ef7b2c78ffc052464 Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Wed, 11 Oct 2023 16:25:26 +0200 Subject: [PATCH 06/14] Make lock refresh and lock failure cache configurable #543 Make lock refresh and lock failure cache configurable, the defaults should be as they're today. Now values would be picked from ecc.yml --- .../ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java index e6d643d0d..b1f895f72 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java @@ -381,8 +381,7 @@ public final CASLockFactory build() private DistributedLock doTryLock(final String dataCenter, final String resource, final int priority, - final Map metadata) - throws LockException + final Map metadata) throws LockException { LOG.trace("Trying lock for {} - {}", dataCenter, resource); From f8caa6dc234a1776d4d479e848c7ef7fa32587e6 Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Wed, 11 Oct 2023 16:25:26 +0200 Subject: [PATCH 07/14] Make lock refresh and lock failure cache configurable #543 Make lock refresh and lock failure cache configurable, the defaults should be as they're today. Now values would be picked from ecc.yml --- .../lockfactory/CasLockFactoryConfig.java | 6 +-- application/src/main/resources/ecc.yml | 4 +- .../config/TestCasLockFactoryConfig.java | 46 ++++++++----------- application/src/test/resources/all_set.yml | 2 +- .../ecchronos/core/CASLockFactory.java | 27 ++++++----- .../CASLockFactoryCacheContext.java | 6 +-- 6 files changed, 40 insertions(+), 51 deletions(-) rename core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/{utils => }/CASLockFactoryCacheContext.java (93%) diff --git a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java index 910023dd1..a49dcb259 100644 --- a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java +++ b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java @@ -21,8 +21,8 @@ public class CasLockFactoryConfig private static final long DEFAULT_LOCK_TIME_IN_SECONDS = 600L; private static final long DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS = 60L; private static final long DEFAULT_EXPIRY_TIME_IN_SECONDS = 30L; - private static final String DEFAULT_KEY_SPACE_NAME = "ecchronos"; - private String myKeyspaceName = DEFAULT_KEY_SPACE_NAME; + private static final String DEFAULT_KEYSPACE_NAME = "ecchronos"; + private String myKeyspaceName = DEFAULT_KEYSPACE_NAME; private long myLockTimeInSeconds = DEFAULT_LOCK_TIME_IN_SECONDS; private long myLockUpdateTimeInSeconds = DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS; private long myExpiryTimeInSeconds = DEFAULT_EXPIRY_TIME_IN_SECONDS; @@ -54,7 +54,7 @@ public final long getFailureCacheExpiryTimeInSeconds() return myExpiryTimeInSeconds; } - @JsonProperty ("cache_expiry_time_in_second") + @JsonProperty ("cache_expiry_time_in_seconds") public final void setFailureCacheExpiryTimeInSeconds(final long expiryTimeInSeconds) { myExpiryTimeInSeconds = expiryTimeInSeconds; diff --git a/application/src/main/resources/ecc.yml b/application/src/main/resources/ecc.yml index 62f73002f..0dcfd0476 100644 --- a/application/src/main/resources/ecc.yml +++ b/application/src/main/resources/ecc.yml @@ -239,6 +239,8 @@ lock_factory: keyspace: ecchronos ## ## The duration for which the lock is held, in seconds + ## and directly tied with the TTL(time to live) + ## of 'ecchronos.lock' table. ## lock_time_in_seconds: 600 ## @@ -251,7 +253,7 @@ lock_factory: ## all subsequent attempts will be failed until ## the cache expiration time is reached. ## - cache_expiry_time_in_second: 30 + cache_expiry_time_in_seconds: 30 run_policy: time_based: diff --git a/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java b/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java index 791c9ebe5..726f50c52 100644 --- a/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java +++ b/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Telefonaktiebolaget LM Ericsson + * Copyright 2023 Telefonaktiebolaget LM Ericsson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,43 +27,33 @@ public class TestCasLockFactoryConfig { - private static CasLockFactoryConfig casLockFactoryConfig; - - @Before - public void setup() throws IOException - { - if (casLockFactoryConfig == null) - { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - File file = new File(classLoader.getResource("all_set.yml").getFile()); - ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - Config config = mapper.readValue(file, Config.class); - casLockFactoryConfig = config.getLockFactory().getCasLockFactoryConfig(); - - } - } - @Test - public void testKeyspaceNameFromYaml() + public void testCasLockFactoryConfigWithProvidedValue() throws IOException { + CasLockFactoryConfig casLockFactoryConfig = getCasLockFactoryConfig("all_set.yml"); assertThat(casLockFactoryConfig.getKeyspaceName()).isEqualTo("ecc"); - } - - @Test - public void testLockTimeInSecondsFromYaml() - { assertThat(casLockFactoryConfig.getLockTimeInSeconds()).isEqualTo(800L); + assertThat(casLockFactoryConfig.getLockUpdateTimeInSeconds()).isEqualTo(80L); + assertThat(casLockFactoryConfig.getFailureCacheExpiryTimeInSeconds()).isEqualTo(100L); } @Test - public void testLockUpdateTimeInSecondsFromYaml() + public void testCasLockFactoryConfigDefaultValue() throws IOException { - assertThat(casLockFactoryConfig.getLockUpdateTimeInSeconds()).isEqualTo(80L); + CasLockFactoryConfig casLockFactoryConfig = getCasLockFactoryConfig("nothing_set.yml"); + assertThat(casLockFactoryConfig.getKeyspaceName()).isEqualTo("ecchronos"); + assertThat(casLockFactoryConfig.getLockTimeInSeconds()).isEqualTo(600L); + assertThat(casLockFactoryConfig.getLockUpdateTimeInSeconds()).isEqualTo(60L); + assertThat(casLockFactoryConfig.getFailureCacheExpiryTimeInSeconds()).isEqualTo(30L); + } - @Test - public void testExpiryTimeInSecondsFromYaml() + private CasLockFactoryConfig getCasLockFactoryConfig(final String fileName) throws IOException { - assertThat(casLockFactoryConfig.getFailureCacheExpiryTimeInSeconds()).isEqualTo(100L); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + File file = new File(classLoader.getResource(fileName).getFile()); + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + Config config = mapper.readValue(file, Config.class); + return config.getLockFactory().getCasLockFactoryConfig(); } } diff --git a/application/src/test/resources/all_set.yml b/application/src/test/resources/all_set.yml index 558003345..64ba659b2 100644 --- a/application/src/test/resources/all_set.yml +++ b/application/src/test/resources/all_set.yml @@ -81,7 +81,7 @@ lock_factory: keyspace: ecc lock_time_in_seconds: 800 lock_update_time_in_seconds: 80 - cache_expiry_time_in_second: 100 + cache_expiry_time_in_seconds: 100 run_policy: diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java index b1f895f72..53888874e 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java @@ -32,7 +32,6 @@ import com.ericsson.bss.cassandra.ecchronos.connection.StatementDecorator; import com.ericsson.bss.cassandra.ecchronos.core.exceptions.LockException; import com.ericsson.bss.cassandra.ecchronos.core.scheduling.LockFactory; -import com.ericsson.bss.cassandra.ecchronos.core.utils.CASLockFactoryCacheContext; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.slf4j.Logger; @@ -109,14 +108,10 @@ public final class CASLockFactory implements LockFactory, Closeable private final PreparedStatement myRemoveLockStatement; private final PreparedStatement myUpdateLockStatement; private final PreparedStatement myRemoveLockPriorityStatement; - private final CASLockFactoryCacheContext myCasLockFactoryContext; + private final CASLockFactoryCacheContext myCasLockFactoryCacheContext; private CASLockFactory(final Builder builder) { - int myFailedLockRetryAttempts = (int) (builder.myLockTimeInSeconds / builder.myLockUpdateTimeInSeconds) - 1; - CASLockFactoryCacheContext.Builder casLockFactoryContextBuilder = CASLockFactoryCacheContext.newBuilder() - .withLockUpdateTimeInSeconds(builder.myLockUpdateTimeInSeconds) - .withFailedLockRetryAttempts(myFailedLockRetryAttempts); myStatementDecorator = builder.myStatementDecorator; myHostStates = builder.myHostStates; myKeyspaceName = builder.myKeyspaceName; @@ -207,8 +202,12 @@ private CASLockFactory(final Builder builder) myUuid = hostId; - casLockFactoryContextBuilder.withLockCache(new LockCache(this::doTryLock, builder.myCacheExpiryTimeInSeconds)); - myCasLockFactoryContext = casLockFactoryContextBuilder.build(); + int myFailedLockRetryAttempts = (int) (builder.myLockTimeInSeconds / builder.myLockUpdateTimeInSeconds) - 1; + myCasLockFactoryCacheContext = CASLockFactoryCacheContext.newBuilder() + .withLockUpdateTimeInSeconds(builder.myLockUpdateTimeInSeconds) + .withFailedLockRetryAttempts(myFailedLockRetryAttempts) + .withLockCache(new LockCache(this::doTryLock, builder.myCacheExpiryTimeInSeconds)) + .build(); } @Override @@ -218,7 +217,7 @@ public DistributedLock tryLock(final String dataCenter, final Map metadata) throws LockException { - return myCasLockFactoryContext.getLockCache() + return myCasLockFactoryCacheContext.getLockCache() .getLock(dataCenter, resource, priority, metadata); } @@ -269,7 +268,7 @@ public boolean sufficientNodesForLocking(final String dataCenter, final String r @Override public Optional getCachedFailure(final String dataCenter, final String resource) { - return myCasLockFactoryContext.getLockCache().getCachedFailure(dataCenter, resource); + return myCasLockFactoryCacheContext.getLockCache().getCachedFailure(dataCenter, resource); } @Override @@ -536,9 +535,9 @@ public boolean lock() if (tryLock()) { LOG.trace("Lock for resource {} acquired", myResource); - long lockUpdateTimeInSeconds = myCasLockFactoryContext.getLockUpdateTimeInSeconds(); - ScheduledFuture future = myExecutor.scheduleAtFixedRate(this, lockUpdateTimeInSeconds, - lockUpdateTimeInSeconds, TimeUnit.SECONDS); + ScheduledFuture future = myExecutor.scheduleAtFixedRate(this, + myCasLockFactoryCacheContext.getLockUpdateTimeInSeconds(), + myCasLockFactoryCacheContext.getLockUpdateTimeInSeconds(), TimeUnit.SECONDS); myUpdateFuture.set(future); return true; @@ -560,7 +559,7 @@ public void run() { int failedAttempts = myFailedUpdateAttempts.incrementAndGet(); - if (failedAttempts >= myCasLockFactoryContext.getFailedLockRetryAttempts()) + if (failedAttempts >= myCasLockFactoryCacheContext.getFailedLockRetryAttempts()) { LOG.error("Unable to re-lock resource '{}' after {} failed attempts", myResource, failedAttempts); } diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/utils/CASLockFactoryCacheContext.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactoryCacheContext.java similarity index 93% rename from core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/utils/CASLockFactoryCacheContext.java rename to core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactoryCacheContext.java index 16586b71e..531cb7d5d 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/utils/CASLockFactoryCacheContext.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactoryCacheContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Telefonaktiebolaget LM Ericsson + * Copyright 2023 Telefonaktiebolaget LM Ericsson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,9 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ericsson.bss.cassandra.ecchronos.core.utils; - -import com.ericsson.bss.cassandra.ecchronos.core.LockCache; +package com.ericsson.bss.cassandra.ecchronos.core; /** * Represents a container for cache-related configurations and state for the CASLockFactory. From 6f4307471e201062484fd3991cc4354ccd6df50c Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Wed, 11 Oct 2023 16:25:26 +0200 Subject: [PATCH 08/14] Make lock refresh + failure cache configurable #543 Make lock refresh and lock failure cache configurable, the defaults should be as they're today. Now values would be picked from ecc.yml --- CHANGES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fe73a64ee..8a2b262eb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,7 +17,8 @@ * Bump guava from 18.0 to 31.1 - Issue #491 * Reread repair configuration when a node state changes - Issues #470 and #478 * Support configuring backoff for failed jobs - Issue #475 -* Dropping keyspaces does not clean up schedules - Issue #469 +* Dropping keyspaces does not clean up schedules - Issue #469 +* Make lock refresh + failure cache configurable #543 ### Merged from 1.2 From d997f4f99af4157e2afd3f8c49ccc5b1863c9cc1 Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Wed, 11 Oct 2023 16:25:26 +0200 Subject: [PATCH 09/14] Make lock refresh + failure cache configurable #543 Make lock failure cache configurable, the defaults should be as they're today. Now failure cache expiry time value would be picked from ecc.yml. Lock refresh is dynamic based on the TTL of ecchronos.lock table --- CHANGES.md | 4 +- .../application/ECChronosInternals.java | 2 - .../lockfactory/CasLockFactoryConfig.java | 26 ------------ application/src/main/resources/ecc.yml | 10 ----- .../config/TestCasLockFactoryConfig.java | 6 --- application/src/test/resources/all_set.yml | 2 - .../core/osgi/CASLockFactoryService.java | 13 ------ .../ecchronos/core/CASLockFactory.java | 41 +++++++++++-------- .../core/CASLockFactoryCacheContext.java | 4 +- 9 files changed, 27 insertions(+), 81 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8a2b262eb..a25c0aff6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,8 +17,8 @@ * Bump guava from 18.0 to 31.1 - Issue #491 * Reread repair configuration when a node state changes - Issues #470 and #478 * Support configuring backoff for failed jobs - Issue #475 -* Dropping keyspaces does not clean up schedules - Issue #469 -* Make lock refresh + failure cache configurable #543 +* Dropping keyspaces does not clean up schedules - Issue #469 +* Make lock refresh + failure cache configurable - Issues #543 and #587 ### Merged from 1.2 diff --git a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java index f13b25785..a48acecb7 100644 --- a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java +++ b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/ECChronosInternals.java @@ -84,8 +84,6 @@ public ECChronosInternals(final Config configuration, .withHostStates(myHostStatesImpl) .withStatementDecorator(statementDecorator) .withKeyspaceName(casLockFactoryConfig.getKeyspaceName()) - .withLockTimeInSeconds(casLockFactoryConfig.getLockTimeInSeconds()) - .withLockUpdateTimeInSeconds(casLockFactoryConfig.getLockUpdateTimeInSeconds()) .withCacheExpiryInSeconds(casLockFactoryConfig.getFailureCacheExpiryTimeInSeconds()) .build(); diff --git a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java index a49dcb259..7150dea35 100644 --- a/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java +++ b/application/src/main/java/com/ericsson/bss/cassandra/ecchronos/application/config/lockfactory/CasLockFactoryConfig.java @@ -18,37 +18,11 @@ public class CasLockFactoryConfig { - private static final long DEFAULT_LOCK_TIME_IN_SECONDS = 600L; - private static final long DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS = 60L; private static final long DEFAULT_EXPIRY_TIME_IN_SECONDS = 30L; private static final String DEFAULT_KEYSPACE_NAME = "ecchronos"; private String myKeyspaceName = DEFAULT_KEYSPACE_NAME; - private long myLockTimeInSeconds = DEFAULT_LOCK_TIME_IN_SECONDS; - private long myLockUpdateTimeInSeconds = DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS; private long myExpiryTimeInSeconds = DEFAULT_EXPIRY_TIME_IN_SECONDS; - public final long getLockTimeInSeconds() - { - return myLockTimeInSeconds; - } - - @JsonProperty ("lock_time_in_seconds") - public final void setLockTimeInSeconds(final long lockTimeInSeconds) - { - myLockTimeInSeconds = lockTimeInSeconds; - } - - public final long getLockUpdateTimeInSeconds() - { - return myLockUpdateTimeInSeconds; - } - - @JsonProperty ("lock_update_time_in_seconds") - public final void setLockUpdateTimeInSeconds(final long lockUpdateTimeInSeconds) - { - myLockUpdateTimeInSeconds = lockUpdateTimeInSeconds; - } - public final long getFailureCacheExpiryTimeInSeconds() { return myExpiryTimeInSeconds; diff --git a/application/src/main/resources/ecc.yml b/application/src/main/resources/ecc.yml index 0dcfd0476..d16288bec 100644 --- a/application/src/main/resources/ecc.yml +++ b/application/src/main/resources/ecc.yml @@ -238,16 +238,6 @@ lock_factory: ## keyspace: ecchronos ## - ## The duration for which the lock is held, in seconds - ## and directly tied with the TTL(time to live) - ## of 'ecchronos.lock' table. - ## - lock_time_in_seconds: 600 - ## - ## The interval at which the lock is updated, in seconds - ## - lock_update_time_in_seconds: 60 - ## ## The number of seconds until the lock failure cache expires. ## If an attempt to secure a lock is unsuccessful, ## all subsequent attempts will be failed until diff --git a/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java b/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java index 726f50c52..1aeafa5bb 100644 --- a/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java +++ b/application/src/test/java/com/ericsson/bss/cassandra/ecchronos/application/config/TestCasLockFactoryConfig.java @@ -17,7 +17,6 @@ import com.ericsson.bss.cassandra.ecchronos.application.config.lockfactory.CasLockFactoryConfig; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import org.junit.Before; import org.junit.Test; import java.io.File; @@ -32,8 +31,6 @@ public void testCasLockFactoryConfigWithProvidedValue() throws IOException { CasLockFactoryConfig casLockFactoryConfig = getCasLockFactoryConfig("all_set.yml"); assertThat(casLockFactoryConfig.getKeyspaceName()).isEqualTo("ecc"); - assertThat(casLockFactoryConfig.getLockTimeInSeconds()).isEqualTo(800L); - assertThat(casLockFactoryConfig.getLockUpdateTimeInSeconds()).isEqualTo(80L); assertThat(casLockFactoryConfig.getFailureCacheExpiryTimeInSeconds()).isEqualTo(100L); } @@ -42,10 +39,7 @@ public void testCasLockFactoryConfigDefaultValue() throws IOException { CasLockFactoryConfig casLockFactoryConfig = getCasLockFactoryConfig("nothing_set.yml"); assertThat(casLockFactoryConfig.getKeyspaceName()).isEqualTo("ecchronos"); - assertThat(casLockFactoryConfig.getLockTimeInSeconds()).isEqualTo(600L); - assertThat(casLockFactoryConfig.getLockUpdateTimeInSeconds()).isEqualTo(60L); assertThat(casLockFactoryConfig.getFailureCacheExpiryTimeInSeconds()).isEqualTo(30L); - } private CasLockFactoryConfig getCasLockFactoryConfig(final String fileName) throws IOException diff --git a/application/src/test/resources/all_set.yml b/application/src/test/resources/all_set.yml index 64ba659b2..db2e66c80 100644 --- a/application/src/test/resources/all_set.yml +++ b/application/src/test/resources/all_set.yml @@ -79,8 +79,6 @@ statistics: lock_factory: cas: keyspace: ecc - lock_time_in_seconds: 800 - lock_update_time_in_seconds: 80 cache_expiry_time_in_seconds: 100 diff --git a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java index 92a4b04fc..e1f23be13 100644 --- a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java +++ b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java @@ -38,9 +38,6 @@ public class CASLockFactoryService implements LockFactory { private static final String DEFAULT_KEYSPACE_NAME = "ecchronos"; - private static final long DEFAULT_LOCK_TIME_IN_SECONDS = 600L; - private static final long DEFAULT_UPDATE_TIME_IN_SECONDS = 60L; - @Reference(service = NativeConnectionProvider.class, cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.STATIC) @@ -66,8 +63,6 @@ public final synchronized void activate(final Configuration configuration) .withHostStates(myHostStates) .withStatementDecorator(myStatementDecorator) .withKeyspaceName(configuration.keyspaceName()) - .withLockTimeInSeconds(configuration.lockTimeInSeconds()) - .withLockUpdateTimeInSeconds(configuration.lockUpdateTimeInSeconds()) .build(); } @@ -105,13 +100,5 @@ public final boolean sufficientNodesForLocking(final String dataCenter, final St @AttributeDefinition(name = "The lock factory keyspace to use", description = "The name of the keyspace containing the lock factory tables") String keyspaceName() default DEFAULT_KEYSPACE_NAME; - @AttributeDefinition(name = "Lock Time", - description = "The duration for which the lock is held, in seconds") - long lockTimeInSeconds() default DEFAULT_LOCK_TIME_IN_SECONDS; - - @AttributeDefinition(name = "Lock Update Time", - description = "The interval at which the lock is updated, in seconds") - long lockUpdateTimeInSeconds() default DEFAULT_UPDATE_TIME_IN_SECONDS; - } } diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java index 53888874e..e9ddc899f 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java @@ -15,6 +15,7 @@ package com.ericsson.bss.cassandra.ecchronos.core; import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.PreparedStatement; @@ -26,6 +27,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import com.datastax.oss.driver.api.querybuilder.QueryBuilder; import com.ericsson.bss.cassandra.ecchronos.connection.DataCenterAwareStatement; import com.ericsson.bss.cassandra.ecchronos.connection.NativeConnectionProvider; @@ -90,6 +92,8 @@ public final class CASLockFactory implements LockFactory, Closeable private static final String COLUMN_PRIORITY = "priority"; private static final String TABLE_LOCK = "lock"; private static final String TABLE_LOCK_PRIORITY = "lock_priority"; + private static final int REFRESH_INTERVAL_RATIO = 10; + private static final int DEFAULT_LOCK_TIME_IN_SECONDS = 600; private final UUID myUuid; @@ -202,14 +206,31 @@ private CASLockFactory(final Builder builder) myUuid = hostId; - int myFailedLockRetryAttempts = (int) (builder.myLockTimeInSeconds / builder.myLockUpdateTimeInSeconds) - 1; + int lockTimeInSeconds = getDefaultTimeToLiveFromLockTable(); + int lockUpdateTimeInSeconds = lockTimeInSeconds / REFRESH_INTERVAL_RATIO; + int myFailedLockRetryAttempts = (lockTimeInSeconds / lockUpdateTimeInSeconds) - 1; myCasLockFactoryCacheContext = CASLockFactoryCacheContext.newBuilder() - .withLockUpdateTimeInSeconds(builder.myLockUpdateTimeInSeconds) + .withLockUpdateTimeInSeconds(lockUpdateTimeInSeconds) .withFailedLockRetryAttempts(myFailedLockRetryAttempts) .withLockCache(new LockCache(this::doTryLock, builder.myCacheExpiryTimeInSeconds)) .build(); } + private int getDefaultTimeToLiveFromLockTable() + { + TableMetadata tableMetadata = mySession.getMetadata() + .getKeyspace(myKeyspaceName) + .flatMap(ks -> ks.getTable(TABLE_LOCK)) + .orElse(null); + + if (tableMetadata != null && tableMetadata.getOptions() != null) + { + Map tableOptions = tableMetadata.getOptions(); + return (Integer) tableOptions.get(CqlIdentifier.fromInternal("default_time_to_live")); + } + return DEFAULT_LOCK_TIME_IN_SECONDS; + } + @Override public DistributedLock tryLock(final String dataCenter, final String resource, @@ -302,16 +323,12 @@ public static Builder builder() public static class Builder { private static final String DEFAULT_KEYSPACE_NAME = "ecchronos"; - private static final long DEFAULT_LOCK_TIME_IN_SECONDS = 600L; - private static final long DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS = 60L; private static final long DEFAULT_EXPIRY_TIME_IN_SECONDS = 30L; private NativeConnectionProvider myNativeConnectionProvider; private HostStates myHostStates; private StatementDecorator myStatementDecorator; private String myKeyspaceName = DEFAULT_KEYSPACE_NAME; - private long myLockTimeInSeconds = DEFAULT_LOCK_TIME_IN_SECONDS; - private long myLockUpdateTimeInSeconds = DEFAULT_LOCK_UPDATE_TIME_IN_SECONDS; private long myCacheExpiryTimeInSeconds = DEFAULT_EXPIRY_TIME_IN_SECONDS; public final Builder withNativeConnectionProvider(final NativeConnectionProvider nativeConnectionProvider) @@ -338,18 +355,6 @@ public final Builder withKeyspaceName(final String keyspaceName) return this; } - public final Builder withLockTimeInSeconds(final long lockTimeInSeconds) - { - myLockTimeInSeconds = lockTimeInSeconds; - return this; - } - - public final Builder withLockUpdateTimeInSeconds(final long lockUpdateTimeInSeconds) - { - myLockUpdateTimeInSeconds = lockUpdateTimeInSeconds; - return this; - } - public final Builder withCacheExpiryInSeconds(final long cacheExpiryInSeconds) { myCacheExpiryTimeInSeconds = cacheExpiryInSeconds; diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactoryCacheContext.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactoryCacheContext.java index 531cb7d5d..f0c241521 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactoryCacheContext.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactoryCacheContext.java @@ -54,10 +54,10 @@ public static Builder newBuilder() public static class Builder { private LockCache myLockCache; - private long myLockUpdateTimeInSeconds; + private int myLockUpdateTimeInSeconds; private int myFailedLockRetryAttempts; - public final Builder withLockUpdateTimeInSeconds(final long lockTimeInSeconds) + public final Builder withLockUpdateTimeInSeconds(final int lockTimeInSeconds) { myLockUpdateTimeInSeconds = lockTimeInSeconds; return this; From a7494c3c2ac387f6561a3e7bce2924c8e98ae682 Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Wed, 11 Oct 2023 16:25:26 +0200 Subject: [PATCH 10/14] Dynamic lock Refresh and configurable failure Cache #543 Make expiry time of lock failure cache configurable, the defaults should be as they're today. Now failure cache expiry time value would be picked from ecc.yml. Lock refresh is dynamic based on the TTL of ecchronos.lock table --- CHANGES.md | 2 +- .../ecchronos/core/CASLockFactory.java | 25 +++++++++++++------ .../ecchronos/core/TestCASLockFactory.java | 15 ++++++++++- docs/UPGRADE.md | 11 ++++++++ 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a25c0aff6..94ef4483b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,7 +18,7 @@ * Reread repair configuration when a node state changes - Issues #470 and #478 * Support configuring backoff for failed jobs - Issue #475 * Dropping keyspaces does not clean up schedules - Issue #469 -* Make lock refresh + failure cache configurable - Issues #543 and #587 +* Make lock refresh + failure cache configurable - Issues #543 ### Merged from 1.2 diff --git a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java index e9ddc899f..1009d1077 100644 --- a/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java +++ b/core/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/CASLockFactory.java @@ -205,14 +205,19 @@ private CASLockFactory(final Builder builder) } myUuid = hostId; + myCasLockFactoryCacheContext = buildCasLockFactoryCacheContext(builder.myCacheExpiryTimeInSeconds); + } + private CASLockFactoryCacheContext buildCasLockFactoryCacheContext(final long cacheExpiryTimeInSeconds) + { int lockTimeInSeconds = getDefaultTimeToLiveFromLockTable(); int lockUpdateTimeInSeconds = lockTimeInSeconds / REFRESH_INTERVAL_RATIO; int myFailedLockRetryAttempts = (lockTimeInSeconds / lockUpdateTimeInSeconds) - 1; - myCasLockFactoryCacheContext = CASLockFactoryCacheContext.newBuilder() + + return CASLockFactoryCacheContext.newBuilder() .withLockUpdateTimeInSeconds(lockUpdateTimeInSeconds) .withFailedLockRetryAttempts(myFailedLockRetryAttempts) - .withLockCache(new LockCache(this::doTryLock, builder.myCacheExpiryTimeInSeconds)) + .withLockCache(new LockCache(this::doTryLock, cacheExpiryTimeInSeconds)) .build(); } @@ -222,13 +227,13 @@ private int getDefaultTimeToLiveFromLockTable() .getKeyspace(myKeyspaceName) .flatMap(ks -> ks.getTable(TABLE_LOCK)) .orElse(null); - - if (tableMetadata != null && tableMetadata.getOptions() != null) + if (tableMetadata == null || tableMetadata.getOptions() == null) { - Map tableOptions = tableMetadata.getOptions(); - return (Integer) tableOptions.get(CqlIdentifier.fromInternal("default_time_to_live")); + LOG.warn("Could not parse default ttl of {}.{}", myKeyspaceName, TABLE_LOCK); + return DEFAULT_LOCK_TIME_IN_SECONDS; } - return DEFAULT_LOCK_TIME_IN_SECONDS; + Map tableOptions = tableMetadata.getOptions(); + return (Integer) tableOptions.get(CqlIdentifier.fromInternal("default_time_to_live")); } @Override @@ -315,6 +320,12 @@ UUID getHostId() return myUuid; } + @VisibleForTesting + CASLockFactoryCacheContext getCasLockFactoryCacheContext() + { + return myCasLockFactoryCacheContext; + } + public static Builder builder() { return new Builder(); diff --git a/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java b/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java index 60307fff9..f63a77622 100644 --- a/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java +++ b/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java @@ -126,7 +126,20 @@ public void testCleanup() execute(myRemoveLockStatement.bind("lock")); myLockFactory.close(); } - + @Test + public void testGetDefaultTimeToLiveFromLockTable() throws LockException + { + String alterLockTable = String.format("ALTER TABLE %s.%s WITH default_time_to_live = 1200;", myKeyspaceName, TABLE_LOCK); + mySession.execute(alterLockTable); + myLockFactory = new CASLockFactory.Builder() + .withNativeConnectionProvider(getNativeConnectionProvider()) + .withHostStates(hostStates) + .withStatementDecorator(s -> s) + .withKeyspaceName(myKeyspaceName) + .build(); + assertThat(myLockFactory.getCasLockFactoryCacheContext().getFailedLockRetryAttempts()).isEqualTo(9); + assertThat(myLockFactory.getCasLockFactoryCacheContext().getLockUpdateTimeInSeconds()).isEqualTo(120); + } @Test public void testGetLock() throws LockException { diff --git a/docs/UPGRADE.md b/docs/UPGRADE.md index c2f2733b8..ae7fe5265 100644 --- a/docs/UPGRADE.md +++ b/docs/UPGRADE.md @@ -22,6 +22,17 @@ The command to add the column is shown below: ALTER TABLE ecchronos.on_demand_repair_status ADD repair_type text; ``` +## Lock Refresh and failure cache + +Added support for dynamic calculation of the lock refresh rate based on the Time-To-Live (TTL) from +the ecchronos.lock table. The refresh rate is calculated by the formula TTL/10. +This update ensures that the lock refresh rate aligns consistently with the actual TTL of the ecchronos.lock table. + +Additionally, a feature has been introduced that allows users to configure the expiry time of the lock failure cache +directly within the `ecc.yaml` file. + +It’s important to note that while making these changes, the default behavior remains unchanged. + # Upgrade to 4.x ## Metrics From 3c0d44d25084f52e6dbcfbc73601935da3249445 Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Thu, 19 Oct 2023 15:30:54 +0200 Subject: [PATCH 11/14] Fixed comments --- CHANGES.md | 2 +- .../core/osgi/CASLockFactoryService.java | 3 +- .../ecchronos/core/TestCASLockFactory.java | 33 +++++++++++++------ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 94ef4483b..0f12ce237 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## Version 5.0.0 (Not yet released) +* Make locks dynamic based on TTL of lock table - Issue #543 * Add new repair type parallel_vnode - Issue #554 * Add validation of repair interval and alarms - Issue #560 * Insert into repair history only on session finish - Issue #565 @@ -18,7 +19,6 @@ * Reread repair configuration when a node state changes - Issues #470 and #478 * Support configuring backoff for failed jobs - Issue #475 * Dropping keyspaces does not clean up schedules - Issue #469 -* Make lock refresh + failure cache configurable - Issues #543 ### Merged from 1.2 diff --git a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java index e1f23be13..96a699379 100644 --- a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java +++ b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java @@ -38,6 +38,7 @@ public class CASLockFactoryService implements LockFactory { private static final String DEFAULT_KEYSPACE_NAME = "ecchronos"; + @Reference(service = NativeConnectionProvider.class, cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.STATIC) @@ -101,4 +102,4 @@ public final boolean sufficientNodesForLocking(final String dataCenter, final St description = "The name of the keyspace containing the lock factory tables") String keyspaceName() default DEFAULT_KEYSPACE_NAME; } -} +} \ No newline at end of file diff --git a/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java b/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java index f63a77622..e6b370c2d 100644 --- a/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java +++ b/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java @@ -66,11 +66,12 @@ import org.junit.runners.Parameterized; @NotThreadSafe -@RunWith(Parameterized.class) +@RunWith (Parameterized.class) public class TestCASLockFactory extends AbstractCassandraTest { @Parameterized.Parameters - public static Collection keyspaceNames() { + public static Collection keyspaceNames() + { return Arrays.asList("ecchronos", "anotherkeyspace"); } @@ -92,9 +93,14 @@ public static Collection keyspaceNames() { @Before public void startup() { - mySession.execute(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'NetworkTopologyStrategy', 'DC1': 1}", myKeyspaceName)); - mySession.execute(String.format("CREATE TABLE IF NOT EXISTS %s.lock (resource text, node uuid, metadata map, PRIMARY KEY(resource)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", myKeyspaceName)); - mySession.execute(String.format("CREATE TABLE IF NOT EXISTS %s.lock_priority (resource text, node uuid, priority int, PRIMARY KEY(resource, node)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", myKeyspaceName)); + mySession.execute(String.format( + "CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'NetworkTopologyStrategy', 'DC1': 1}", myKeyspaceName)); + mySession.execute(String.format( + "CREATE TABLE IF NOT EXISTS %s.lock (resource text, node uuid, metadata map, PRIMARY KEY(resource)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", + myKeyspaceName)); + mySession.execute(String.format( + "CREATE TABLE IF NOT EXISTS %s.lock_priority (resource text, node uuid, priority int, PRIMARY KEY(resource, node)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", + myKeyspaceName)); hostStates = mock(HostStates.class); when(hostStates.isUp(any(Node.class))).thenReturn(true); @@ -113,9 +119,12 @@ public void startup() .build() .setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM) .setSerialConsistencyLevel(ConsistencyLevel.LOCAL_SERIAL)); - myRemoveLockStatement = mySession.prepare(String.format("DELETE FROM %s.%s WHERE resource=? IF EXISTS", myKeyspaceName, TABLE_LOCK)); - myCompeteStatement = mySession.prepare(String.format("INSERT INTO %s.%s (resource, node, priority) VALUES (?, ?, ?)", myKeyspaceName, TABLE_LOCK_PRIORITY)); - myGetPrioritiesStatement = mySession.prepare(String.format("SELECT * FROM %s.%s WHERE resource=?", myKeyspaceName, TABLE_LOCK_PRIORITY)); + myRemoveLockStatement = + mySession.prepare(String.format("DELETE FROM %s.%s WHERE resource=? IF EXISTS", myKeyspaceName, TABLE_LOCK)); + myCompeteStatement = mySession.prepare( + String.format("INSERT INTO %s.%s (resource, node, priority) VALUES (?, ?, ?)", myKeyspaceName, TABLE_LOCK_PRIORITY)); + myGetPrioritiesStatement = + mySession.prepare(String.format("SELECT * FROM %s.%s WHERE resource=?", myKeyspaceName, TABLE_LOCK_PRIORITY)); } @After @@ -126,6 +135,7 @@ public void testCleanup() execute(myRemoveLockStatement.bind("lock")); myLockFactory.close(); } + @Test public void testGetDefaultTimeToLiveFromLockTable() throws LockException { @@ -140,6 +150,7 @@ public void testGetDefaultTimeToLiveFromLockTable() throws LockException assertThat(myLockFactory.getCasLockFactoryCacheContext().getFailedLockRetryAttempts()).isEqualTo(9); assertThat(myLockFactory.getCasLockFactoryCacheContext().getLockUpdateTimeInSeconds()).isEqualTo(120); } + @Test public void testGetLock() throws LockException { @@ -171,7 +182,7 @@ public void testGlobalLockTakenThrowsException() assertThat(myLockFactory.getCachedFailure(null, "lock")).isNotEmpty(); } - @Test(timeout = 1000) + @Test (timeout = 1000) public void testGlobalLockTakenIsCachedOnSecondTry() throws InterruptedException { execute(myLockStatement.bind("lock", UUID.randomUUID(), new HashMap<>())); @@ -340,7 +351,9 @@ public void testActivateWithoutAllTablesCausesIllegalStateException() .withKeyspaceName(myKeyspaceName) .build()); - mySession.execute(String.format("CREATE TABLE IF NOT EXISTS %s.%s (resource text, node uuid, metadata map, PRIMARY KEY(resource)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", myKeyspaceName, TABLE_LOCK)); + mySession.execute(String.format( + "CREATE TABLE IF NOT EXISTS %s.%s (resource text, node uuid, metadata map, PRIMARY KEY(resource)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", + myKeyspaceName, TABLE_LOCK)); } @Test From 5df6d28486d19fd959e17829dd779c1fd0113d2c Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Thu, 19 Oct 2023 15:49:28 +0200 Subject: [PATCH 12/14] Fixed formatting --- .../core/osgi/CASLockFactoryService.java | 105 ------------------ .../ecchronos/core/TestCASLockFactory.java | 33 ++---- 2 files changed, 11 insertions(+), 127 deletions(-) delete mode 100644 core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java diff --git a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java deleted file mode 100644 index 96a699379..000000000 --- a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2018 Telefonaktiebolaget LM Ericsson - * - * Licensed 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 com.ericsson.bss.cassandra.ecchronos.core.osgi; - -import com.ericsson.bss.cassandra.ecchronos.core.CASLockFactory; -import com.ericsson.bss.cassandra.ecchronos.core.HostStates; -import com.ericsson.bss.cassandra.ecchronos.core.exceptions.LockException; - -import com.ericsson.bss.cassandra.ecchronos.connection.NativeConnectionProvider; -import com.ericsson.bss.cassandra.ecchronos.connection.StatementDecorator; -import com.ericsson.bss.cassandra.ecchronos.core.scheduling.LockFactory; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.osgi.service.metatype.annotations.AttributeDefinition; -import org.osgi.service.metatype.annotations.Designate; -import org.osgi.service.metatype.annotations.ObjectClassDefinition; - -import java.util.Map; - -@Component(service = LockFactory.class) -@Designate(ocd = CASLockFactoryService.Configuration.class) -public class CASLockFactoryService implements LockFactory -{ - private static final String DEFAULT_KEYSPACE_NAME = "ecchronos"; - - @Reference(service = NativeConnectionProvider.class, - cardinality = ReferenceCardinality.MANDATORY, - policy = ReferencePolicy.STATIC) - private volatile NativeConnectionProvider myNativeConnectionProvider; - - @Reference (service = StatementDecorator.class, - cardinality = ReferenceCardinality.MANDATORY, - policy = ReferencePolicy.STATIC) - private volatile StatementDecorator myStatementDecorator; - - @Reference (service = HostStates.class, - cardinality = ReferenceCardinality.MANDATORY, - policy = ReferencePolicy.STATIC) - private volatile HostStates myHostStates; - - private volatile CASLockFactory myDelegateLockFactory; - - @Activate - public final synchronized void activate(final Configuration configuration) - { - myDelegateLockFactory = CASLockFactory.builder() - .withNativeConnectionProvider(myNativeConnectionProvider) - .withHostStates(myHostStates) - .withStatementDecorator(myStatementDecorator) - .withKeyspaceName(configuration.keyspaceName()) - .build(); - } - - @Deactivate - public final synchronized void deactivate() - { - myDelegateLockFactory.close(); - } - - @Override - public final DistributedLock tryLock(final String dataCenter, - final String resource, - final int priority, - final Map metadata) - throws LockException - { - return myDelegateLockFactory.tryLock(dataCenter, resource, priority, metadata); - } - - @Override - public final Map getLockMetadata(final String dataCenter, final String resource) - { - return myDelegateLockFactory.getLockMetadata(dataCenter, resource); - } - - @Override - public final boolean sufficientNodesForLocking(final String dataCenter, final String resource) - { - return myDelegateLockFactory.sufficientNodesForLocking(dataCenter, resource); - } - - @ObjectClassDefinition - public @interface Configuration - { - @AttributeDefinition(name = "The lock factory keyspace to use", - description = "The name of the keyspace containing the lock factory tables") - String keyspaceName() default DEFAULT_KEYSPACE_NAME; - } -} \ No newline at end of file diff --git a/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java b/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java index e6b370c2d..db42000f0 100644 --- a/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java +++ b/core/src/test/java/com/ericsson/bss/cassandra/ecchronos/core/TestCASLockFactory.java @@ -66,12 +66,11 @@ import org.junit.runners.Parameterized; @NotThreadSafe -@RunWith (Parameterized.class) +@RunWith(Parameterized.class) public class TestCASLockFactory extends AbstractCassandraTest { @Parameterized.Parameters - public static Collection keyspaceNames() - { + public static Collection keyspaceNames() { return Arrays.asList("ecchronos", "anotherkeyspace"); } @@ -93,14 +92,9 @@ public static Collection keyspaceNames() @Before public void startup() { - mySession.execute(String.format( - "CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'NetworkTopologyStrategy', 'DC1': 1}", myKeyspaceName)); - mySession.execute(String.format( - "CREATE TABLE IF NOT EXISTS %s.lock (resource text, node uuid, metadata map, PRIMARY KEY(resource)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", - myKeyspaceName)); - mySession.execute(String.format( - "CREATE TABLE IF NOT EXISTS %s.lock_priority (resource text, node uuid, priority int, PRIMARY KEY(resource, node)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", - myKeyspaceName)); + mySession.execute(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'NetworkTopologyStrategy', 'DC1': 1}", myKeyspaceName)); + mySession.execute(String.format("CREATE TABLE IF NOT EXISTS %s.lock (resource text, node uuid, metadata map, PRIMARY KEY(resource)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", myKeyspaceName)); + mySession.execute(String.format("CREATE TABLE IF NOT EXISTS %s.lock_priority (resource text, node uuid, priority int, PRIMARY KEY(resource, node)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", myKeyspaceName)); hostStates = mock(HostStates.class); when(hostStates.isUp(any(Node.class))).thenReturn(true); @@ -119,12 +113,9 @@ public void startup() .build() .setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM) .setSerialConsistencyLevel(ConsistencyLevel.LOCAL_SERIAL)); - myRemoveLockStatement = - mySession.prepare(String.format("DELETE FROM %s.%s WHERE resource=? IF EXISTS", myKeyspaceName, TABLE_LOCK)); - myCompeteStatement = mySession.prepare( - String.format("INSERT INTO %s.%s (resource, node, priority) VALUES (?, ?, ?)", myKeyspaceName, TABLE_LOCK_PRIORITY)); - myGetPrioritiesStatement = - mySession.prepare(String.format("SELECT * FROM %s.%s WHERE resource=?", myKeyspaceName, TABLE_LOCK_PRIORITY)); + myRemoveLockStatement = mySession.prepare(String.format("DELETE FROM %s.%s WHERE resource=? IF EXISTS", myKeyspaceName, TABLE_LOCK)); + myCompeteStatement = mySession.prepare(String.format("INSERT INTO %s.%s (resource, node, priority) VALUES (?, ?, ?)", myKeyspaceName, TABLE_LOCK_PRIORITY)); + myGetPrioritiesStatement = mySession.prepare(String.format("SELECT * FROM %s.%s WHERE resource=?", myKeyspaceName, TABLE_LOCK_PRIORITY)); } @After @@ -182,7 +173,7 @@ public void testGlobalLockTakenThrowsException() assertThat(myLockFactory.getCachedFailure(null, "lock")).isNotEmpty(); } - @Test (timeout = 1000) + @Test(timeout = 1000) public void testGlobalLockTakenIsCachedOnSecondTry() throws InterruptedException { execute(myLockStatement.bind("lock", UUID.randomUUID(), new HashMap<>())); @@ -351,9 +342,7 @@ public void testActivateWithoutAllTablesCausesIllegalStateException() .withKeyspaceName(myKeyspaceName) .build()); - mySession.execute(String.format( - "CREATE TABLE IF NOT EXISTS %s.%s (resource text, node uuid, metadata map, PRIMARY KEY(resource)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", - myKeyspaceName, TABLE_LOCK)); + mySession.execute(String.format("CREATE TABLE IF NOT EXISTS %s.%s (resource text, node uuid, metadata map, PRIMARY KEY(resource)) WITH default_time_to_live = 600 AND gc_grace_seconds = 0", myKeyspaceName, TABLE_LOCK)); } @Test @@ -429,4 +418,4 @@ private long getWriteCount(String tableName) return tableMetrics.writeLatency.latency.getCount(); } -} +} \ No newline at end of file From da7882e1c736f0f4afdfe7dc70e68a6c6d684922 Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Fri, 20 Oct 2023 09:24:29 +0200 Subject: [PATCH 13/14] Added deleted file --- .../core/osgi/CASLockFactoryService.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java diff --git a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java new file mode 100644 index 000000000..96a699379 --- /dev/null +++ b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java @@ -0,0 +1,105 @@ +/* + * Copyright 2018 Telefonaktiebolaget LM Ericsson + * + * Licensed 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 com.ericsson.bss.cassandra.ecchronos.core.osgi; + +import com.ericsson.bss.cassandra.ecchronos.core.CASLockFactory; +import com.ericsson.bss.cassandra.ecchronos.core.HostStates; +import com.ericsson.bss.cassandra.ecchronos.core.exceptions.LockException; + +import com.ericsson.bss.cassandra.ecchronos.connection.NativeConnectionProvider; +import com.ericsson.bss.cassandra.ecchronos.connection.StatementDecorator; +import com.ericsson.bss.cassandra.ecchronos.core.scheduling.LockFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import java.util.Map; + +@Component(service = LockFactory.class) +@Designate(ocd = CASLockFactoryService.Configuration.class) +public class CASLockFactoryService implements LockFactory +{ + private static final String DEFAULT_KEYSPACE_NAME = "ecchronos"; + + @Reference(service = NativeConnectionProvider.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.STATIC) + private volatile NativeConnectionProvider myNativeConnectionProvider; + + @Reference (service = StatementDecorator.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.STATIC) + private volatile StatementDecorator myStatementDecorator; + + @Reference (service = HostStates.class, + cardinality = ReferenceCardinality.MANDATORY, + policy = ReferencePolicy.STATIC) + private volatile HostStates myHostStates; + + private volatile CASLockFactory myDelegateLockFactory; + + @Activate + public final synchronized void activate(final Configuration configuration) + { + myDelegateLockFactory = CASLockFactory.builder() + .withNativeConnectionProvider(myNativeConnectionProvider) + .withHostStates(myHostStates) + .withStatementDecorator(myStatementDecorator) + .withKeyspaceName(configuration.keyspaceName()) + .build(); + } + + @Deactivate + public final synchronized void deactivate() + { + myDelegateLockFactory.close(); + } + + @Override + public final DistributedLock tryLock(final String dataCenter, + final String resource, + final int priority, + final Map metadata) + throws LockException + { + return myDelegateLockFactory.tryLock(dataCenter, resource, priority, metadata); + } + + @Override + public final Map getLockMetadata(final String dataCenter, final String resource) + { + return myDelegateLockFactory.getLockMetadata(dataCenter, resource); + } + + @Override + public final boolean sufficientNodesForLocking(final String dataCenter, final String resource) + { + return myDelegateLockFactory.sufficientNodesForLocking(dataCenter, resource); + } + + @ObjectClassDefinition + public @interface Configuration + { + @AttributeDefinition(name = "The lock factory keyspace to use", + description = "The name of the keyspace containing the lock factory tables") + String keyspaceName() default DEFAULT_KEYSPACE_NAME; + } +} \ No newline at end of file From 4433bcfa3f14d218e0549aa7e3514b62c9d5d4cc Mon Sep 17 00:00:00 2001 From: sajid riaz Date: Fri, 20 Oct 2023 10:22:11 +0200 Subject: [PATCH 14/14] checkout origin/master --- .../cassandra/ecchronos/core/osgi/CASLockFactoryService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java index 96a699379..0655e5ff6 100644 --- a/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java +++ b/core.osgi/src/main/java/com/ericsson/bss/cassandra/ecchronos/core/osgi/CASLockFactoryService.java @@ -102,4 +102,4 @@ public final boolean sufficientNodesForLocking(final String dataCenter, final St description = "The name of the keyspace containing the lock factory tables") String keyspaceName() default DEFAULT_KEYSPACE_NAME; } -} \ No newline at end of file +}