You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2020/04/17 00:32:15 UTC
[james-project] 23/39: JAMES-3137 Split responsibilities between
guice & backend
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 9c27d702c7681c1ded61e784943b10060b4c72b9
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Apr 7 17:39:35 2020 +0700
JAMES-3137 Split responsibilities between guice & backend
- ResilientClusterProvider is responsible of establishing a usable connection
to a running Cassandra Cluster
- KeyspaceFactory allows to create a given keyspace but should not enforce
application specific decisions
This means that:
- Keyspace creation decisions (and relevant testing) needs to be
performed at the Guice level
- ResilientClusterProvider needs to be tested for handling not-yet
available connection.
- ResilientClusterProvider needs to play a sample query instead of creating keyspaces
---
.../backends/cassandra/init/KeyspaceFactory.java | 31 +----
.../cassandra/init/ResilientClusterProvider.java | 4 +-
.../init/SessionWithInitializedTablesFactory.java | 30 +---
.../init/configuration/ClusterConfiguration.java | 88 +-----------
.../init/configuration/KeyspaceConfiguration.java | 98 +++++++++++++
.../james/backends/cassandra/CassandraCluster.java | 20 ++-
.../cassandra/CassandraClusterExtension.java | 13 ++
.../james/backends/cassandra/DockerCassandra.java | 63 ++++-----
.../cassandra/DockerCassandraExtension.java | 6 +
.../backends/cassandra/DockerCassandraRule.java | 5 +
.../init/ResilientClusterProviderTest.java | 135 +++++-------------
.../SessionWithInitializedTablesFactoryTest.java | 8 +-
.../modules/mailbox/CassandraSessionModule.java | 81 +++++++++--
.../modules/mailbox/KeyspacesConfiguration.java | 155 +++++++++++++++++++++
.../org/apache/james/server/CassandraProbe.java | 9 +-
.../java/org/apache/james/DockerCassandraRule.java | 10 +-
.../org/apache/james/KeyspaceCreationTest.java | 143 +++++++++++++++++++
.../mailbox/KeyspacesConfigurationTest.java | 61 ++++++++
.../rabbitmq/FixingGhostMailboxTest.java | 5 +-
19 files changed, 670 insertions(+), 295 deletions(-)
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/KeyspaceFactory.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/KeyspaceFactory.java
index b931272..5f23aad 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/KeyspaceFactory.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/KeyspaceFactory.java
@@ -22,7 +22,7 @@ package org.apache.james.backends.cassandra.init;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
-import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
+import org.apache.james.backends.cassandra.init.configuration.KeyspaceConfiguration;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
@@ -31,43 +31,24 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
public class KeyspaceFactory {
-
private static final String SYSTEM_SCHEMA = "system_schema";
private static final String KEYSPACES = "keyspaces";
private static final String KEYSPACE_NAME = "keyspace_name";
- private static final int CACHE_REPLICATION_FACTOR = 1;
-
- public static void createKeyspace(ClusterConfiguration clusterConfiguration, Cluster cluster) {
- if (clusterConfiguration.shouldCreateKeyspace()) {
- doCreateKeyspace(clusterConfiguration, cluster);
- doCreateCacheKeyspace(clusterConfiguration, cluster);
- }
- }
- private static void doCreateKeyspace(ClusterConfiguration clusterConfiguration, Cluster cluster) {
- createKeyspace(cluster, clusterConfiguration.getKeyspace(),
- clusterConfiguration.getReplicationFactor(),
- clusterConfiguration.isDurableWrites());
- }
-
- private static void createKeyspace(Cluster cluster, String keyspace, int replicationFactor, boolean durableWrites) {
+ public static void createKeyspace(KeyspaceConfiguration configuration, Cluster cluster) {
try (Session session = cluster.connect()) {
- if (!keyspaceExist(cluster, keyspace)) {
- session.execute(SchemaBuilder.createKeyspace(keyspace)
+ if (!keyspaceExist(cluster, configuration.getKeyspace())) {
+ session.execute(SchemaBuilder.createKeyspace(configuration.getKeyspace())
.with()
.replication(ImmutableMap.<String, Object>builder()
.put("class", "SimpleStrategy")
- .put("replication_factor", replicationFactor)
+ .put("replication_factor", configuration.getReplicationFactor())
.build())
- .durableWrites(durableWrites));
+ .durableWrites(configuration.isDurableWrites()));
}
}
}
- private static void doCreateCacheKeyspace(ClusterConfiguration clusterConfiguration, Cluster cluster) {
- createKeyspace(cluster, clusterConfiguration.getCacheKeyspace(), CACHE_REPLICATION_FACTOR, clusterConfiguration.isDurableWrites());
- }
-
@VisibleForTesting
public static boolean keyspaceExist(Cluster cluster, String keyspaceName) {
try (Session session = cluster.connect(SYSTEM_SCHEMA)) {
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ResilientClusterProvider.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ResilientClusterProvider.java
index 5b823bf..f91e68d 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ResilientClusterProvider.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ResilientClusterProvider.java
@@ -19,6 +19,8 @@
package org.apache.james.backends.cassandra.init;
+import static org.apache.james.backends.cassandra.init.KeyspaceFactory.keyspaceExist;
+
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.Callable;
@@ -64,7 +66,7 @@ public class ResilientClusterProvider implements Provider<Cluster> {
return () -> {
Cluster cluster = ClusterFactory.create(configuration);
try {
- KeyspaceFactory.createKeyspace(configuration, cluster);
+ keyspaceExist(cluster, "any"); // plays a sample query to ensure we can contact the cluster
return cluster;
} catch (Exception e) {
cluster.close();
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactory.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactory.java
index b3d18f4..a996966 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactory.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactory.java
@@ -23,15 +23,13 @@ import java.util.stream.Stream;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.apache.james.backends.cassandra.components.CassandraModule;
import org.apache.james.backends.cassandra.components.CassandraTable;
import org.apache.james.backends.cassandra.components.CassandraType;
-import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
-import org.apache.james.backends.cassandra.init.configuration.InjectionNames;
+import org.apache.james.backends.cassandra.init.configuration.KeyspaceConfiguration;
import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDAO;
import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionManager;
@@ -41,19 +39,14 @@ import com.datastax.driver.core.Session;
@Singleton
public class SessionWithInitializedTablesFactory implements Provider<Session> {
private final CassandraModule module;
- private final CassandraModule cacheModule;
private final Session session;
- private final Session cacheSession;
@Inject
- public SessionWithInitializedTablesFactory(ClusterConfiguration clusterConfiguration,
+ public SessionWithInitializedTablesFactory(KeyspaceConfiguration keyspaceConfiguration,
Cluster cluster,
- CassandraModule module,
- @Named(InjectionNames.CACHE) CassandraModule cacheModule) {
+ CassandraModule module) {
this.module = module;
- this.cacheModule = cacheModule;
- this.session = createSession(cluster, clusterConfiguration.getKeyspace());
- this.cacheSession = createCacheSession(cluster, clusterConfiguration.getKeyspace());
+ this.session = createSession(cluster, keyspaceConfiguration.getKeyspace());
}
private Session createSession(Cluster cluster, String keyspace) {
@@ -71,17 +64,6 @@ public class SessionWithInitializedTablesFactory implements Provider<Session> {
}
}
- private Session createCacheSession(Cluster cluster, String keyspace) {
- Session session = cluster.connect(keyspace);
- try {
- allOperationsAreFullyPerformed(session, cacheModule);
- return session;
- } catch (Exception e) {
- session.close();
- throw e;
- }
- }
-
private boolean allOperationsAreFullyPerformed(Session session, CassandraModule module) {
Stream<Boolean> operations = Stream.of(createTypes(session, module), createTables(session, module));
return operations.allMatch(updated -> updated);
@@ -102,10 +84,6 @@ public class SessionWithInitializedTablesFactory implements Provider<Session> {
return session;
}
- public Session getCacheSession() {
- return cacheSession;
- }
-
@PreDestroy
public synchronized void destroy() {
session.close();
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/ClusterConfiguration.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/ClusterConfiguration.java
index cb56e8a..eb9eeee 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/ClusterConfiguration.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/ClusterConfiguration.java
@@ -38,9 +38,6 @@ public class ClusterConfiguration {
public static class Builder {
private ImmutableList.Builder<Host> hosts;
private boolean createKeyspace;
- private Optional<String> keyspace;
- private Optional<String> cacheKeyspace;
- private Optional<Integer> replicationFactor;
private Optional<Integer> minDelay;
private Optional<Integer> maxRetry;
private Optional<QueryLoggerConfiguration> queryLoggerConfiguration;
@@ -48,16 +45,12 @@ public class ClusterConfiguration {
private Optional<Integer> readTimeoutMillis;
private Optional<Integer> connectTimeoutMillis;
private Optional<Boolean> useSsl;
- private Optional<Boolean> durableWrites;
private Optional<String> username;
private Optional<String> password;
public Builder() {
hosts = ImmutableList.builder();
createKeyspace = false;
- keyspace = Optional.empty();
- cacheKeyspace = Optional.empty();
- replicationFactor = Optional.empty();
minDelay = Optional.empty();
maxRetry = Optional.empty();
queryLoggerConfiguration = Optional.empty();
@@ -67,7 +60,6 @@ public class ClusterConfiguration {
username = Optional.empty();
password = Optional.empty();
useSsl = Optional.empty();
- durableWrites = Optional.empty();
}
public Builder host(Host host) {
@@ -90,33 +82,6 @@ public class ClusterConfiguration {
return this;
}
- public Builder keyspace(Optional<String> keyspace) {
- this.keyspace = keyspace;
- return this;
- }
-
- public Builder cacheKeyspace(Optional<String> cacheKeyspace) {
- this.keyspace = keyspace;
- return this;
- }
-
- public Builder cacheKeyspace(String cacheKeyspace) {
- return cacheKeyspace(Optional.of(cacheKeyspace));
- }
-
- public Builder keyspace(String keyspace) {
- return keyspace(Optional.of(keyspace));
- }
-
- public Builder replicationFactor(Optional<Integer> replicationFactor) {
- this.replicationFactor = replicationFactor;
- return this;
- }
-
- public Builder replicationFactor(int replicationFactor) {
- return replicationFactor(Optional.of(replicationFactor));
- }
-
public Builder minDelay(Optional<Integer> minDelay) {
this.minDelay = minDelay;
return this;
@@ -198,19 +163,10 @@ public class ClusterConfiguration {
return connectTimeoutMillis(Optional.of(connectTimeoutMillis));
}
- public Builder disableDurableWrites() {
- this.durableWrites = Optional.of(false);
-
- return this;
- }
-
public ClusterConfiguration build() {
return new ClusterConfiguration(
hosts.build(),
createKeyspace,
- keyspace.orElse(DEFAULT_KEYSPACE),
- cacheKeyspace.orElse(DEFAULT_CACHE_KEYSPACE),
- replicationFactor.orElse(DEFAULT_REPLICATION_FACTOR),
minDelay.orElse(DEFAULT_CONNECTION_MIN_DELAY),
maxRetry.orElse(DEFAULT_CONNECTION_MAX_RETRIES),
queryLoggerConfiguration,
@@ -219,27 +175,20 @@ public class ClusterConfiguration {
connectTimeoutMillis.orElse(DEFAULT_CONNECT_TIMEOUT_MILLIS),
useSsl.orElse(false),
username,
- password,
- durableWrites.orElse(true));
+ password);
}
}
private static final String CASSANDRA_NODES = "cassandra.nodes";
public static final String CASSANDRA_CREATE_KEYSPACE = "cassandra.keyspace.create";
- public static final String CASSANDRA_KEYSPACE = "cassandra.keyspace";
- public static final String CASSANDRA_CACHE_KEYSPACE = "cassandra.keyspace.cache";
public static final String CASSANDRA_USER = "cassandra.user";
public static final String CASSANDRA_PASSWORD = "cassandra.password";
public static final String CASSANDRA_SSL = "cassandra.ssl";
- public static final String REPLICATION_FACTOR = "cassandra.replication.factor";
public static final String READ_TIMEOUT_MILLIS = "cassandra.readTimeoutMillis";
public static final String CONNECT_TIMEOUT_MILLIS = "cassandra.connectTimeoutMillis";
public static final String CONNECTION_MAX_RETRY = "cassandra.retryConnection.maxRetries";
public static final String CONNECTION_RETRY_MIN_DELAY = "cassandra.retryConnection.minDelay";
- private static final String DEFAULT_KEYSPACE = "apache_james";
- private static final String DEFAULT_CACHE_KEYSPACE = "apache_james_cache";
- private static final int DEFAULT_REPLICATION_FACTOR = 1;
private static final int DEFAULT_CONNECTION_MAX_RETRIES = 10;
private static final int DEFAULT_CONNECTION_MIN_DELAY = 5000;
private static final int DEFAULT_READ_TIMEOUT_MILLIS = 5000;
@@ -259,9 +208,6 @@ public class ClusterConfiguration {
ClusterConfiguration.Builder builder = ClusterConfiguration.builder()
.hosts(listCassandraServers(configuration))
- .keyspace(Optional.ofNullable(configuration.getString(CASSANDRA_KEYSPACE, null)))
- .cacheKeyspace(Optional.ofNullable(configuration.getString(CASSANDRA_CACHE_KEYSPACE, null)))
- .replicationFactor(Optional.ofNullable(configuration.getInteger(REPLICATION_FACTOR, null)))
.minDelay(Optional.ofNullable(configuration.getInteger(CONNECTION_RETRY_MIN_DELAY, null)))
.maxRetry(Optional.ofNullable(configuration.getInteger(CONNECTION_MAX_RETRY, null)))
.queryLoggerConfiguration(QueryLoggerConfiguration.from(configuration))
@@ -318,9 +264,6 @@ public class ClusterConfiguration {
private final List<Host> hosts;
private final boolean createKeyspace;
- private final String keyspace;
- private final String cacheKeyspace;
- private final int replicationFactor;
private final int minDelay;
private final int maxRetry;
private final Optional<QueryLoggerConfiguration> queryLoggerConfiguration;
@@ -330,17 +273,13 @@ public class ClusterConfiguration {
private final boolean useSsl;
private final Optional<String> username;
private final Optional<String> password;
- private final boolean durableWrites;
- public ClusterConfiguration(List<Host> hosts, boolean createKeyspace, String keyspace, String cacheKeyspace, int replicationFactor, int minDelay, int maxRetry,
+ public ClusterConfiguration(List<Host> hosts, boolean createKeyspace, int minDelay, int maxRetry,
Optional<QueryLoggerConfiguration> queryLoggerConfiguration, Optional<PoolingOptions> poolingOptions,
int readTimeoutMillis, int connectTimeoutMillis, boolean useSsl, Optional<String> username,
- Optional<String> password, boolean durableWrites) {
+ Optional<String> password) {
this.hosts = hosts;
this.createKeyspace = createKeyspace;
- this.keyspace = keyspace;
- this.cacheKeyspace = cacheKeyspace;
- this.replicationFactor = replicationFactor;
this.minDelay = minDelay;
this.maxRetry = maxRetry;
this.queryLoggerConfiguration = queryLoggerConfiguration;
@@ -350,11 +289,6 @@ public class ClusterConfiguration {
this.useSsl = useSsl;
this.username = username;
this.password = password;
- this.durableWrites = durableWrites;
- }
-
- public boolean isDurableWrites() {
- return durableWrites;
}
public List<Host> getHosts() {
@@ -365,18 +299,6 @@ public class ClusterConfiguration {
return createKeyspace;
}
- public String getKeyspace() {
- return keyspace;
- }
-
- public String getCacheKeyspace() {
- return cacheKeyspace;
- }
-
- public int getReplicationFactor() {
- return replicationFactor;
- }
-
public int getMinDelay() {
return minDelay;
}
@@ -422,8 +344,6 @@ public class ClusterConfiguration {
&& Objects.equals(this.maxRetry, that.maxRetry)
&& Objects.equals(this.hosts, that.hosts)
&& Objects.equals(this.createKeyspace, that.createKeyspace)
- && Objects.equals(this.keyspace, that.keyspace)
- && Objects.equals(this.replicationFactor, that.replicationFactor)
&& Objects.equals(this.queryLoggerConfiguration, that.queryLoggerConfiguration)
&& Objects.equals(this.poolingOptions, that.poolingOptions)
&& Objects.equals(this.readTimeoutMillis, that.readTimeoutMillis)
@@ -437,7 +357,7 @@ public class ClusterConfiguration {
@Override
public final int hashCode() {
- return Objects.hash(hosts, createKeyspace, keyspace, replicationFactor, minDelay, maxRetry, queryLoggerConfiguration, poolingOptions,
+ return Objects.hash(hosts, createKeyspace, minDelay, maxRetry, queryLoggerConfiguration, poolingOptions,
readTimeoutMillis, connectTimeoutMillis, username, useSsl, password);
}
}
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/KeyspaceConfiguration.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/KeyspaceConfiguration.java
new file mode 100644
index 0000000..3464336
--- /dev/null
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/KeyspaceConfiguration.java
@@ -0,0 +1,98 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.backends.cassandra.init.configuration;
+
+import java.util.Objects;
+
+import com.google.common.base.Preconditions;
+
+public class KeyspaceConfiguration {
+
+ public interface Builder {
+ @FunctionalInterface
+ interface RequireKeyspace {
+ RequireReplicationFactor keyspace(String name);
+ }
+
+ @FunctionalInterface
+ interface RequireReplicationFactor {
+ RequireDurableWrites replicationFactor(int replicationFactor);
+ }
+
+ @FunctionalInterface
+ interface RequireDurableWrites {
+ KeyspaceConfiguration durableWrites(boolean durableWrites);
+
+ default KeyspaceConfiguration disableDurableWrites() {
+ return durableWrites(false);
+ }
+ }
+ }
+
+ private static final String DEFAULT_KEYSPACE = "apache_james";
+ private static final int DEFAULT_REPLICATION_FACTOR = 1;
+
+ private static final boolean DEFAULT_SSL = false;
+
+ public static Builder.RequireKeyspace builder() {
+ return name -> replicationFactor -> durableWrites -> new KeyspaceConfiguration(name, replicationFactor, durableWrites);
+ }
+
+ private final String keyspace;
+ private final int replicationFactor;
+ private final boolean durableWrites;
+
+ public KeyspaceConfiguration(String keyspace, int replicationFactor, boolean durableWrites) {
+ Preconditions.checkArgument(replicationFactor > 0, "'' needs to be strictly positive");
+
+ this.keyspace = keyspace;
+ this.replicationFactor = replicationFactor;
+ this.durableWrites = durableWrites;
+ }
+
+ public boolean isDurableWrites() {
+ return durableWrites;
+ }
+
+ public String getKeyspace() {
+ return keyspace;
+ }
+
+ public int getReplicationFactor() {
+ return replicationFactor;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (o instanceof KeyspaceConfiguration) {
+ KeyspaceConfiguration that = (KeyspaceConfiguration) o;
+
+ return Objects.equals(this.keyspace, that.keyspace)
+ && Objects.equals(this.replicationFactor, that.replicationFactor)
+ && Objects.equals(this.durableWrites, that.durableWrites);
+ }
+ return false;
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(keyspace, replicationFactor, durableWrites);
+ }
+}
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java
index 3be615e..f4b576c 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraCluster.java
@@ -28,11 +28,13 @@ import org.apache.james.backends.cassandra.init.CassandraTypesProvider;
import org.apache.james.backends.cassandra.init.ClusterFactory;
import org.apache.james.backends.cassandra.init.SessionWithInitializedTablesFactory;
import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
+import org.apache.james.backends.cassandra.init.configuration.KeyspaceConfiguration;
import org.apache.james.util.Host;
import com.datastax.driver.core.Cluster;
public final class CassandraCluster implements AutoCloseable {
+ private static final String KEYSPACE = "testing";
public static CassandraCluster create(CassandraModule module, Host host) {
assertClusterNotRunning();
@@ -53,18 +55,26 @@ public final class CassandraCluster implements AutoCloseable {
private final Cluster nonPrivilegedCluster;
private final TestingSession nonPrivilegedSession;
private final CassandraTypesProvider typesProvider;
+ private final ClusterConfiguration clusterConfiguration;
private CassandraCluster(CassandraModule module, Host host) throws RuntimeException {
this.module = module;
- ClusterConfiguration configuration = DockerCassandra.configurationBuilder(host)
- .build();
- this.nonPrivilegedCluster = ClusterFactory.create(configuration);
- this.nonPrivilegedSession = new TestingSession(new SessionWithInitializedTablesFactory(configuration,
- nonPrivilegedCluster, module, CassandraModule.EMPTY_MODULE).get());
+ this.clusterConfiguration = DockerCassandra.configurationBuilder(host).build();
+ this.nonPrivilegedCluster = ClusterFactory.create(clusterConfiguration);
+ KeyspaceConfiguration keyspaceConfiguration = KeyspaceConfiguration.builder()
+ .keyspace(KEYSPACE)
+ .replicationFactor(1)
+ .disableDurableWrites();
+ this.nonPrivilegedSession = new TestingSession(new SessionWithInitializedTablesFactory(keyspaceConfiguration,
+ nonPrivilegedCluster, module).get());
this.typesProvider = new CassandraTypesProvider(module, nonPrivilegedSession);
}
+ public ClusterConfiguration getClusterConfiguration() {
+ return clusterConfiguration;
+ }
+
public TestingSession getConf() {
return nonPrivilegedSession;
}
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraClusterExtension.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraClusterExtension.java
index 35fc63a..c5ffbe5 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraClusterExtension.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraClusterExtension.java
@@ -20,6 +20,7 @@
package org.apache.james.backends.cassandra;
import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
@@ -48,6 +49,18 @@ public class CassandraClusterExtension implements BeforeAllCallback, BeforeEachC
}
}
+ public ClusterConfiguration.Builder clusterConfiguration() {
+ return cassandraExtension.clusterConfiguration();
+ }
+
+ public void pause() {
+ cassandraExtension.getDockerCassandra().getContainer().pause();
+ }
+
+ public void unpause() {
+ cassandraExtension.getDockerCassandra().getContainer().unpause();
+ }
+
private void start() {
cassandraCluster = CassandraCluster.create(cassandraModule, cassandraExtension.getDockerCassandra().getHost());
}
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandra.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandra.java
index b4db209..e11113d 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandra.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandra.java
@@ -22,6 +22,7 @@ package org.apache.james.backends.cassandra;
import org.apache.james.backends.cassandra.init.ClusterFactory;
import org.apache.james.backends.cassandra.init.KeyspaceFactory;
import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
+import org.apache.james.backends.cassandra.init.configuration.KeyspaceConfiguration;
import org.apache.james.util.Host;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -57,18 +58,16 @@ public class DockerCassandra {
this.cassandra = cassandra;
}
- public void initializeKeyspace(String keyspace) {
- ClusterConfiguration configuration = configurationBuilder(keyspace).build();
-
- try (Cluster privilegedCluster = ClusterFactory.create(configuration)) {
+ public void initializeKeyspace(KeyspaceConfiguration configuration) {
+ try (Cluster privilegedCluster = ClusterFactory.create(cassandra.superUserConfigurationBuilder().build())) {
provisionNonPrivilegedUser(privilegedCluster);
KeyspaceFactory.createKeyspace(configuration, privilegedCluster);
- grantPermissionToTestingUser(privilegedCluster, keyspace);
+ grantPermissionToTestingUser(privilegedCluster, configuration.getKeyspace());
}
}
public void dropKeyspace(String keyspace) {
- try (Cluster cluster = ClusterFactory.create(configurationBuilder(keyspace).build())) {
+ try (Cluster cluster = ClusterFactory.create(cassandra.superUserConfigurationBuilder().build())) {
try (Session cassandraSession = cluster.newSession()) {
boolean applied = cassandraSession.execute(
SchemaBuilder.dropKeyspace(keyspace)
@@ -80,13 +79,6 @@ public class DockerCassandra {
}
}
}
-
- }
-
- public boolean keyspaceExist(String keyspace) {
- try (Cluster cluster = ClusterFactory.create(configurationBuilder(keyspace).build())) {
- return KeyspaceFactory.keyspaceExist(cluster, keyspace);
- }
}
private void provisionNonPrivilegedUser(Cluster privilegedCluster) {
@@ -104,33 +96,19 @@ public class DockerCassandra {
session.execute("GRANT DROP ON KEYSPACE " + keyspace + " TO " + CASSANDRA_TESTING_USER);
}
}
-
- private ClusterConfiguration.Builder configurationBuilder(String keyspace) {
- return ClusterConfiguration.builder()
- .host(cassandra.getHost())
- .username(CASSANDRA_SUPER_USER)
- .password(CASSANDRA_SUPER_USER_PASSWORD)
- .keyspace(keyspace)
- .createKeyspace()
- .disableDurableWrites()
- .replicationFactor(1)
- .maxRetry(RELAXED_RETRIES);
- }
}
public static ClusterConfiguration.Builder configurationBuilder(Host... hosts) {
return ClusterConfiguration.builder()
.hosts(hosts)
- .keyspace(KEYSPACE)
.username(CASSANDRA_TESTING_USER)
.password(CASSANDRA_TESTING_PASSWORD)
- .disableDurableWrites()
- .replicationFactor(1)
.maxRetry(RELAXED_RETRIES);
}
private static final Logger logger = LoggerFactory.getLogger(DockerCassandra.class);
- private static final String KEYSPACE = "testing";
+ public static final String KEYSPACE = "testing";
+ public static final String CACHE_KEYSPACE = "testing_cache";
private static final int RELAXED_RETRIES = 2;
public static final String CASSANDRA_TESTING_USER = "james_testing";
@@ -186,7 +164,8 @@ public class DockerCassandra {
public void start() {
if (!cassandraContainer.isRunning()) {
cassandraContainer.start();
- administrator().initializeKeyspace(KEYSPACE);
+ administrator().initializeKeyspace(mainKeyspaceConfiguration());
+ administrator().initializeKeyspace(cacheKeyspaceConfiguration());
}
}
@@ -239,8 +218,26 @@ public class DockerCassandra {
return configurationBuilder(getHost());
}
- public ClusterConfiguration.Builder configurationBuilderForSuperUser() {
- return administrator()
- .configurationBuilder(KEYSPACE);
+ public ClusterConfiguration.Builder superUserConfigurationBuilder() {
+ return ClusterConfiguration.builder()
+ .host(getHost())
+ .username(CassandraResourcesManager.CASSANDRA_SUPER_USER)
+ .password(CassandraResourcesManager.CASSANDRA_SUPER_USER_PASSWORD)
+ .createKeyspace()
+ .maxRetry(RELAXED_RETRIES);
+ }
+
+ public static KeyspaceConfiguration mainKeyspaceConfiguration() {
+ return KeyspaceConfiguration.builder()
+ .keyspace(KEYSPACE)
+ .replicationFactor(1)
+ .disableDurableWrites();
+ }
+
+ public static KeyspaceConfiguration cacheKeyspaceConfiguration() {
+ return KeyspaceConfiguration.builder()
+ .keyspace(CACHE_KEYSPACE)
+ .replicationFactor(1)
+ .disableDurableWrites();
}
}
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandraExtension.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandraExtension.java
index fdf585c..5bd77e4 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandraExtension.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandraExtension.java
@@ -19,6 +19,7 @@
package org.apache.james.backends.cassandra;
+import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
import org.apache.james.util.Host;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
@@ -37,6 +38,11 @@ public class DockerCassandraExtension implements BeforeAllCallback, AfterAllCall
cassandraContainer = new DockerCassandraRule();
}
+
+ ClusterConfiguration.Builder clusterConfiguration() {
+ return cassandraContainer.clusterConfiguration();
+ }
+
@Override
public void beforeAll(ExtensionContext context) {
cassandraContainer.start();
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandraRule.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandraRule.java
index aa67a4c..44c8e7f 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandraRule.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandraRule.java
@@ -19,6 +19,7 @@
package org.apache.james.backends.cassandra;
+import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
import org.apache.james.util.Host;
import org.junit.rules.ExternalResource;
import org.testcontainers.containers.GenericContainer;
@@ -73,4 +74,8 @@ public class DockerCassandraRule extends ExternalResource {
DockerCassandraSingleton.singleton.unpause();
}
+ ClusterConfiguration.Builder clusterConfiguration() {
+ return DockerCassandraSingleton.singleton.configurationBuilder();
+ }
+
}
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/ResilientClusterProviderTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/ResilientClusterProviderTest.java
index 5614391..29142d0 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/ResilientClusterProviderTest.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/ResilientClusterProviderTest.java
@@ -19,127 +19,60 @@
package org.apache.james.backends.cassandra.init;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import java.time.Duration;
+
import org.apache.james.backends.cassandra.CassandraClusterExtension;
-import org.apache.james.backends.cassandra.DockerCassandra;
import org.apache.james.backends.cassandra.components.CassandraModule;
-import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-class ResilientClusterProviderTest {
-
- private static final String KEYSPACE = "my_keyspace";
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
+class ResilientClusterProviderTest {
@RegisterExtension
static CassandraClusterExtension cassandraExtension = new CassandraClusterExtension(CassandraModule.EMPTY_MODULE);
- @AfterEach
- void tearDown(DockerCassandra dockerCassandra) {
- dockerCassandra.administrator()
- .dropKeyspace(KEYSPACE);
+ @Test
+ void getShouldNotThrowWhenHealthyCassandra() {
+ assertThatCode(() -> new ResilientClusterProvider(cassandraExtension.clusterConfiguration().build())
+ .get())
+ .doesNotThrowAnyException();
}
- @Nested
- class WhenAllowCreatingKeySpace {
-
- @Test
- void initializationShouldThrowWhenKeyspaceDoesntExist(DockerCassandra dockerCassandra) {
- assertThatThrownBy(() -> new ResilientClusterProvider(
- dockerCassandra.configurationBuilder()
- .keyspace(KEYSPACE)
- .createKeyspace()
- .build()))
- .isInstanceOf(IllegalStateException.class)
- .hasStackTraceContaining("User james_testing has no CREATE permission on <all keyspaces> or any of its parents");
- }
-
- @Test
- void initializationWithPrivilegedUserShouldCreateKeySpaceWhenNotExisted(DockerCassandra dockerCassandra) {
- new ResilientClusterProvider(dockerCassandra.configurationBuilderForSuperUser()
- .keyspace(KEYSPACE)
- .createKeyspace()
- .build());
-
- assertThat(dockerCassandra.administrator()
- .keyspaceExist(KEYSPACE))
- .isTrue();
- }
-
- @Test
- void initializationShouldNotThrowWhenKeyspaceAlreadyExisted(DockerCassandra dockerCassandra) {
- ClusterConfiguration configuration = dockerCassandra.configurationBuilder()
- .keyspace(KEYSPACE)
- .createKeyspace()
- .build();
- dockerCassandra.administrator()
- .initializeKeyspace(KEYSPACE);
-
- assertThatCode(() -> new ResilientClusterProvider(configuration))
- .doesNotThrowAnyException();
- }
-
- @Test
- void initializationShouldNotImpactKeyspaceExistenceWhenItAlreadyExisted(DockerCassandra dockerCassandra) {
- ClusterConfiguration configuration = dockerCassandra.configurationBuilder()
- .keyspace(KEYSPACE)
- .createKeyspace()
- .build();
- dockerCassandra.administrator()
- .initializeKeyspace(KEYSPACE);
-
- new ResilientClusterProvider(configuration);
-
- assertThat(dockerCassandra.administrator()
- .keyspaceExist(KEYSPACE))
- .isTrue();
+ @Test
+ void getShouldThrowWhenNotHealthyCassandra() {
+ cassandraExtension.pause();
+ try {
+ assertThatThrownBy(() -> new ResilientClusterProvider(cassandraExtension.clusterConfiguration()
+ .maxRetry(1)
+ .minDelay(1)
+ .build())
+ .get())
+ .isInstanceOf(Exception.class);
+ } finally {
+ cassandraExtension.unpause();
}
}
- @Nested
- class WhenProhibitCreatingKeySpace {
-
- @Test
- void initializationShouldNotCreateWhenKeyspaceDoesntExist(DockerCassandra dockerCassandra) {
- new ResilientClusterProvider(dockerCassandra.configurationBuilder()
- .keyspace(KEYSPACE)
- .build());
+ @Test
+ void getShouldRecoverFromTemporaryOutage() {
+ cassandraExtension.pause();
- assertThat(dockerCassandra.administrator()
- .keyspaceExist(KEYSPACE))
- .isFalse();
- }
+ try {
+ Mono.delay(Duration.ofMillis(200))
+ .then(Mono.fromRunnable(cassandraExtension::unpause))
+ .subscribeOn(Schedulers.elastic())
+ .subscribe();
- @Test
- void initializationShouldNotThrowWhenKeyspaceAlreadyExisted(DockerCassandra dockerCassandra) {
- ClusterConfiguration configuration = dockerCassandra.configurationBuilder()
- .keyspace(KEYSPACE)
- .build();
- dockerCassandra.administrator()
- .initializeKeyspace(KEYSPACE);
-
- assertThatCode(() -> new ResilientClusterProvider(configuration))
+ assertThatCode(() -> new ResilientClusterProvider(cassandraExtension.clusterConfiguration().build())
+ .get())
.doesNotThrowAnyException();
- }
-
- @Test
- void initializationShouldNotImpactKeyspaceExistenceWhenItAlreadyExisted(DockerCassandra dockerCassandra) {
- ClusterConfiguration configuration = dockerCassandra.configurationBuilder()
- .keyspace(KEYSPACE)
- .build();
- dockerCassandra.administrator()
- .initializeKeyspace(KEYSPACE);
-
- new ResilientClusterProvider(configuration);
-
- assertThat(dockerCassandra.administrator()
- .keyspaceExist(KEYSPACE))
- .isTrue();
+ } finally {
+ cassandraExtension.unpause();
}
}
}
\ No newline at end of file
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java
index c971fea..84a1a71 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java
@@ -30,6 +30,7 @@ import org.apache.james.backends.cassandra.DockerCassandra;
import org.apache.james.backends.cassandra.DockerCassandraExtension;
import org.apache.james.backends.cassandra.components.CassandraModule;
import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
+import org.apache.james.backends.cassandra.init.configuration.KeyspaceConfiguration;
import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDAO;
import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionManager;
import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
@@ -123,11 +124,12 @@ class SessionWithInitializedTablesFactoryTest {
ClusterConfiguration clusterConfiguration = DockerCassandra.configurationBuilder(cassandraServer.getHost())
.build();
Cluster cluster = ClusterFactory.create(clusterConfiguration);
+ KeyspaceConfiguration keyspaceConfiguration = DockerCassandra.mainKeyspaceConfiguration();
+ KeyspaceFactory.createKeyspace(keyspaceConfiguration, cluster);
return () -> new SessionWithInitializedTablesFactory(
- clusterConfiguration,
+ keyspaceConfiguration,
cluster,
- MODULE,
- CassandraModule.EMPTY_MODULE)
+ MODULE)
.get();
}
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraSessionModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraSessionModule.java
index dcf3842..e68713e 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraSessionModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraSessionModule.java
@@ -25,11 +25,13 @@ import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.james.backends.cassandra.components.CassandraModule;
import org.apache.james.backends.cassandra.init.CassandraZonedDateTimeModule;
+import org.apache.james.backends.cassandra.init.KeyspaceFactory;
import org.apache.james.backends.cassandra.init.ResilientClusterProvider;
import org.apache.james.backends.cassandra.init.SessionWithInitializedTablesFactory;
import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
import org.apache.james.backends.cassandra.init.configuration.InjectionNames;
+import org.apache.james.backends.cassandra.init.configuration.KeyspaceConfiguration;
import org.apache.james.backends.cassandra.utils.CassandraHealthCheck;
import org.apache.james.backends.cassandra.utils.CassandraUtils;
import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDAO;
@@ -37,6 +39,7 @@ import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionManage
import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
import org.apache.james.core.healthcheck.HealthCheck;
import org.apache.james.lifecycle.api.StartUpCheck;
+import org.apache.james.lifecycle.api.Startable;
import org.apache.james.mailbox.store.BatchSizes;
import org.apache.james.server.CassandraProbe;
import org.apache.james.util.Host;
@@ -49,6 +52,7 @@ import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
@@ -70,6 +74,14 @@ public class CassandraSessionModule extends AbstractModule {
bind(CassandraUtils.class).in(Scopes.SINGLETON);
bind(Cluster.class).toProvider(ResilientClusterProvider.class);
+ bind(InitializedCluster.class).in(Scopes.SINGLETON);
+ bind(MainSessionWithInitializedTablesFactory.class).in(Scopes.SINGLETON);
+ bind(CacheSessionWithInitializedTablesFactory.class).in(Scopes.SINGLETON);
+
+ bind(Session.class).toProvider(MainSessionWithInitializedTablesFactory.class);
+ bind(Session.class).annotatedWith(Names.named(InjectionNames.CACHE))
+ .toProvider(CacheSessionWithInitializedTablesFactory.class);
+
Multibinder<CassandraModule> cassandraDataDefinitions = Multibinder.newSetBinder(binder(), CassandraModule.class);
cassandraDataDefinitions.addBinding().toInstance(CassandraZonedDateTimeModule.MODULE);
cassandraDataDefinitions.addBinding().toInstance(CassandraSchemaVersionModule.MODULE);
@@ -89,19 +101,6 @@ public class CassandraSessionModule extends AbstractModule {
@Provides
@Singleton
- Session provideSession(SessionWithInitializedTablesFactory sessionFactory) {
- return sessionFactory.get();
- }
-
- @Named(InjectionNames.CACHE)
- @Provides
- @Singleton
- Session provideCacheSession(SessionWithInitializedTablesFactory sessionFactory) {
- return sessionFactory.getCacheSession();
- }
-
- @Provides
- @Singleton
CassandraModule composeDataDefinitions(Set<CassandraModule> modules) {
return CassandraModule.aggregateModules(modules);
}
@@ -157,4 +156,60 @@ public class CassandraSessionModule extends AbstractModule {
.build();
}
}
+
+ @Provides
+ @Singleton
+ KeyspacesConfiguration provideKeyspacesConfiguration(PropertiesProvider propertiesProvider) throws ConfigurationException {
+ try {
+ return KeyspacesConfiguration.from(propertiesProvider.getConfiguration(CASSANDRA_FILE_NAME));
+ } catch (FileNotFoundException e) {
+ LOGGER.warn("Could not locate cassandra configuration file. Using default keyspaces configuration instead");
+ return KeyspacesConfiguration.builder().build();
+ }
+ }
+
+ @Provides
+ @Singleton
+ KeyspaceConfiguration provideMainKeyspaceConfiguration(KeyspacesConfiguration keyspacesConfiguration) {
+ return keyspacesConfiguration.mainKeyspaceConfiguration();
+ }
+
+ @Named(InjectionNames.CACHE)
+ @Provides
+ @Singleton
+ KeyspaceConfiguration provideCacheKeyspaceConfiguration(KeyspacesConfiguration keyspacesConfiguration) {
+ return keyspacesConfiguration.cacheKeyspaceConfiguration();
+ }
+
+ private static class MainSessionWithInitializedTablesFactory extends SessionWithInitializedTablesFactory {
+ @Inject
+ public MainSessionWithInitializedTablesFactory(KeyspaceConfiguration keyspaceConfiguration,
+ InitializedCluster cluster,
+ CassandraModule module) {
+ super(keyspaceConfiguration, cluster.cluster, module);
+ }
+ }
+
+ private static class CacheSessionWithInitializedTablesFactory extends SessionWithInitializedTablesFactory {
+ @Inject
+ public CacheSessionWithInitializedTablesFactory(@Named(InjectionNames.CACHE) KeyspaceConfiguration keyspaceConfiguration,
+ InitializedCluster cluster,
+ @Named(InjectionNames.CACHE) CassandraModule module) {
+ super(keyspaceConfiguration, cluster.cluster, module);
+ }
+ }
+
+ private static class InitializedCluster implements Startable {
+ private final Cluster cluster;
+
+ @Inject
+ private InitializedCluster(Cluster cluster, ClusterConfiguration clusterConfiguration, KeyspacesConfiguration keyspacesConfiguration) {
+ this.cluster = cluster;
+
+ if (clusterConfiguration.shouldCreateKeyspace()) {
+ KeyspaceFactory.createKeyspace(keyspacesConfiguration.mainKeyspaceConfiguration(), cluster);
+ KeyspaceFactory.createKeyspace(keyspacesConfiguration.cacheKeyspaceConfiguration(), cluster);
+ }
+ }
+ }
}
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/KeyspacesConfiguration.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/KeyspacesConfiguration.java
new file mode 100644
index 0000000..e1dd2e2
--- /dev/null
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/KeyspacesConfiguration.java
@@ -0,0 +1,155 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.modules.mailbox;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.commons.configuration2.Configuration;
+import org.apache.james.backends.cassandra.init.configuration.KeyspaceConfiguration;
+
+import com.google.common.base.Preconditions;
+
+public class KeyspacesConfiguration {
+ public static class Builder {
+ private Optional<String> keyspace;
+ private Optional<String> cacheKeyspace;
+ private Optional<Integer> replicationFactor;
+ private Optional<Boolean> durableWrites;
+
+ public Builder() {
+ keyspace = Optional.empty();
+ cacheKeyspace = Optional.empty();
+ replicationFactor = Optional.empty();
+ durableWrites = Optional.empty();
+ }
+
+ public Builder keyspace(Optional<String> keyspace) {
+ this.keyspace = keyspace;
+ return this;
+ }
+
+ public Builder cacheKeyspace(Optional<String> cacheKeyspace) {
+ this.cacheKeyspace = cacheKeyspace;
+ return this;
+ }
+
+ public Builder cacheKeyspace(String cacheKeyspace) {
+ return cacheKeyspace(Optional.of(cacheKeyspace));
+ }
+
+ public Builder keyspace(String keyspace) {
+ return keyspace(Optional.of(keyspace));
+ }
+
+ public Builder replicationFactor(Optional<Integer> replicationFactor) {
+ this.replicationFactor = replicationFactor;
+ return this;
+ }
+
+ public Builder replicationFactor(int replicationFactor) {
+ return replicationFactor(Optional.of(replicationFactor));
+ }
+
+ public Builder disableDurableWrites() {
+ this.durableWrites = Optional.of(false);
+
+ return this;
+ }
+
+ public KeyspacesConfiguration build() {
+ String keyspace = this.keyspace.orElse(DEFAULT_KEYSPACE);
+ String cacheKeyspace = this.cacheKeyspace.orElse(DEFAULT_CACHE_KEYSPACE);
+ Preconditions.checkState(!keyspace.equals(cacheKeyspace),
+ "'cassandra.keyspace' and 'cassandra.keyspace.cache' needs to have distinct values");
+
+ return new KeyspacesConfiguration(
+ keyspace,
+ cacheKeyspace,
+ replicationFactor.orElse(DEFAULT_REPLICATION_FACTOR),
+ durableWrites.orElse(true));
+ }
+ }
+
+ public static final String CASSANDRA_KEYSPACE = "cassandra.keyspace";
+ public static final String CASSANDRA_CACHE_KEYSPACE = "cassandra.keyspace.cache";
+ public static final String REPLICATION_FACTOR = "cassandra.replication.factor";
+
+ private static final String DEFAULT_KEYSPACE = "apache_james";
+ private static final String DEFAULT_CACHE_KEYSPACE = "apache_james_cache";
+ private static final int DEFAULT_REPLICATION_FACTOR = 1;
+
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeyspacesConfiguration from(Configuration configuration) {
+ return KeyspacesConfiguration.builder()
+ .keyspace(Optional.ofNullable(configuration.getString(CASSANDRA_KEYSPACE, null)))
+ .cacheKeyspace(Optional.ofNullable(configuration.getString(CASSANDRA_CACHE_KEYSPACE, null)))
+ .replicationFactor(Optional.ofNullable(configuration.getInteger(REPLICATION_FACTOR, null)))
+ .build();
+ }
+
+ private final String keyspace;
+ private final String cacheKeyspace;
+ private final int replicationFactor;
+ private final boolean durableWrites;
+
+ public KeyspacesConfiguration(String keyspace, String cacheKeyspace, int replicationFactor, boolean durableWrites) {
+ this.keyspace = keyspace;
+ this.cacheKeyspace = cacheKeyspace;
+ this.replicationFactor = replicationFactor;
+ this.durableWrites = durableWrites;
+ }
+
+ public KeyspaceConfiguration mainKeyspaceConfiguration() {
+ return KeyspaceConfiguration.builder()
+ .keyspace(keyspace)
+ .replicationFactor(replicationFactor)
+ .durableWrites(durableWrites);
+ }
+
+ public KeyspaceConfiguration cacheKeyspaceConfiguration() {
+ return KeyspaceConfiguration.builder()
+ .keyspace(cacheKeyspace)
+ .replicationFactor(1)
+ .durableWrites(durableWrites);
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (o instanceof KeyspacesConfiguration) {
+ KeyspacesConfiguration that = (KeyspacesConfiguration) o;
+
+ return Objects.equals(this.replicationFactor, that.replicationFactor)
+ && Objects.equals(this.durableWrites, that.durableWrites)
+ && Objects.equals(this.keyspace, that.keyspace)
+ && Objects.equals(this.cacheKeyspace, that.cacheKeyspace);
+ }
+ return false;
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(keyspace, cacheKeyspace, replicationFactor, durableWrites);
+ }
+}
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/server/CassandraProbe.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/server/CassandraProbe.java
index 6483b16..a0f17e0 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/server/CassandraProbe.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/server/CassandraProbe.java
@@ -22,17 +22,24 @@ package org.apache.james.server;
import javax.inject.Inject;
import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
+import org.apache.james.backends.cassandra.init.configuration.KeyspaceConfiguration;
import org.apache.james.utils.GuiceProbe;
public class CassandraProbe implements GuiceProbe {
private final ClusterConfiguration clusterConfiguration;
+ private final KeyspaceConfiguration mainKeyspaceConfiguration;
@Inject
- public CassandraProbe(ClusterConfiguration configuration) {
+ public CassandraProbe(ClusterConfiguration configuration, KeyspaceConfiguration mainKeyspaceConfiguration) {
this.clusterConfiguration = configuration;
+ this.mainKeyspaceConfiguration = mainKeyspaceConfiguration;
}
public ClusterConfiguration getConfiguration() {
return clusterConfiguration;
}
+
+ public KeyspaceConfiguration getMainKeyspaceConfiguration() {
+ return mainKeyspaceConfiguration;
+ }
}
diff --git a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DockerCassandraRule.java b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DockerCassandraRule.java
index d003410..6ed7d39 100644
--- a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DockerCassandraRule.java
+++ b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DockerCassandraRule.java
@@ -21,6 +21,7 @@ package org.apache.james;
import org.apache.james.backends.cassandra.DockerCassandra;
import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
+import org.apache.james.modules.mailbox.KeyspacesConfiguration;
import org.apache.james.server.CassandraTruncateTableTask;
import org.apache.james.util.Host;
import org.junit.runner.Description;
@@ -46,11 +47,18 @@ public class DockerCassandraRule implements GuiceModuleTestRule {
@Override
public Module getModule() {
- return Modules.combine((binder) -> binder.bind(ClusterConfiguration.class)
+ return Modules.combine(binder -> binder.bind(ClusterConfiguration.class)
.toInstance(DockerCassandra.configurationBuilder(cassandraContainer.getHost())
.maxRetry(20)
.minDelay(5000)
.build()),
+ binder -> binder.bind(KeyspacesConfiguration.class)
+ .toInstance(KeyspacesConfiguration.builder()
+ .keyspace(DockerCassandra.KEYSPACE)
+ .cacheKeyspace(DockerCassandra.CACHE_KEYSPACE)
+ .replicationFactor(1)
+ .disableDurableWrites()
+ .build()),
binder -> Multibinder.newSetBinder(binder, CleanupTasksPerformer.CleanupTask.class)
.addBinding()
.to(CassandraTruncateTableTask.class));
diff --git a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/KeyspaceCreationTest.java b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/KeyspaceCreationTest.java
new file mode 100644
index 0000000..728c733
--- /dev/null
+++ b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/KeyspaceCreationTest.java
@@ -0,0 +1,143 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james;
+
+import static org.apache.james.CassandraJamesServerMain.ALL_BUT_JMX_CASSANDRA_MODULE;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.apache.james.backends.cassandra.DockerCassandraSingleton;
+import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.apache.james.modules.mailbox.KeyspacesConfiguration;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class KeyspaceCreationTest {
+ @Nested
+ class CreateWhenKeyspaceExists {
+ @RegisterExtension
+ JamesServerExtension testExtension = new JamesServerBuilder()
+ .extension(new DockerElasticSearchExtension())
+ .extension(new CassandraExtension())
+ .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+ .combineWith(ALL_BUT_JMX_CASSANDRA_MODULE)
+ .overrideWith(TestJMAPServerModule.limitToTenMessages()))
+ .overrideServerModule(binder -> binder.bind(ClusterConfiguration.class)
+ .toInstance(DockerCassandraSingleton.singleton.configurationBuilder()
+ .createKeyspace()
+ .build()))
+ .disableAutoStart()
+ .build();
+
+ @Test
+ void startShouldNotThrowWhenKeyspaceExists(GuiceJamesServer jamesServer) {
+ assertThatCode(jamesServer::start)
+ .doesNotThrowAnyException();
+ }
+ }
+
+ @Nested
+ class CreateWhenDoesNotExistAndHasRights {
+ @RegisterExtension
+ JamesServerExtension testExtension = new JamesServerBuilder()
+ .extension(new DockerElasticSearchExtension())
+ .extension(new CassandraExtension())
+ .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+ .combineWith(ALL_BUT_JMX_CASSANDRA_MODULE)
+ .overrideWith(TestJMAPServerModule.limitToTenMessages()))
+ .overrideServerModule(binder -> binder.bind(KeyspacesConfiguration.class).toInstance(KeyspacesConfiguration.builder()
+ .keyspace("non_existing_keyspace")
+ .cacheKeyspace("cache_non_existing_keyspace")
+ .replicationFactor(1)
+ .disableDurableWrites()
+ .build()))
+ .overrideServerModule(binder -> binder.bind(ClusterConfiguration.class)
+ .toInstance(DockerCassandraSingleton.singleton.superUserConfigurationBuilder()
+ .createKeyspace()
+ .build()))
+ .disableAutoStart()
+ .build();
+
+ @Test
+ void startShouldNotThrowWhenCreateAKeyspaceWithAuthorizedSession(GuiceJamesServer jamesServer) {
+ assertThatCode(jamesServer::start)
+ .doesNotThrowAnyException();
+ }
+ }
+
+ @Nested
+ class CreateWhenDoesNotExistAndDoNotHaveRights {
+ @RegisterExtension
+ JamesServerExtension testExtension = new JamesServerBuilder()
+ .extension(new DockerElasticSearchExtension())
+ .extension(new CassandraExtension())
+ .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+ .combineWith(ALL_BUT_JMX_CASSANDRA_MODULE)
+ .overrideWith(TestJMAPServerModule.limitToTenMessages()))
+ .overrideServerModule(binder -> binder.bind(ClusterConfiguration.class)
+ .toInstance(DockerCassandraSingleton.singleton.configurationBuilder()
+ .createKeyspace()
+ .build()))
+ .overrideServerModule(binder -> binder.bind(KeyspacesConfiguration.class).toInstance(KeyspacesConfiguration.builder()
+ .keyspace("non_existing_keyspace")
+ .cacheKeyspace("cache_non_existing_keyspace")
+ .replicationFactor(1)
+ .disableDurableWrites()
+ .build()))
+ .disableAutoStart()
+ .build();
+
+ @Test
+ void startShouldThrowWhenAttemptToCreateAKeyspace(GuiceJamesServer jamesServer) {
+ assertThatThrownBy(jamesServer::start)
+ .isInstanceOf(Exception.class);
+ }
+ }
+
+ @Nested
+ class StartWhenKeyspaceDoesNotExist {
+ @RegisterExtension
+ JamesServerExtension testExtension = new JamesServerBuilder()
+ .extension(new DockerElasticSearchExtension())
+ .extension(new CassandraExtension())
+ .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+ .combineWith(ALL_BUT_JMX_CASSANDRA_MODULE)
+ .overrideWith(TestJMAPServerModule.limitToTenMessages()))
+ .overrideServerModule(binder -> binder.bind(ClusterConfiguration.class)
+ .toInstance(DockerCassandraSingleton.singleton.configurationBuilder()
+ .build()))
+ .overrideServerModule(binder -> binder.bind(KeyspacesConfiguration.class).toInstance(KeyspacesConfiguration.builder()
+ .keyspace("non_existing_keyspace")
+ .cacheKeyspace("cache_non_existing_keyspace")
+ .replicationFactor(1)
+ .disableDurableWrites()
+ .build()))
+ .disableAutoStart()
+ .build();
+
+ @Test
+ void startShouldThrowWhenAttemptToUseANonExistingKeyspace(GuiceJamesServer jamesServer) {
+ assertThatThrownBy(jamesServer::start)
+ .isInstanceOf(Exception.class);
+ }
+ }
+}
diff --git a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/modules/mailbox/KeyspacesConfigurationTest.java b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/modules/mailbox/KeyspacesConfigurationTest.java
new file mode 100644
index 0000000..27206be
--- /dev/null
+++ b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/modules/mailbox/KeyspacesConfigurationTest.java
@@ -0,0 +1,61 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.modules.mailbox;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.Test;
+
+class KeyspacesConfigurationTest {
+ @Test
+ void buildShouldThrowWhenSameValues() {
+ assertThatThrownBy(() -> KeyspacesConfiguration.builder()
+ .keyspace("keyspace")
+ .cacheKeyspace("keyspace")
+ .build())
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void buildShouldThrowWhenSameValuesDefaultKeyspace() {
+ assertThatThrownBy(() -> KeyspacesConfiguration.builder()
+ .cacheKeyspace("apache_james")
+ .build())
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void buildShouldThrowWhenSameValuesDefaultCacheKeyspace() {
+ assertThatThrownBy(() -> KeyspacesConfiguration.builder()
+ .keyspace("apache_james_cache")
+ .build())
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void buildShouldNotThrowWhenDifferentValues() {
+ assertThatCode(() -> KeyspacesConfiguration.builder()
+ .keyspace("keyspace")
+ .cacheKeyspace("keyspace2")
+ .build())
+ .doesNotThrowAnyException();
+ }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java
index 49685df..f545818 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java
@@ -151,9 +151,10 @@ class FixingGhostMailboxTest {
.addUser(BOB, BOB_SECRET);
accessToken = authenticateJamesUser(baseUri(jmapPort), Username.of(ALICE), ALICE_SECRET);
- ClusterConfiguration cassandraConfiguration = server.getProbe(CassandraProbe.class).getConfiguration();
+ CassandraProbe probe = server.getProbe(CassandraProbe.class);
+ ClusterConfiguration cassandraConfiguration = probe.getConfiguration();
try (Cluster cluster = ClusterFactory.create(cassandraConfiguration)) {
- try (Session session = cluster.connect(cassandraConfiguration.getKeyspace())) {
+ try (Session session = cluster.connect(probe.getMainKeyspaceConfiguration().getKeyspace())) {
simulateGhostMailboxBug(session);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org