Delete any leftover usernames in the accounts db
The account username field should not currently be populated
This commit is contained in:
		
							parent
							
								
									4d78437fe4
								
							
						
					
					
						commit
						a44c18e9b7
					
				| 
						 | 
					@ -185,6 +185,7 @@ import org.whispersystems.textsecuregcm.storage.MessagesCache;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb;
 | 
					import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
 | 
					import org.whispersystems.textsecuregcm.storage.MessagesManager;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.NonNormalizedAccountCrawlerListener;
 | 
					import org.whispersystems.textsecuregcm.storage.NonNormalizedAccountCrawlerListener;
 | 
				
			||||||
 | 
					import org.whispersystems.textsecuregcm.storage.UsernameCleaner;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
 | 
					import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.Profiles;
 | 
					import org.whispersystems.textsecuregcm.storage.Profiles;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
 | 
					import org.whispersystems.textsecuregcm.storage.ProfilesManager;
 | 
				
			||||||
| 
						 | 
					@ -531,6 +532,17 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
 | 
				
			||||||
        config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs()
 | 
					        config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs()
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    AccountDatabaseCrawlerCache usernameCleanerAccountDatabaseCrawlerCache =
 | 
				
			||||||
 | 
					        new AccountDatabaseCrawlerCache(cacheCluster, AccountDatabaseCrawlerCache.USERNAME_CLEANER_PREFIX);
 | 
				
			||||||
 | 
					    AccountDatabaseCrawler usernameCleanerAccountDatabaseCrawler = new AccountDatabaseCrawler("username cleaner crawler",
 | 
				
			||||||
 | 
					        accountsManager,
 | 
				
			||||||
 | 
					        usernameCleanerAccountDatabaseCrawlerCache,
 | 
				
			||||||
 | 
					        List.of(new UsernameCleaner(accountsManager)),
 | 
				
			||||||
 | 
					        config.getAccountDatabaseCrawlerConfiguration().getChunkSize(),
 | 
				
			||||||
 | 
					        config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO listeners must be ordered so that ones that directly update accounts come last, so that read-only ones are not working with stale data
 | 
					    // TODO listeners must be ordered so that ones that directly update accounts come last, so that read-only ones are not working with stale data
 | 
				
			||||||
    final List<AccountDatabaseCrawlerListener> accountDatabaseCrawlerListeners = List.of(
 | 
					    final List<AccountDatabaseCrawlerListener> accountDatabaseCrawlerListeners = List.of(
 | 
				
			||||||
        new NonNormalizedAccountCrawlerListener(accountsManager, metricsCluster),
 | 
					        new NonNormalizedAccountCrawlerListener(accountsManager, metricsCluster),
 | 
				
			||||||
| 
						 | 
					@ -560,6 +572,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
 | 
				
			||||||
    environment.lifecycle().manage(accountDatabaseCrawler);
 | 
					    environment.lifecycle().manage(accountDatabaseCrawler);
 | 
				
			||||||
    environment.lifecycle().manage(directoryReconciliationAccountDatabaseCrawler);
 | 
					    environment.lifecycle().manage(directoryReconciliationAccountDatabaseCrawler);
 | 
				
			||||||
    environment.lifecycle().manage(accountCleanerAccountDatabaseCrawler);
 | 
					    environment.lifecycle().manage(accountCleanerAccountDatabaseCrawler);
 | 
				
			||||||
 | 
					    environment.lifecycle().manage(usernameCleanerAccountDatabaseCrawler);
 | 
				
			||||||
    environment.lifecycle().manage(deletedAccountsTableCrawler);
 | 
					    environment.lifecycle().manage(deletedAccountsTableCrawler);
 | 
				
			||||||
    environment.lifecycle().manage(messagesCache);
 | 
					    environment.lifecycle().manage(messagesCache);
 | 
				
			||||||
    environment.lifecycle().manage(messagePersister);
 | 
					    environment.lifecycle().manage(messagePersister);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,7 @@ public class AccountDatabaseCrawlerCache {
 | 
				
			||||||
  public static final String GENERAL_PURPOSE_PREFIX = "";
 | 
					  public static final String GENERAL_PURPOSE_PREFIX = "";
 | 
				
			||||||
  public static final String DIRECTORY_RECONCILER_PREFIX = "directory-reconciler";
 | 
					  public static final String DIRECTORY_RECONCILER_PREFIX = "directory-reconciler";
 | 
				
			||||||
  public static final String ACCOUNT_CLEANER_PREFIX = "account-cleaner";
 | 
					  public static final String ACCOUNT_CLEANER_PREFIX = "account-cleaner";
 | 
				
			||||||
 | 
					  public static final String USERNAME_CLEANER_PREFIX = "username-cleaner";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private static final String ACTIVE_WORKER_KEY = "account_database_crawler_cache_active_worker";
 | 
					  private static final String ACTIVE_WORKER_KEY = "account_database_crawler_cache_active_worker";
 | 
				
			||||||
  private static final String LAST_UUID_KEY = "account_database_crawler_cache_last_uuid";
 | 
					  private static final String LAST_UUID_KEY = "account_database_crawler_cache_last_uuid";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2022 Signal Messenger, LLC
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package org.whispersystems.textsecuregcm.storage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.micrometer.core.instrument.Metrics;
 | 
				
			||||||
 | 
					import io.micrometer.core.instrument.Tags;
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class UsernameCleaner extends AccountDatabaseCrawlerListener {
 | 
				
			||||||
 | 
					  private static final String DELETED_USERNAME_COUNTER = name(UsernameCleaner.class, "deletedUsernames");
 | 
				
			||||||
 | 
					  private static final Logger logger = LoggerFactory.getLogger(UsernameCleaner.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private final AccountsManager accountsManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public UsernameCleaner(AccountsManager accountsManager) {
 | 
				
			||||||
 | 
					    this.accountsManager = accountsManager;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Override
 | 
				
			||||||
 | 
					  public void onCrawlStart() {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Override
 | 
				
			||||||
 | 
					  protected void onCrawlChunk(final Optional<UUID> fromUuid, final List<Account> chunkAccounts) {
 | 
				
			||||||
 | 
					    for (Account account : chunkAccounts) {
 | 
				
			||||||
 | 
					      if (account.getUsername().isPresent()) {
 | 
				
			||||||
 | 
					        logger.info("Deleting username present for account {}", account.getUuid());
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          this.accountsManager.clearUsername(account);
 | 
				
			||||||
 | 
					          Metrics.counter(DELETED_USERNAME_COUNTER, Tags.of("outcome", "success")).increment();
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					          logger.warn("Failed to clear username on account {}", account.getUuid(), e);
 | 
				
			||||||
 | 
					          Metrics.counter(DELETED_USERNAME_COUNTER, Tags.of("outcome", "error")).increment();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Override
 | 
				
			||||||
 | 
					  public void onCrawlEnd(final Optional<UUID> fromUuid) {
 | 
				
			||||||
 | 
					    logger.info("Username cleaner crawl completed");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2022 Signal Messenger, LLC
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package org.whispersystems.textsecuregcm.storage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.BeforeEach;
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UsernameCleanerTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private final AccountsManager accountsManager = mock(AccountsManager.class);
 | 
				
			||||||
 | 
					  private final Account hasUsername = mock(Account.class);
 | 
				
			||||||
 | 
					  private final Account noUsername = mock(Account.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @BeforeEach
 | 
				
			||||||
 | 
					  void setup() {
 | 
				
			||||||
 | 
					    when(hasUsername.getUsername()).thenReturn(Optional.of("n00bkiller"));
 | 
				
			||||||
 | 
					    when(noUsername.getUsername()).thenReturn(Optional.empty());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  void testAccounts() throws AccountDatabaseCrawlerRestartException {
 | 
				
			||||||
 | 
					    UsernameCleaner accountCleaner = new UsernameCleaner(accountsManager);
 | 
				
			||||||
 | 
					    accountCleaner.onCrawlStart();
 | 
				
			||||||
 | 
					    accountCleaner.timeAndProcessCrawlChunk(Optional.empty(), Arrays.asList(hasUsername, noUsername));
 | 
				
			||||||
 | 
					    accountCleaner.onCrawlEnd(Optional.empty());
 | 
				
			||||||
 | 
					    verify(accountsManager).clearUsername(hasUsername);
 | 
				
			||||||
 | 
					    verify(accountsManager, never()).clearUsername(noUsername);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue