Use `ClientReleasesManager` when deciding whether to add client version tags

This commit is contained in:
Jon Chambers 2023-07-25 18:17:03 -04:00 committed by Jon Chambers
parent 6f4801fd6f
commit 4ead8527c8
19 changed files with 117 additions and 164 deletions

View File

@ -88,6 +88,8 @@ dynamoDbTables:
phoneNumberIdentifierTableName: Example_Accounts_PhoneNumberIdentifiers phoneNumberIdentifierTableName: Example_Accounts_PhoneNumberIdentifiers
usernamesTableName: Example_Accounts_Usernames usernamesTableName: Example_Accounts_Usernames
scanPageSize: 100 scanPageSize: 100
clientReleases:
tableName: Example_ClientReleases
deletedAccounts: deletedAccounts:
tableName: Example_DeletedAccounts tableName: Example_DeletedAccounts
deletedAccountsLock: deletedAccountsLock:

View File

@ -6,6 +6,7 @@ package org.whispersystems.textsecuregcm;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration; import io.dropwizard.Configuration;
import java.time.Duration;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
@ -24,6 +25,7 @@ import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguratio
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration; import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration; import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration;
import org.whispersystems.textsecuregcm.configuration.CdnConfiguration; import org.whispersystems.textsecuregcm.configuration.CdnConfiguration;
import org.whispersystems.textsecuregcm.configuration.ClientReleaseConfiguration;
import org.whispersystems.textsecuregcm.configuration.DatadogConfiguration; import org.whispersystems.textsecuregcm.configuration.DatadogConfiguration;
import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration; import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration; import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration;
@ -281,6 +283,11 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty @JsonProperty
private int grpcPort; private int grpcPort;
@Valid
@NotNull
@JsonProperty
private ClientReleaseConfiguration clientRelease = new ClientReleaseConfiguration(Duration.ofHours(4));
public AdminEventLoggingConfiguration getAdminEventLoggingConfiguration() { public AdminEventLoggingConfiguration getAdminEventLoggingConfiguration() {
return adminEventLoggingConfiguration; return adminEventLoggingConfiguration;
} }
@ -468,4 +475,7 @@ public class WhisperServerConfiguration extends Configuration {
return grpcPort; return grpcPort;
} }
public ClientReleaseConfiguration getClientReleaseConfiguration() {
return clientRelease;
}
} }

View File

@ -168,6 +168,8 @@ import org.whispersystems.textsecuregcm.storage.AccountLockManager;
import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.Accounts;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.ChangeNumberManager; import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
import org.whispersystems.textsecuregcm.storage.ClientReleases;
import org.whispersystems.textsecuregcm.storage.DeletedAccounts; import org.whispersystems.textsecuregcm.storage.DeletedAccounts;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager; import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager;
@ -320,6 +322,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getDynamoDbTables().getAccounts().getPhoneNumberIdentifierTableName(), config.getDynamoDbTables().getAccounts().getPhoneNumberIdentifierTableName(),
config.getDynamoDbTables().getAccounts().getUsernamesTableName(), config.getDynamoDbTables().getAccounts().getUsernamesTableName(),
config.getDynamoDbTables().getAccounts().getScanPageSize()); config.getDynamoDbTables().getAccounts().getScanPageSize());
ClientReleases clientReleases = new ClientReleases(dynamoDbAsyncClient,
config.getDynamoDbTables().getClientReleases().getTableName());
PhoneNumberIdentifiers phoneNumberIdentifiers = new PhoneNumberIdentifiers(dynamoDbClient, PhoneNumberIdentifiers phoneNumberIdentifiers = new PhoneNumberIdentifiers(dynamoDbClient,
config.getDynamoDbTables().getPhoneNumberIdentifiers().getTableName()); config.getDynamoDbTables().getPhoneNumberIdentifiers().getTableName());
Profiles profiles = new Profiles(dynamoDbClient, dynamoDbAsyncClient, Profiles profiles = new Profiles(dynamoDbClient, dynamoDbAsyncClient,
@ -499,7 +503,11 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster); ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster);
MessagesCache messagesCache = new MessagesCache(messagesCluster, messagesCluster, MessagesCache messagesCache = new MessagesCache(messagesCluster, messagesCluster,
keyspaceNotificationDispatchExecutor, messageDeliveryScheduler, messageDeletionAsyncExecutor, clock); keyspaceNotificationDispatchExecutor, messageDeliveryScheduler, messageDeletionAsyncExecutor, clock);
PushLatencyManager pushLatencyManager = new PushLatencyManager(metricsCluster, dynamicConfigurationManager); ClientReleaseManager clientReleaseManager = new ClientReleaseManager(clientReleases,
recurringJobExecutor,
config.getClientReleaseConfiguration().refreshInterval(),
Clock.systemUTC());
PushLatencyManager pushLatencyManager = new PushLatencyManager(metricsCluster, clientReleaseManager);
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster,
config.getReportMessageConfiguration().getCounterTtl()); config.getReportMessageConfiguration().getCounterTtl());
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, reportMessageManager, MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, reportMessageManager,
@ -587,6 +595,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
environment.lifecycle().manage(clientPresenceManager); environment.lifecycle().manage(clientPresenceManager);
environment.lifecycle().manage(currencyManager); environment.lifecycle().manage(currencyManager);
environment.lifecycle().manage(registrationServiceClient); environment.lifecycle().manage(registrationServiceClient);
environment.lifecycle().manage(clientReleaseManager);
final RegistrationCaptchaManager registrationCaptchaManager = new RegistrationCaptchaManager(captchaChecker, final RegistrationCaptchaManager registrationCaptchaManager = new RegistrationCaptchaManager(captchaChecker,
rateLimiters, config.getTestDevices(), dynamicConfigurationManager); rateLimiters, config.getTestDevices(), dynamicConfigurationManager);
@ -662,7 +671,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator)); webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator));
webSocketEnvironment.setConnectListener( webSocketEnvironment.setConnectListener(
new AuthenticatedConnectListener(receiptSender, messagesManager, pushNotificationManager, new AuthenticatedConnectListener(receiptSender, messagesManager, pushNotificationManager,
clientPresenceManager, websocketScheduledExecutor, messageDeliveryScheduler, dynamicConfigurationManager)); clientPresenceManager, websocketScheduledExecutor, messageDeliveryScheduler, clientReleaseManager));
webSocketEnvironment.jersey() webSocketEnvironment.jersey()
.register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager)); .register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
webSocketEnvironment.jersey().register(new RequestStatisticsFilter(TrafficSource.WEBSOCKET)); webSocketEnvironment.jersey().register(new RequestStatisticsFilter(TrafficSource.WEBSOCKET));
@ -740,7 +749,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
ReceiptCredentialPresentation::new), ReceiptCredentialPresentation::new),
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccounts, new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccounts,
messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor, messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor,
messageDeliveryScheduler, reportSpamTokenProvider, dynamicConfigurationManager), messageDeliveryScheduler, reportSpamTokenProvider, clientReleaseManager, dynamicConfigurationManager),
new PaymentsController(currencyManager, paymentsCredentialsGenerator), new PaymentsController(currencyManager, paymentsCredentialsGenerator),
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager, new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager,
profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner,

View File

@ -0,0 +1,12 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotNull;
import java.time.Duration;
public record ClientReleaseConfiguration(@NotNull Duration refreshInterval) {
}

View File

@ -47,6 +47,7 @@ public class DynamoDbTables {
} }
private final AccountsTableConfiguration accounts; private final AccountsTableConfiguration accounts;
private final Table clientReleases;
private final Table deletedAccounts; private final Table deletedAccounts;
private final Table deletedAccountsLock; private final Table deletedAccountsLock;
private final IssuedReceiptsTableConfiguration issuedReceipts; private final IssuedReceiptsTableConfiguration issuedReceipts;
@ -69,6 +70,7 @@ public class DynamoDbTables {
public DynamoDbTables( public DynamoDbTables(
@JsonProperty("accounts") final AccountsTableConfiguration accounts, @JsonProperty("accounts") final AccountsTableConfiguration accounts,
@JsonProperty("clientReleases") final Table clientReleases,
@JsonProperty("deletedAccounts") final Table deletedAccounts, @JsonProperty("deletedAccounts") final Table deletedAccounts,
@JsonProperty("deletedAccountsLock") final Table deletedAccountsLock, @JsonProperty("deletedAccountsLock") final Table deletedAccountsLock,
@JsonProperty("issuedReceipts") final IssuedReceiptsTableConfiguration issuedReceipts, @JsonProperty("issuedReceipts") final IssuedReceiptsTableConfiguration issuedReceipts,
@ -90,6 +92,7 @@ public class DynamoDbTables {
@JsonProperty("verificationSessions") final Table verificationSessions) { @JsonProperty("verificationSessions") final Table verificationSessions) {
this.accounts = accounts; this.accounts = accounts;
this.clientReleases = clientReleases;
this.deletedAccounts = deletedAccounts; this.deletedAccounts = deletedAccounts;
this.deletedAccountsLock = deletedAccountsLock; this.deletedAccountsLock = deletedAccountsLock;
this.issuedReceipts = issuedReceipts; this.issuedReceipts = issuedReceipts;
@ -117,6 +120,12 @@ public class DynamoDbTables {
return accounts; return accounts;
} }
@NotNull
@Valid
public Table getClientReleases() {
return clientReleases;
}
@NotNull @NotNull
@Valid @Valid
public Table getDeletedAccounts() { public Table getDeletedAccounts() {

View File

@ -39,10 +39,6 @@ public class DynamicConfiguration {
@Valid @Valid
private DynamicCaptchaConfiguration captcha = new DynamicCaptchaConfiguration(); private DynamicCaptchaConfiguration captcha = new DynamicCaptchaConfiguration();
@JsonProperty
@Valid
private DynamicPushLatencyConfiguration pushLatency = new DynamicPushLatencyConfiguration(Collections.emptyMap());
@JsonProperty @JsonProperty
@Valid @Valid
private DynamicTurnConfiguration turn = new DynamicTurnConfiguration(); private DynamicTurnConfiguration turn = new DynamicTurnConfiguration();
@ -59,10 +55,6 @@ public class DynamicConfiguration {
@Valid @Valid
DynamicECPreKeyMigrationConfiguration ecPreKeyMigration = new DynamicECPreKeyMigrationConfiguration(true, false); DynamicECPreKeyMigrationConfiguration ecPreKeyMigration = new DynamicECPreKeyMigrationConfiguration(true, false);
@JsonProperty
@Valid
DynamicDeliveryLatencyConfiguration deliveryLatency = new DynamicDeliveryLatencyConfiguration(Collections.emptyMap());
@JsonProperty @JsonProperty
@Valid @Valid
DynamicInboundMessageByteLimitConfiguration inboundMessageByteLimit = new DynamicInboundMessageByteLimitConfiguration(true); DynamicInboundMessageByteLimitConfiguration inboundMessageByteLimit = new DynamicInboundMessageByteLimitConfiguration(true);
@ -93,10 +85,6 @@ public class DynamicConfiguration {
return captcha; return captcha;
} }
public DynamicPushLatencyConfiguration getPushLatencyConfiguration() {
return pushLatency;
}
public DynamicTurnConfiguration getTurnConfiguration() { public DynamicTurnConfiguration getTurnConfiguration() {
return turn; return turn;
} }
@ -113,10 +101,6 @@ public class DynamicConfiguration {
return ecPreKeyMigration; return ecPreKeyMigration;
} }
public DynamicDeliveryLatencyConfiguration getDeliveryLatencyConfiguration() {
return deliveryLatency;
}
public DynamicInboundMessageByteLimitConfiguration getInboundMessageByteLimitConfiguration() { public DynamicInboundMessageByteLimitConfiguration getInboundMessageByteLimitConfiguration() {
return inboundMessageByteLimit; return inboundMessageByteLimit;
} }

View File

@ -1,14 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.vdurmont.semver4j.Semver;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
import java.util.Map;
import java.util.Set;
public record DynamicDeliveryLatencyConfiguration(Map<ClientPlatform, Set<Semver>> instrumentedVersions) {
}

View File

@ -1,16 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.vdurmont.semver4j.Semver;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
import java.util.Map;
import java.util.Set;
public record DynamicPushLatencyConfiguration(Map<ClientPlatform, Set<Semver>> instrumentedVersions) {
}

View File

@ -97,6 +97,7 @@ import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider; import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
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.ClientReleaseManager;
import org.whispersystems.textsecuregcm.storage.DeletedAccounts; import org.whispersystems.textsecuregcm.storage.DeletedAccounts;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
@ -127,6 +128,7 @@ public class MessageController {
private final ExecutorService multiRecipientMessageExecutor; private final ExecutorService multiRecipientMessageExecutor;
private final Scheduler messageDeliveryScheduler; private final Scheduler messageDeliveryScheduler;
private final ReportSpamTokenProvider reportSpamTokenProvider; private final ReportSpamTokenProvider reportSpamTokenProvider;
private final ClientReleaseManager clientReleaseManager;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager; private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
private static final String REJECT_OVERSIZE_MESSAGE_COUNTER = name(MessageController.class, "rejectOversizeMessage"); private static final String REJECT_OVERSIZE_MESSAGE_COUNTER = name(MessageController.class, "rejectOversizeMessage");
@ -161,6 +163,7 @@ public class MessageController {
@Nonnull ExecutorService multiRecipientMessageExecutor, @Nonnull ExecutorService multiRecipientMessageExecutor,
Scheduler messageDeliveryScheduler, Scheduler messageDeliveryScheduler,
@Nonnull ReportSpamTokenProvider reportSpamTokenProvider, @Nonnull ReportSpamTokenProvider reportSpamTokenProvider,
final ClientReleaseManager clientReleaseManager,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) { final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
this.rateLimiters = rateLimiters; this.rateLimiters = rateLimiters;
this.messageSender = messageSender; this.messageSender = messageSender;
@ -173,6 +176,7 @@ public class MessageController {
this.multiRecipientMessageExecutor = Objects.requireNonNull(multiRecipientMessageExecutor); this.multiRecipientMessageExecutor = Objects.requireNonNull(multiRecipientMessageExecutor);
this.messageDeliveryScheduler = messageDeliveryScheduler; this.messageDeliveryScheduler = messageDeliveryScheduler;
this.reportSpamTokenProvider = reportSpamTokenProvider; this.reportSpamTokenProvider = reportSpamTokenProvider;
this.clientReleaseManager = clientReleaseManager;
this.dynamicConfigurationManager = dynamicConfigurationManager; this.dynamicConfigurationManager = dynamicConfigurationManager;
} }
@ -543,8 +547,7 @@ public class MessageController {
.map(OutgoingMessageEntity::fromEnvelope) .map(OutgoingMessageEntity::fromEnvelope)
.peek(outgoingMessageEntity -> { .peek(outgoingMessageEntity -> {
MessageMetrics.measureAccountOutgoingMessageUuidMismatches(auth.getAccount(), outgoingMessageEntity); MessageMetrics.measureAccountOutgoingMessageUuidMismatches(auth.getAccount(), outgoingMessageEntity);
MessageMetrics.measureOutgoingMessageLatency(outgoingMessageEntity.serverTimestamp(), "rest", userAgent, MessageMetrics.measureOutgoingMessageLatency(outgoingMessageEntity.serverTimestamp(), "rest", userAgent, clientReleaseManager);
dynamicConfigurationManager.getConfiguration().getDeliveryLatencyConfiguration().instrumentedVersions());
}) })
.collect(Collectors.toList()), .collect(Collectors.toList()),
messagesAndHasMore.second()); messagesAndHasMore.second());

View File

@ -22,6 +22,7 @@ import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity; import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier; import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
public final class MessageMetrics { public final class MessageMetrics {
@ -61,13 +62,13 @@ public final class MessageMetrics {
public static void measureOutgoingMessageLatency(final long serverTimestamp, public static void measureOutgoingMessageLatency(final long serverTimestamp,
final String channel, final String channel,
final String userAgent, final String userAgent,
final Map<ClientPlatform, Set<Semver>> taggedVersions) { final ClientReleaseManager clientReleaseManager) {
final List<Tag> tags = new ArrayList<>(3); final List<Tag> tags = new ArrayList<>(3);
tags.add(UserAgentTagUtil.getPlatformTag(userAgent)); tags.add(UserAgentTagUtil.getPlatformTag(userAgent));
tags.add(Tag.of("channel", channel)); tags.add(Tag.of("channel", channel));
UserAgentTagUtil.getClientVersionTag(userAgent, taggedVersions).ifPresent(tags::add); UserAgentTagUtil.getClientVersionTag(userAgent, clientReleaseManager).ifPresent(tags::add);
Metrics.timer(DELIVERY_LATENCY_TIMER_NAME, tags) Metrics.timer(DELIVERY_LATENCY_TIMER_NAME, tags)
.record(Duration.between(Instant.ofEpochMilli(serverTimestamp), Instant.now())); .record(Duration.between(Instant.ofEpochMilli(serverTimestamp), Instant.now()));

View File

@ -11,6 +11,7 @@ import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException; import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException;
import org.whispersystems.textsecuregcm.util.ua.UserAgent; import org.whispersystems.textsecuregcm.util.ua.UserAgent;
@ -39,11 +40,11 @@ public class UserAgentTagUtil {
return Tag.of(PLATFORM_TAG, platform); return Tag.of(PLATFORM_TAG, platform);
} }
public static Optional<Tag> getClientVersionTag(final String userAgentString, final Map<ClientPlatform, Set<Semver>> taggedVersions) { public static Optional<Tag> getClientVersionTag(final String userAgentString, final ClientReleaseManager clientReleaseManager) {
try { try {
final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString); final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString);
if (taggedVersions.getOrDefault(userAgent.getPlatform(), Collections.emptySet()).contains(userAgent.getVersion())) { if (clientReleaseManager.isVersionActive(userAgent.getPlatform(), userAgent.getVersion())) {
return Optional.of(Tag.of(VERSION_TAG, userAgent.getVersion().toString())); return Optional.of(Tag.of(VERSION_TAG, userAgent.getVersion().toString()));
} }
} catch (final UnrecognizedUserAgentException ignored) { } catch (final UnrecognizedUserAgentException ignored) {

View File

@ -22,10 +22,9 @@ import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.SystemMapper;
/** /**
@ -40,7 +39,7 @@ import org.whispersystems.textsecuregcm.util.SystemMapper;
public class PushLatencyManager { public class PushLatencyManager {
private final FaultTolerantRedisCluster redisCluster; private final FaultTolerantRedisCluster redisCluster;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager; private final ClientReleaseManager clientReleaseManager;
private final Clock clock; private final Clock clock;
@ -59,18 +58,18 @@ public class PushLatencyManager {
} }
public PushLatencyManager(final FaultTolerantRedisCluster redisCluster, public PushLatencyManager(final FaultTolerantRedisCluster redisCluster,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) { final ClientReleaseManager clientReleaseManager) {
this(redisCluster, dynamicConfigurationManager, Clock.systemUTC()); this(redisCluster, clientReleaseManager, Clock.systemUTC());
} }
@VisibleForTesting @VisibleForTesting
PushLatencyManager(final FaultTolerantRedisCluster redisCluster, PushLatencyManager(final FaultTolerantRedisCluster redisCluster,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager, final ClientReleaseManager clientReleaseManager,
final Clock clock) { final Clock clock) {
this.redisCluster = redisCluster; this.redisCluster = redisCluster;
this.dynamicConfigurationManager = dynamicConfigurationManager; this.clientReleaseManager = clientReleaseManager;
this.clock = clock; this.clock = clock;
} }
@ -99,8 +98,7 @@ public class PushLatencyManager {
tags.add(UserAgentTagUtil.getPlatformTag(userAgentString)); tags.add(UserAgentTagUtil.getPlatformTag(userAgentString));
tags.add(Tag.of("pushType", pushRecord.pushType().name().toLowerCase())); tags.add(Tag.of("pushType", pushRecord.pushType().name().toLowerCase()));
UserAgentTagUtil.getClientVersionTag(userAgentString, UserAgentTagUtil.getClientVersionTag(userAgentString, clientReleaseManager)
dynamicConfigurationManager.getConfiguration().getPushLatencyConfiguration().instrumentedVersions())
.ifPresent(tags::add); .ifPresent(tags::add);
pushRecord.urgent().ifPresent(urgent -> tags.add(Tag.of("urgent", String.valueOf(urgent)))); pushRecord.urgent().ifPresent(urgent -> tags.add(Tag.of("urgent", String.valueOf(urgent))));

View File

@ -23,7 +23,6 @@ import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil; import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
@ -31,8 +30,8 @@ import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.redis.RedisOperation; import org.whispersystems.textsecuregcm.redis.RedisOperation;
import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
@ -69,7 +68,7 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
private final ClientPresenceManager clientPresenceManager; private final ClientPresenceManager clientPresenceManager;
private final ScheduledExecutorService scheduledExecutorService; private final ScheduledExecutorService scheduledExecutorService;
private final Scheduler messageDeliveryScheduler; private final Scheduler messageDeliveryScheduler;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager; private final ClientReleaseManager clientReleaseManager;
private final Map<ClientPlatform, AtomicInteger> openAuthenticatedWebsocketsByClientPlatform; private final Map<ClientPlatform, AtomicInteger> openAuthenticatedWebsocketsByClientPlatform;
private final Map<ClientPlatform, AtomicInteger> openUnauthenticatedWebsocketsByClientPlatform; private final Map<ClientPlatform, AtomicInteger> openUnauthenticatedWebsocketsByClientPlatform;
@ -87,14 +86,14 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
ClientPresenceManager clientPresenceManager, ClientPresenceManager clientPresenceManager,
ScheduledExecutorService scheduledExecutorService, ScheduledExecutorService scheduledExecutorService,
Scheduler messageDeliveryScheduler, Scheduler messageDeliveryScheduler,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) { ClientReleaseManager clientReleaseManager) {
this.receiptSender = receiptSender; this.receiptSender = receiptSender;
this.messagesManager = messagesManager; this.messagesManager = messagesManager;
this.pushNotificationManager = pushNotificationManager; this.pushNotificationManager = pushNotificationManager;
this.clientPresenceManager = clientPresenceManager; this.clientPresenceManager = clientPresenceManager;
this.scheduledExecutorService = scheduledExecutorService; this.scheduledExecutorService = scheduledExecutorService;
this.messageDeliveryScheduler = messageDeliveryScheduler; this.messageDeliveryScheduler = messageDeliveryScheduler;
this.dynamicConfigurationManager = dynamicConfigurationManager; this.clientReleaseManager = clientReleaseManager;
openAuthenticatedWebsocketsByClientPlatform = new EnumMap<>(ClientPlatform.class); openAuthenticatedWebsocketsByClientPlatform = new EnumMap<>(ClientPlatform.class);
openUnauthenticatedWebsocketsByClientPlatform = new EnumMap<>(ClientPlatform.class); openUnauthenticatedWebsocketsByClientPlatform = new EnumMap<>(ClientPlatform.class);
@ -157,7 +156,7 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
context.getClient(), context.getClient(),
scheduledExecutorService, scheduledExecutorService,
messageDeliveryScheduler, messageDeliveryScheduler,
dynamicConfigurationManager); clientReleaseManager);
openWebsocketAtomicInteger.incrementAndGet(); openWebsocketAtomicInteger.incrementAndGet();
openWebsocketCounter.inc(); openWebsocketCounter.inc();

View File

@ -37,7 +37,6 @@ import org.reactivestreams.Publisher;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.MessageController; import org.whispersystems.textsecuregcm.controllers.MessageController;
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
@ -47,8 +46,8 @@ import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.push.DisplacedPresenceListener; import org.whispersystems.textsecuregcm.push.DisplacedPresenceListener;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.MessageAvailabilityListener; import org.whispersystems.textsecuregcm.storage.MessageAvailabilityListener;
import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.Constants;
@ -132,7 +131,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
private final Random random = new Random(); private final Random random = new Random();
private final Scheduler messageDeliveryScheduler; private final Scheduler messageDeliveryScheduler;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager; private final ClientReleaseManager clientReleaseManager;
private enum StoredMessageState { private enum StoredMessageState {
EMPTY, EMPTY,
@ -147,7 +146,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
WebSocketClient client, WebSocketClient client,
ScheduledExecutorService scheduledExecutorService, ScheduledExecutorService scheduledExecutorService,
Scheduler messageDeliveryScheduler, Scheduler messageDeliveryScheduler,
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) { ClientReleaseManager clientReleaseManager) {
this(receiptSender, this(receiptSender,
messagesManager, messagesManager,
@ -156,7 +155,8 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
client, client,
DEFAULT_SEND_FUTURES_TIMEOUT_MILLIS, DEFAULT_SEND_FUTURES_TIMEOUT_MILLIS,
scheduledExecutorService, scheduledExecutorService,
messageDeliveryScheduler, dynamicConfigurationManager); messageDeliveryScheduler,
clientReleaseManager);
} }
@VisibleForTesting @VisibleForTesting
@ -168,7 +168,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
int sendFuturesTimeoutMillis, int sendFuturesTimeoutMillis,
ScheduledExecutorService scheduledExecutorService, ScheduledExecutorService scheduledExecutorService,
Scheduler messageDeliveryScheduler, Scheduler messageDeliveryScheduler,
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) { ClientReleaseManager clientReleaseManager) {
this.receiptSender = receiptSender; this.receiptSender = receiptSender;
this.messagesManager = messagesManager; this.messagesManager = messagesManager;
@ -178,7 +178,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
this.sendFuturesTimeoutMillis = sendFuturesTimeoutMillis; this.sendFuturesTimeoutMillis = sendFuturesTimeoutMillis;
this.scheduledExecutorService = scheduledExecutorService; this.scheduledExecutorService = scheduledExecutorService;
this.messageDeliveryScheduler = messageDeliveryScheduler; this.messageDeliveryScheduler = messageDeliveryScheduler;
this.dynamicConfigurationManager = dynamicConfigurationManager; this.clientReleaseManager = clientReleaseManager;
} }
public void start() { public void start() {
@ -217,8 +217,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
if (throwable != null) { if (throwable != null) {
sendFailuresMeter.mark(); sendFailuresMeter.mark();
} else { } else {
MessageMetrics.measureOutgoingMessageLatency(message.getServerTimestamp(), "websocket", client.getUserAgent(), MessageMetrics.measureOutgoingMessageLatency(message.getServerTimestamp(), "websocket", client.getUserAgent(), clientReleaseManager);
dynamicConfigurationManager.getConfiguration().getDeliveryLatencyConfiguration().instrumentedVersions());
} }
}).thenCompose(response -> { }).thenCompose(response -> {
final CompletableFuture<Void> result; final CompletableFuture<Void> result;

View File

@ -81,7 +81,6 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.auth.OptionalAccess;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicDeliveryLatencyConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicInboundMessageByteLimitConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicInboundMessageByteLimitConfiguration;
import org.whispersystems.textsecuregcm.entities.AccountMismatchedDevices; import org.whispersystems.textsecuregcm.entities.AccountMismatchedDevices;
import org.whispersystems.textsecuregcm.entities.AccountStaleDevices; import org.whispersystems.textsecuregcm.entities.AccountStaleDevices;
@ -112,6 +111,7 @@ import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider; import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
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.ClientReleaseManager;
import org.whispersystems.textsecuregcm.storage.DeletedAccounts; import org.whispersystems.textsecuregcm.storage.DeletedAccounts;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
@ -179,7 +179,7 @@ class MessageControllerTest {
.addResource( .addResource(
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccounts, new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccounts,
messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor, messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor,
messageDeliveryScheduler, ReportSpamTokenProvider.noop(), dynamicConfigurationManager)) messageDeliveryScheduler, ReportSpamTokenProvider.noop(), mock(ClientReleaseManager.class), dynamicConfigurationManager))
.build(); .build();
@BeforeEach @BeforeEach
@ -209,16 +209,12 @@ class MessageControllerTest {
when(accountsManager.getByServiceIdentifier(new PniServiceIdentifier(MULTI_DEVICE_PNI))).thenReturn(Optional.of(multiDeviceAccount)); when(accountsManager.getByServiceIdentifier(new PniServiceIdentifier(MULTI_DEVICE_PNI))).thenReturn(Optional.of(multiDeviceAccount));
when(accountsManager.getByServiceIdentifier(new AciServiceIdentifier(INTERNATIONAL_UUID))).thenReturn(Optional.of(internationalAccount)); when(accountsManager.getByServiceIdentifier(new AciServiceIdentifier(INTERNATIONAL_UUID))).thenReturn(Optional.of(internationalAccount));
final DynamicDeliveryLatencyConfiguration deliveryLatencyConfiguration = mock(DynamicDeliveryLatencyConfiguration.class);
when(deliveryLatencyConfiguration.instrumentedVersions()).thenReturn(Collections.emptyMap());
final DynamicInboundMessageByteLimitConfiguration inboundMessageByteLimitConfiguration = final DynamicInboundMessageByteLimitConfiguration inboundMessageByteLimitConfiguration =
mock(DynamicInboundMessageByteLimitConfiguration.class); mock(DynamicInboundMessageByteLimitConfiguration.class);
when(inboundMessageByteLimitConfiguration.enforceInboundLimit()).thenReturn(false); when(inboundMessageByteLimitConfiguration.enforceInboundLimit()).thenReturn(false);
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class); final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
when(dynamicConfiguration.getDeliveryLatencyConfiguration()).thenReturn(deliveryLatencyConfiguration);
when(dynamicConfiguration.getInboundMessageByteLimitConfiguration()).thenReturn(inboundMessageByteLimitConfiguration); when(dynamicConfiguration.getInboundMessageByteLimitConfiguration()).thenReturn(inboundMessageByteLimitConfiguration);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);

View File

@ -6,6 +6,9 @@
package org.whispersystems.textsecuregcm.metrics; package org.whispersystems.textsecuregcm.metrics;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.vdurmont.semver4j.Semver; import com.vdurmont.semver4j.Semver;
import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tag;
@ -17,6 +20,7 @@ import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
class UserAgentTagUtilTest { class UserAgentTagUtilTest {
@ -49,26 +53,21 @@ class UserAgentTagUtilTest {
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@ParameterizedTest @ParameterizedTest
@MethodSource @MethodSource
void getClientVersionTag(final String userAgent, final Map<ClientPlatform, Set<Semver>> taggedVersions, final Optional<Tag> expectedTag) { void getClientVersionTag(final String userAgent, final boolean isVersionLive, final Optional<Tag> expectedTag) {
assertEquals(expectedTag, UserAgentTagUtil.getClientVersionTag(userAgent, taggedVersions)); final ClientReleaseManager clientReleaseManager = mock(ClientReleaseManager.class);
when(clientReleaseManager.isVersionActive(any(), any())).thenReturn(isVersionLive);
assertEquals(expectedTag, UserAgentTagUtil.getClientVersionTag(userAgent, clientReleaseManager));
} }
private static Stream<Arguments> getClientVersionTag() { private static Stream<Arguments> getClientVersionTag() {
return Stream.of( return Stream.of(
Arguments.of("Signal-Android/1.2.3 (Android 9)", Arguments.of("Signal-Android/1.2.3 (Android 9)",
Map.of(ClientPlatform.ANDROID, Set.of(new Semver("1.2.3"))), true,
Optional.of(Tag.of(UserAgentTagUtil.VERSION_TAG, "1.2.3"))), Optional.of(Tag.of(UserAgentTagUtil.VERSION_TAG, "1.2.3"))),
Arguments.of("Signal-Android/1.2.3 (Android 9)", Arguments.of("Signal-Android/1.2.3 (Android 9)",
Collections.emptyMap(), false,
Optional.empty()),
Arguments.of("Signal-Android/1.2.3.0-bobsbootlegclient",
Map.of(ClientPlatform.ANDROID, Set.of(new Semver("1.2.3"))),
Optional.empty()),
Arguments.of("Signal-Desktop/1.2.3",
Map.of(ClientPlatform.ANDROID, Set.of(new Semver("1.2.3"))),
Optional.empty()) Optional.empty())
); );
} }

View File

@ -9,47 +9,28 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.time.Clock; import java.time.Clock;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.Collections;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicPushLatencyConfiguration;
import org.whispersystems.textsecuregcm.push.PushLatencyManager.PushRecord; import org.whispersystems.textsecuregcm.push.PushLatencyManager.PushRecord;
import org.whispersystems.textsecuregcm.push.PushLatencyManager.PushType; import org.whispersystems.textsecuregcm.push.PushLatencyManager.PushType;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
class PushLatencyManagerTest { class PushLatencyManagerTest {
@RegisterExtension @RegisterExtension
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
private DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
@BeforeEach
void setUp() {
//noinspection unchecked
dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
final DynamicPushLatencyConfiguration dynamicPushLatencyConfiguration = mock(DynamicPushLatencyConfiguration.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
when(dynamicConfiguration.getPushLatencyConfiguration()).thenReturn(dynamicPushLatencyConfiguration);
when(dynamicPushLatencyConfiguration.instrumentedVersions()).thenReturn(Collections.emptyMap());
}
@ParameterizedTest @ParameterizedTest
@MethodSource @MethodSource
void testTakeRecord(final boolean isVoip, final boolean isUrgent) throws ExecutionException, InterruptedException { void testTakeRecord(final boolean isVoip, final boolean isUrgent) throws ExecutionException, InterruptedException {
@ -59,7 +40,7 @@ class PushLatencyManagerTest {
final Instant pushTimestamp = Instant.now(); final Instant pushTimestamp = Instant.now();
final PushLatencyManager pushLatencyManager = new PushLatencyManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(), final PushLatencyManager pushLatencyManager = new PushLatencyManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
dynamicConfigurationManager, Clock.fixed(pushTimestamp, ZoneId.systemDefault())); mock(ClientReleaseManager.class), Clock.fixed(pushTimestamp, ZoneId.systemDefault()));
assertNull(pushLatencyManager.takePushRecord(accountUuid, deviceId).get()); assertNull(pushLatencyManager.takePushRecord(accountUuid, deviceId).get());

View File

@ -25,7 +25,6 @@ import java.io.IOException;
import java.time.Clock; import java.time.Clock;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -45,15 +44,13 @@ import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicDeliveryLatencyConfiguration;
import org.whispersystems.textsecuregcm.entities.MessageProtos; import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension; import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import org.whispersystems.textsecuregcm.storage.MessagesCache; import org.whispersystems.textsecuregcm.storage.MessagesCache;
@ -83,7 +80,7 @@ class WebSocketConnectionIntegrationTest {
private Device device; private Device device;
private WebSocketClient webSocketClient; private WebSocketClient webSocketClient;
private Scheduler messageDeliveryScheduler; private Scheduler messageDeliveryScheduler;
private DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager; private ClientReleaseManager clientReleaseManager;
private long serialTimestamp = System.currentTimeMillis(); private long serialTimestamp = System.currentTimeMillis();
@ -102,15 +99,7 @@ class WebSocketConnectionIntegrationTest {
account = mock(Account.class); account = mock(Account.class);
device = mock(Device.class); device = mock(Device.class);
webSocketClient = mock(WebSocketClient.class); webSocketClient = mock(WebSocketClient.class);
clientReleaseManager = mock(ClientReleaseManager.class);
final DynamicDeliveryLatencyConfiguration deliveryLatencyConfiguration = mock(DynamicDeliveryLatencyConfiguration.class);
when(deliveryLatencyConfiguration.instrumentedVersions()).thenReturn(Collections.emptyMap());
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
when(dynamicConfiguration.getDeliveryLatencyConfiguration()).thenReturn(deliveryLatencyConfiguration);
dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
when(account.getNumber()).thenReturn("+18005551234"); when(account.getNumber()).thenReturn("+18005551234");
when(account.getUuid()).thenReturn(UUID.randomUUID()); when(account.getUuid()).thenReturn(UUID.randomUUID());
@ -140,7 +129,8 @@ class WebSocketConnectionIntegrationTest {
device, device,
webSocketClient, webSocketClient,
scheduledExecutorService, scheduledExecutorService,
messageDeliveryScheduler, dynamicConfigurationManager); messageDeliveryScheduler,
clientReleaseManager);
final List<MessageProtos.Envelope> expectedMessages = new ArrayList<>(persistedMessageCount + cachedMessageCount); final List<MessageProtos.Envelope> expectedMessages = new ArrayList<>(persistedMessageCount + cachedMessageCount);
@ -224,7 +214,8 @@ class WebSocketConnectionIntegrationTest {
device, device,
webSocketClient, webSocketClient,
scheduledExecutorService, scheduledExecutorService,
messageDeliveryScheduler, dynamicConfigurationManager); messageDeliveryScheduler,
clientReleaseManager);
final int persistedMessageCount = 207; final int persistedMessageCount = 207;
final int cachedMessageCount = 173; final int cachedMessageCount = 173;
@ -291,7 +282,7 @@ class WebSocketConnectionIntegrationTest {
100, // use a very short timeout, so that this test completes quickly 100, // use a very short timeout, so that this test completes quickly
scheduledExecutorService, scheduledExecutorService,
messageDeliveryScheduler, messageDeliveryScheduler,
dynamicConfigurationManager); clientReleaseManager);
final int persistedMessageCount = 207; final int persistedMessageCount = 207;
final int cachedMessageCount = 173; final int cachedMessageCount = 173;

View File

@ -33,7 +33,6 @@ import io.lettuce.core.RedisException;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -53,16 +52,14 @@ import org.junit.jupiter.api.Test;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicDeliveryLatencyConfiguration;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
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.ClientReleaseManager;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.websocket.WebSocketClient; import org.whispersystems.websocket.WebSocketClient;
@ -93,7 +90,7 @@ class WebSocketConnectionTest {
private ReceiptSender receiptSender; private ReceiptSender receiptSender;
private ScheduledExecutorService retrySchedulingExecutor; private ScheduledExecutorService retrySchedulingExecutor;
private Scheduler messageDeliveryScheduler; private Scheduler messageDeliveryScheduler;
private DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager; private ClientReleaseManager clientReleaseManager;
@BeforeEach @BeforeEach
void setup() { void setup() {
@ -107,15 +104,7 @@ class WebSocketConnectionTest {
receiptSender = mock(ReceiptSender.class); receiptSender = mock(ReceiptSender.class);
retrySchedulingExecutor = mock(ScheduledExecutorService.class); retrySchedulingExecutor = mock(ScheduledExecutorService.class);
messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery"); messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery");
clientReleaseManager = mock(ClientReleaseManager.class);
final DynamicDeliveryLatencyConfiguration deliveryLatencyConfiguration = mock(DynamicDeliveryLatencyConfiguration.class);
when(deliveryLatencyConfiguration.instrumentedVersions()).thenReturn(Collections.emptyMap());
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
when(dynamicConfiguration.getDeliveryLatencyConfiguration()).thenReturn(deliveryLatencyConfiguration);
dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
} }
@AfterEach @AfterEach
@ -129,7 +118,7 @@ class WebSocketConnectionTest {
WebSocketAccountAuthenticator webSocketAuthenticator = new WebSocketAccountAuthenticator(accountAuthenticator); WebSocketAccountAuthenticator webSocketAuthenticator = new WebSocketAccountAuthenticator(accountAuthenticator);
AuthenticatedConnectListener connectListener = new AuthenticatedConnectListener(receiptSender, messagesManager, AuthenticatedConnectListener connectListener = new AuthenticatedConnectListener(receiptSender, messagesManager,
mock(PushNotificationManager.class), mock(ClientPresenceManager.class), mock(PushNotificationManager.class), mock(ClientPresenceManager.class),
retrySchedulingExecutor, messageDeliveryScheduler, dynamicConfigurationManager); retrySchedulingExecutor, messageDeliveryScheduler, clientReleaseManager);
WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class); WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class);
when(accountAuthenticator.authenticate(eq(new BasicCredentials(VALID_USER, VALID_PASSWORD)))) when(accountAuthenticator.authenticate(eq(new BasicCredentials(VALID_USER, VALID_PASSWORD))))
@ -209,7 +198,7 @@ class WebSocketConnectionTest {
}); });
WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager,
auth, device, client, retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); auth, device, client, retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
connection.start(); connection.start();
verify(client, times(3)).sendRequest(eq("PUT"), eq("/api/v1/message"), any(List.class), verify(client, times(3)).sendRequest(eq("PUT"), eq("/api/v1/message"), any(List.class),
@ -237,7 +226,7 @@ class WebSocketConnectionTest {
public void testOnlineSend() { public void testOnlineSend() {
final WebSocketClient client = mock(WebSocketClient.class); final WebSocketClient client = mock(WebSocketClient.class);
final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
final UUID accountUuid = UUID.randomUUID(); final UUID accountUuid = UUID.randomUUID();
@ -357,7 +346,7 @@ class WebSocketConnectionTest {
}); });
WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager,
auth, device, client, retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); auth, device, client, retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
connection.start(); connection.start();
@ -381,7 +370,7 @@ class WebSocketConnectionTest {
void testProcessStoredMessageConcurrency() { void testProcessStoredMessageConcurrency() {
final WebSocketClient client = mock(WebSocketClient.class); final WebSocketClient client = mock(WebSocketClient.class);
final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
when(account.getNumber()).thenReturn("+18005551234"); when(account.getNumber()).thenReturn("+18005551234");
when(account.getUuid()).thenReturn(UUID.randomUUID()); when(account.getUuid()).thenReturn(UUID.randomUUID());
@ -446,7 +435,7 @@ class WebSocketConnectionTest {
void testProcessStoredMessagesMultiplePages() { void testProcessStoredMessagesMultiplePages() {
final WebSocketClient client = mock(WebSocketClient.class); final WebSocketClient client = mock(WebSocketClient.class);
final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
when(account.getNumber()).thenReturn("+18005551234"); when(account.getNumber()).thenReturn("+18005551234");
final UUID accountUuid = UUID.randomUUID(); final UUID accountUuid = UUID.randomUUID();
@ -498,7 +487,7 @@ class WebSocketConnectionTest {
void testProcessStoredMessagesContainsSenderUuid() { void testProcessStoredMessagesContainsSenderUuid() {
final WebSocketClient client = mock(WebSocketClient.class); final WebSocketClient client = mock(WebSocketClient.class);
final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
when(account.getNumber()).thenReturn("+18005551234"); when(account.getNumber()).thenReturn("+18005551234");
final UUID accountUuid = UUID.randomUUID(); final UUID accountUuid = UUID.randomUUID();
@ -560,7 +549,7 @@ class WebSocketConnectionTest {
void testProcessStoredMessagesSingleEmptyCall() { void testProcessStoredMessagesSingleEmptyCall() {
final WebSocketClient client = mock(WebSocketClient.class); final WebSocketClient client = mock(WebSocketClient.class);
final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
final UUID accountUuid = UUID.randomUUID(); final UUID accountUuid = UUID.randomUUID();
@ -589,7 +578,7 @@ class WebSocketConnectionTest {
public void testRequeryOnStateMismatch() { public void testRequeryOnStateMismatch() {
final WebSocketClient client = mock(WebSocketClient.class); final WebSocketClient client = mock(WebSocketClient.class);
final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
final UUID accountUuid = UUID.randomUUID(); final UUID accountUuid = UUID.randomUUID();
when(account.getNumber()).thenReturn("+18005551234"); when(account.getNumber()).thenReturn("+18005551234");
@ -645,7 +634,7 @@ class WebSocketConnectionTest {
void testProcessCachedMessagesOnly() { void testProcessCachedMessagesOnly() {
final WebSocketClient client = mock(WebSocketClient.class); final WebSocketClient client = mock(WebSocketClient.class);
final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
final UUID accountUuid = UUID.randomUUID(); final UUID accountUuid = UUID.randomUUID();
@ -677,7 +666,7 @@ class WebSocketConnectionTest {
void testProcessDatabaseMessagesAfterPersist() { void testProcessDatabaseMessagesAfterPersist() {
final WebSocketClient client = mock(WebSocketClient.class); final WebSocketClient client = mock(WebSocketClient.class);
final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
final UUID accountUuid = UUID.randomUUID(); final UUID accountUuid = UUID.randomUUID();
@ -724,7 +713,7 @@ class WebSocketConnectionTest {
when(client.isOpen()).thenReturn(true); when(client.isOpen()).thenReturn(true);
WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
connection.start(); connection.start();
verify(retrySchedulingExecutor, times(WebSocketConnection.MAX_CONSECUTIVE_RETRIES)).schedule(any(Runnable.class), verify(retrySchedulingExecutor, times(WebSocketConnection.MAX_CONSECUTIVE_RETRIES)).schedule(any(Runnable.class),
@ -748,7 +737,7 @@ class WebSocketConnectionTest {
when(client.isOpen()).thenReturn(false); when(client.isOpen()).thenReturn(false);
WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
connection.start(); connection.start();
verify(retrySchedulingExecutor, never()).schedule(any(Runnable.class), anyLong(), any()); verify(retrySchedulingExecutor, never()).schedule(any(Runnable.class), anyLong(), any());
@ -782,7 +771,7 @@ class WebSocketConnectionTest {
CompletableFuture.completedFuture(Optional.empty())); CompletableFuture.completedFuture(Optional.empty()));
WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, messageDeliveryScheduler, dynamicConfigurationManager); retrySchedulingExecutor, messageDeliveryScheduler, clientReleaseManager);
connection.start(); connection.start();
@ -839,7 +828,7 @@ class WebSocketConnectionTest {
CompletableFuture.completedFuture(Optional.empty())); CompletableFuture.completedFuture(Optional.empty()));
WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client,
retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager);
connection.start(); connection.start();