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