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