mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 05:08:03 +01:00
Add a cluster-backed message cache.
This commit is contained in:
committed by
Jon Chambers
parent
639898ec07
commit
6fc1b4c6c0
@@ -36,7 +36,7 @@ public abstract class AbstractRedisClusterTest {
|
||||
private FaultTolerantRedisCluster redisCluster;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpBeforeClass() throws IOException, URISyntaxException, InterruptedException {
|
||||
public static void setUpBeforeClass() throws Exception {
|
||||
assumeFalse(System.getProperty("os.name").equalsIgnoreCase("windows"));
|
||||
|
||||
clusterNodes = new RedisServer[NODE_COUNT];
|
||||
@@ -50,7 +50,7 @@ public abstract class AbstractRedisClusterTest {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
public void setUp() throws Exception {
|
||||
final List<String> urls = Arrays.stream(clusterNodes)
|
||||
.map(node -> String.format("redis://127.0.0.1:%d", node.ports().get(0)))
|
||||
.collect(Collectors.toList());
|
||||
@@ -63,12 +63,12 @@ public abstract class AbstractRedisClusterTest {
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
public void tearDown() throws Exception {
|
||||
redisCluster.stop();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownAfterClass() {
|
||||
public static void tearDownAfterClass() throws Exception {
|
||||
for (final RedisServer node : clusterNodes) {
|
||||
node.stop();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import junitparams.JUnitParamsRunner;
|
||||
import junitparams.Parameters;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
|
||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(JUnitParamsRunner.class)
|
||||
public abstract class AbstractMessagesCacheTest extends AbstractRedisClusterTest {
|
||||
|
||||
private static final String DESTINATION_ACCOUNT = "+18005551234";
|
||||
private static final int DESTINATION_DEVICE_ID = 7;
|
||||
|
||||
private final Random random = new Random();
|
||||
private long serialTimestamp = 0;
|
||||
|
||||
protected abstract UserMessagesCache getMessagesCache();
|
||||
|
||||
@Test
|
||||
@Parameters({"true", "false"})
|
||||
public void testInsert(final boolean sealedSender) {
|
||||
final UUID messageGuid = UUID.randomUUID();
|
||||
assertTrue(getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, generateRandomMessage(messageGuid, sealedSender)) > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameters({"true", "false"})
|
||||
public void testRemoveById(final boolean sealedSender) {
|
||||
final UUID messageGuid = UUID.randomUUID();
|
||||
final MessageProtos.Envelope message = generateRandomMessage(messageGuid, sealedSender);
|
||||
|
||||
final long messageId = getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, message);
|
||||
final Optional<OutgoingMessageEntity> maybeRemovedMessage = getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, messageId);
|
||||
|
||||
assertTrue(maybeRemovedMessage.isPresent());
|
||||
assertEquals(UserMessagesCache.constructEntityFromEnvelope(messageId, message), maybeRemovedMessage.get());
|
||||
assertEquals(Optional.empty(), getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, messageId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveBySender() {
|
||||
final UUID messageGuid = UUID.randomUUID();
|
||||
final MessageProtos.Envelope message = generateRandomMessage(messageGuid, false);
|
||||
|
||||
getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, message);
|
||||
final Optional<OutgoingMessageEntity> maybeRemovedMessage = getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, message.getSource(), message.getTimestamp());
|
||||
|
||||
assertTrue(maybeRemovedMessage.isPresent());
|
||||
assertEquals(UserMessagesCache.constructEntityFromEnvelope(0, message), maybeRemovedMessage.get());
|
||||
assertEquals(Optional.empty(), getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, message.getSource(), message.getTimestamp()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameters({"true", "false"})
|
||||
public void testRemoveByUUID(final boolean sealedSender) {
|
||||
final UUID messageGuid = UUID.randomUUID();
|
||||
|
||||
assertEquals(Optional.empty(), getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, messageGuid));
|
||||
|
||||
final MessageProtos.Envelope message = generateRandomMessage(messageGuid, sealedSender);
|
||||
|
||||
getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, message);
|
||||
final Optional<OutgoingMessageEntity> maybeRemovedMessage = getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, messageGuid);
|
||||
|
||||
assertTrue(maybeRemovedMessage.isPresent());
|
||||
assertEquals(UserMessagesCache.constructEntityFromEnvelope(0, message), maybeRemovedMessage.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameters({"true", "false"})
|
||||
public void testGetMessages(final boolean sealedSender) {
|
||||
final int messageCount = 100;
|
||||
|
||||
final List<OutgoingMessageEntity> expectedMessages = new ArrayList<>(messageCount);
|
||||
|
||||
for (int i = 0; i < messageCount; i++) {
|
||||
final UUID messageGuid = UUID.randomUUID();
|
||||
final MessageProtos.Envelope message = generateRandomMessage(messageGuid, sealedSender);
|
||||
final long messageId = getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, message);
|
||||
|
||||
expectedMessages.add(UserMessagesCache.constructEntityFromEnvelope(messageId, message));
|
||||
}
|
||||
|
||||
assertEquals(expectedMessages, getMessagesCache().get(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, messageCount));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameters({"true", "false"})
|
||||
public void testClearQueueForDevice(final boolean sealedSender) {
|
||||
final int messageCount = 100;
|
||||
|
||||
for (final int deviceId : new int[] { DESTINATION_DEVICE_ID, DESTINATION_DEVICE_ID + 1 }) {
|
||||
for (int i = 0; i < messageCount; i++) {
|
||||
final UUID messageGuid = UUID.randomUUID();
|
||||
final MessageProtos.Envelope message = generateRandomMessage(messageGuid, sealedSender);
|
||||
|
||||
getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, deviceId, message);
|
||||
}
|
||||
}
|
||||
|
||||
getMessagesCache().clear(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID);
|
||||
|
||||
assertEquals(Collections.emptyList(), getMessagesCache().get(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, messageCount));
|
||||
assertEquals(messageCount, getMessagesCache().get(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID + 1, messageCount).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameters({"true", "false"})
|
||||
public void testClearQueueForAccount(final boolean sealedSender) {
|
||||
final int messageCount = 100;
|
||||
|
||||
for (final int deviceId : new int[] { DESTINATION_DEVICE_ID, DESTINATION_DEVICE_ID + 1 }) {
|
||||
for (int i = 0; i < messageCount; i++) {
|
||||
final UUID messageGuid = UUID.randomUUID();
|
||||
final MessageProtos.Envelope message = generateRandomMessage(messageGuid, sealedSender);
|
||||
|
||||
getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, deviceId, message);
|
||||
}
|
||||
}
|
||||
|
||||
getMessagesCache().clear(DESTINATION_ACCOUNT);
|
||||
|
||||
assertEquals(Collections.emptyList(), getMessagesCache().get(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, messageCount));
|
||||
assertEquals(Collections.emptyList(), getMessagesCache().get(DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID + 1, messageCount));
|
||||
}
|
||||
|
||||
protected MessageProtos.Envelope generateRandomMessage(final UUID messageGuid, final boolean sealedSender) {
|
||||
final MessageProtos.Envelope.Builder envelopeBuilder = MessageProtos.Envelope.newBuilder()
|
||||
.setTimestamp(serialTimestamp++)
|
||||
.setServerTimestamp(serialTimestamp++)
|
||||
.setContent(ByteString.copyFromUtf8(RandomStringUtils.randomAlphanumeric(256)))
|
||||
.setType(MessageProtos.Envelope.Type.CIPHERTEXT)
|
||||
.setServerGuid(messageGuid.toString());
|
||||
|
||||
if (!sealedSender) {
|
||||
envelopeBuilder.setSourceDevice(random.nextInt(256))
|
||||
.setSource("+1" + RandomStringUtils.randomNumeric(10));
|
||||
}
|
||||
|
||||
return envelopeBuilder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
|
||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
|
||||
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
|
||||
import redis.embedded.RedisServer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class MessagesCacheTest extends AbstractMessagesCacheTest {
|
||||
|
||||
private RedisServer redisServer;
|
||||
private MessagesCache messagesCache;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
redisServer = new RedisServer(AbstractRedisClusterTest.getNextRedisClusterPort());
|
||||
redisServer.start();
|
||||
|
||||
final String redisUrl = String.format("redis://127.0.0.1:%d", redisServer.ports().get(0));
|
||||
final RedisClientFactory clientFactory = new RedisClientFactory("message-cache-test", redisUrl, List.of(redisUrl), new CircuitBreakerConfiguration());
|
||||
final ReplicatedJedisPool jedisPool = clientFactory.getRedisClientPool();
|
||||
|
||||
messagesCache = new MessagesCache(jedisPool, mock(Messages.class), mock(AccountsManager.class), 60, mock(RedisClusterMessagesCache.class));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
redisServer.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UserMessagesCache getMessagesCache() {
|
||||
return messagesCache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import junitparams.Parameters;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class RedisClusterMessagesCacheTest extends AbstractMessagesCacheTest {
|
||||
|
||||
private static final String DESTINATION_ACCOUNT = "+18005551234";
|
||||
private static final int DESTINATION_DEVICE_ID = 7;
|
||||
|
||||
private RedisClusterMessagesCache messagesCache;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
try {
|
||||
messagesCache = new RedisClusterMessagesCache(getRedisCluster());
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
getRedisCluster().useWriteCluster(connection -> connection.sync().flushall());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UserMessagesCache getMessagesCache() {
|
||||
return messagesCache;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameters({"true", "false"})
|
||||
public void testInsertWithPrescribedId(final boolean sealedSender) {
|
||||
final UUID firstMessageGuid = UUID.randomUUID();
|
||||
final UUID secondMessageGuid = UUID.randomUUID();
|
||||
final long messageId = 74;
|
||||
|
||||
assertEquals(messageId, messagesCache.insert(firstMessageGuid, DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, generateRandomMessage(firstMessageGuid, sealedSender), messageId));
|
||||
assertEquals(messageId + 1, messagesCache.insert(secondMessageGuid, DESTINATION_ACCOUNT, DESTINATION_DEVICE_ID, generateRandomMessage(secondMessageGuid, sealedSender)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user