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 ad...@apache.org on 2017/07/27 06:53:50 UTC

[3/9] james-project git commit: JAMES-2096 Introduce Cassandra webadmin migration endpoints

JAMES-2096 Introduce Cassandra webadmin migration endpoints


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/f495121d
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/f495121d
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/f495121d

Branch: refs/heads/master
Commit: f495121dff2ac1c81215898cfff5ea9d25d6ad4b
Parents: 4f13da5
Author: Antoine Duprat <ad...@linagora.com>
Authored: Tue Jul 11 15:32:12 2017 +0200
Committer: benwa <bt...@linagora.com>
Committed: Tue Jul 25 17:51:01 2017 +0700

----------------------------------------------------------------------
 server/pom.xml                                  |   6 +
 .../webadmin/webadmin-cassandra/pom.xml         | 251 ++++++++++++++++++
 .../webadmin/dto/CassandraVersionRequest.java   |  40 +++
 .../webadmin/dto/CassandraVersionResponse.java  |  35 +++
 .../routes/CassandraMigrationRoutes.java        | 105 ++++++++
 .../service/CassandraMigrationService.java      |  98 +++++++
 .../webadmin/service/MigrationException.java    |  26 ++
 .../james/webadmin/dto/VersionRequestTest.java  |  67 +++++
 .../routes/CassandraMigrationRoutesTest.java    | 202 ++++++++++++++
 .../service/CassandraMigrationServiceTest.java  | 263 +++++++++++++++++++
 10 files changed, 1093 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/f495121d/server/pom.xml
----------------------------------------------------------------------
diff --git a/server/pom.xml b/server/pom.xml
index 4e52dae..2f90850 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -102,6 +102,7 @@
         <module>protocols/protocols-managesieve</module>
         <module>protocols/protocols-pop3</module>
         <module>protocols/protocols-smtp</module>
+        <module>protocols/webadmin/webadmin-cassandra</module>
         <module>protocols/webadmin/webadmin-core</module>
         <module>protocols/webadmin/webadmin-data</module>
         <module>protocols/webadmin/webadmin-mailbox</module>
@@ -679,6 +680,11 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.james</groupId>
+                <artifactId>james-server-webadmin-cassandra</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.james</groupId>
                 <artifactId>james-server-webadmin-core</artifactId>
                 <version>${project.version}</version>
             </dependency>

http://git-wip-us.apache.org/repos/asf/james-project/blob/f495121d/server/protocols/webadmin/webadmin-cassandra/pom.xml
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-cassandra/pom.xml b/server/protocols/webadmin/webadmin-cassandra/pom.xml
new file mode 100644
index 0000000..0d68ced
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra/pom.xml
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>james-server</artifactId>
+        <groupId>org.apache.james</groupId>
+        <version>3.1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>james-server-webadmin-cassandra</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Apache James :: Server :: Web Admin :: Cassandra</name>
+
+    <profiles>
+        <profile>
+            <id>noTest</id>
+            <activation>
+                <os>
+                    <family>windows</family>
+                </os>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <skipTests>true</skipTests>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>disable-build-for-older-jdk</id>
+            <activation>
+                <jdk>(,1.8)</jdk>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-jar-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>default-jar</id>
+                                <phase>none</phase>
+                            </execution>
+                            <execution>
+                                <id>jar</id>
+                                <phase>none</phase>
+                            </execution>
+                            <execution>
+                                <id>test-jar</id>
+                                <phase>none</phase>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>default-compile</id>
+                                <phase>none</phase>
+                            </execution>
+                            <execution>
+                                <id>default-testCompile</id>
+                                <phase>none</phase>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>default-test</id>
+                                <phase>none</phase>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-source-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>attach-sources</id>
+                                <phase>none</phase>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-install-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>default-install</id>
+                                <phase>none</phase>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-resources-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>default-resources</id>
+                                <phase>none</phase>
+                            </execution>
+                            <execution>
+                                <id>default-testResources</id>
+                                <phase>none</phase>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-site-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>attach-descriptor</id>
+                                <phase>none</phase>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>build-for-jdk-8</id>
+            <activation>
+                <jdk>[1.8,)</jdk>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.james</groupId>
+                    <artifactId>apache-james-mailbox-cassandra</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.james</groupId>
+                    <artifactId>james-server-webadmin-core</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.james</groupId>
+                    <artifactId>metrics-logger</artifactId>
+                    <scope>test</scope>
+                </dependency>
+
+                <dependency>
+                    <groupId>com.jayway.restassured</groupId>
+                    <artifactId>rest-assured</artifactId>
+                    <scope>test</scope>
+                </dependency>
+                <dependency>
+                    <groupId>javax.inject</groupId>
+                    <artifactId>javax.inject</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>junit</groupId>
+                    <artifactId>junit</artifactId>
+                    <scope>test</scope>
+                </dependency>
+                <dependency>
+                    <groupId>org.assertj</groupId>
+                    <artifactId>assertj-core</artifactId>
+                    <version>${assertj-3.version}</version>
+                    <scope>test</scope>
+                </dependency>
+                <dependency>
+                    <groupId>org.mockito</groupId>
+                    <artifactId>mockito-core</artifactId>
+                    <scope>test</scope>
+                </dependency>
+                <dependency>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </dependency>
+            </dependencies>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-assembly-plugin</artifactId>
+                        <configuration>
+                            <archive>
+                                <manifest>
+                                    <mainClass>fully.qualified.MainClass</mainClass>
+                                </manifest>
+                            </archive>
+                            <descriptorRefs>
+                                <descriptorRef>jar-with-dependencies</descriptorRef>
+                            </descriptorRefs>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <configuration>
+                            <source>1.8</source>
+                            <target>1.8</target>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>animal-sniffer-java-8</id>
+            <activation>
+                <jdk>[1.8,)</jdk>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>animal-sniffer-maven-plugin</artifactId>
+                        <configuration>
+                            <signature>
+                                <groupId>org.codehaus.mojo.signature</groupId>
+                                <artifactId>java18</artifactId>
+                                <version>1.0</version>
+                            </signature>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <id>check_java_8</id>
+                                <phase>test</phase>
+                                <goals>
+                                    <goal>check</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>

http://git-wip-us.apache.org/repos/asf/james-project/blob/f495121d/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/dto/CassandraVersionRequest.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/dto/CassandraVersionRequest.java b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/dto/CassandraVersionRequest.java
new file mode 100644
index 0000000..5eaef46
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/dto/CassandraVersionRequest.java
@@ -0,0 +1,40 @@
+/****************************************************************
+ * 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.webadmin.dto;
+
+import com.google.common.base.Preconditions;
+
+public class CassandraVersionRequest {
+    public static CassandraVersionRequest parse(String version) {
+        Preconditions.checkNotNull(version, "Version is mandatory");
+        return new CassandraVersionRequest(Integer.valueOf(version));
+    }
+
+    private final int value;
+
+    private CassandraVersionRequest(int value) {
+        Preconditions.checkArgument(value >= 0);
+        this.value = value;
+    }
+
+    public int getValue() {
+        return value;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/f495121d/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/dto/CassandraVersionResponse.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/dto/CassandraVersionResponse.java b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/dto/CassandraVersionResponse.java
new file mode 100644
index 0000000..34e98a8
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/dto/CassandraVersionResponse.java
@@ -0,0 +1,35 @@
+/****************************************************************
+ * 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.webadmin.dto;
+
+import java.util.Optional;
+
+public class CassandraVersionResponse {
+
+    private final Optional<Integer> version;
+
+    public CassandraVersionResponse(Optional<Integer> version) {
+        this.version = version;
+    }
+
+    public Optional<Integer> getversion() {
+        return version;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/f495121d/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/CassandraMigrationRoutes.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/CassandraMigrationRoutes.java b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/CassandraMigrationRoutes.java
new file mode 100644
index 0000000..c9564e7
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/CassandraMigrationRoutes.java
@@ -0,0 +1,105 @@
+/****************************************************************
+ * 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.webadmin.routes;
+
+import javax.inject.Inject;
+
+import org.apache.james.webadmin.Constants;
+import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.dto.CassandraVersionRequest;
+import org.apache.james.webadmin.service.CassandraMigrationService;
+import org.apache.james.webadmin.service.MigrationException;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import spark.Service;
+
+public class CassandraMigrationRoutes implements Routes {
+
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CassandraMigrationRoutes.class);
+
+    public static final String VERSION_BASE = "/cassandra/version";
+    private static final String VERSION_BASE_LATEST = VERSION_BASE + "/latest";
+    private static final String VERSION_UPGRADE_BASE = VERSION_BASE + "/upgrade";
+    private static final String VERSION_UPGRADE_TO_LATEST_BASE = VERSION_UPGRADE_BASE + "/latest";
+    private static final int NO_CONTENT = 204;
+    private static final int INVALID_VERSION = 400;
+    private static final int MIGRATION_CAN_NOT_BE_PERFORMED = 410;
+    private static final int INTERNAL_ERROR = 500;
+
+    private final CassandraMigrationService cassandraMigrationService;
+    private final JsonTransformer jsonTransformer;
+
+    @Inject
+    public CassandraMigrationRoutes(CassandraMigrationService cassandraMigrationService, JsonTransformer jsonTransformer) {
+        this.cassandraMigrationService = cassandraMigrationService;
+        this.jsonTransformer = jsonTransformer;
+    }
+
+    @Override
+    public void define(Service service) {
+        service.get(VERSION_BASE,
+            (request, response) -> cassandraMigrationService.getCurrentVersion(),
+            jsonTransformer);
+
+        service.get(VERSION_BASE_LATEST,
+            (request, response) -> cassandraMigrationService.getLatestVersion(),
+            jsonTransformer);
+
+        service.post(VERSION_UPGRADE_BASE, (request, response) -> {
+            LOGGER.debug("Cassandra upgrade launched");
+            try {
+                CassandraVersionRequest cassandraVersionRequest = CassandraVersionRequest.parse(request.body());
+                cassandraMigrationService.upgradeToVersion(cassandraVersionRequest.getValue());
+                response.status(NO_CONTENT);
+            } catch (NullPointerException | IllegalArgumentException e) {
+                LOGGER.info("Invalid request for version upgrade");
+                response.status(INVALID_VERSION);
+            } catch (IllegalStateException e) {
+                LOGGER.info("The migration requested can not be performed.", e);
+                response.status(MIGRATION_CAN_NOT_BE_PERFORMED);
+                response.body(e.getMessage());
+            } catch (MigrationException e) {
+                LOGGER.error("An error lead to partial migration process", e);
+                response.status(INTERNAL_ERROR);
+                response.body(e.getMessage());
+            }
+            return Constants.EMPTY_BODY;
+        });
+
+        service.post(VERSION_UPGRADE_TO_LATEST_BASE, (request, response) -> {
+            try {
+                cassandraMigrationService.upgradeToLastVersion();
+            } catch (IllegalStateException e) {
+                LOGGER.info("The migration requested can not be performed.", e);
+                response.status(MIGRATION_CAN_NOT_BE_PERFORMED);
+                response.body(e.getMessage());
+            } catch (MigrationException e) {
+                LOGGER.error("An error lead to partial migration process", e);
+                response.status(INTERNAL_ERROR);
+                response.body(e.getMessage());
+            }
+
+            return Constants.EMPTY_BODY;
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/f495121d/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/service/CassandraMigrationService.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/service/CassandraMigrationService.java b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/service/CassandraMigrationService.java
new file mode 100644
index 0000000..b18770f
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/service/CassandraMigrationService.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.webadmin.service;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.IntStream;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDAO;
+import org.apache.james.mailbox.cassandra.mail.migration.Migration;
+import org.apache.james.webadmin.dto.CassandraVersionResponse;
+
+import com.google.common.base.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CassandraMigrationService {
+    private static final int FIRST_VERSION = 1;
+    public static final String LATEST_VERSION = "latestVersion";
+    private final CassandraSchemaVersionDAO schemaVersionDAO;
+    private final int latestVersion;
+    private final Map<Integer, Migration> allMigrationClazz;
+    private final Logger LOG = LoggerFactory.getLogger(CassandraMigrationService.class);
+
+    @Inject
+    public CassandraMigrationService(CassandraSchemaVersionDAO schemaVersionDAO, Map<Integer, Migration> allMigrationClazz, @Named(LATEST_VERSION) int latestVersion) {
+        Preconditions.checkArgument(latestVersion >= 0, "The latest version must be positive");
+        this.schemaVersionDAO = schemaVersionDAO;
+        this.latestVersion = latestVersion;
+        this.allMigrationClazz = allMigrationClazz;
+    }
+
+    public CassandraVersionResponse getCurrentVersion() {
+        return new CassandraVersionResponse(schemaVersionDAO.getCurrentSchemaVersion().join());
+    }
+
+    public CassandraVersionResponse getLatestVersion() {
+        return new CassandraVersionResponse(Optional.of(latestVersion));
+    }
+
+    public synchronized void upgradeToVersion(int newVersion) {
+        int currentVersion = schemaVersionDAO.getCurrentSchemaVersion().join().orElse(FIRST_VERSION);
+        if (currentVersion >= newVersion) {
+            throw new IllegalStateException("Current version is already up to date");
+        }
+
+        IntStream.range(currentVersion, newVersion)
+            .boxed()
+            .forEach(this::doMigration);
+    }
+
+    public void upgradeToLastVersion() {
+        upgradeToVersion(latestVersion);
+    }
+
+    private void doMigration(Integer version) {
+        if (allMigrationClazz.containsKey(version)) {
+            LOG.info("Migrating to version {} ", version + 1);
+            boolean migrationSuccess = allMigrationClazz.get(version).run();
+            if (migrationSuccess) {
+                schemaVersionDAO.updateVersion(version + 1);
+                LOG.info("Migrating to version {} done", version + 1);
+            } else {
+                String message = String.format("Migrating to version %d partially done. " +
+                    "Please check logs for cause of failure and re-run this migration.",
+                    version + 1);
+                LOG.warn(message);
+                throw new MigrationException(message);
+            }
+        } else {
+            String message = String.format("Can not migrate to %d. No migration class registered.", version + 1);
+            LOG.error(message);
+            throw new NotImplementedException(message);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/f495121d/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/service/MigrationException.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/service/MigrationException.java b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/service/MigrationException.java
new file mode 100644
index 0000000..7b9d74f
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/service/MigrationException.java
@@ -0,0 +1,26 @@
+/****************************************************************
+ * 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.webadmin.service;
+
+public class MigrationException extends RuntimeException {
+    public MigrationException(String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/f495121d/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/dto/VersionRequestTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/dto/VersionRequestTest.java b/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/dto/VersionRequestTest.java
new file mode 100644
index 0000000..48ce134
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/dto/VersionRequestTest.java
@@ -0,0 +1,67 @@
+/****************************************************************
+ * 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.webadmin.dto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class VersionRequestTest {
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    @Test
+    public void parseShouldThrowWhenNullVersion() throws Exception {
+        expectedException.expect(NullPointerException.class);
+
+        CassandraVersionRequest.parse(null);
+    }
+
+    @Test
+    public void parseShouldThrowWhenNonIntegerVersion() throws Exception {
+        expectedException.expect(IllegalArgumentException.class);
+
+        CassandraVersionRequest.parse("NoInt");
+    }
+
+    @Test
+    public void parseShouldThrowWhenNegativeVersion() throws Exception {
+        expectedException.expect(IllegalArgumentException.class);
+
+        CassandraVersionRequest.parse("-1");
+    }
+
+    @Test
+    public void parseShouldAcceptZeroVersion() throws Exception {
+        CassandraVersionRequest cassandraVersionRequest = CassandraVersionRequest.parse("0");
+
+        assertThat(cassandraVersionRequest.getValue()).isEqualTo(0);
+    }
+
+    @Test
+    public void parseShouldParseTheVersionValue() throws Exception {
+        CassandraVersionRequest cassandraVersionRequest = CassandraVersionRequest.parse("1");
+
+        assertThat(cassandraVersionRequest.getValue()).isEqualTo(1);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/f495121d/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/routes/CassandraMigrationRoutesTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/routes/CassandraMigrationRoutesTest.java b/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/routes/CassandraMigrationRoutesTest.java
new file mode 100644
index 0000000..5944044
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/routes/CassandraMigrationRoutesTest.java
@@ -0,0 +1,202 @@
+/****************************************************************
+ * 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.webadmin.routes;
+
+import static com.jayway.restassured.RestAssured.given;
+import static com.jayway.restassured.RestAssured.when;
+import static com.jayway.restassured.config.EncoderConfig.encoderConfig;
+import static com.jayway.restassured.config.RestAssuredConfig.newConfig;
+import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION;
+import static org.hamcrest.CoreMatchers.is;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDAO;
+import org.apache.james.mailbox.cassandra.mail.migration.Migration;
+import org.apache.james.metrics.logger.DefaultMetricFactory;
+import org.apache.james.webadmin.WebAdminServer;
+import org.apache.james.webadmin.service.CassandraMigrationService;
+import org.apache.james.webadmin.utils.JsonTransformer;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableMap;
+import com.jayway.restassured.RestAssured;
+import com.jayway.restassured.builder.RequestSpecBuilder;
+import com.jayway.restassured.http.ContentType;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class CassandraMigrationRoutesTest {
+
+    public static final boolean MIGRATED = true;
+    private static final Integer LATEST_VERSION = 3;
+    private static final Integer CURRENT_VERSION = 2;
+    private static final Integer OLDER_VERSION = 1;
+    private WebAdminServer webAdminServer;
+    private CassandraSchemaVersionDAO schemaVersionDAO;
+    private Migration successfulMigration;
+
+    private void createServer() throws Exception {
+        successfulMigration = mock(Migration.class);
+        when(successfulMigration.run()).thenReturn(MIGRATED);
+        Map<Integer, Migration> allMigrationClazz = ImmutableMap.<Integer, Migration>builder()
+            .put(OLDER_VERSION, successfulMigration)
+            .put(CURRENT_VERSION, successfulMigration)
+            .put(LATEST_VERSION, successfulMigration)
+            .build();
+        schemaVersionDAO = mock(CassandraSchemaVersionDAO.class);
+
+        webAdminServer = new WebAdminServer(
+            new DefaultMetricFactory(),
+            new CassandraMigrationRoutes(new CassandraMigrationService(schemaVersionDAO, allMigrationClazz, LATEST_VERSION), new JsonTransformer()));
+        webAdminServer.configure(NO_CONFIGURATION);
+        webAdminServer.await();
+
+        RestAssured.requestSpecification = new RequestSpecBuilder()
+            .setContentType(ContentType.JSON)
+            .setAccept(ContentType.JSON)
+            .setBasePath(CassandraMigrationRoutes.VERSION_BASE)
+            .setPort(webAdminServer.getPort().toInt())
+            .setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(Charsets.UTF_8)))
+            .build();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        createServer();
+    }
+
+    @After
+    public void tearDown() {
+        webAdminServer.destroy();
+    }
+
+    @Test
+    public void getShouldReturnTheCurrentVersion() throws Exception {
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(CURRENT_VERSION)));
+
+        when()
+            .get()
+        .then()
+            .statusCode(200)
+            .body(is("{\"version\":2}"));
+    }
+
+    @Test
+    public void getShouldReturnTheLatestVersionWhenSetUpTheLatestVersion() throws Exception {
+        when()
+            .get("/latest")
+        .then()
+            .statusCode(200)
+            .body(is("{\"version\":" + LATEST_VERSION + "}"));
+    }
+
+    @Ignore
+    @Test
+    public void postShouldReturnConflictWhenMigrationOnRunning() throws Exception {
+        when()
+            .post("/upgrade")
+        .then()
+            .statusCode(409);
+    }
+
+    @Test
+    public void postShouldReturnErrorCodeWhenInvalidVersion() throws Exception {
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(OLDER_VERSION)));
+
+        given()
+            .body(String.valueOf("NonInt"))
+        .with()
+            .post("/upgrade")
+        .then()
+            .statusCode(400);
+
+        verifyNoMoreInteractions(schemaVersionDAO);
+    }
+
+    @Test
+    public void postShouldDoMigrationToNewVersion() throws Exception {
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(OLDER_VERSION)));
+
+        given()
+            .body(String.valueOf(CURRENT_VERSION))
+        .with()
+            .post("/upgrade")
+        .then()
+            .statusCode(204);
+
+        verify(schemaVersionDAO, times(1)).getCurrentSchemaVersion();
+        verify(schemaVersionDAO, times(1)).updateVersion(eq(CURRENT_VERSION));
+        verifyNoMoreInteractions(schemaVersionDAO);
+    }
+
+    @Test
+    public void postShouldNotDoMigrationWhenCurrentVersionIsNewerThan() throws Exception {
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(CURRENT_VERSION)));
+
+        given()
+            .body(String.valueOf(OLDER_VERSION))
+        .with()
+            .post("/upgrade")
+        .then()
+            .statusCode(410);
+
+        verify(schemaVersionDAO, times(1)).getCurrentSchemaVersion();
+        verifyNoMoreInteractions(schemaVersionDAO);
+    }
+
+    @Test
+    public void postShouldDoMigrationToLatestVersion() throws Exception {
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(OLDER_VERSION)));
+
+        when()
+            .post("/upgrade/latest")
+        .then()
+            .statusCode(200);
+
+        verify(schemaVersionDAO, times(1)).getCurrentSchemaVersion();
+        verify(schemaVersionDAO, times(1)).updateVersion(eq(CURRENT_VERSION));
+        verify(schemaVersionDAO, times(1)).updateVersion(eq(LATEST_VERSION));
+        verifyNoMoreInteractions(schemaVersionDAO);
+    }
+
+    @Test
+    public void postShouldNotDoMigrationToLatestVersionWhenItIsUpToDate() throws Exception {
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(LATEST_VERSION)));
+
+        when()
+            .post("/upgrade/latest")
+        .then()
+            .statusCode(410);
+
+        verify(schemaVersionDAO, times(1)).getCurrentSchemaVersion();
+        verifyNoMoreInteractions(schemaVersionDAO);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/f495121d/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/service/CassandraMigrationServiceTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/service/CassandraMigrationServiceTest.java b/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/service/CassandraMigrationServiceTest.java
new file mode 100644
index 0000000..7134e70
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra/src/test/java/org/apache/james/webadmin/service/CassandraMigrationServiceTest.java
@@ -0,0 +1,263 @@
+/****************************************************************
+ * 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.webadmin.service;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDAO;
+import org.apache.james.mailbox.cassandra.mail.migration.Migration;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import com.datastax.driver.core.Session;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+
+public class CassandraMigrationServiceTest {
+    private static final int LATEST_VERSION = 3;
+    private static final int CURRENT_VERSION = 2;
+    private static final int OLDER_VERSION = 1;
+    private static final boolean MIGRATED = true;
+    private static final boolean MIGRATION_FAILED = false;
+    private CassandraMigrationService testee;
+    private CassandraSchemaVersionDAO schemaVersionDAO;
+    private ExecutorService executorService;
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+    private Migration successfulMigration;
+
+    @Before
+    public void setUp() throws Exception {
+        schemaVersionDAO = mock(CassandraSchemaVersionDAO.class);
+        successfulMigration = mock(Migration.class);
+        when(successfulMigration.run()).thenReturn(MIGRATED);
+        Map<Integer, Migration> allMigrationClazz = ImmutableMap.<Integer, Migration>builder()
+            .put(OLDER_VERSION, successfulMigration)
+            .put(CURRENT_VERSION, successfulMigration)
+            .put(LATEST_VERSION, successfulMigration)
+            .build();
+        testee = new CassandraMigrationService(schemaVersionDAO, allMigrationClazz, LATEST_VERSION);
+        executorService = Executors.newFixedThreadPool(2);
+    }
+
+    @After
+    public void tearDown() {
+        executorService.shutdownNow();
+    }
+
+    @Test
+    public void getCurrentVersionShouldReturnCurrentVersion() throws Exception {
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(CURRENT_VERSION)));
+
+        assertThat(testee.getCurrentVersion().getversion().get()).isEqualTo(CURRENT_VERSION);
+    }
+
+    @Test
+    public void getLatestVersionShouldReturnTheLatestVersion() throws Exception {
+        assertThat(testee.getLatestVersion().getversion().get()).isEqualTo(LATEST_VERSION);
+    }
+
+    @Test
+    public void upgradeToVersionShouldThrowWhenCurrentVersionIsUpToDate() throws Exception {
+        expectedException.expect(IllegalStateException.class);
+
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(CURRENT_VERSION)));
+
+        testee.upgradeToVersion(OLDER_VERSION);
+    }
+
+    @Test
+    public void upgradeToVersionShouldUpdateToVersion() throws Exception {
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(OLDER_VERSION)));
+
+        testee.upgradeToVersion(CURRENT_VERSION);
+
+        verify(schemaVersionDAO, times(1)).updateVersion(eq(CURRENT_VERSION));
+    }
+
+    @Test
+    public void upgradeToLastVersionShouldThrowWhenVersionIsUpToDate() throws Exception {
+        expectedException.expect(IllegalStateException.class);
+
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(LATEST_VERSION)));
+
+        testee.upgradeToLastVersion();
+    }
+
+    @Test
+    public void upgradeToLastVersionShouldUpdateToLatestVersion() throws Exception {
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(OLDER_VERSION)));
+
+        testee.upgradeToLastVersion();
+
+        verify(schemaVersionDAO, times(1)).updateVersion(eq(LATEST_VERSION));
+    }
+
+    @Test
+    public void upgradeToVersionShouldThrowOnMissingVersion() throws Exception {
+        Map<Integer, Migration> allMigrationClazz = ImmutableMap.<Integer, Migration>builder()
+            .put(OLDER_VERSION, successfulMigration)
+            .put(LATEST_VERSION, successfulMigration)
+            .build();
+        testee = new CassandraMigrationService(schemaVersionDAO, allMigrationClazz, LATEST_VERSION);
+        when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(OLDER_VERSION)));
+
+        expectedException.expect(NotImplementedException.class);
+
+        testee.upgradeToVersion(LATEST_VERSION);
+    }
+
+    @Test
+    public void upgradeToVersionShouldUpdateIntermediarySuccessfulMigrationsInCaseOfError() throws Exception {
+        try {
+            Map<Integer, Migration> allMigrationClazz = ImmutableMap.<Integer, Migration>builder()
+                .put(OLDER_VERSION, successfulMigration)
+                .put(LATEST_VERSION, successfulMigration)
+                .build();
+            testee = new CassandraMigrationService(schemaVersionDAO, allMigrationClazz, LATEST_VERSION);
+            when(schemaVersionDAO.getCurrentSchemaVersion()).thenReturn(CompletableFuture.completedFuture(Optional.of(OLDER_VERSION)));
+
+            expectedException.expect(RuntimeException.class);
+
+            testee.upgradeToVersion(LATEST_VERSION);
+        } finally {
+            verify(schemaVersionDAO).updateVersion(CURRENT_VERSION);
+        }
+    }
+
+    @Test
+    public void concurrentMigrationsShouldFail() throws Exception {
+        // Given a stateful migration service
+        Migration wait1SecondMigration = mock(Migration.class);
+        doAnswer(invocation -> {
+            Thread.sleep(1000);
+            return MIGRATED;
+        }).when(wait1SecondMigration).run();
+        Map<Integer, Migration> allMigrationClazz = ImmutableMap.<Integer, Migration>builder()
+            .put(OLDER_VERSION, wait1SecondMigration)
+            .put(CURRENT_VERSION, wait1SecondMigration)
+            .put(LATEST_VERSION, wait1SecondMigration)
+            .build();
+        testee = new CassandraMigrationService(new InMemorySchemaDAO(OLDER_VERSION), allMigrationClazz, LATEST_VERSION);
+
+        // When I perform a concurrent migration
+        AtomicInteger encounteredExceptionCount = new AtomicInteger(0);
+        executorService.submit(() -> testee.upgradeToVersion(LATEST_VERSION));
+        executorService.submit(() -> {
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                throw Throwables.propagate(e);
+            }
+
+            try {
+                testee.upgradeToVersion(LATEST_VERSION);
+            } catch (IllegalStateException e) {
+                encounteredExceptionCount.incrementAndGet();
+            }
+        });
+        executorService.awaitTermination(10, TimeUnit.SECONDS);
+
+        // Then the second migration fails
+        assertThat(encounteredExceptionCount.get()).isEqualTo(1);
+    }
+
+    @Test
+    public void partialMigrationShouldThrow() throws Exception {
+        Migration migration1 = mock(Migration.class);
+        when(migration1.run()).thenReturn(MIGRATION_FAILED);
+        Migration migration2 = successfulMigration;
+
+        Map<Integer, Migration> allMigrationClazz = ImmutableMap.<Integer, Migration>builder()
+            .put(OLDER_VERSION, migration1)
+            .put(CURRENT_VERSION, migration2)
+            .build();
+        testee = new CassandraMigrationService(new InMemorySchemaDAO(OLDER_VERSION), allMigrationClazz, LATEST_VERSION);
+
+        expectedException.expect(MigrationException.class);
+
+        testee.upgradeToVersion(LATEST_VERSION);
+    }
+
+    @Test
+    public void partialMigrationShouldAbortMigrations() throws Exception {
+        Migration migration1 = mock(Migration.class);
+        when(migration1.run()).thenReturn(MIGRATION_FAILED);
+        Migration migration2 = mock(Migration.class);
+        when(migration2.run()).thenReturn(MIGRATED);
+
+        Map<Integer, Migration> allMigrationClazz = ImmutableMap.<Integer, Migration>builder()
+            .put(OLDER_VERSION, migration1)
+            .put(CURRENT_VERSION, migration2)
+            .build();
+        testee = new CassandraMigrationService(new InMemorySchemaDAO(OLDER_VERSION), allMigrationClazz, LATEST_VERSION);
+
+        expectedException.expect(MigrationException.class);
+
+        try {
+            testee.upgradeToVersion(LATEST_VERSION);
+        } finally {
+            verify(migration1, times(1)).run();
+            verifyNoMoreInteractions(migration1);
+            verifyZeroInteractions(migration2);
+        }
+    }
+
+    public static class InMemorySchemaDAO extends CassandraSchemaVersionDAO {
+        private int currentVersion;
+
+        public InMemorySchemaDAO(int currentVersion) {
+            super(mock(Session.class), null);
+            this.currentVersion = currentVersion;
+        }
+
+        @Override
+        public CompletableFuture<Optional<Integer>> getCurrentSchemaVersion() {
+            return CompletableFuture.completedFuture(Optional.of(currentVersion));
+        }
+
+        @Override
+        public CompletableFuture<Void> updateVersion(int newVersion) {
+            currentVersion = newVersion;
+            return CompletableFuture.completedFuture(null);
+        }
+    }
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org