Repository: james-project Updated Branches: refs/heads/master e745d958b -> 829074f99
JAMES-2649 Add a module for alias sources Cassandra projection consistency and tests Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/dbb95982 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/dbb95982 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/dbb95982 Branch: refs/heads/master Commit: dbb95982c7b67336394077992419ec64016b8b35 Parents: e745d95 Author: Rene Cordier <[email protected]> Authored: Fri Jan 25 15:38:17 2019 +0700 Committer: Benoit Tellier <[email protected]> Committed: Fri Feb 1 15:32:32 2019 +0700 ---------------------------------------------------------------------- pom.xml | 5 + server/container/guice/cassandra-guice/pom.xml | 4 + .../apache/james/CassandraJamesServerMain.java | 2 + server/container/guice/pom.xml | 6 + .../protocols/webadmin-cassandra-data/pom.xml | 53 +++++ .../server/CassandraDataRoutesModules.java | 40 ++++ .../cassandra/CassandraMappingsSourcesDAO.java | 11 + .../migration/MappingsSourcesMigration.java | 1 - .../CassandraMappingsSourcesDAOTest.java | 15 +- .../protocols/webadmin-integration-test/pom.xml | 5 + .../integration/UnauthorizedEndpointsTest.java | 4 +- .../WebAdminServerIntegrationTest.java | 36 ++++ server/protocols/webadmin/pom.xml | 1 + .../webadmin/webadmin-cassandra-data/pom.xml | 88 ++++++++ .../james/webadmin/dto/ActionMappings.java | 36 ++++ .../routes/CassandraMappingsRoutes.java | 106 ++++++++++ .../service/CassandraMappingsService.java | 52 +++++ ...ssandraMappingsSolveInconsistenciesTask.java | 56 +++++ .../james/webadmin/dto/ActionMappingsTest.java | 49 +++++ .../routes/CassandraMappingsRoutesTest.java | 208 +++++++++++++++++++ .../apache/james/webadmin/WebAdminServer.java | 11 + .../james/webadmin/routes/ErrorRoutes.java | 15 +- .../james/webadmin/routes/ErrorRoutesTest.java | 15 +- .../webadmin/routes/SieveScriptRoutesTest.java | 4 +- src/site/markdown/server/manage-webadmin.md | 34 +++ 25 files changed, 846 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 1bc0311..f5d0c86 100644 --- a/pom.xml +++ b/pom.xml @@ -1621,6 +1621,11 @@ </dependency> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>james-server-webadmin-cassandra-data</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>james-server-webadmin-core</artifactId> <version>${project.version}</version> </dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/container/guice/cassandra-guice/pom.xml ---------------------------------------------------------------------- diff --git a/server/container/guice/cassandra-guice/pom.xml b/server/container/guice/cassandra-guice/pom.xml index 81df22e..1c9b572 100644 --- a/server/container/guice/cassandra-guice/pom.xml +++ b/server/container/guice/cassandra-guice/pom.xml @@ -188,6 +188,10 @@ </dependency> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>james-server-guice-webadmin-cassandra-data</artifactId> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>james-server-guice-webadmin-data</artifactId> </dependency> <dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java ---------------------------------------------------------------------- diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java index 697c7d7..db1b48e 100644 --- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java +++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java @@ -44,6 +44,7 @@ import org.apache.james.modules.protocols.ManageSieveServerModule; import org.apache.james.modules.protocols.POP3ServerModule; import org.apache.james.modules.protocols.ProtocolHandlerModule; import org.apache.james.modules.protocols.SMTPServerModule; +import org.apache.james.modules.server.CassandraDataRoutesModules; import org.apache.james.modules.server.CassandraRoutesModule; import org.apache.james.modules.server.DLPRoutesModule; import org.apache.james.modules.server.DataRoutesModules; @@ -67,6 +68,7 @@ public class CassandraJamesServerMain { public static final Module WEBADMIN = Modules.combine( new CassandraRoutesModule(), + new CassandraDataRoutesModules(), new DataRoutesModules(), new MailboxRoutesModule(), new MailQueueRoutesModule(), http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/container/guice/pom.xml ---------------------------------------------------------------------- diff --git a/server/container/guice/pom.xml b/server/container/guice/pom.xml index b4e4e3e..2e588e5 100644 --- a/server/container/guice/pom.xml +++ b/server/container/guice/pom.xml @@ -64,6 +64,7 @@ <module>protocols/smtp</module> <module>protocols/webadmin</module> <module>protocols/webadmin-cassandra</module> + <module>protocols/webadmin-cassandra-data</module> <module>protocols/webadmin-data</module> <module>protocols/webadmin-mailbox</module> <module>protocols/webadmin-mailqueue</module> @@ -170,6 +171,11 @@ </dependency> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>james-server-guice-webadmin-cassandra-data</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>james-server-guice-webadmin-data</artifactId> <version>${project.version}</version> </dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/container/guice/protocols/webadmin-cassandra-data/pom.xml ---------------------------------------------------------------------- diff --git a/server/container/guice/protocols/webadmin-cassandra-data/pom.xml b/server/container/guice/protocols/webadmin-cassandra-data/pom.xml new file mode 100644 index 0000000..f479e02 --- /dev/null +++ b/server/container/guice/protocols/webadmin-cassandra-data/pom.xml @@ -0,0 +1,53 @@ +<?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/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.james</groupId> + <artifactId>james-server-guice</artifactId> + <version>3.3.0-SNAPSHOT</version> + <relativePath>../../pom.xml</relativePath> + </parent> + + <artifactId>james-server-guice-webadmin-cassandra-data</artifactId> + + <name>Apache James :: Server :: Guice :: Webadmin :: Cassandra :: Data</name> + <description>Webadmin cassandra data modules for Guice implementation of James server</description> + + <dependencies> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-guice-configuration</artifactId> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-webadmin-cassandra-data</artifactId> + </dependency> + <dependency> + <groupId>com.google.inject</groupId> + <artifactId>guice</artifactId> + </dependency> + <dependency> + <groupId>com.google.inject.extensions</groupId> + <artifactId>guice-multibindings</artifactId> + </dependency> + </dependencies> +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java ---------------------------------------------------------------------- diff --git a/server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java b/server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java new file mode 100644 index 0000000..8c2b4ee --- /dev/null +++ b/server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.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.modules.server; + +import org.apache.james.webadmin.Routes; +import org.apache.james.webadmin.routes.CassandraMappingsRoutes; +import org.apache.james.webadmin.service.CassandraMappingsService; + +import com.google.inject.AbstractModule; +import com.google.inject.Scopes; +import com.google.inject.multibindings.Multibinder; + +public class CassandraDataRoutesModules extends AbstractModule { + + @Override + protected void configure() { + bind(CassandraMappingsRoutes.class).in(Scopes.SINGLETON); + bind(CassandraMappingsService.class).in(Scopes.SINGLETON); + + Multibinder<Routes> routesMultibinder = Multibinder.newSetBinder(binder(), Routes.class); + routesMultibinder.addBinding().to(CassandraMappingsRoutes.class); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAO.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAO.java b/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAO.java index 6ff3072..d7d5fb3 100644 --- a/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAO.java +++ b/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAO.java @@ -24,6 +24,7 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.delete; import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto; import static com.datastax.driver.core.querybuilder.QueryBuilder.select; +import static com.datastax.driver.core.querybuilder.QueryBuilder.truncate; import static org.apache.james.rrt.cassandra.tables.CassandraMappingsSourcesTable.MAPPING_TYPE; import static org.apache.james.rrt.cassandra.tables.CassandraMappingsSourcesTable.MAPPING_VALUE; import static org.apache.james.rrt.cassandra.tables.CassandraMappingsSourcesTable.SOURCE; @@ -46,6 +47,7 @@ public class CassandraMappingsSourcesDAO { private final PreparedStatement insertStatement; private final PreparedStatement deleteStatement; private final PreparedStatement retrieveSourcesStatement; + private final PreparedStatement truncateStatement; @Inject public CassandraMappingsSourcesDAO(Session session) { @@ -53,6 +55,7 @@ public class CassandraMappingsSourcesDAO { this.insertStatement = prepareInsertStatement(session); this.deleteStatement = prepareDelete(session); this.retrieveSourcesStatement = prepareRetrieveSourcesStatement(session); + this.truncateStatement = prepareTruncateStatement(session); } private PreparedStatement prepareInsertStatement(Session session) { @@ -77,6 +80,10 @@ public class CassandraMappingsSourcesDAO { .and(eq(MAPPING_VALUE, bindMarker(MAPPING_VALUE)))); } + private PreparedStatement prepareTruncateStatement(Session session) { + return session.prepare(truncate(TABLE_NAME)); + } + public Mono<Void> addMapping(Mapping mapping, MappingSource source) { return executor.executeVoidReactor(insertStatement.bind() .setString(MAPPING_TYPE, mapping.getType().asPrefix()) @@ -98,4 +105,8 @@ public class CassandraMappingsSourcesDAO { .flatMapMany(Flux::fromIterable) .map(row -> MappingSource.parse(row.getString(SOURCE))); } + + public Mono<Void> removeAllData() { + return executor.executeVoidReactor(truncateStatement.bind()); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/migration/MappingsSourcesMigration.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/migration/MappingsSourcesMigration.java b/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/migration/MappingsSourcesMigration.java index 226add2..1e612bc 100644 --- a/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/migration/MappingsSourcesMigration.java +++ b/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/migration/MappingsSourcesMigration.java @@ -45,7 +45,6 @@ public class MappingsSourcesMigration implements Migration { this.cassandraMappingsSourcesDAO = cassandraMappingsSourcesDAO; } - @Override public Result run() { return cassandraRecipientRewriteTableDAO.getAllMappings() http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAOTest.java ---------------------------------------------------------------------- diff --git a/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAOTest.java b/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAOTest.java index b277a4e..045eb1b 100644 --- a/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAOTest.java +++ b/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAOTest.java @@ -34,6 +34,7 @@ class CassandraMappingsSourcesDAOTest { private static final String USER = "test"; private static final String ADDRESS = "test@domain"; private static final MappingSource SOURCE = MappingSource.fromUser(USER, Domain.LOCALHOST); + private static final MappingSource SOURCE_2 = MappingSource.fromUser("bob", Domain.LOCALHOST); private static final Mapping MAPPING = Mapping.alias(ADDRESS); @RegisterExtension @@ -69,11 +70,19 @@ class CassandraMappingsSourcesDAOTest { @Test void retrieveSourcesShouldReturnMultipleStoredMappingSourcesForMapping() { - MappingSource source2 = MappingSource.fromUser("bob", Domain.LOCALHOST); + dao.addMapping(MAPPING, SOURCE).block(); + dao.addMapping(MAPPING, SOURCE_2).block(); + + assertThat(dao.retrieveSources(MAPPING).collectList().block()).containsOnly(SOURCE, SOURCE_2); + } + @Test + void retrieveSourcesShouldReturnEmptyAfterTruncateData() { dao.addMapping(MAPPING, SOURCE).block(); - dao.addMapping(MAPPING, source2).block(); + dao.addMapping(MAPPING, SOURCE_2).block(); + + dao.removeAllData().block(); - assertThat(dao.retrieveSources(MAPPING).collectList().block()).containsOnly(SOURCE, source2); + assertThat(dao.retrieveSources(MAPPING).collectList().block()).isEmpty(); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin-integration-test/pom.xml ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin-integration-test/pom.xml b/server/protocols/webadmin-integration-test/pom.xml index a585ffb..15f0ea1 100644 --- a/server/protocols/webadmin-integration-test/pom.xml +++ b/server/protocols/webadmin-integration-test/pom.xml @@ -88,6 +88,11 @@ </dependency> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>james-server-webadmin-cassandra-data</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>james-server-webadmin-data</artifactId> <scope>test</scope> </dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java index 14abe53..7c49324 100644 --- a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java +++ b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java @@ -25,6 +25,7 @@ import org.apache.james.GuiceJamesServer; import org.apache.james.utils.WebAdminGuiceProbe; import org.apache.james.webadmin.WebAdminUtils; import org.apache.james.webadmin.routes.AliasRoutes; +import org.apache.james.webadmin.routes.CassandraMappingsRoutes; import org.apache.james.webadmin.routes.CassandraMigrationRoutes; import org.apache.james.webadmin.routes.DLPConfigurationRoutes; import org.apache.james.webadmin.routes.DomainMappingsRoutes; @@ -107,7 +108,8 @@ class UnauthorizedEndpointsTest { @ParameterizedTest @ValueSource(strings = { CassandraMigrationRoutes.VERSION_BASE + "/upgrade", - CassandraMigrationRoutes.VERSION_BASE + "/upgrade/latest" + CassandraMigrationRoutes.VERSION_BASE + "/upgrade/latest", + CassandraMappingsRoutes.ROOT_PATH }) void checkUrlProtectionOnPost(String url) { when() http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java index 153a76e..85a68c5 100644 --- a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java +++ b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java @@ -25,6 +25,7 @@ import static io.restassured.RestAssured.with; import static org.apache.james.webadmin.Constants.JSON_CONTENT_TYPE; import static org.apache.james.webadmin.Constants.SEPARATOR; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -41,12 +42,14 @@ import org.apache.james.utils.DataProbeImpl; import org.apache.james.utils.WebAdminGuiceProbe; import org.apache.james.webadmin.WebAdminUtils; import org.apache.james.webadmin.routes.AliasRoutes; +import org.apache.james.webadmin.routes.CassandraMappingsRoutes; import org.apache.james.webadmin.routes.DomainsRoutes; import org.apache.james.webadmin.routes.ForwardRoutes; import org.apache.james.webadmin.routes.GroupsRoutes; import org.apache.james.webadmin.routes.HealthCheckRoutes; import org.apache.james.webadmin.routes.MailQueueRoutes; import org.apache.james.webadmin.routes.MailRepositoriesRoutes; +import org.apache.james.webadmin.routes.TasksRoutes; import org.apache.james.webadmin.routes.UserMailboxesRoutes; import org.apache.james.webadmin.routes.UserRoutes; import org.apache.james.webadmin.swagger.routes.SwaggerRoutes; @@ -58,6 +61,7 @@ import org.junit.Rule; import org.junit.Test; import io.restassured.RestAssured; +import io.restassured.http.ContentType; public class WebAdminServerIntegrationTest { @@ -361,6 +365,7 @@ public class WebAdminServerIntegrationTest { .body(containsString("\"tags\":[\"Address Forwards\"]")) .body(containsString("\"tags\":[\"Address Aliases\"]")) .body(containsString("\"tags\":[\"Address Groups\"]")) + .body(containsString("\"tags\":[\"Cassandra Mappings Operations\"]")) .body(containsString("{\"name\":\"ReIndexing (mailboxes)\"}")) .body(containsString("{\"name\":\"MessageIdReIndexing\"}")); } @@ -372,4 +377,35 @@ public class WebAdminServerIntegrationTest { .then() .statusCode(HttpStatus.OK_200); } + + @Test + public void cassandraMappingsEndpointShouldKeepDataConsistencyWhenDataValid() { + String alias1 = "[email protected]"; + String alias2 = "[email protected]"; + + with() + .put(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME + "/sources/" + alias1); + with() + .put(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME + "/sources/" + alias2); + + String taskId = with() + .queryParam("action", "SolveInconsistencies") + .post(CassandraMappingsRoutes.ROOT_PATH) + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")); + + when() + .get(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME) + .then() + .contentType(ContentType.JSON) + .statusCode(HttpStatus.OK_200) + .body("source", hasItems(alias1, alias2)); + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/pom.xml ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/pom.xml b/server/protocols/webadmin/pom.xml index a9725ea..3655376 100644 --- a/server/protocols/webadmin/pom.xml +++ b/server/protocols/webadmin/pom.xml @@ -34,6 +34,7 @@ <modules> <module>webadmin-cassandra</module> + <module>webadmin-cassandra-data</module> <module>webadmin-core</module> <module>webadmin-data</module> <module>webadmin-mailbox</module> http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/pom.xml ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-cassandra-data/pom.xml b/server/protocols/webadmin/webadmin-cassandra-data/pom.xml new file mode 100644 index 0000000..47383d3 --- /dev/null +++ b/server/protocols/webadmin/webadmin-cassandra-data/pom.xml @@ -0,0 +1,88 @@ +<?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/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.james</groupId> + <artifactId>james-server</artifactId> + <version>3.3.0-SNAPSHOT</version> + <relativePath>../../../pom.xml</relativePath> + </parent> + + <artifactId>james-server-webadmin-cassandra-data</artifactId> + <packaging>jar</packaging> + + <name>Apache James :: Server :: Web Admin :: Cassandra :: Data</name> + + <dependencies> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>apache-james-backends-cassandra</artifactId> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>apache-james-backends-cassandra</artifactId> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-data-cassandra</artifactId> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-webadmin-core</artifactId> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-webadmin-core</artifactId> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>metrics-logger</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.rest-assured</groupId> + <artifactId>rest-assured</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>testcontainers</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/dto/ActionMappings.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/dto/ActionMappings.java b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/dto/ActionMappings.java new file mode 100644 index 0000000..7ca05b1 --- /dev/null +++ b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/dto/ActionMappings.java @@ -0,0 +1,36 @@ +/**************************************************************** + * 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.Arrays; + +import com.google.common.base.Preconditions; + +public enum ActionMappings { + SolveInconsistencies; + + public static ActionMappings parse(String action) { + Preconditions.checkArgument(action != null, "'action' url parameter is mandatory"); + return Arrays.stream(ActionMappings.values()) + .filter(element -> element.toString().equalsIgnoreCase(action)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("'" + action + "' is not a valid action query parameter")); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/routes/CassandraMappingsRoutes.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/routes/CassandraMappingsRoutes.java b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/routes/CassandraMappingsRoutes.java new file mode 100644 index 0000000..15c3d4e --- /dev/null +++ b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/routes/CassandraMappingsRoutes.java @@ -0,0 +1,106 @@ +/**************************************************************** + * 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 javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.apache.james.task.Task; +import org.apache.james.task.TaskId; +import org.apache.james.task.TaskManager; +import org.apache.james.webadmin.Constants; +import org.apache.james.webadmin.Routes; +import org.apache.james.webadmin.dto.ActionMappings; +import org.apache.james.webadmin.dto.TaskIdDto; +import org.apache.james.webadmin.service.CassandraMappingsService; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.eclipse.jetty.http.HttpStatus; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.ResponseHeader; +import spark.Request; +import spark.Response; +import spark.Service; + +@Api(tags = "Cassandra Mappings Operations") +@Path(CassandraMappingsRoutes.ROOT_PATH) +@Produces(Constants.JSON_CONTENT_TYPE) +public class CassandraMappingsRoutes implements Routes { + public static final String ROOT_PATH = "cassandra/mappings"; + + private final CassandraMappingsService cassandraMappingsService; + private final TaskManager taskManager; + private final JsonTransformer jsonTransformer; + + private static final String INVALID_ACTION_ARGUMENT_REQUEST = "Invalid action argument for performing operation on mappings data"; + private static final String ACTION_REQUEST_CAN_NOT_BE_DONE = "The action requested for performing operation on mappings data cannot be performed"; + + @Inject + CassandraMappingsRoutes(CassandraMappingsService cassandraMappingsService, TaskManager taskManager, JsonTransformer jsonTransformer) { + this.cassandraMappingsService = cassandraMappingsService; + this.taskManager = taskManager; + this.jsonTransformer = jsonTransformer; + } + + @Override + public String getBasePath() { + return ROOT_PATH; + } + + @Override + public void define(Service service) { + service.post(ROOT_PATH, this::performActionOnMappings, jsonTransformer); + } + + @POST + @Path(ROOT_PATH) + @ApiOperation(value = "Performing operations on cassandra data mappings") + @ApiImplicitParams({ + @ApiImplicitParam( + required = true, + dataType = "String", + name = "action", + paramType = "query", + example = "?action=SolveInconsistencies", + value = "Specify the action to perform on mappings. For now only 'SolveInconsistencies' is supported as an action, " + + "and its purpose is to clean 'mappings_sources' projection table and repopulate it."), + }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.CREATED_201, message = "The taskId of the given scheduled task", response = TaskIdDto.class, + responseHeaders = { + @ResponseHeader(name = "Location", description = "URL of the resource associated with the scheduled task") + }), + @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = INVALID_ACTION_ARGUMENT_REQUEST), + @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = ACTION_REQUEST_CAN_NOT_BE_DONE) + }) + public TaskIdDto performActionOnMappings(Request request, Response response) { + ActionMappings action = ActionMappings.parse(request.queryParams("action")); + Task task = cassandraMappingsService.createActionTask(action); + TaskId taskId = taskManager.submit(task); + return TaskIdDto.respond(response, taskId); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsService.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsService.java b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsService.java new file mode 100644 index 0000000..e1733ef --- /dev/null +++ b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsService.java @@ -0,0 +1,52 @@ +/**************************************************************** + * 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 javax.inject.Inject; + +import org.apache.james.rrt.cassandra.CassandraMappingsSourcesDAO; +import org.apache.james.rrt.cassandra.migration.MappingsSourcesMigration; +import org.apache.james.task.Task; +import org.apache.james.webadmin.dto.ActionMappings; + +public class CassandraMappingsService { + private final MappingsSourcesMigration mappingsSourcesMigration; + private final CassandraMappingsSourcesDAO cassandraMappingsSourcesDAO; + + @Inject + public CassandraMappingsService(MappingsSourcesMigration mappingsSourcesMigration, + CassandraMappingsSourcesDAO cassandraMappingsSourcesDAO) { + this.mappingsSourcesMigration = mappingsSourcesMigration; + this.cassandraMappingsSourcesDAO = cassandraMappingsSourcesDAO; + } + + public Task createActionTask(ActionMappings action) { + switch (action) { + case SolveInconsistencies: + return solveMappingsSourcesInconsistencies(); + default: + throw new IllegalArgumentException(action + " is not a supported action"); + } + } + + private Task solveMappingsSourcesInconsistencies() { + return new CassandraMappingsSolveInconsistenciesTask(mappingsSourcesMigration, cassandraMappingsSourcesDAO); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsSolveInconsistenciesTask.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsSolveInconsistenciesTask.java b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsSolveInconsistenciesTask.java new file mode 100644 index 0000000..14a1c47 --- /dev/null +++ b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsSolveInconsistenciesTask.java @@ -0,0 +1,56 @@ +/**************************************************************** + * 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 javax.inject.Inject; + +import org.apache.james.rrt.cassandra.CassandraMappingsSourcesDAO; +import org.apache.james.rrt.cassandra.migration.MappingsSourcesMigration; +import org.apache.james.task.Task; + +import reactor.core.publisher.Mono; + +public class CassandraMappingsSolveInconsistenciesTask implements Task { + public static final String TYPE = "cassandraMappingsSolveInconsistencies"; + + private final MappingsSourcesMigration mappingsSourcesMigration; + private final CassandraMappingsSourcesDAO cassandraMappingsSourcesDAO; + + @Inject + CassandraMappingsSolveInconsistenciesTask(MappingsSourcesMigration mappingsSourcesMigration, + CassandraMappingsSourcesDAO cassandraMappingsSourcesDAO) { + this.mappingsSourcesMigration = mappingsSourcesMigration; + this.cassandraMappingsSourcesDAO = cassandraMappingsSourcesDAO; + } + + @Override + public Result run() { + return cassandraMappingsSourcesDAO.removeAllData() + .doOnError(e -> LOGGER.error("Error while cleaning up data in mappings sources projection table")) + .then(Mono.fromCallable(mappingsSourcesMigration::run)) + .onErrorResume(e -> Mono.just(Result.PARTIAL)) + .block(); + } + + @Override + public String type() { + return TYPE; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/dto/ActionMappingsTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/dto/ActionMappingsTest.java b/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/dto/ActionMappingsTest.java new file mode 100644 index 0000000..3cfe691 --- /dev/null +++ b/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/dto/ActionMappingsTest.java @@ -0,0 +1,49 @@ +/**************************************************************** + * 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 static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class ActionMappingsTest { + private static final String ACTION = "SolveInconsistencies"; + + @Test + void parseShouldSucceedWithCorrectActionMappingsArgument() { + assertThat(ActionMappings.parse(ACTION)).isEqualTo(ActionMappings.SolveInconsistencies); + } + + @Test + void parseShouldFailWithIncorrectActionMappingsArgument() { + assertThatThrownBy(() -> ActionMappings.parse("incorrect-action")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("'incorrect-action' is not a valid action query parameter"); + } + + @Test + void parseShouldFailWithMissingActionMappingsArgument() { + assertThatThrownBy(() -> ActionMappings.parse(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("'action' url parameter is mandatory"); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/routes/CassandraMappingsRoutesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/routes/CassandraMappingsRoutesTest.java b/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/routes/CassandraMappingsRoutesTest.java new file mode 100644 index 0000000..fedea9c --- /dev/null +++ b/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/routes/CassandraMappingsRoutesTest.java @@ -0,0 +1,208 @@ +/**************************************************************** + * 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 io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static io.restassured.RestAssured.with; +import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.james.backends.cassandra.CassandraCluster; +import org.apache.james.backends.cassandra.CassandraClusterExtension; +import org.apache.james.backends.cassandra.utils.CassandraUtils; +import org.apache.james.core.Domain; +import org.apache.james.metrics.logger.DefaultMetricFactory; +import org.apache.james.rrt.cassandra.CassandraMappingsSourcesDAO; +import org.apache.james.rrt.cassandra.CassandraRRTModule; +import org.apache.james.rrt.cassandra.CassandraRecipientRewriteTableDAO; +import org.apache.james.rrt.cassandra.migration.MappingsSourcesMigration; +import org.apache.james.rrt.lib.Mapping; +import org.apache.james.rrt.lib.MappingSource; +import org.apache.james.task.MemoryTaskManager; +import org.apache.james.webadmin.WebAdminServer; +import org.apache.james.webadmin.WebAdminUtils; +import org.apache.james.webadmin.service.CassandraMappingsService; +import org.apache.james.webadmin.service.CassandraMappingsSolveInconsistenciesTask; +import org.apache.james.webadmin.utils.ErrorResponder; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.restassured.RestAssured; + +class CassandraMappingsRoutesTest { + private static final String MAPPINGS_ACTION = "SolveInconsistencies"; + private static final MappingSource SOURCE_1 = MappingSource.fromUser("bob", Domain.LOCALHOST); + private static final MappingSource SOURCE_2 = MappingSource.fromUser("alice", Domain.LOCALHOST); + private static final Mapping MAPPING = Mapping.alias("bob-alias@domain"); + + private WebAdminServer webAdminServer; + + private MappingsSourcesMigration mappingsSourcesMigration; + private CassandraRecipientRewriteTableDAO cassandraRecipientRewriteTableDAO; + private CassandraMappingsSourcesDAO cassandraMappingsSourcesDAO; + private MemoryTaskManager taskManager; + + @RegisterExtension + static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(CassandraRRTModule.MODULE); + + @BeforeEach + void setUp(CassandraCluster cassandra) throws ConfigurationException { + cassandraRecipientRewriteTableDAO = new CassandraRecipientRewriteTableDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION); + cassandraMappingsSourcesDAO = new CassandraMappingsSourcesDAO(cassandra.getConf()); + mappingsSourcesMigration = new MappingsSourcesMigration(cassandraRecipientRewriteTableDAO, cassandraMappingsSourcesDAO); + + CassandraMappingsService cassandraMappingsService = new CassandraMappingsService(mappingsSourcesMigration, cassandraMappingsSourcesDAO); + + JsonTransformer jsonTransformer = new JsonTransformer(); + taskManager = new MemoryTaskManager(); + webAdminServer = WebAdminUtils.createWebAdminServer( + new DefaultMetricFactory(), + new CassandraMappingsRoutes(cassandraMappingsService, taskManager, jsonTransformer), + new TasksRoutes(taskManager, jsonTransformer)); + + webAdminServer.configure(NO_CONFIGURATION); + webAdminServer.await(); + + RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer) + .setBasePath(CassandraMappingsRoutes.ROOT_PATH) + .build(); + } + + @AfterEach + void tearDown() { + webAdminServer.destroy(); + taskManager.stop(); + } + + @Test + void postMappingsActionWithSolvedInconsistenciesQueryParamShouldCreateATask() { + given() + .queryParam("action", MAPPINGS_ACTION) + .when() + .post() + .then() + .statusCode(HttpStatus.CREATED_201) + .header("Location", is(notNullValue())) + .body("taskId", is(notNullValue())); + } + + @Test + void postMappingsActionWithSolvedInconsistenciesQueryParamShouldHaveSuccessfulCompletedTask() { + String taskId = with() + .queryParam("action", MAPPINGS_ACTION) + .post() + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(taskId)) + .body("type", is(CassandraMappingsSolveInconsistenciesTask.TYPE)) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + + @Test + void postMappingsActionShouldRejectInvalidActions() { + given() + .queryParam("action", "invalid-action") + .when() + .post() + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Invalid arguments supplied in the user request")) + .body("details", is("'invalid-action' is not a valid action query parameter")); + } + + @Test + void postMappingsActionShouldRequireAction() { + when() + .post() + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Invalid arguments supplied in the user request")) + .body("details", is("'action' url parameter is mandatory")); + } + + @Test + void postMappingsActionShouldResolveRRTInconsistencies() { + cassandraRecipientRewriteTableDAO.addMapping(SOURCE_1, MAPPING).block(); + cassandraRecipientRewriteTableDAO.addMapping(SOURCE_2, MAPPING).block(); + + cassandraMappingsSourcesDAO.addMapping(MAPPING, SOURCE_1).block(); + + String taskId = with() + .queryParam("action", MAPPINGS_ACTION) + .post() + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")); + + assertThat(cassandraMappingsSourcesDAO.retrieveSources(MAPPING).collectList().block()) + .containsOnly(SOURCE_1, SOURCE_2); + } + + @Test + void postMappingsActionShouldResolveMappingsSourcesInconsistencies() { + cassandraRecipientRewriteTableDAO.addMapping(SOURCE_1, MAPPING).block(); + + cassandraMappingsSourcesDAO.addMapping(MAPPING, SOURCE_1).block(); + cassandraMappingsSourcesDAO.addMapping(MAPPING, SOURCE_2).block(); + + String taskId = with() + .queryParam("action", MAPPINGS_ACTION) + .post() + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")); + + assertThat(cassandraMappingsSourcesDAO.retrieveSources(MAPPING).collectList().block()) + .containsOnly(SOURCE_1); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/WebAdminServer.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/WebAdminServer.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/WebAdminServer.java index 595db70..1080cf3 100644 --- a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/WebAdminServer.java +++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/WebAdminServer.java @@ -154,6 +154,17 @@ public class WebAdminServer implements Configurable { .cause(ex) .asString()); }); + + service.exception(IllegalArgumentException.class, (ex, req, res) -> { + LOGGER.info("Invalid arguments supplied in the user request", ex); + res.status(BAD_REQUEST_400); + res.body(ErrorResponder.builder() + .statusCode(BAD_REQUEST_400) + .type(INVALID_ARGUMENT) + .message("Invalid arguments supplied in the user request") + .cause(ex) + .asString()); + }); } @PreDestroy http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutes.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutes.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutes.java index a2563b9..35876e5 100644 --- a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutes.java +++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutes.java @@ -26,14 +26,16 @@ import spark.Service; public class ErrorRoutes implements Routes { - public static final String BASE_URL = "/errors/"; - public static final String INTERNAL_SERVER_ERROR = "internalServerError"; - public static final String JSON_EXTRACT_EXCEPTION = "jsonExtractException"; + static final String BASE_URL = "/errors/"; + static final String INTERNAL_SERVER_ERROR = "internalServerError"; + static final String JSON_EXTRACT_EXCEPTION = "jsonExtractException"; + static final String INVALID_ARGUMENT_EXCEPTION = "illegalArgumentException"; @Override public void define(Service service) { defineInternalError(service); defineJsonExtractException(service); + defineIllegalArgumentException(service); } @Override @@ -52,4 +54,11 @@ public class ErrorRoutes implements Routes { service.get(BASE_URL + JSON_EXTRACT_EXCEPTION, (req, res) -> new JsonExtractor<>(Long.class).parse("a non valid JSON")); } + + private void defineIllegalArgumentException(Service service) { + service.get(BASE_URL + INVALID_ARGUMENT_EXCEPTION, + (req, res) -> { + throw new IllegalArgumentException("Argument is non valid"); + }); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutesTest.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutesTest.java index f8896f9..bc7918c 100644 --- a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutesTest.java +++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutesTest.java @@ -22,6 +22,7 @@ package org.apache.james.webadmin.routes; import static io.restassured.RestAssured.when; import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION; import static org.apache.james.webadmin.routes.ErrorRoutes.INTERNAL_SERVER_ERROR; +import static org.apache.james.webadmin.routes.ErrorRoutes.INVALID_ARGUMENT_EXCEPTION; import static org.apache.james.webadmin.routes.ErrorRoutes.JSON_EXTRACT_EXCEPTION; import static org.apache.james.webadmin.utils.ErrorResponder.ErrorType.INVALID_ARGUMENT; import static org.apache.james.webadmin.utils.ErrorResponder.ErrorType.SERVER_ERROR; @@ -88,7 +89,7 @@ public class ErrorRoutesTest { } @Test - public void defineJsonExtractExceptionShouldReturnBadRequestJsonFormat() throws InterruptedException { + public void defineJsonExtractExceptionShouldReturnBadRequestJsonFormat() { when() .get(JSON_EXTRACT_EXCEPTION) .then() @@ -98,4 +99,16 @@ public class ErrorRoutesTest { .body("message", equalTo("JSON payload of the request is not valid")) .body("details", containsString("Unrecognized token 'a': was expecting ('true', 'false' or 'null')")); } + + @Test + public void defineIllegalExceptionShouldReturnBadRequestJsonFormat() { + when() + .get(INVALID_ARGUMENT_EXCEPTION) + .then() + .statusCode(BAD_REQUEST_400) + .body("statusCode", equalTo(BAD_REQUEST_400)) + .body("type", equalTo(INVALID_ARGUMENT.getType())) + .body("message", equalTo("Invalid arguments supplied in the user request")) + .body("details", containsString("Argument is non valid")); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/SieveScriptRoutesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/SieveScriptRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/SieveScriptRoutesTest.java index 5c3fb28..25f26ad 100644 --- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/SieveScriptRoutesTest.java +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/SieveScriptRoutesTest.java @@ -166,14 +166,14 @@ public class SieveScriptRoutesTest { } @Test - public void defineAddActiveSieveScriptShouldReturnInternalErrorWhenScriptIsNotSet() { + public void defineAddActiveSieveScriptShouldReturnBadRequestWhenScriptIsNotSet() { given() .pathParam("userName", "userA") .pathParam("scriptName", "scriptA") .when() .put("sieve/{userName}/scripts/{scriptName}") .then() - .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500); + .statusCode(HttpStatus.BAD_REQUEST_400); } @Test http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/src/site/markdown/server/manage-webadmin.md ---------------------------------------------------------------------- diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md index 65d28d6..6ee80a8 100644 --- a/src/site/markdown/server/manage-webadmin.md +++ b/src/site/markdown/server/manage-webadmin.md @@ -44,6 +44,7 @@ as exposed above). To avoid information duplication, this is ommited on endpoint - [Administrating Sieve quotas](#Administrating_Sieve_quotas) - [ReIndexing](#ReIndexing) - [Task management](#Task_management) + - [Cassandra extra operations](#Cassandra_extra_operations) ## HealthCheck @@ -2515,3 +2516,36 @@ Response codes: - 200: A list of corresponding tasks is returned - 400: Invalid status value + +## Cassandra extra operations + +Some webadmin features to manage some extra operations on Cassandra tables, like solving inconsistencies on projection tables. +Such inconsistencies can be for example created by a fail of the DAO to add a mapping into 'mappings_sources`, while it was successful +regarding the `rrt` table. + + - [Operations on mappings sources](#Operations_on_mappings_sources) + +### Operations on mappings sources + +You can do a series of action on `mappings_sources` projection table : + +``` +curl -XPOST /cassandra/mappings?action=[ACTION] +``` + +Will return the taskId corresponding to the related task. Actions supported so far are : + + - SolveInconsistencies : cleans up first all the mappings in `mappings_sources` index and then repopulate it correctly. In the meantime, +listing sources of a mapping might create temporary inconsistencies during the process. + +For example : + +``` +curl -XPOST /cassandra/mappings?action=SolveInconsistencies +``` + +Response codes : + + - 201: the taskId of the created task + - 400: Invalid action argument for performing operation on mappings data + --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
