You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/03/28 08:23:18 UTC
[james-project] branch master updated: JAMES-3885 Change username - migrate quotas (#1500)
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push:
new 96975734cc JAMES-3885 Change username - migrate quotas (#1500)
96975734cc is described below
commit 96975734ccc0178de92bcd48559f76e2f32e64d1
Author: vttran <vt...@linagora.com>
AuthorDate: Tue Mar 28 15:23:10 2023 +0700
JAMES-3885 Change username - migrate quotas (#1500)
---
.../docs/modules/ROOT/pages/operate/webadmin.adoc | 1 +
.../modules/mailbox/CassandraMailboxModule.java | 2 +
.../james/modules/mailbox/JPAMailboxModule.java | 2 +
.../james/modules/mailbox/MemoryMailboxModule.java | 2 +
.../mailbox/QuotaUsernameChangeTaskStep.java | 126 +++++++++++
.../mailbox/QuotaUsernameChangeTaskStepTest.java | 252 +++++++++++++++++++++
.../MemoryUsernameChangeIntegrationTest.java | 65 ++++++
src/site/markdown/server/manage-webadmin.md | 2 +-
8 files changed, 451 insertions(+), 1 deletion(-)
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
index ad8936c269..0fcca321ba 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
@@ -638,6 +638,7 @@ Implemented migration steps are:
- `MailboxUsernameChangeTaskStep`: migrates mailboxes belonging to the old user to the account of the new user. It also
migrates user's mailbox subscriptions.
- `ACLUsernameChangeTaskStep`: migrates ACLs on mailboxes the migrated user has access to and updates subscriptions accordingly.
+ - `QuotaUsernameChangeTaskStep`: migrates quotas user from old user to new user.
Response codes:
diff --git a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
index cf38c919b4..cd0a699521 100644
--- a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
+++ b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
@@ -24,6 +24,7 @@ import javax.inject.Singleton;
import org.apache.james.adapter.mailbox.ACLUsernameChangeTaskStep;
import org.apache.james.adapter.mailbox.MailboxUsernameChangeTaskStep;
+import org.apache.james.adapter.mailbox.QuotaUsernameChangeTaskStep;
import org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
import org.apache.james.backends.cassandra.components.CassandraModule;
import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionManager;
@@ -247,6 +248,7 @@ public class CassandraMailboxModule extends AbstractModule {
Multibinder<UsernameChangeTaskStep> usernameChangeTaskStepMultibinder = Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class);
usernameChangeTaskStepMultibinder.addBinding().to(MailboxUsernameChangeTaskStep.class);
usernameChangeTaskStepMultibinder.addBinding().to(ACLUsernameChangeTaskStep.class);
+ usernameChangeTaskStepMultibinder.addBinding().to(QuotaUsernameChangeTaskStep.class);
}
@Provides
diff --git a/server/container/guice/mailbox-jpa/src/main/java/org/apache/james/modules/mailbox/JPAMailboxModule.java b/server/container/guice/mailbox-jpa/src/main/java/org/apache/james/modules/mailbox/JPAMailboxModule.java
index 62138140bf..5bf648d6d8 100644
--- a/server/container/guice/mailbox-jpa/src/main/java/org/apache/james/modules/mailbox/JPAMailboxModule.java
+++ b/server/container/guice/mailbox-jpa/src/main/java/org/apache/james/modules/mailbox/JPAMailboxModule.java
@@ -24,6 +24,7 @@ import javax.inject.Singleton;
import org.apache.james.adapter.mailbox.ACLUsernameChangeTaskStep;
import org.apache.james.adapter.mailbox.MailboxUsernameChangeTaskStep;
+import org.apache.james.adapter.mailbox.QuotaUsernameChangeTaskStep;
import org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
import org.apache.james.adapter.mailbox.UserRepositoryAuthorizator;
import org.apache.james.events.EventListener;
@@ -128,6 +129,7 @@ public class JPAMailboxModule extends AbstractModule {
Multibinder<UsernameChangeTaskStep> usernameChangeTaskStepMultibinder = Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class);
usernameChangeTaskStepMultibinder.addBinding().to(MailboxUsernameChangeTaskStep.class);
usernameChangeTaskStepMultibinder.addBinding().to(ACLUsernameChangeTaskStep.class);
+ usernameChangeTaskStepMultibinder.addBinding().to(QuotaUsernameChangeTaskStep.class);
}
@Singleton
diff --git a/server/container/guice/memory/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java b/server/container/guice/memory/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java
index 033d4daccb..61083eb515 100644
--- a/server/container/guice/memory/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java
+++ b/server/container/guice/memory/src/main/java/org/apache/james/modules/mailbox/MemoryMailboxModule.java
@@ -26,6 +26,7 @@ import javax.inject.Singleton;
import org.apache.james.adapter.mailbox.ACLUsernameChangeTaskStep;
import org.apache.james.adapter.mailbox.DelegationStoreAuthorizator;
import org.apache.james.adapter.mailbox.MailboxUsernameChangeTaskStep;
+import org.apache.james.adapter.mailbox.QuotaUsernameChangeTaskStep;
import org.apache.james.adapter.mailbox.UserRepositoryAuthenticator;
import org.apache.james.events.EventListener;
import org.apache.james.jmap.api.change.EmailChangeRepository;
@@ -160,6 +161,7 @@ public class MemoryMailboxModule extends AbstractModule {
Multibinder<UsernameChangeTaskStep> usernameChangeTaskStepMultibinder = Multibinder.newSetBinder(binder(), UsernameChangeTaskStep.class);
usernameChangeTaskStepMultibinder.addBinding().to(MailboxUsernameChangeTaskStep.class);
usernameChangeTaskStepMultibinder.addBinding().to(ACLUsernameChangeTaskStep.class);
+ usernameChangeTaskStepMultibinder.addBinding().to(QuotaUsernameChangeTaskStep.class);
}
@Singleton
diff --git a/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/QuotaUsernameChangeTaskStep.java b/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/QuotaUsernameChangeTaskStep.java
new file mode 100644
index 0000000000..75738437bb
--- /dev/null
+++ b/server/container/mailbox-adapter/src/main/java/org/apache/james/adapter/mailbox/QuotaUsernameChangeTaskStep.java
@@ -0,0 +1,126 @@
+/****************************************************************
+ * 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.adapter.mailbox;
+
+import java.time.Instant;
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.events.EventBus;
+import org.apache.james.events.RegistrationKey;
+import org.apache.james.mailbox.model.CurrentQuotas;
+import org.apache.james.mailbox.model.Quota;
+import org.apache.james.mailbox.model.QuotaOperation;
+import org.apache.james.mailbox.model.QuotaRoot;
+import org.apache.james.mailbox.quota.CurrentQuotaManager;
+import org.apache.james.mailbox.quota.MaxQuotaManager;
+import org.apache.james.mailbox.quota.QuotaManager;
+import org.apache.james.mailbox.quota.UserQuotaRootResolver;
+import org.apache.james.mailbox.store.event.EventFactory;
+import org.apache.james.user.api.UsernameChangeTaskStep;
+import org.reactivestreams.Publisher;
+
+import com.google.common.collect.ImmutableSet;
+
+import reactor.core.publisher.Mono;
+
+public class QuotaUsernameChangeTaskStep implements UsernameChangeTaskStep {
+ private static final ImmutableSet<RegistrationKey> NO_REGISTRATION_KEYS = ImmutableSet.of();
+ private final QuotaManager quotaManager;
+ private final MaxQuotaManager maxQuotaManager;
+ private final CurrentQuotaManager currentQuotaManager;
+ private final UserQuotaRootResolver userQuotaRootResolver;
+ private final EventBus eventBus;
+
+ @Inject
+ public QuotaUsernameChangeTaskStep(QuotaManager quotaManager,
+ CurrentQuotaManager currentQuotaManager,
+ UserQuotaRootResolver userQuotaRootResolver,
+ MaxQuotaManager maxQuotaManager,
+ EventBus eventBus) {
+ this.quotaManager = quotaManager;
+ this.currentQuotaManager = currentQuotaManager;
+ this.userQuotaRootResolver = userQuotaRootResolver;
+ this.maxQuotaManager = maxQuotaManager;
+ this.eventBus = eventBus;
+ }
+
+ @Override
+ public StepName name() {
+ return new StepName("QuotaUsernameChangeTaskStep");
+ }
+
+ @Override
+ public int priority() {
+ return 3;
+ }
+
+ @Override
+ public Publisher<Void> changeUsername(Username oldUsername, Username newUsername) {
+ return Mono.from(quotaManager.getQuotasReactive(userQuotaRootResolver.forUser(oldUsername)))
+ .flatMap(quotas -> Mono.fromCallable(() -> userQuotaRootResolver.forUser(newUsername))
+ .flatMap(newUserQuotaRoot -> setQuotaForNewUser(newUserQuotaRoot, quotas)
+ .then(dispatchNewEventQuota(newUserQuotaRoot, newUsername))));
+ }
+
+ private Mono<Void> setQuotaForNewUser(QuotaRoot quotaRoot, QuotaManager.Quotas quotas) {
+ return setMaxQuota(quotaRoot, quotas)
+ .then(setCurrentQuota(quotaRoot, quotas));
+ }
+
+ private Mono<Void> setMaxQuota(QuotaRoot quotaRoot, QuotaManager.Quotas quotas) {
+ return Mono.zip(setMaxMessagesQuota(quotaRoot, quotas).thenReturn(quotaRoot),
+ setMaxStorageQuota(quotaRoot, quotas).thenReturn(quotaRoot))
+ .then();
+ }
+
+ private Mono<Void> setMaxStorageQuota(QuotaRoot quotaRoot, QuotaManager.Quotas quotas) {
+ return Mono.justOrEmpty(Optional.ofNullable(quotas.getStorageQuota().getLimitByScope()
+ .get(Quota.Scope.User)))
+ .flatMap(quotaSizeLimit -> Mono.from(maxQuotaManager.setMaxStorageReactive(quotaRoot, quotaSizeLimit)));
+ }
+
+ private Mono<Void> setMaxMessagesQuota(QuotaRoot quotaRoot, QuotaManager.Quotas quotas) {
+ return Mono.justOrEmpty(Optional.ofNullable(quotas.getMessageQuota().getLimitByScope()
+ .get(Quota.Scope.User)))
+ .flatMap(quotaCountLimit -> Mono.from(maxQuotaManager.setMaxMessageReactive(quotaRoot, quotaCountLimit)));
+ }
+
+ private Mono<Void> setCurrentQuota(QuotaRoot quotaRoot, QuotaManager.Quotas quotas) {
+ return Mono.from(currentQuotaManager.setCurrentQuotas(QuotaOperation.from(quotaRoot,
+ new CurrentQuotas(quotas.getMessageQuota().getUsed(), quotas.getStorageQuota().getUsed()))));
+ }
+
+ private Mono<Void> dispatchNewEventQuota(QuotaRoot quotaRoot, Username username) {
+ return Mono.from(quotaManager.getQuotasReactive(quotaRoot))
+ .flatMap(quotas -> eventBus.dispatch(
+ EventFactory.quotaUpdated()
+ .randomEventId()
+ .user(username)
+ .quotaRoot(quotaRoot)
+ .quotaCount(quotas.getMessageQuota())
+ .quotaSize(quotas.getStorageQuota())
+ .instant(Instant.now())
+ .build(),
+ NO_REGISTRATION_KEYS));
+ }
+}
diff --git a/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/QuotaUsernameChangeTaskStepTest.java b/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/QuotaUsernameChangeTaskStepTest.java
new file mode 100644
index 0000000000..5ea415be8c
--- /dev/null
+++ b/server/container/mailbox-adapter/src/test/java/org/apache/james/adapter/mailbox/QuotaUsernameChangeTaskStepTest.java
@@ -0,0 +1,252 @@
+/****************************************************************
+ * 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.adapter.mailbox;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.SoftAssertions.assertSoftly;
+import static org.awaitility.Durations.TEN_SECONDS;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.james.core.Username;
+import org.apache.james.core.quota.QuotaCountLimit;
+import org.apache.james.core.quota.QuotaCountUsage;
+import org.apache.james.core.quota.QuotaSizeLimit;
+import org.apache.james.core.quota.QuotaSizeUsage;
+import org.apache.james.events.Event;
+import org.apache.james.events.EventBus;
+import org.apache.james.events.EventBusTestFixture;
+import org.apache.james.events.EventListener;
+import org.apache.james.events.Group;
+import org.apache.james.mailbox.events.GenericGroup;
+import org.apache.james.mailbox.events.MailboxEvents;
+import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
+import org.apache.james.mailbox.model.CurrentQuotas;
+import org.apache.james.mailbox.model.Quota;
+import org.apache.james.mailbox.model.QuotaOperation;
+import org.apache.james.mailbox.model.QuotaRoot;
+import org.apache.james.mailbox.quota.CurrentQuotaManager;
+import org.apache.james.mailbox.quota.MaxQuotaManager;
+import org.apache.james.mailbox.quota.QuotaManager;
+import org.apache.james.mailbox.quota.UserQuotaRootResolver;
+import org.apache.james.mailbox.store.event.EventFactory;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.reactivestreams.Publisher;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
+
+import reactor.core.publisher.Mono;
+
+class QuotaUsernameChangeTaskStepTest {
+ private static final Username ALICE = Username.of("alice");
+ private static final Username BOB = Username.of("bob");
+
+ private QuotaUsernameChangeTaskStep testee;
+ private QuotaManager quotaManager;
+ private CurrentQuotaManager currentQuotaManager;
+ private UserQuotaRootResolver quotaRootResolver;
+ private MaxQuotaManager maxQuotaManager;
+
+ private ArrayList<Event> eventStore;
+
+ @BeforeEach
+ void setUp() {
+ InMemoryIntegrationResources resources = InMemoryIntegrationResources.defaultResources();
+ quotaManager = resources.getQuotaManager();
+ currentQuotaManager = resources.getCurrentQuotaManager();
+ quotaRootResolver = resources.getDefaultUserQuotaRootResolver();
+ maxQuotaManager = resources.getMaxQuotaManager();
+
+ eventStore = new ArrayList<>();
+
+ EventBus eventBus = resources.getEventBus();
+
+ eventBus.register(new EventListener.GroupEventListener() {
+ @Override
+ public Group getDefaultGroup() {
+ return new GenericGroup("test");
+ }
+
+ @Override
+ public void event(Event event) {
+ eventStore.add(event);
+ }
+ });
+
+ testee = new QuotaUsernameChangeTaskStep(
+ quotaManager,
+ currentQuotaManager,
+ quotaRootResolver,
+ maxQuotaManager,
+ eventBus);
+ }
+
+ @Test
+ void shouldMigrateQuotas() throws Exception {
+ QuotaRoot bobQuotaRoot = quotaRootResolver.forUser(BOB);
+ maxQuotaManager.setMaxMessage(bobQuotaRoot, QuotaCountLimit.count(50));
+ maxQuotaManager.setMaxStorage(bobQuotaRoot, QuotaSizeLimit.size(100));
+ Mono.from(currentQuotaManager.setCurrentQuotas(QuotaOperation.from(bobQuotaRoot, new CurrentQuotas(
+ QuotaCountUsage.count(5), QuotaSizeUsage.size(10)
+ )))).block();
+
+ Mono.from(testee.changeUsername(BOB, ALICE)).block();
+
+ QuotaManager.Quotas aliceQuotas = quotaManager.getQuotas(quotaRootResolver.forUser(ALICE));
+
+ assertSoftly(softly -> {
+ softly.assertThat(aliceQuotas.getMessageQuota())
+ .isEqualTo(Quota.<QuotaCountLimit, QuotaCountUsage>builder().used(QuotaCountUsage.count(5)).computedLimit(QuotaCountLimit.count(50)).build());
+
+ softly.assertThat(aliceQuotas.getStorageQuota())
+ .isEqualTo(Quota.<QuotaSizeLimit, QuotaSizeUsage>builder().used(QuotaSizeUsage.size(10)).computedLimit(QuotaSizeLimit.size(100)).build());
+ });
+ }
+
+ @Test
+ void migrateShouldNotThrowWhenNoQuotas() throws Exception {
+ assertThatCode(() -> Mono.from(testee.changeUsername(BOB, ALICE)).block())
+ .doesNotThrowAnyException();
+ QuotaManager.Quotas aliceQuotas = quotaManager.getQuotas(quotaRootResolver.forUser(ALICE));
+ assertSoftly(softly -> {
+ softly.assertThat(aliceQuotas.getMessageQuota())
+ .isEqualTo(Quota.<QuotaCountLimit, QuotaCountUsage>builder().used(QuotaCountUsage.count(0)).computedLimit(QuotaCountLimit.unlimited()).build());
+
+ softly.assertThat(aliceQuotas.getStorageQuota())
+ .isEqualTo(Quota.<QuotaSizeLimit, QuotaSizeUsage>builder().used(QuotaSizeUsage.size(0)).computedLimit(QuotaSizeLimit.unlimited()).build());
+ });
+ }
+
+ @Test
+ void migrateShouldSucceedWhenUnlimitedQuotas() throws Exception {
+ QuotaRoot bobQuotaRoot = quotaRootResolver.forUser(BOB);
+ maxQuotaManager.setMaxMessage(bobQuotaRoot, QuotaCountLimit.unlimited());
+ maxQuotaManager.setMaxStorage(bobQuotaRoot, QuotaSizeLimit.unlimited());
+ Mono.from(currentQuotaManager.setCurrentQuotas(QuotaOperation.from(bobQuotaRoot, new CurrentQuotas(
+ QuotaCountUsage.count(5), QuotaSizeUsage.size(10)
+ )))).block();
+
+ Mono.from(testee.changeUsername(BOB, ALICE)).block();
+
+ QuotaManager.Quotas aliceQuotas = quotaManager.getQuotas(quotaRootResolver.forUser(ALICE));
+ assertSoftly(softly -> {
+ softly.assertThat(aliceQuotas.getMessageQuota())
+ .isEqualTo(Quota.<QuotaCountLimit, QuotaCountUsage>builder().used(QuotaCountUsage.count(5)).computedLimit(QuotaCountLimit.unlimited()).build());
+
+ softly.assertThat(aliceQuotas.getStorageQuota())
+ .isEqualTo(Quota.<QuotaSizeLimit, QuotaSizeUsage>builder().used(QuotaSizeUsage.size(10)).computedLimit(QuotaSizeLimit.unlimited()).build());
+ });
+ }
+
+ @Test
+ void migrateShouldSucceedWhenOnlyMessagesQuota() throws Exception {
+ QuotaRoot bobQuotaRoot = quotaRootResolver.forUser(BOB);
+ maxQuotaManager.setMaxMessage(bobQuotaRoot, QuotaCountLimit.count(10));
+ Mono.from(testee.changeUsername(BOB, ALICE)).block();
+
+ QuotaManager.Quotas aliceQuotas = quotaManager.getQuotas(quotaRootResolver.forUser(ALICE));
+ assertSoftly(softly -> {
+ softly.assertThat(aliceQuotas.getMessageQuota())
+ .isEqualTo(Quota.<QuotaCountLimit, QuotaCountUsage>builder().used(QuotaCountUsage.count(0)).computedLimit(QuotaCountLimit.count(10)).build());
+
+ softly.assertThat(aliceQuotas.getStorageQuota())
+ .isEqualTo(Quota.<QuotaSizeLimit, QuotaSizeUsage>builder().used(QuotaSizeUsage.size(0)).computedLimit(QuotaSizeLimit.unlimited()).build());
+ });
+ }
+
+ @Test
+ void migrateShouldSucceedWhenOnlyStorageQuota() throws Exception {
+ QuotaRoot bobQuotaRoot = quotaRootResolver.forUser(BOB);
+ maxQuotaManager.setMaxStorage(bobQuotaRoot, QuotaSizeLimit.size(10));
+ Mono.from(testee.changeUsername(BOB, ALICE)).block();
+
+ QuotaManager.Quotas aliceQuotas = quotaManager.getQuotas(quotaRootResolver.forUser(ALICE));
+ assertSoftly(softly -> {
+ softly.assertThat(aliceQuotas.getMessageQuota())
+ .isEqualTo(Quota.<QuotaCountLimit, QuotaCountUsage>builder().used(QuotaCountUsage.count(0)).computedLimit(QuotaCountLimit.unlimited()).build());
+
+ softly.assertThat(aliceQuotas.getStorageQuota())
+ .isEqualTo(Quota.<QuotaSizeLimit, QuotaSizeUsage>builder().used(QuotaSizeUsage.size(0)).computedLimit(QuotaSizeLimit.size(10)).build());
+ });
+ }
+
+ @Test
+ void migrateShouldSucceedWhenAliceAlreadyQuotas() throws Exception {
+ QuotaRoot bobQuotaRoot = quotaRootResolver.forUser(BOB);
+ maxQuotaManager.setMaxMessage(bobQuotaRoot, QuotaCountLimit.count(50));
+ maxQuotaManager.setMaxStorage(bobQuotaRoot, QuotaSizeLimit.size(100));
+ Mono.from(currentQuotaManager.setCurrentQuotas(QuotaOperation.from(bobQuotaRoot, new CurrentQuotas(
+ QuotaCountUsage.count(5), QuotaSizeUsage.size(10)
+ )))).block();
+
+ QuotaRoot aliceQuotaRoot = quotaRootResolver.forUser(ALICE);
+ maxQuotaManager.setMaxMessage(aliceQuotaRoot, QuotaCountLimit.count(55));
+ maxQuotaManager.setMaxStorage(aliceQuotaRoot, QuotaSizeLimit.size(150));
+ Mono.from(currentQuotaManager.setCurrentQuotas(QuotaOperation.from(aliceQuotaRoot, new CurrentQuotas(
+ QuotaCountUsage.count(7), QuotaSizeUsage.size(8)
+ )))).block();
+
+ Mono.from(testee.changeUsername(BOB, ALICE)).block();
+
+ QuotaManager.Quotas aliceQuotas = quotaManager.getQuotas(quotaRootResolver.forUser(ALICE));
+
+ assertSoftly(softly -> {
+ softly.assertThat(aliceQuotas.getMessageQuota())
+ .isEqualTo(Quota.<QuotaCountLimit, QuotaCountUsage>builder().used(QuotaCountUsage.count(5)).computedLimit(QuotaCountLimit.count(50)).build());
+
+ softly.assertThat(aliceQuotas.getStorageQuota())
+ .isEqualTo(Quota.<QuotaSizeLimit, QuotaSizeUsage>builder().used(QuotaSizeUsage.size(10)).computedLimit(QuotaSizeLimit.size(100)).build());
+ });
+ }
+
+ @Test
+ void migrateShouldDispatchQuotaUpdateEvent() throws Exception {
+ QuotaRoot bobQuotaRoot = quotaRootResolver.forUser(BOB);
+ maxQuotaManager.setMaxMessage(bobQuotaRoot, QuotaCountLimit.count(50));
+ maxQuotaManager.setMaxStorage(bobQuotaRoot, QuotaSizeLimit.size(100));
+ Mono.from(currentQuotaManager.setCurrentQuotas(QuotaOperation.from(bobQuotaRoot, new CurrentQuotas(
+ QuotaCountUsage.count(5), QuotaSizeUsage.size(10)
+ )))).block();
+
+ Mono.from(testee.changeUsername(BOB, ALICE)).block();
+
+
+ Awaitility.await()
+ .atMost(TEN_SECONDS)
+ .untilAsserted(() -> assertThat(eventStore.size()).isEqualTo(1));
+
+ MailboxEvents.QuotaUsageUpdatedEvent quotaUsageUpdatedEvent = (MailboxEvents.QuotaUsageUpdatedEvent) eventStore.get(0);
+
+ assertSoftly(softly -> {
+ softly.assertThat(quotaUsageUpdatedEvent.getCountQuota())
+ .isEqualTo(Quota.<QuotaCountLimit, QuotaCountUsage>builder().used(QuotaCountUsage.count(5)).computedLimit(QuotaCountLimit.count(50)).build());
+ softly.assertThat(quotaUsageUpdatedEvent.getSizeQuota())
+ .isEqualTo(Quota.<QuotaSizeLimit, QuotaSizeUsage>builder().used(QuotaSizeUsage.size(10)).computedLimit(QuotaSizeLimit.size(100)).build());
+ softly.assertThat(quotaUsageUpdatedEvent.getUsername())
+ .isEqualTo(ALICE);
+ softly.assertThat(quotaUsageUpdatedEvent.getQuotaRoot())
+ .isEqualTo(quotaRootResolver.forUser(ALICE));
+ });
+ }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java
index 8fb644729a..bd0ea5e116 100644
--- a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/memory/MemoryUsernameChangeIntegrationTest.java
@@ -29,29 +29,42 @@ import static org.apache.james.jmap.JMAPTestingConstants.CEDRIC_PASSWORD;
import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN;
import static org.apache.james.jmap.JMAPTestingConstants.jmapRequestSpecBuilder;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.SoftAssertions.assertSoftly;
+import static org.awaitility.Durations.TEN_SECONDS;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasSize;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
+import org.apache.http.HttpStatus;
import org.apache.james.GuiceJamesServer;
import org.apache.james.JamesServerBuilder;
import org.apache.james.JamesServerExtension;
import org.apache.james.MemoryJamesConfiguration;
import org.apache.james.MemoryJamesServerMain;
import org.apache.james.core.Username;
+import org.apache.james.core.quota.QuotaCountLimit;
+import org.apache.james.core.quota.QuotaCountUsage;
+import org.apache.james.core.quota.QuotaSizeLimit;
+import org.apache.james.core.quota.QuotaSizeUsage;
import org.apache.james.jmap.api.filtering.FilteringManagement;
import org.apache.james.jmap.api.filtering.Rule;
import org.apache.james.jmap.api.filtering.Rules;
import org.apache.james.jmap.api.filtering.Version;
import org.apache.james.jmap.draft.JmapGuiceProbe;
+import org.apache.james.mailbox.MessageManager;
import org.apache.james.mailbox.model.MailboxACL;
import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.Quota;
+import org.apache.james.mailbox.model.QuotaRoot;
+import org.apache.james.mime4j.dom.Message;
import org.apache.james.modules.ACLProbeImpl;
import org.apache.james.modules.MailboxProbeImpl;
+import org.apache.james.modules.QuotaProbesImpl;
import org.apache.james.modules.TestJMAPServerModule;
import org.apache.james.probe.DataProbe;
import org.apache.james.util.Port;
@@ -59,10 +72,13 @@ import org.apache.james.utils.DataProbeImpl;
import org.apache.james.utils.GuiceProbe;
import org.apache.james.utils.WebAdminGuiceProbe;
import org.apache.james.webadmin.WebAdminUtils;
+import org.awaitility.Awaitility;
+import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import com.github.fge.lambdas.Throwing;
import com.google.inject.multibindings.Multibinder;
import io.restassured.RestAssured;
@@ -228,4 +244,53 @@ class MemoryUsernameChangeIntegrationTest {
assertThat(filterProbe.listRulesForUser(ALICE).getRules())
.isEmpty();
}
+
+ @Test
+ void shouldAdaptQuotas(GuiceJamesServer server) throws Exception {
+ server.getProbe(MailboxProbeImpl.class).createMailbox(MailboxPath.inbox(BOB));
+
+ QuotaProbesImpl quotaProbes = server.getProbe(QuotaProbesImpl.class);
+ QuotaRoot bobQuotaRoot = quotaProbes.getQuotaRoot(MailboxPath.inbox(BOB));
+
+ quotaProbes.setMaxMessageCount(bobQuotaRoot, QuotaCountLimit.count(50));
+ quotaProbes.setMaxStorage(bobQuotaRoot, QuotaSizeLimit.size(1000));
+
+ server.getProbe(MailboxProbeImpl.class)
+ .appendMessage(BOB.asString(), MailboxPath.inbox(BOB),
+ MessageManager.AppendCommand.from(Message.Builder.of()
+ .setSubject("test")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build()));
+
+ Awaitility.await()
+ .atMost(TEN_SECONDS)
+ .untilAsserted(() -> {
+ assertThat(quotaProbes.getMessageCountQuota(bobQuotaRoot)
+ .getUsed().asLong())
+ .isEqualTo(1L);
+ assertThat(quotaProbes.getStorageQuota(bobQuotaRoot)
+ .getUsed().asLong())
+ .isEqualTo(85L);
+ });
+
+ String taskId = webAdminApi
+ .queryParam("action", "rename")
+ .post("/users/" + BOB.asString() + "/rename/" + ALICE.asString())
+ .jsonPath()
+ .get("taskId");
+
+ webAdminApi.get("/tasks/" + taskId + "/await")
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .body("additionalInformation.status.QuotaUsernameChangeTaskStep", Matchers.is("DONE"));
+
+ QuotaRoot aliceQuotaRoot = quotaProbes.getQuotaRoot(MailboxPath.inbox(ALICE));
+
+ assertSoftly(softly -> {
+ softly.assertThat(Throwing.supplier(() -> quotaProbes.getMessageCountQuota(aliceQuotaRoot)).get())
+ .isEqualTo(Quota.<QuotaCountLimit, QuotaCountUsage>builder().used(QuotaCountUsage.count(1)).computedLimit(QuotaCountLimit.count(50)).build());
+ softly.assertThat(Throwing.supplier(() -> quotaProbes.getStorageQuota(aliceQuotaRoot)).get())
+ .isEqualTo(Quota.<QuotaSizeLimit, QuotaSizeUsage>builder().used(QuotaSizeUsage.size(85)).computedLimit(QuotaSizeLimit.size(1000)).build());
+ });
+ }
}
diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index a3edcf7f75..14eadcc942 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -490,7 +490,7 @@ Implemented migration steps are:
- `MailboxUsernameChangeTaskStep`: migrates mailboxes belonging to the old user to the account of the new user. It also
migrates user's mailbox subscriptions.
- `ACLUsernameChangeTaskStep`: migrates ACLs on mailboxes the migrated user has access to and updates subscriptions accordingly.
-
+ - `QuotaUsernameChangeTaskStep`: migrates quotas user from old user to new user.
Response codes:
* 201: Success. Corresponding task id is returned.
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org