Collapse the feature flag system into the dynamic config system.
This commit is contained in:
		
							parent
							
								
									d6319aeb92
								
							
						
					
					
						commit
						ff448950ed
					
				| 
						 | 
					@ -139,8 +139,6 @@ import org.whispersystems.textsecuregcm.storage.DirectoryReconciler;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.DirectoryReconciliationClient;
 | 
					import org.whispersystems.textsecuregcm.storage.DirectoryReconciliationClient;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
 | 
					import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
 | 
					import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FeatureFlags;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.KeysDynamoDb;
 | 
					import org.whispersystems.textsecuregcm.storage.KeysDynamoDb;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.MessagePersister;
 | 
					import org.whispersystems.textsecuregcm.storage.MessagePersister;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.MessagesCache;
 | 
					import org.whispersystems.textsecuregcm.storage.MessagesCache;
 | 
				
			||||||
| 
						 | 
					@ -166,13 +164,10 @@ import org.whispersystems.textsecuregcm.websocket.DeadLetterHandler;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener;
 | 
					import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator;
 | 
					import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.CertificateCommand;
 | 
					import org.whispersystems.textsecuregcm.workers.CertificateCommand;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.DeleteFeatureFlagTask;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.DeleteUserCommand;
 | 
					import org.whispersystems.textsecuregcm.workers.DeleteUserCommand;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.GetRedisCommandStatsCommand;
 | 
					import org.whispersystems.textsecuregcm.workers.GetRedisCommandStatsCommand;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.GetRedisSlowlogCommand;
 | 
					import org.whispersystems.textsecuregcm.workers.GetRedisSlowlogCommand;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.ListFeatureFlagsTask;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.SetCrawlerAccelerationTask;
 | 
					import org.whispersystems.textsecuregcm.workers.SetCrawlerAccelerationTask;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.SetFeatureFlagTask;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.SetRequestLoggingEnabledTask;
 | 
					import org.whispersystems.textsecuregcm.workers.SetRequestLoggingEnabledTask;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.VacuumCommand;
 | 
					import org.whispersystems.textsecuregcm.workers.VacuumCommand;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.workers.ZkParamsCommand;
 | 
					import org.whispersystems.textsecuregcm.workers.ZkParamsCommand;
 | 
				
			||||||
| 
						 | 
					@ -284,7 +279,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
 | 
				
			||||||
    MessagesDynamoDb  messagesDynamoDb  = new MessagesDynamoDb(messageDynamoDb, config.getMessageDynamoDbConfiguration().getTableName(), config.getMessageDynamoDbConfiguration().getTimeToLive());
 | 
					    MessagesDynamoDb  messagesDynamoDb  = new MessagesDynamoDb(messageDynamoDb, config.getMessageDynamoDbConfiguration().getTableName(), config.getMessageDynamoDbConfiguration().getTimeToLive());
 | 
				
			||||||
    AbusiveHostRules  abusiveHostRules  = new AbusiveHostRules(abuseDatabase);
 | 
					    AbusiveHostRules  abusiveHostRules  = new AbusiveHostRules(abuseDatabase);
 | 
				
			||||||
    RemoteConfigs     remoteConfigs     = new RemoteConfigs(accountDatabase);
 | 
					    RemoteConfigs     remoteConfigs     = new RemoteConfigs(accountDatabase);
 | 
				
			||||||
    FeatureFlags      featureFlags      = new FeatureFlags(accountDatabase);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RedisClientFactory  pubSubClientFactory = new RedisClientFactory("pubsub_cache", config.getPubsubCacheConfiguration().getUrl(), config.getPubsubCacheConfiguration().getReplicaUrls(), config.getPubsubCacheConfiguration().getCircuitBreakerConfiguration());
 | 
					    RedisClientFactory  pubSubClientFactory = new RedisClientFactory("pubsub_cache", config.getPubsubCacheConfiguration().getUrl(), config.getPubsubCacheConfiguration().getReplicaUrls(), config.getPubsubCacheConfiguration().getCircuitBreakerConfiguration());
 | 
				
			||||||
    ReplicatedJedisPool pubsubClient        = pubSubClientFactory.getRedisClientPool();
 | 
					    ReplicatedJedisPool pubsubClient        = pubSubClientFactory.getRedisClientPool();
 | 
				
			||||||
| 
						 | 
					@ -330,7 +324,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
 | 
				
			||||||
    MessagesManager            messagesManager            = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager);
 | 
					    MessagesManager            messagesManager            = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager);
 | 
				
			||||||
    AccountsManager            accountsManager            = new AccountsManager(accounts, cacheCluster, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager);
 | 
					    AccountsManager            accountsManager            = new AccountsManager(accounts, cacheCluster, directoryQueue, keysDynamoDb, messagesManager, usernamesManager, profilesManager);
 | 
				
			||||||
    RemoteConfigsManager       remoteConfigsManager       = new RemoteConfigsManager(remoteConfigs);
 | 
					    RemoteConfigsManager       remoteConfigsManager       = new RemoteConfigsManager(remoteConfigs);
 | 
				
			||||||
    FeatureFlagsManager        featureFlagsManager        = new FeatureFlagsManager(featureFlags, recurringJobExecutor);
 | 
					 | 
				
			||||||
    DeadLetterHandler          deadLetterHandler          = new DeadLetterHandler(accountsManager, messagesManager);
 | 
					    DeadLetterHandler          deadLetterHandler          = new DeadLetterHandler(accountsManager, messagesManager);
 | 
				
			||||||
    DispatchManager            dispatchManager            = new DispatchManager(pubSubClientFactory, Optional.of(deadLetterHandler));
 | 
					    DispatchManager            dispatchManager            = new DispatchManager(pubSubClientFactory, Optional.of(deadLetterHandler));
 | 
				
			||||||
    PubSubManager              pubSubManager              = new PubSubManager(pubsubClient, dispatchManager);
 | 
					    PubSubManager              pubSubManager              = new PubSubManager(pubsubClient, dispatchManager);
 | 
				
			||||||
| 
						 | 
					@ -358,7 +351,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
 | 
				
			||||||
    TurnTokenGenerator       turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
 | 
					    TurnTokenGenerator       turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
 | 
				
			||||||
    RecaptchaClient          recaptchaClient    = new RecaptchaClient(config.getRecaptchaConfiguration().getSecret());
 | 
					    RecaptchaClient          recaptchaClient    = new RecaptchaClient(config.getRecaptchaConfiguration().getSecret());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MessagePersister messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager, featureFlagsManager, Duration.ofMinutes(config.getMessageCacheConfiguration().getPersistDelayMinutes()));
 | 
					    MessagePersister messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager, dynamicConfigurationManager, Duration.ofMinutes(config.getMessageCacheConfiguration().getPersistDelayMinutes()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final List<AccountDatabaseCrawlerListener> accountDatabaseCrawlerListeners = new ArrayList<>();
 | 
					    final List<AccountDatabaseCrawlerListener> accountDatabaseCrawlerListeners = new ArrayList<>();
 | 
				
			||||||
    accountDatabaseCrawlerListeners.add(new PushFeedbackProcessor(accountsManager, directoryQueue));
 | 
					    accountDatabaseCrawlerListeners.add(new PushFeedbackProcessor(accountsManager, directoryQueue));
 | 
				
			||||||
| 
						 | 
					@ -383,7 +376,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
 | 
				
			||||||
    environment.lifecycle().manage(messagesCache);
 | 
					    environment.lifecycle().manage(messagesCache);
 | 
				
			||||||
    environment.lifecycle().manage(messagePersister);
 | 
					    environment.lifecycle().manage(messagePersister);
 | 
				
			||||||
    environment.lifecycle().manage(clientPresenceManager);
 | 
					    environment.lifecycle().manage(clientPresenceManager);
 | 
				
			||||||
    environment.lifecycle().manage(featureFlagsManager);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    AWSCredentials         credentials               = new BasicAWSCredentials(config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret());
 | 
					    AWSCredentials         credentials               = new BasicAWSCredentials(config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret());
 | 
				
			||||||
    AWSCredentialsProvider credentialsProvider       = new AWSStaticCredentialsProvider(credentials);
 | 
					    AWSCredentialsProvider credentialsProvider       = new AWSStaticCredentialsProvider(credentials);
 | 
				
			||||||
| 
						 | 
					@ -472,9 +464,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    environment.admin().addTask(new SetRequestLoggingEnabledTask());
 | 
					    environment.admin().addTask(new SetRequestLoggingEnabledTask());
 | 
				
			||||||
    environment.admin().addTask(new SetCrawlerAccelerationTask(accountDatabaseCrawlerCache));
 | 
					    environment.admin().addTask(new SetCrawlerAccelerationTask(accountDatabaseCrawlerCache));
 | 
				
			||||||
    environment.admin().addTask(new ListFeatureFlagsTask(featureFlagsManager));
 | 
					 | 
				
			||||||
    environment.admin().addTask(new SetFeatureFlagTask(featureFlagsManager));
 | 
					 | 
				
			||||||
    environment.admin().addTask(new DeleteFeatureFlagTask(featureFlagsManager));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ import javax.validation.Valid;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.Optional;
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class DynamicConfiguration {
 | 
					public class DynamicConfiguration {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +22,9 @@ public class DynamicConfiguration {
 | 
				
			||||||
  @Valid
 | 
					  @Valid
 | 
				
			||||||
  private DynamicRemoteDeprecationConfiguration remoteDeprecation = new DynamicRemoteDeprecationConfiguration();
 | 
					  private DynamicRemoteDeprecationConfiguration remoteDeprecation = new DynamicRemoteDeprecationConfiguration();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @JsonProperty
 | 
				
			||||||
 | 
					  private Set<String> featureFlags = Collections.emptySet();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration(final String experimentName) {
 | 
					  public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration(final String experimentName) {
 | 
				
			||||||
    return Optional.ofNullable(experiments.get(experimentName));
 | 
					    return Optional.ofNullable(experiments.get(experimentName));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -32,4 +36,8 @@ public class DynamicConfiguration {
 | 
				
			||||||
  public DynamicRemoteDeprecationConfiguration getRemoteDeprecationConfiguration() {
 | 
					  public DynamicRemoteDeprecationConfiguration getRemoteDeprecationConfiguration() {
 | 
				
			||||||
    return remoteDeprecation;
 | 
					    return remoteDeprecation;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public Set<String> getActiveFeatureFlags() {
 | 
				
			||||||
 | 
					    return featureFlags;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,82 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2013-2020 Signal Messenger, LLC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.storage;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.codahale.metrics.MetricRegistry;
 | 
					 | 
				
			||||||
import com.codahale.metrics.SharedMetricRegistries;
 | 
					 | 
				
			||||||
import com.codahale.metrics.Timer;
 | 
					 | 
				
			||||||
import org.jdbi.v3.core.mapper.RowMapper;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.util.Constants;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.util.Pair;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static com.codahale.metrics.MetricRegistry.name;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The feature flag database is a persistent store of the state of all server-side feature flags. Feature flags are
 | 
					 | 
				
			||||||
 * identified by a human-readable name (e.g. "invert-nano-flappers") and are either active or inactive.
 | 
					 | 
				
			||||||
 * <p/>
 | 
					 | 
				
			||||||
 * The feature flag database provides the most up-to-date possible view of feature flags, but does so at the cost of
 | 
					 | 
				
			||||||
 * interacting with a remote data store. In nearly all cases, callers should prefer a cached, eventually-consistent
 | 
					 | 
				
			||||||
 * view of feature flags (see {@link FeatureFlagsManager}).
 | 
					 | 
				
			||||||
 * <p/>
 | 
					 | 
				
			||||||
 * When an operation requiring a feature flag has finished, callers should delete the feature flag to prevent
 | 
					 | 
				
			||||||
 * accumulation of non-functional flags.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class FeatureFlags {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final FaultTolerantDatabase database;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
 | 
					 | 
				
			||||||
    private final Timer getAllTimer             = metricRegistry.timer(name(getClass(), "getAll"));
 | 
					 | 
				
			||||||
    private final Timer updateTimer             = metricRegistry.timer(name(getClass(), "update"));
 | 
					 | 
				
			||||||
    private final Timer deleteTimer             = metricRegistry.timer(name(getClass(), "delete"));
 | 
					 | 
				
			||||||
    private final Timer vacuumTimer             = metricRegistry.timer(name(getClass(), "vacuum"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static final RowMapper<Pair<String, Boolean>> PAIR_ROW_MAPPER = (resultSet, statementContext) ->
 | 
					 | 
				
			||||||
            new Pair<>(resultSet.getString("flag"), resultSet.getBoolean("active"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public FeatureFlags(final FaultTolerantDatabase database) {
 | 
					 | 
				
			||||||
        this.database = database;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public Map<String, Boolean> getFeatureFlags() {
 | 
					 | 
				
			||||||
        try (final Timer.Context ignored = getAllTimer.time()) {
 | 
					 | 
				
			||||||
            return database.with(jdbi -> jdbi.withHandle(handle -> handle.createQuery("SELECT flag, active FROM feature_flags")
 | 
					 | 
				
			||||||
                                             .map(PAIR_ROW_MAPPER)
 | 
					 | 
				
			||||||
                                             .list()
 | 
					 | 
				
			||||||
                                             .stream()
 | 
					 | 
				
			||||||
                                             .collect(Collectors.toMap(Pair::first, Pair::second))));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void setFlag(final String featureFlag, final boolean active) {
 | 
					 | 
				
			||||||
        try (final Timer.Context ignored = updateTimer.time()) {
 | 
					 | 
				
			||||||
            database.use(jdbi -> jdbi.withHandle(handle -> handle.createUpdate("INSERT INTO feature_flags (flag, active) VALUES (:featureFlag, :active) ON CONFLICT (flag) DO UPDATE SET active = EXCLUDED.active")
 | 
					 | 
				
			||||||
                                     .bind("featureFlag", featureFlag)
 | 
					 | 
				
			||||||
                                     .bind("active", active)
 | 
					 | 
				
			||||||
                                     .execute()));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void deleteFlag(final String featureFlag) {
 | 
					 | 
				
			||||||
        try (final Timer.Context ignored = deleteTimer.time()) {
 | 
					 | 
				
			||||||
            database.use(jdbi -> jdbi.withHandle(handle -> handle.createUpdate("DELETE FROM feature_flags WHERE flag = :featureFlag")
 | 
					 | 
				
			||||||
                    .bind("featureFlag", featureFlag)
 | 
					 | 
				
			||||||
                    .execute()));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void vacuum() {
 | 
					 | 
				
			||||||
        try (final Timer.Context ignored = vacuumTimer.time()) {
 | 
					 | 
				
			||||||
            database.use(jdbi -> jdbi.useHandle(handle -> {
 | 
					 | 
				
			||||||
                handle.execute("VACUUM feature_flags");
 | 
					 | 
				
			||||||
            }));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,102 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2013-2020 Signal Messenger, LLC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.storage;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.google.common.annotations.VisibleForTesting;
 | 
					 | 
				
			||||||
import io.dropwizard.lifecycle.Managed;
 | 
					 | 
				
			||||||
import io.micrometer.core.instrument.Gauge;
 | 
					 | 
				
			||||||
import io.micrometer.core.instrument.Metrics;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.time.Duration;
 | 
					 | 
				
			||||||
import java.util.Collections;
 | 
					 | 
				
			||||||
import java.util.HashSet;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
import java.util.Set;
 | 
					 | 
				
			||||||
import java.util.concurrent.ScheduledExecutorService;
 | 
					 | 
				
			||||||
import java.util.concurrent.ScheduledFuture;
 | 
					 | 
				
			||||||
import java.util.concurrent.TimeUnit;
 | 
					 | 
				
			||||||
import java.util.concurrent.atomic.AtomicReference;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static com.codahale.metrics.MetricRegistry.name;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The feature flags manager provides a high-throughput, eventually-consistent view of feature flags. This is the main
 | 
					 | 
				
			||||||
 * channel through which callers should interact with feature flags.
 | 
					 | 
				
			||||||
 * <p/>
 | 
					 | 
				
			||||||
 * Feature flags are intended to provide temporary control over server-side features (i.e. for migrations or experiments
 | 
					 | 
				
			||||||
 * with new services). Each flag is identified by a human-readable name (e.g. "invert-nano-flappers") and is either
 | 
					 | 
				
			||||||
 * active or inactive. Flags (including flags that have not been set) are inactive by default.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class FeatureFlagsManager implements Managed {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final FeatureFlags                          featureFlagDatabase;
 | 
					 | 
				
			||||||
    private final ScheduledExecutorService              refreshExecutorService;
 | 
					 | 
				
			||||||
    private       ScheduledFuture<?>                    refreshFuture;
 | 
					 | 
				
			||||||
    private final AtomicReference<Map<String, Boolean>> featureFlags = new AtomicReference<>(Collections.emptyMap());
 | 
					 | 
				
			||||||
    private final Set<Gauge>                            gauges = new HashSet<>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static final String GAUGE_NAME    = "status";
 | 
					 | 
				
			||||||
    private static final String FLAG_TAG_NAME = "flag";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static final Duration REFRESH_INTERVAL = Duration.ofSeconds(30);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public FeatureFlagsManager(final FeatureFlags featureFlagDatabase, final ScheduledExecutorService refreshExecutorService) {
 | 
					 | 
				
			||||||
        this.featureFlagDatabase    = featureFlagDatabase;
 | 
					 | 
				
			||||||
        this.refreshExecutorService = refreshExecutorService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        refreshFeatureFlags();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void start() {
 | 
					 | 
				
			||||||
        refreshFuture = refreshExecutorService.scheduleAtFixedRate(this::refreshFeatureFlags, 0, REFRESH_INTERVAL.toSeconds(), TimeUnit.SECONDS);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void stop() {
 | 
					 | 
				
			||||||
        refreshFuture.cancel(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public boolean isFeatureFlagActive(final String featureFlag) {
 | 
					 | 
				
			||||||
        return featureFlags.get().getOrDefault(featureFlag, false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void setFeatureFlag(final String featureFlag, final boolean active) {
 | 
					 | 
				
			||||||
        featureFlagDatabase.setFlag(featureFlag, active);
 | 
					 | 
				
			||||||
        refreshFeatureFlags();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void deleteFeatureFlag(final String featureFlag) {
 | 
					 | 
				
			||||||
        featureFlagDatabase.deleteFlag(featureFlag);
 | 
					 | 
				
			||||||
        refreshFeatureFlags();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public Map<String, Boolean> getAllFlags() {
 | 
					 | 
				
			||||||
        return featureFlags.get();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @VisibleForTesting
 | 
					 | 
				
			||||||
    void refreshFeatureFlags() {
 | 
					 | 
				
			||||||
        final Map<String, Boolean> refreshedFeatureFlags = featureFlagDatabase.getFeatureFlags();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        featureFlags.set(Collections.unmodifiableMap(refreshedFeatureFlags));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (final Gauge gauge : gauges) {
 | 
					 | 
				
			||||||
            Metrics.globalRegistry.remove(gauge);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        gauges.clear();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (final Map.Entry<String, Boolean> entry : refreshedFeatureFlags.entrySet()) {
 | 
					 | 
				
			||||||
            final String  featureFlag = entry.getKey();
 | 
					 | 
				
			||||||
            final boolean active      = entry.getValue();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            gauges.add(Gauge.builder(name(getClass(), GAUGE_NAME), () -> active ? 1 : 0)
 | 
					 | 
				
			||||||
                            .tag(FLAG_TAG_NAME, featureFlag)
 | 
					 | 
				
			||||||
                            .register(Metrics.globalRegistry));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,6 @@ public class MessagePersister implements Managed {
 | 
				
			||||||
    private final MessagesCache               messagesCache;
 | 
					    private final MessagesCache               messagesCache;
 | 
				
			||||||
    private final MessagesManager             messagesManager;
 | 
					    private final MessagesManager             messagesManager;
 | 
				
			||||||
    private final AccountsManager             accountsManager;
 | 
					    private final AccountsManager             accountsManager;
 | 
				
			||||||
    private final FeatureFlagsManager featureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final Duration        persistDelay;
 | 
					    private final Duration        persistDelay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,21 +48,21 @@ public class MessagePersister implements Managed {
 | 
				
			||||||
    static final int QUEUE_BATCH_LIMIT   = 100;
 | 
					    static final int QUEUE_BATCH_LIMIT   = 100;
 | 
				
			||||||
    static final int MESSAGE_BATCH_LIMIT = 100;
 | 
					    static final int MESSAGE_BATCH_LIMIT = 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final String DISABLE_PERSISTER_FEATURE_FLAG = "DISABLE_MESSAGE_PERSISTER";
 | 
				
			||||||
    private static final int WORKER_THREAD_COUNT = 4;
 | 
					    private static final int WORKER_THREAD_COUNT = 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final Logger logger = LoggerFactory.getLogger(MessagePersister.class);
 | 
					    private static final Logger logger = LoggerFactory.getLogger(MessagePersister.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public MessagePersister(final MessagesCache messagesCache, final MessagesManager messagesManager, final AccountsManager accountsManager, final FeatureFlagsManager featureFlagsManager, final Duration persistDelay) {
 | 
					    public MessagePersister(final MessagesCache messagesCache, final MessagesManager messagesManager, final AccountsManager accountsManager, final DynamicConfigurationManager dynamicConfigurationManager, final Duration persistDelay) {
 | 
				
			||||||
        this.messagesCache               = messagesCache;
 | 
					        this.messagesCache               = messagesCache;
 | 
				
			||||||
        this.messagesManager             = messagesManager;
 | 
					        this.messagesManager             = messagesManager;
 | 
				
			||||||
        this.accountsManager             = accountsManager;
 | 
					        this.accountsManager             = accountsManager;
 | 
				
			||||||
        this.featureFlagsManager = featureFlagsManager;
 | 
					 | 
				
			||||||
        this.persistDelay                = persistDelay;
 | 
					        this.persistDelay                = persistDelay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (int i = 0; i < workerThreads.length; i++) {
 | 
					        for (int i = 0; i < workerThreads.length; i++) {
 | 
				
			||||||
            workerThreads[i] = new Thread(() -> {
 | 
					            workerThreads[i] = new Thread(() -> {
 | 
				
			||||||
                while (running) {
 | 
					                while (running) {
 | 
				
			||||||
                    if (featureFlagsManager.isFeatureFlagActive("DISABLE_MESSAGE_PERSISTER")) {
 | 
					                    if (dynamicConfigurationManager.getConfiguration().getActiveFeatureFlags().contains(DISABLE_PERSISTER_FEATURE_FLAG)) {
 | 
				
			||||||
                        Util.sleep(1000);
 | 
					                        Util.sleep(1000);
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        try {
 | 
					                        try {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,31 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2021 Signal Messenger, LLC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.workers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import io.dropwizard.servlets.tasks.Task;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.PrintWriter;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public abstract class AbstractFeatureFlagTask extends Task {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final FeatureFlagsManager featureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected AbstractFeatureFlagTask(final String name, final FeatureFlagsManager featureFlagsManager) {
 | 
					 | 
				
			||||||
        super(name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.featureFlagsManager = featureFlagsManager;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected FeatureFlagsManager getFeatureFlagsManager() {
 | 
					 | 
				
			||||||
        return featureFlagsManager;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected void printFeatureFlags(final PrintWriter out) {
 | 
					 | 
				
			||||||
        out.println("Feature flags:");
 | 
					 | 
				
			||||||
        featureFlagsManager.getAllFlags().forEach((flag, active) -> out.println(flag + ": " + active));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,35 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2021 Signal Messenger, LLC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.workers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.PrintWriter;
 | 
					 | 
				
			||||||
import java.util.Collections;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class DeleteFeatureFlagTask extends AbstractFeatureFlagTask {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public DeleteFeatureFlagTask(final FeatureFlagsManager featureFlagsManager) {
 | 
					 | 
				
			||||||
        super("delete-feature-flag", featureFlagsManager);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void execute(final Map<String, List<String>> parameters, final PrintWriter out) {
 | 
					 | 
				
			||||||
        if (parameters.containsKey("flag")) {
 | 
					 | 
				
			||||||
            for (final String flag : parameters.getOrDefault("flag", Collections.emptyList())) {
 | 
					 | 
				
			||||||
                out.println("Deleting feature flag: " + flag);
 | 
					 | 
				
			||||||
                getFeatureFlagsManager().deleteFeatureFlag(flag);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            out.println();
 | 
					 | 
				
			||||||
            printFeatureFlags(out);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            out.println("Usage: delete-feature-flag?flag=FLAG_NAME[&flag=FLAG_NAME2&flag=...]");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2021 Signal Messenger, LLC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.workers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.PrintWriter;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class ListFeatureFlagsTask extends AbstractFeatureFlagTask {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public ListFeatureFlagsTask(final FeatureFlagsManager featureFlagsManager) {
 | 
					 | 
				
			||||||
        super("list-feature-flags", featureFlagsManager);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void execute(final Map<String, List<String>> parameters, final PrintWriter out) {
 | 
					 | 
				
			||||||
        printFeatureFlags(out);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,40 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2021 Signal Messenger, LLC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.workers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.PrintWriter;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
import java.util.Optional;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class SetFeatureFlagTask extends AbstractFeatureFlagTask {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public SetFeatureFlagTask(final FeatureFlagsManager featureFlagsManager) {
 | 
					 | 
				
			||||||
        super("set-feature-flag", featureFlagsManager);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void execute(final Map<String, List<String>> parameters, final PrintWriter out) {
 | 
					 | 
				
			||||||
        final Optional<String> maybeFlag = Optional.ofNullable(parameters.get("flag"))
 | 
					 | 
				
			||||||
                                                   .flatMap(values -> values.stream().findFirst());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final Optional<Boolean> maybeActive = Optional.ofNullable(parameters.get("active"))
 | 
					 | 
				
			||||||
                                                      .flatMap(values -> values.stream().findFirst())
 | 
					 | 
				
			||||||
                                                      .map(Boolean::valueOf);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (maybeFlag.isPresent() && maybeActive.isPresent()) {
 | 
					 | 
				
			||||||
            getFeatureFlagsManager().setFeatureFlag(maybeFlag.get(), maybeActive.get());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            out.format("Set %s to %s\n", maybeFlag.get(), maybeActive.get());
 | 
					 | 
				
			||||||
            out.println();
 | 
					 | 
				
			||||||
            printFeatureFlags(out);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            out.println("Usage: set-feature-flag?flag=FLAG_NAME&active=[true|false]");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,6 @@ import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.configuration.DatabaseConfiguration;
 | 
					import org.whispersystems.textsecuregcm.configuration.DatabaseConfiguration;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.Accounts;
 | 
					import org.whispersystems.textsecuregcm.storage.Accounts;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
 | 
					import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FeatureFlags;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.PendingAccounts;
 | 
					import org.whispersystems.textsecuregcm.storage.PendingAccounts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import io.dropwizard.cli.ConfiguredCommand;
 | 
					import io.dropwizard.cli.ConfiguredCommand;
 | 
				
			||||||
| 
						 | 
					@ -40,7 +39,6 @@ public class VacuumCommand extends ConfiguredCommand<WhisperServerConfiguration>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Accounts        accounts        = new Accounts(accountDatabase);
 | 
					    Accounts        accounts        = new Accounts(accountDatabase);
 | 
				
			||||||
    PendingAccounts pendingAccounts = new PendingAccounts(accountDatabase);
 | 
					    PendingAccounts pendingAccounts = new PendingAccounts(accountDatabase);
 | 
				
			||||||
    FeatureFlags    featureFlags    = new FeatureFlags(accountDatabase);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.info("Vacuuming accounts...");
 | 
					    logger.info("Vacuuming accounts...");
 | 
				
			||||||
    accounts.vacuum();
 | 
					    accounts.vacuum();
 | 
				
			||||||
| 
						 | 
					@ -48,9 +46,6 @@ public class VacuumCommand extends ConfiguredCommand<WhisperServerConfiguration>
 | 
				
			||||||
    logger.info("Vacuuming pending_accounts...");
 | 
					    logger.info("Vacuuming pending_accounts...");
 | 
				
			||||||
    pendingAccounts.vacuum();
 | 
					    pendingAccounts.vacuum();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.info("Vacuuming feature flags...");
 | 
					 | 
				
			||||||
    featureFlags.vacuum();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Thread.sleep(3000);
 | 
					    Thread.sleep(3000);
 | 
				
			||||||
    System.exit(0);
 | 
					    System.exit(0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,8 @@ public class DynamicConfigurationTest {
 | 
				
			||||||
  public void testParseExperimentConfig() throws JsonProcessingException {
 | 
					  public void testParseExperimentConfig() throws JsonProcessingException {
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      final String emptyConfigYaml = "test: true";
 | 
					      final String emptyConfigYaml = "test: true";
 | 
				
			||||||
            final DynamicConfiguration emptyConfig = DynamicConfigurationManager.OBJECT_MAPPER.readValue(emptyConfigYaml, DynamicConfiguration.class);
 | 
					      final DynamicConfiguration emptyConfig = DynamicConfigurationManager.OBJECT_MAPPER
 | 
				
			||||||
 | 
					          .readValue(emptyConfigYaml, DynamicConfiguration.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      assertFalse(emptyConfig.getExperimentEnrollmentConfiguration("test").isPresent());
 | 
					      assertFalse(emptyConfig.getExperimentEnrollmentConfiguration("test").isPresent());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -43,17 +44,21 @@ public class DynamicConfigurationTest {
 | 
				
			||||||
              "    enrolledUuids:\n" +
 | 
					              "    enrolledUuids:\n" +
 | 
				
			||||||
              "      - 71618739-114c-4b1f-bb0d-6478a44eb600";
 | 
					              "      - 71618739-114c-4b1f-bb0d-6478a44eb600";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            final DynamicConfiguration config = DynamicConfigurationManager.OBJECT_MAPPER.readValue(experimentConfigYaml, DynamicConfiguration.class);
 | 
					      final DynamicConfiguration config = DynamicConfigurationManager.OBJECT_MAPPER
 | 
				
			||||||
 | 
					          .readValue(experimentConfigYaml, DynamicConfiguration.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      assertFalse(config.getExperimentEnrollmentConfiguration("unconfigured").isPresent());
 | 
					      assertFalse(config.getExperimentEnrollmentConfiguration("unconfigured").isPresent());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      assertTrue(config.getExperimentEnrollmentConfiguration("percentageOnly").isPresent());
 | 
					      assertTrue(config.getExperimentEnrollmentConfiguration("percentageOnly").isPresent());
 | 
				
			||||||
      assertEquals(12, config.getExperimentEnrollmentConfiguration("percentageOnly").get().getEnrollmentPercentage());
 | 
					      assertEquals(12, config.getExperimentEnrollmentConfiguration("percentageOnly").get().getEnrollmentPercentage());
 | 
				
			||||||
            assertEquals(Collections.emptySet(), config.getExperimentEnrollmentConfiguration("percentageOnly").get().getEnrolledUuids());
 | 
					      assertEquals(Collections.emptySet(),
 | 
				
			||||||
 | 
					          config.getExperimentEnrollmentConfiguration("percentageOnly").get().getEnrolledUuids());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      assertTrue(config.getExperimentEnrollmentConfiguration("uuidsAndPercentage").isPresent());
 | 
					      assertTrue(config.getExperimentEnrollmentConfiguration("uuidsAndPercentage").isPresent());
 | 
				
			||||||
            assertEquals(77, config.getExperimentEnrollmentConfiguration("uuidsAndPercentage").get().getEnrollmentPercentage());
 | 
					      assertEquals(77,
 | 
				
			||||||
            assertEquals(Set.of(UUID.fromString("717b1c09-ed0b-4120-bb0e-f4697534b8e1"), UUID.fromString("279f264c-56d7-4bbf-b9da-de718ff90903")),
 | 
					          config.getExperimentEnrollmentConfiguration("uuidsAndPercentage").get().getEnrollmentPercentage());
 | 
				
			||||||
 | 
					      assertEquals(Set.of(UUID.fromString("717b1c09-ed0b-4120-bb0e-f4697534b8e1"),
 | 
				
			||||||
 | 
					          UUID.fromString("279f264c-56d7-4bbf-b9da-de718ff90903")),
 | 
				
			||||||
          config.getExperimentEnrollmentConfiguration("uuidsAndPercentage").get().getEnrolledUuids());
 | 
					          config.getExperimentEnrollmentConfiguration("uuidsAndPercentage").get().getEnrolledUuids());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      assertTrue(config.getExperimentEnrollmentConfiguration("uuidsOnly").isPresent());
 | 
					      assertTrue(config.getExperimentEnrollmentConfiguration("uuidsOnly").isPresent());
 | 
				
			||||||
| 
						 | 
					@ -67,7 +72,8 @@ public class DynamicConfigurationTest {
 | 
				
			||||||
  public void testParseRemoteDeprecationConfig() throws JsonProcessingException {
 | 
					  public void testParseRemoteDeprecationConfig() throws JsonProcessingException {
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      final String emptyConfigYaml = "test: true";
 | 
					      final String emptyConfigYaml = "test: true";
 | 
				
			||||||
            final DynamicConfiguration emptyConfig = DynamicConfigurationManager.OBJECT_MAPPER.readValue(emptyConfigYaml, DynamicConfiguration.class);
 | 
					      final DynamicConfiguration emptyConfig = DynamicConfigurationManager.OBJECT_MAPPER
 | 
				
			||||||
 | 
					          .readValue(emptyConfigYaml, DynamicConfiguration.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      assertNotNull(emptyConfig.getRemoteDeprecationConfiguration());
 | 
					      assertNotNull(emptyConfig.getRemoteDeprecationConfiguration());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -86,13 +92,40 @@ public class DynamicConfigurationTest {
 | 
				
			||||||
              "    DESKTOP:\n" +
 | 
					              "    DESKTOP:\n" +
 | 
				
			||||||
              "      - 1.4.0-beta.2";
 | 
					              "      - 1.4.0-beta.2";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            final DynamicConfiguration config = DynamicConfigurationManager.OBJECT_MAPPER.readValue(experimentConfigYaml, DynamicConfiguration.class);
 | 
					      final DynamicConfiguration config = DynamicConfigurationManager.OBJECT_MAPPER
 | 
				
			||||||
            final DynamicRemoteDeprecationConfiguration remoteDeprecationConfiguration = config.getRemoteDeprecationConfiguration();
 | 
					          .readValue(experimentConfigYaml, DynamicConfiguration.class);
 | 
				
			||||||
 | 
					      final DynamicRemoteDeprecationConfiguration remoteDeprecationConfiguration = config
 | 
				
			||||||
 | 
					          .getRemoteDeprecationConfiguration();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            assertEquals(Map.of(ClientPlatform.IOS, new Semver("1.2.3"), ClientPlatform.ANDROID, new Semver("4.5.6")), remoteDeprecationConfiguration.getMinimumVersions());
 | 
					      assertEquals(Map.of(ClientPlatform.IOS, new Semver("1.2.3"), ClientPlatform.ANDROID, new Semver("4.5.6")),
 | 
				
			||||||
            assertEquals(Map.of(ClientPlatform.DESKTOP, new Semver("7.8.9")), remoteDeprecationConfiguration.getVersionsPendingDeprecation());
 | 
					          remoteDeprecationConfiguration.getMinimumVersions());
 | 
				
			||||||
            assertEquals(Map.of(ClientPlatform.DESKTOP, Set.of(new Semver("1.4.0-beta.2"))), remoteDeprecationConfiguration.getBlockedVersions());
 | 
					      assertEquals(Map.of(ClientPlatform.DESKTOP, new Semver("7.8.9")),
 | 
				
			||||||
 | 
					          remoteDeprecationConfiguration.getVersionsPendingDeprecation());
 | 
				
			||||||
 | 
					      assertEquals(Map.of(ClientPlatform.DESKTOP, Set.of(new Semver("1.4.0-beta.2"))),
 | 
				
			||||||
 | 
					          remoteDeprecationConfiguration.getBlockedVersions());
 | 
				
			||||||
      assertTrue(remoteDeprecationConfiguration.getVersionsPendingBlock().isEmpty());
 | 
					      assertTrue(remoteDeprecationConfiguration.getVersionsPendingBlock().isEmpty());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  public void testParseFeatureFlags() throws JsonProcessingException {
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      final String emptyConfigYaml = "test: true";
 | 
				
			||||||
 | 
					      final DynamicConfiguration emptyConfig = DynamicConfigurationManager.OBJECT_MAPPER
 | 
				
			||||||
 | 
					          .readValue(emptyConfigYaml, DynamicConfiguration.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      assertTrue(emptyConfig.getActiveFeatureFlags().isEmpty());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      final String emptyConfigYaml =
 | 
				
			||||||
 | 
					          "featureFlags:\n"
 | 
				
			||||||
 | 
					              + "  - testFlag";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final DynamicConfiguration emptyConfig = DynamicConfigurationManager.OBJECT_MAPPER
 | 
				
			||||||
 | 
					          .readValue(emptyConfigYaml, DynamicConfiguration.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      assertTrue(emptyConfig.getActiveFeatureFlags().contains("testFlag"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,51 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2013-2020 Signal Messenger, LLC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.storage;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
 | 
					 | 
				
			||||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
 | 
					 | 
				
			||||||
import com.opentable.db.postgres.junit.PreparedDbRule;
 | 
					 | 
				
			||||||
import org.jdbi.v3.core.Jdbi;
 | 
					 | 
				
			||||||
import org.junit.Before;
 | 
					 | 
				
			||||||
import org.junit.Rule;
 | 
					 | 
				
			||||||
import org.junit.Test;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.util.concurrent.ScheduledExecutorService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static org.junit.Assert.assertFalse;
 | 
					 | 
				
			||||||
import static org.junit.Assert.assertTrue;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.mock;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class FeatureFlagsManagerTest {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private FeatureFlagsManager featureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Rule
 | 
					 | 
				
			||||||
    public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Before
 | 
					 | 
				
			||||||
    public void setUp() {
 | 
					 | 
				
			||||||
        final FaultTolerantDatabase database = new FaultTolerantDatabase("featureFlagsTest",
 | 
					 | 
				
			||||||
                Jdbi.create(db.getTestDatabase()),
 | 
					 | 
				
			||||||
                new CircuitBreakerConfiguration());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        featureFlagsManager  = new FeatureFlagsManager(new FeatureFlags(database), mock(ScheduledExecutorService.class));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Test
 | 
					 | 
				
			||||||
    public void testIsFeatureFlagActive() {
 | 
					 | 
				
			||||||
        final String flagName = "testFlag";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        assertFalse(featureFlagsManager.isFeatureFlagActive(flagName));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        featureFlagsManager.setFeatureFlag(flagName, true);
 | 
					 | 
				
			||||||
        assertTrue(featureFlagsManager.isFeatureFlagActive(flagName));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        featureFlagsManager.setFeatureFlag(flagName, false);
 | 
					 | 
				
			||||||
        assertFalse(featureFlagsManager.isFeatureFlagActive(flagName));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,68 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2013-2020 Signal Messenger, LLC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.storage;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
 | 
					 | 
				
			||||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
 | 
					 | 
				
			||||||
import com.opentable.db.postgres.junit.PreparedDbRule;
 | 
					 | 
				
			||||||
import org.jdbi.v3.core.Jdbi;
 | 
					 | 
				
			||||||
import org.junit.Before;
 | 
					 | 
				
			||||||
import org.junit.Rule;
 | 
					 | 
				
			||||||
import org.junit.Test;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static org.junit.Assert.assertEquals;
 | 
					 | 
				
			||||||
import static org.junit.Assert.assertTrue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class FeatureFlagsTest {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Rule
 | 
					 | 
				
			||||||
    public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private FeatureFlags featureFlags;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Before
 | 
					 | 
				
			||||||
    public void setUp() {
 | 
					 | 
				
			||||||
        final FaultTolerantDatabase database = new FaultTolerantDatabase("featureFlagsTest",
 | 
					 | 
				
			||||||
                Jdbi.create(db.getTestDatabase()),
 | 
					 | 
				
			||||||
                new CircuitBreakerConfiguration());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.featureFlags = new FeatureFlags(database);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Test
 | 
					 | 
				
			||||||
    public void testSetFlagIsFlagActive() {
 | 
					 | 
				
			||||||
        assertTrue(featureFlags.getFeatureFlags().isEmpty());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        featureFlags.setFlag("testFlag", true);
 | 
					 | 
				
			||||||
        assertEquals(Map.of("testFlag", true), featureFlags.getFeatureFlags());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        featureFlags.setFlag("testFlag", false);
 | 
					 | 
				
			||||||
        assertEquals(Map.of("testFlag", false), featureFlags.getFeatureFlags());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Test
 | 
					 | 
				
			||||||
    public void testDeleteFlag() {
 | 
					 | 
				
			||||||
        assertTrue(featureFlags.getFeatureFlags().isEmpty());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        featureFlags.setFlag("testFlag", true);
 | 
					 | 
				
			||||||
        assertEquals(Map.of("testFlag", true), featureFlags.getFeatureFlags());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        featureFlags.deleteFlag("testFlag");
 | 
					 | 
				
			||||||
        assertTrue(featureFlags.getFeatureFlags().isEmpty());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Test
 | 
					 | 
				
			||||||
    public void testVacuum() {
 | 
					 | 
				
			||||||
        featureFlags.setFlag("testFlag", true);
 | 
					 | 
				
			||||||
        assertEquals(Map.of("testFlag", true), featureFlags.getFeatureFlags());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        featureFlags.vacuum();
 | 
					 | 
				
			||||||
        assertEquals(Map.of("testFlag", true), featureFlags.getFeatureFlags());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -33,6 +33,7 @@ import org.junit.After;
 | 
				
			||||||
import org.junit.Before;
 | 
					import org.junit.Before;
 | 
				
			||||||
import org.junit.Rule;
 | 
					import org.junit.Rule;
 | 
				
			||||||
import org.junit.Test;
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
 | 
					import org.whispersystems.textsecuregcm.entities.MessageProtos;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
 | 
					import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
 | 
					import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
 | 
				
			||||||
| 
						 | 
					@ -63,11 +64,12 @@ public class MessagePersisterIntegrationTest extends AbstractRedisClusterTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(messagesDynamoDbRule.getDynamoDB(), MessagesDynamoDbRule.TABLE_NAME, Duration.ofDays(7));
 | 
					        final MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(messagesDynamoDbRule.getDynamoDB(), MessagesDynamoDbRule.TABLE_NAME, Duration.ofDays(7));
 | 
				
			||||||
        final AccountsManager accountsManager = mock(AccountsManager.class);
 | 
					        final AccountsManager accountsManager = mock(AccountsManager.class);
 | 
				
			||||||
 | 
					        final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        notificationExecutorService = Executors.newSingleThreadExecutor();
 | 
					        notificationExecutorService = Executors.newSingleThreadExecutor();
 | 
				
			||||||
        messagesCache               = new MessagesCache(getRedisCluster(), getRedisCluster(), notificationExecutorService);
 | 
					        messagesCache               = new MessagesCache(getRedisCluster(), getRedisCluster(), notificationExecutorService);
 | 
				
			||||||
        messagesManager             = new MessagesManager(messagesDynamoDb, messagesCache, mock(PushLatencyManager.class));
 | 
					        messagesManager             = new MessagesManager(messagesDynamoDb, messagesCache, mock(PushLatencyManager.class));
 | 
				
			||||||
        messagePersister            = new MessagePersister(messagesCache, messagesManager, accountsManager, mock(FeatureFlagsManager.class), PERSIST_DELAY);
 | 
					        messagePersister            = new MessagePersister(messagesCache, messagesManager, accountsManager, dynamicConfigurationManager, PERSIST_DELAY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        account = mock(Account.class);
 | 
					        account = mock(Account.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -76,6 +78,7 @@ public class MessagePersisterIntegrationTest extends AbstractRedisClusterTest {
 | 
				
			||||||
        when(account.getNumber()).thenReturn("+18005551234");
 | 
					        when(account.getNumber()).thenReturn("+18005551234");
 | 
				
			||||||
        when(account.getUuid()).thenReturn(accountUuid);
 | 
					        when(account.getUuid()).thenReturn(accountUuid);
 | 
				
			||||||
        when(accountsManager.get(accountUuid)).thenReturn(Optional.of(account));
 | 
					        when(accountsManager.get(accountUuid)).thenReturn(Optional.of(account));
 | 
				
			||||||
 | 
					        when(dynamicConfigurationManager.getConfiguration()).thenReturn(new DynamicConfiguration());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        messagesCache.start();
 | 
					        messagesCache.start();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,6 +32,7 @@ import org.junit.Before;
 | 
				
			||||||
import org.junit.Test;
 | 
					import org.junit.Test;
 | 
				
			||||||
import org.mockito.ArgumentCaptor;
 | 
					import org.mockito.ArgumentCaptor;
 | 
				
			||||||
import org.mockito.stubbing.Answer;
 | 
					import org.mockito.stubbing.Answer;
 | 
				
			||||||
 | 
					import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
 | 
					import org.whispersystems.textsecuregcm.entities.MessageProtos;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
 | 
					import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,6 +56,7 @@ public class MessagePersisterTest extends AbstractRedisClusterTest {
 | 
				
			||||||
        super.setUp();
 | 
					        super.setUp();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final MessagesManager messagesManager = mock(MessagesManager.class);
 | 
					        final MessagesManager messagesManager = mock(MessagesManager.class);
 | 
				
			||||||
 | 
					        final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        messagesDynamoDb = mock(MessagesDynamoDb.class);
 | 
					        messagesDynamoDb = mock(MessagesDynamoDb.class);
 | 
				
			||||||
        accountsManager  = mock(AccountsManager.class);
 | 
					        accountsManager  = mock(AccountsManager.class);
 | 
				
			||||||
| 
						 | 
					@ -63,10 +65,11 @@ public class MessagePersisterTest extends AbstractRedisClusterTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        when(accountsManager.get(DESTINATION_ACCOUNT_UUID)).thenReturn(Optional.of(account));
 | 
					        when(accountsManager.get(DESTINATION_ACCOUNT_UUID)).thenReturn(Optional.of(account));
 | 
				
			||||||
        when(account.getNumber()).thenReturn(DESTINATION_ACCOUNT_NUMBER);
 | 
					        when(account.getNumber()).thenReturn(DESTINATION_ACCOUNT_NUMBER);
 | 
				
			||||||
 | 
					        when(dynamicConfigurationManager.getConfiguration()).thenReturn(new DynamicConfiguration());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        notificationExecutorService = Executors.newSingleThreadExecutor();
 | 
					        notificationExecutorService = Executors.newSingleThreadExecutor();
 | 
				
			||||||
        messagesCache               = new MessagesCache(getRedisCluster(), getRedisCluster(), notificationExecutorService);
 | 
					        messagesCache               = new MessagesCache(getRedisCluster(), getRedisCluster(), notificationExecutorService);
 | 
				
			||||||
        messagePersister            = new MessagePersister(messagesCache, messagesManager, accountsManager, mock(FeatureFlagsManager.class), PERSIST_DELAY);
 | 
					        messagePersister            = new MessagePersister(messagesCache, messagesManager, accountsManager, dynamicConfigurationManager, PERSIST_DELAY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        doAnswer(invocation -> {
 | 
					        doAnswer(invocation -> {
 | 
				
			||||||
          final UUID destinationUuid = invocation.getArgument(0);
 | 
					          final UUID destinationUuid = invocation.getArgument(0);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,40 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2021 Signal Messenger, LLC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.workers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.junit.Before;
 | 
					 | 
				
			||||||
import org.junit.Test;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.PrintWriter;
 | 
					 | 
				
			||||||
import java.util.Collections;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.mock;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.verify;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.when;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class DeleteFeatureFlagTaskTest {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private FeatureFlagsManager featureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Before
 | 
					 | 
				
			||||||
    public void setUp() {
 | 
					 | 
				
			||||||
        featureFlagsManager = mock(FeatureFlagsManager.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        when(featureFlagsManager.getAllFlags()).thenReturn(Collections.emptyMap());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Test
 | 
					 | 
				
			||||||
    public void testExecute() {
 | 
					 | 
				
			||||||
        final DeleteFeatureFlagTask task = new DeleteFeatureFlagTask(featureFlagsManager);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        task.execute(Map.of("flag", List.of("test-flag-1", "test-flag-2")), mock(PrintWriter.class));
 | 
					 | 
				
			||||||
        verify(featureFlagsManager).deleteFeatureFlag("test-flag-1");
 | 
					 | 
				
			||||||
        verify(featureFlagsManager).deleteFeatureFlag("test-flag-2");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,40 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2021 Signal Messenger, LLC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.workers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.junit.Before;
 | 
					 | 
				
			||||||
import org.junit.Test;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.PrintWriter;
 | 
					 | 
				
			||||||
import java.util.Collections;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.mock;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.verify;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.when;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class SetFeatureFlagTaskTest {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private FeatureFlagsManager featureFlagsManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Before
 | 
					 | 
				
			||||||
    public void setUp() {
 | 
					 | 
				
			||||||
        featureFlagsManager = mock(FeatureFlagsManager.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        when(featureFlagsManager.getAllFlags()).thenReturn(Collections.emptyMap());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Test
 | 
					 | 
				
			||||||
    public void testExecute() {
 | 
					 | 
				
			||||||
        final SetFeatureFlagTask task = new SetFeatureFlagTask(featureFlagsManager);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        task.execute(Map.of("flag", List.of("test-flag"), "active", List.of("true")), mock(PrintWriter.class));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        verify(featureFlagsManager).setFeatureFlag("test-flag", true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Loading…
	
		Reference in New Issue