Add an experiment to allow a phased transition from the old GCM API to the current FCM API

This commit is contained in:
Jon Chambers 2022-08-01 14:55:12 -04:00 committed by Jon Chambers
parent 421d594507
commit c9ae991aa3
3 changed files with 62 additions and 37 deletions

View File

@ -144,6 +144,7 @@ import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck;
import org.whispersystems.textsecuregcm.push.APNSender; import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ApnFallbackManager; import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.FcmSender;
import org.whispersystems.textsecuregcm.push.GCMSender; import org.whispersystems.textsecuregcm.push.GCMSender;
import org.whispersystems.textsecuregcm.push.MessageSender; import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.ProvisioningManager; import org.whispersystems.textsecuregcm.push.ProvisioningManager;
@ -447,7 +448,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
DispatchManager dispatchManager = new DispatchManager(pubSubClientFactory, Optional.empty()); DispatchManager dispatchManager = new DispatchManager(pubSubClientFactory, Optional.empty());
PubSubManager pubSubManager = new PubSubManager(pubsubClient, dispatchManager); PubSubManager pubSubManager = new PubSubManager(pubsubClient, dispatchManager);
APNSender apnSender = new APNSender(apnSenderExecutor, accountsManager, config.getApnConfiguration()); APNSender apnSender = new APNSender(apnSenderExecutor, accountsManager, config.getApnConfiguration());
GCMSender gcmSender = new GCMSender(gcmSenderExecutor, accountsManager, config.getGcmConfiguration().getApiKey()); FcmSender fcmSender = new FcmSender(gcmSenderExecutor, accountsManager, config.getFcmConfiguration().credentials());
GCMSender gcmSender = new GCMSender(gcmSenderExecutor, accountsManager, config.getGcmConfiguration().getApiKey(), experimentEnrollmentManager, fcmSender);
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), rateLimitersCluster); RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), rateLimitersCluster);
DynamicRateLimiters dynamicRateLimiters = new DynamicRateLimiters(rateLimitersCluster, dynamicConfigurationManager); DynamicRateLimiters dynamicRateLimiters = new DynamicRateLimiters(rateLimitersCluster, dynamicConfigurationManager);
ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager); ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager);

View File

@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.gcm.server.Message; import org.whispersystems.gcm.server.Message;
import org.whispersystems.gcm.server.Result; import org.whispersystems.gcm.server.Result;
import org.whispersystems.gcm.server.Sender; import org.whispersystems.gcm.server.Sender;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
@ -54,20 +55,32 @@ public class GCMSender {
private final Sender signalSender; private final Sender signalSender;
private final ExecutorService executor; private final ExecutorService executor;
public GCMSender(ExecutorService executor, AccountsManager accountsManager, String signalKey) { private final ExperimentEnrollmentManager experimentEnrollmentManager;
this(executor, accountsManager, new Sender(signalKey, SystemMapper.getMapper(), 6)); private final FcmSender fcmSender;
public GCMSender(ExecutorService executor, AccountsManager accountsManager, String signalKey, ExperimentEnrollmentManager experimentEnrollmentManager, FcmSender fcmSender) {
this(executor, accountsManager, new Sender(signalKey, SystemMapper.getMapper(), 6), experimentEnrollmentManager, fcmSender);
CircuitBreakerUtil.registerMetrics(metricRegistry, signalSender.getRetry(), Sender.class); CircuitBreakerUtil.registerMetrics(metricRegistry, signalSender.getRetry(), Sender.class);
} }
@VisibleForTesting @VisibleForTesting
public GCMSender(ExecutorService executor, AccountsManager accountsManager, Sender sender) { public GCMSender(ExecutorService executor, AccountsManager accountsManager, Sender sender, ExperimentEnrollmentManager experimentEnrollmentManager, FcmSender fcmSender) {
this.accountsManager = accountsManager; this.accountsManager = accountsManager;
this.signalSender = sender; this.signalSender = sender;
this.executor = executor; this.executor = executor;
this.experimentEnrollmentManager = experimentEnrollmentManager;
this.fcmSender = fcmSender;
} }
public void sendMessage(GcmMessage message) { public void sendMessage(GcmMessage message) {
final boolean useFcmSender = message.getUuid()
.map(uuid -> experimentEnrollmentManager.isEnrolled(uuid, "fcmSender"))
.orElse(false);
if (useFcmSender) {
fcmSender.sendMessage(message);
} else {
Message.Builder builder = Message.newBuilder() Message.Builder builder = Message.newBuilder()
.withDestination(message.getGcmId()) .withDestination(message.getGcmId())
.withPriority("high"); .withPriority("high");
@ -75,10 +88,17 @@ public class GCMSender {
String key; String key;
switch (message.getType()) { switch (message.getType()) {
case NOTIFICATION: key = "notification"; break; case NOTIFICATION:
case CHALLENGE: key = "challenge"; break; key = "notification";
case RATE_LIMIT_CHALLENGE: key = "rateLimitChallenge"; break; break;
default: throw new AssertionError(); case CHALLENGE:
key = "challenge";
break;
case RATE_LIMIT_CHALLENGE:
key = "rateLimitChallenge";
break;
default:
throw new AssertionError();
} }
Message request = builder.withDataPart(key, message.getData().orElse("")).build(); Message request = builder.withDataPart(key, message.getData().orElse("")).build();
@ -104,6 +124,7 @@ public class GCMSender {
return null; return null;
}); });
} }
}
private void handleBadRegistration(GcmMessage message) { private void handleBadRegistration(GcmMessage message) {
Optional<Account> account = getAccountForEvent(message); Optional<Account> account = getAccountForEvent(message);

View File

@ -19,6 +19,8 @@ import org.junit.jupiter.api.Test;
import org.whispersystems.gcm.server.Message; import org.whispersystems.gcm.server.Message;
import org.whispersystems.gcm.server.Result; import org.whispersystems.gcm.server.Result;
import org.whispersystems.gcm.server.Sender; import org.whispersystems.gcm.server.Sender;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.push.FcmSender;
import org.whispersystems.textsecuregcm.push.GCMSender; import org.whispersystems.textsecuregcm.push.GCMSender;
import org.whispersystems.textsecuregcm.push.GcmMessage; import org.whispersystems.textsecuregcm.push.GcmMessage;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
@ -45,7 +47,7 @@ class GCMSenderTest {
AccountsHelper.setupMockUpdate(accountsManager); AccountsHelper.setupMockUpdate(accountsManager);
GcmMessage message = new GcmMessage("foo", UUID.randomUUID(), 1, GcmMessage.Type.NOTIFICATION, Optional.empty()); GcmMessage message = new GcmMessage("foo", UUID.randomUUID(), 1, GcmMessage.Type.NOTIFICATION, Optional.empty());
GCMSender gcmSender = new GCMSender(executorService, accountsManager, sender); GCMSender gcmSender = new GCMSender(executorService, accountsManager, sender, mock(ExperimentEnrollmentManager.class), mock(FcmSender.class));
CompletableFuture<Result> successFuture = CompletableFuture.completedFuture(successResult); CompletableFuture<Result> successFuture = CompletableFuture.completedFuture(successResult);
@ -81,7 +83,7 @@ class GCMSenderTest {
when(invalidResult.isSuccess()).thenReturn(true); when(invalidResult.isSuccess()).thenReturn(true);
GcmMessage message = new GcmMessage(gcmId, destinationUuid, 1, GcmMessage.Type.NOTIFICATION, Optional.empty()); GcmMessage message = new GcmMessage(gcmId, destinationUuid, 1, GcmMessage.Type.NOTIFICATION, Optional.empty());
GCMSender gcmSender = new GCMSender(executorService, accountsManager, sender); GCMSender gcmSender = new GCMSender(executorService, accountsManager, sender, mock(ExperimentEnrollmentManager.class), mock(FcmSender.class));
CompletableFuture<Result> invalidFuture = CompletableFuture.completedFuture(invalidResult); CompletableFuture<Result> invalidFuture = CompletableFuture.completedFuture(invalidResult);
@ -122,7 +124,7 @@ class GCMSenderTest {
when(canonicalResult.getCanonicalRegistrationId()).thenReturn(canonicalId); when(canonicalResult.getCanonicalRegistrationId()).thenReturn(canonicalId);
GcmMessage message = new GcmMessage(gcmId, destinationUuid, 1, GcmMessage.Type.NOTIFICATION, Optional.empty()); GcmMessage message = new GcmMessage(gcmId, destinationUuid, 1, GcmMessage.Type.NOTIFICATION, Optional.empty());
GCMSender gcmSender = new GCMSender(executorService, accountsManager, sender); GCMSender gcmSender = new GCMSender(executorService, accountsManager, sender, mock(ExperimentEnrollmentManager.class), mock(FcmSender.class));
CompletableFuture<Result> invalidFuture = CompletableFuture.completedFuture(canonicalResult); CompletableFuture<Result> invalidFuture = CompletableFuture.completedFuture(canonicalResult);