Skip to content

Commit

Permalink
Merge pull request #134 from swisspost/feature/NEMOSRV-589_Independen…
Browse files Browse the repository at this point in the history
…t_Storage_Cleanup

Feature/nemosrv 589 independent storage cleanup
  • Loading branch information
mcweba authored Aug 29, 2023
2 parents 3e24ecb + 8c4f8b4 commit 07df564
Show file tree
Hide file tree
Showing 41 changed files with 2,285 additions and 1,234 deletions.
49 changes: 25 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,30 +188,31 @@ The data compression feature is not compatible with all vertx-rest-storage featu

The following configuration values are available:

| Property | Type | Default value | Description |
|:----------------------------------------|:-------|:-------------------------|:--------------------------------------------------------------------------------------------------------------------------------------|
| root | common | . | The prefix for the directory or redis key |
| storageType | common | filesystem | The storage implementation to use. Choose between filesystem or redis |
| port | common | 8989 | The port the mod listens to when HTTP API is enabled. |
| httpRequestHandlerEnabled | common | true | When set to _false_, the storage is accessible throught the event bus only. |
| httpRequestHandlerAuthenticationEnabled | common | false | Enable / disable authentication for the HTTP API |
| httpRequestHandlerUsername | common | | The username for the HTTP API authentication |
| httpRequestHandlerPassword | common | | The password for the HTTP API authentication |
| prefix | common | / | The part of the URL path before this handler (aka "context path" in JEE terminology) |
| storageAddress | common | resource-storage | The eventbus address the mod listens to. |
| editorConfig | common | | Additional configuration values for the editor |
| confirmCollectionDelete | common | false | When set to _true_, an additional _recursive=true_ url parameter has to be set to delete collections |
| redisHost | redis | localhost | The host where redis is running on |
| redisPort | redis | 6379 | The port where redis is running on |
| expirablePrefix | redis | rest-storage:expirable | The prefix for expirable data redis keys |
| resourcesPrefix | redis | rest-storage:resources | The prefix for resources redis keys |
| collectionsPrefix | redis | rest-storage:collections | The prefix for collections redis keys |
| deltaResourcesPrefix | redis | delta:resources | The prefix for delta resources redis keys |
| deltaEtagsPrefix | redis | delta:etags | The prefix for delta etags redis keys |
| lockPrefix | redis | rest-storage:locks | The prefix for lock redis keys |
| resourceCleanupAmount | redis | 100000 | The maximum amount of resources to clean in a single cleanup run |
| rejectStorageWriteOnLowMemory | redis | false | When set to _true_, PUT requests with the x-importance-level header can be rejected when memory gets low |
| freeMemoryCheckIntervalMs | redis | 60000 | The interval in milliseconds to calculate the actual memory usage |
| Property | Type | Default value | Description |
|:----------------------------------------|:-------|:-------------------------|:------------------------------------------------------------------------------------------------------------------------------|
| root | common | . | The prefix for the directory or redis key |
| storageType | common | filesystem | The storage implementation to use. Choose between filesystem or redis |
| port | common | 8989 | The port the mod listens to when HTTP API is enabled. |
| httpRequestHandlerEnabled | common | true | When set to _false_, the storage is accessible throught the event bus only. |
| httpRequestHandlerAuthenticationEnabled | common | false | Enable / disable authentication for the HTTP API |
| httpRequestHandlerUsername | common | | The username for the HTTP API authentication |
| httpRequestHandlerPassword | common | | The password for the HTTP API authentication |
| prefix | common | / | The part of the URL path before this handler (aka "context path" in JEE terminology) |
| storageAddress | common | resource-storage | The eventbus address the mod listens to. |
| editorConfig | common | | Additional configuration values for the editor |
| confirmCollectionDelete | common | false | When set to _true_, an additional _recursive=true_ url parameter has to be set to delete collections |
| redisHost | redis | localhost | The host where redis is running on |
| redisPort | redis | 6379 | The port where redis is running on |
| expirablePrefix | redis | rest-storage:expirable | The prefix for expirable data redis keys |
| resourcesPrefix | redis | rest-storage:resources | The prefix for resources redis keys |
| collectionsPrefix | redis | rest-storage:collections | The prefix for collections redis keys |
| deltaResourcesPrefix | redis | delta:resources | The prefix for delta resources redis keys |
| deltaEtagsPrefix | redis | delta:etags | The prefix for delta etags redis keys |
| lockPrefix | redis | rest-storage:locks | The prefix for lock redis keys |
| resourceCleanupAmount | redis | 100000 | The maximum amount of resources to clean in a single cleanup run |
| resourceCleanupIntervalSec | redis | | The interval (in seconds) how often to peform the storage cleanup. When set to _null_ no periodic storage cleanup is peformed |
| rejectStorageWriteOnLowMemory | redis | false | When set to _true_, PUT requests with the x-importance-level header can be rejected when memory gets low |
| freeMemoryCheckIntervalMs | redis | 60000 | The interval in milliseconds to calculate the actual memory usage |

### Configuration util

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@
<mockito.version>1.10.19</mockito.version>
<rest-assured.version>4.3.0</rest-assured.version>
<awaitility.version>1.6.5</awaitility.version>
<jedis.version>2.9.0</jedis.version>
<jedis.version>3.7.0</jedis.version>
<project.build.sourceEncoding>UTF8</project.build.sourceEncoding>
<sonatypeOssDistMgmtSnapshotsUrl>
https://oss.sonatype.org/content/repositories/snapshots/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.vertx.redis.client.RedisAPI;
import io.vertx.redis.client.RedisOptions;
import org.apache.commons.lang.StringUtils;
import org.swisspush.reststorage.redis.RedisProvider;
import org.swisspush.reststorage.util.ModuleConfiguration;

import java.util.concurrent.atomic.AtomicReference;
Expand All @@ -20,7 +21,7 @@ public class DefaultRedisProvider implements RedisProvider {

private final Vertx vertx;

private ModuleConfiguration configuration;
private final ModuleConfiguration configuration;

private RedisAPI redisAPI;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class FileSystemStorage implements Storage {
private final int rootLen;
private final FileSystemDirLister fileSystemDirLister;

private Logger log = LoggerFactory.getLogger(FileSystemStorage.class);
private final Logger log = LoggerFactory.getLogger(FileSystemStorage.class);

public FileSystemStorage(Vertx vertx, String root) {
this.vertx = vertx;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class MimeTypeResolver {

private Map<String, String> mimeTypes = new HashMap<>();

private String defaultMimeType;
private final String defaultMimeType;

public MimeTypeResolver(String defaultMimeType) {
this.defaultMimeType = defaultMimeType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private User(String name, String password) {
}
}

private User user;
private final User user;

public ModuleConfigurationAuthentication(ModuleConfiguration configuration) {
Objects.requireNonNull(configuration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public class RedisRestStorageRunner {

public static void main(String[] args) {
ModuleConfiguration modConfig = new ModuleConfiguration()
.storageType(ModuleConfiguration.StorageType.redis);
.storageType(ModuleConfiguration.StorageType.redis)
.resourceCleanupIntervalSec(10);

Check warning on line 18 in src/main/java/org/swisspush/reststorage/RedisRestStorageRunner.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/swisspush/reststorage/RedisRestStorageRunner.java#L17-L18

Added lines #L17 - L18 were not covered by tests

Vertx.vertx().deployVerticle(new RestStorageMod(), new DeploymentOptions().setConfig(modConfig.asJsonObject()), event ->
LoggerFactory.getLogger(RedisRestStorageRunner.class).info("rest-storage started"));
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/swisspush/reststorage/RestStorageMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import io.vertx.core.http.HttpServerRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.swisspush.reststorage.redis.RedisProvider;
import org.swisspush.reststorage.redis.RedisStorage;
import org.swisspush.reststorage.util.ModuleConfiguration;

public class RestStorageMod extends AbstractVerticle {

private Logger log = LoggerFactory.getLogger(RestStorageMod.class);
private final Logger log = LoggerFactory.getLogger(RestStorageMod.class);

private RedisProvider redisProvider;

Expand Down
36 changes: 36 additions & 0 deletions src/main/java/org/swisspush/reststorage/lock/Lock.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.swisspush.reststorage.lock;

import io.vertx.core.Future;

/**
* Cluster wide locks allow you to obtain exclusive locks across the cluster.
* This is useful when you want to do something or access a resource on only one node of a cluster at any one time.
*
* @author https://github.com/mcweba [Marc-Andre Weber]
*/
public interface Lock {
/**
* Try to acquire a lock.
* The <code>token</code> parameter value must be unique across all clients and all lock requests. The <code>lockExpiryMs</code>
* parameter defines the expiry of the lock.
* When not manually released, the lock will be released automatically when expired.
*
* @param lock The name of the lock to acquire
* @param token A unique token to define the owner of the lock
* @param lockExpiryMs The lock expiry in milliseconds
* @return Returns a Future holding a Boolean value whether the lock could be successfully acquired or not
*/
Future<Boolean> acquireLock(String lock, String token, long lockExpiryMs);

/**
* Try to release a lock.
* The <code>token</code> parameter value is used to verify that only the owner of the lock can release it.
* The <code>token</code> parameter value also prevents the original owner of an already expired lock to release a lock
* which has been acquired by another client.
*
* @param lock The name of the lock to release
* @param token A unique token to verify if the owner of the lock tries to release the lock
* @return Returns a Promise holding a Boolean value whether the lock could be successfully released or not
*/
Future<Boolean> releaseLock(String lock, String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.swisspush.reststorage.lock.impl;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray;
import io.vertx.redis.client.Command;
import io.vertx.redis.client.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.swisspush.reststorage.redis.RedisProvider;
import org.swisspush.reststorage.lock.Lock;
import org.swisspush.reststorage.lock.lua.LockLuaScripts;
import org.swisspush.reststorage.lock.lua.LuaScriptState;
import org.swisspush.reststorage.lock.lua.ReleaseLockRedisCommand;
import org.swisspush.reststorage.redis.RedisUtils;
import org.swisspush.reststorage.util.FailedAsyncResult;

import java.util.Collections;
import java.util.List;

/**
* Implementation of the {@link Lock} interface based on a redis database.
*
* @author https://github.com/mcweba [Marc-Andre Weber]
*/
public class RedisBasedLock implements Lock {

private final Logger log = LoggerFactory.getLogger(RedisBasedLock.class);

public static final String STORAGE_PREFIX = "rest-storage-lock:";

private final LuaScriptState releaseLockLuaScriptState;
private final RedisProvider redisProvider;

public RedisBasedLock(RedisProvider redisProvider) {
this.redisProvider = redisProvider;
this.releaseLockLuaScriptState = new LuaScriptState(LockLuaScripts.LOCK_RELEASE, redisProvider, false);
}

private void redisSetWithOptions(String key, String value, boolean nx, long px, Handler<AsyncResult<Response>> handler) {
JsonArray options = new JsonArray();
options.add("PX").add(px);
if (nx) {
options.add("NX");
}
redisProvider.redis().onSuccess(redisAPI -> redisAPI.send(Command.SET, RedisUtils.toPayload(key, value, options).toArray(new String[0]))
.onComplete(handler)).onFailure(throwable -> handler.handle(new FailedAsyncResult<>(throwable)));
}

@Override
public Future<Boolean> acquireLock(String lock, String token, long lockExpiryMs) {
Promise<Boolean> promise = Promise.promise();
redisSetWithOptions(buildLockKey(lock), token, true, lockExpiryMs, event -> {
if (event.succeeded()) {
if (event.result() != null) {
promise.complete("OK".equalsIgnoreCase(event.result().toString()));
} else {
promise.complete(false);
}
} else {
promise.fail(event.cause().getMessage());

Check warning on line 63 in src/main/java/org/swisspush/reststorage/lock/impl/RedisBasedLock.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/swisspush/reststorage/lock/impl/RedisBasedLock.java#L63

Added line #L63 was not covered by tests
}
});
return promise.future();
}

@Override
public Future<Boolean> releaseLock(String lock, String token) {
Promise<Boolean> promise = Promise.promise();
List<String> keys = Collections.singletonList(buildLockKey(lock));
List<String> arguments = Collections.singletonList(token);
ReleaseLockRedisCommand cmd = new ReleaseLockRedisCommand(releaseLockLuaScriptState,
keys, arguments, redisProvider, log, promise);
cmd.exec(0);
return promise.future();
}

private String buildLockKey(String lock) {
return STORAGE_PREFIX + lock;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.swisspush.reststorage.lock.lua;


/**
* @author https://github.com/mcweba [Marc-Andre Weber]
*/
public enum LockLuaScripts implements LuaScript {

LOCK_RELEASE("lock_release.lua");

private final String file;

LockLuaScripts(String file) {
this.file = file;
}

@Override
public String getFilename() {
return file;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.swisspush.reststorage.lock.lua;

/**
* @author https://github.com/mcweba [Marc-Andre Weber]
*/
public interface LuaScript {
String getFilename();
}
Loading

0 comments on commit 07df564

Please sign in to comment.