Separated persistence layer, created submodule for MapDB persistence letting the broker use the in-memory message store
This commit is contained in:
@@ -20,9 +20,9 @@ import io.moquette.BrokerConstants;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.config.MemoryConfig;
|
||||
import io.moquette.spi.impl.subscriptions.Topic;
|
||||
import io.moquette.spi.impl.MemoryMessagesStore;
|
||||
import io.moquette.spi.impl.MemoryStorageService;
|
||||
import io.moquette.spi.persistence.MemorySessionStore;
|
||||
import io.moquette.persistence.MemoryMessagesStore;
|
||||
import io.moquette.persistence.MemoryStorageService;
|
||||
import io.moquette.persistence.MemorySessionStore;
|
||||
import io.moquette.spi.security.IAuthenticator;
|
||||
import io.moquette.spi.security.IAuthorizator;
|
||||
import org.junit.After;
|
||||
|
||||
@@ -55,7 +55,7 @@ public class IntegrationUtils {
|
||||
return testProperties;
|
||||
}
|
||||
|
||||
public static void cleanPersistenceFile(IConfig config) {
|
||||
/*public static void cleanPersistenceFile(IConfig config) {
|
||||
String fileName = config.getProperty(PERSISTENT_STORE_PROPERTY_NAME);
|
||||
cleanPersistenceFile(fileName);
|
||||
}
|
||||
@@ -68,5 +68,5 @@ public class IntegrationUtils {
|
||||
new File(fileName + ".t").delete();
|
||||
}
|
||||
assertFalse(dbFile.exists());
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ public class ServerIntegrationDBAuthenticatorTest {
|
||||
|
||||
private void stopServer() {
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
private Properties addDBAuthenticatorConf(Properties properties) {
|
||||
@@ -67,9 +66,6 @@ public class ServerIntegrationDBAuthenticatorTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
String dbPath = IntegrationUtils.localMapDBPath();
|
||||
IntegrationUtils.cleanPersistenceFile(dbPath);
|
||||
|
||||
startServer();
|
||||
|
||||
m_client = new MqttClient("tcp://localhost:1883", "TestClient", s_dataStore);
|
||||
|
||||
@@ -62,9 +62,6 @@ public class ServerIntegrationEmbeddedPublishTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
String dbPath = IntegrationUtils.localMapDBPath();
|
||||
IntegrationUtils.cleanPersistenceFile(dbPath);
|
||||
|
||||
startServer();
|
||||
|
||||
m_subscriber = new MqttClient("tcp://localhost:1883", "TestClient", s_dataStore);
|
||||
@@ -79,7 +76,6 @@ public class ServerIntegrationEmbeddedPublishTest {
|
||||
}
|
||||
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
private void subscribeToWithQos(String topic, int qos) throws Exception {
|
||||
|
||||
@@ -69,7 +69,6 @@ public class ServerIntegrationFuseTest {
|
||||
}
|
||||
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
-3
@@ -67,9 +67,6 @@ public class ServerIntegrationHazelcastHandlerInterceptorTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
String dbPath = IntegrationUtils.localMapDBPath();
|
||||
IntegrationUtils.cleanPersistenceFile(dbPath);
|
||||
|
||||
final Properties configProps = addHazelCastConf(
|
||||
IntegrationUtils.prepareTestClusterProperties(1883),
|
||||
1883,
|
||||
|
||||
@@ -57,9 +57,6 @@ public class ServerIntegrationPahoTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
String dbPath = IntegrationUtils.localMapDBPath();
|
||||
IntegrationUtils.cleanPersistenceFile(dbPath);
|
||||
|
||||
startServer();
|
||||
|
||||
m_client = new MqttClient("tcp://localhost:1883", "TestClient", s_dataStore);
|
||||
@@ -84,7 +81,6 @@ public class ServerIntegrationPahoTest {
|
||||
|
||||
private void stopServer() {
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -176,6 +172,7 @@ public class ServerIntegrationPahoTest {
|
||||
m_client.disconnect();
|
||||
}
|
||||
|
||||
@Ignore("This test hasn't any meaning using in memory storage service")
|
||||
@Test
|
||||
public void testCleanSession_maintainClientSubscriptions_withServerRestart() throws Exception {
|
||||
LOG.info("*** testCleanSession_maintainClientSubscriptions_withServerRestart ***");
|
||||
|
||||
@@ -89,7 +89,6 @@ public class ServerIntegrationQoSValidationTest {
|
||||
}
|
||||
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -65,7 +65,6 @@ public class ServerIntegrationWebSocketTest {
|
||||
client.stop();
|
||||
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+4
-16
@@ -70,7 +70,6 @@ public class ServerLowlevelMessagesIntegrationTests {
|
||||
Thread.sleep(300); // to let the close event pass before server stop event
|
||||
m_server.stopServer();
|
||||
LOG.debug("After asked server to stop");
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -93,22 +92,11 @@ public class ServerLowlevelMessagesIntegrationTests {
|
||||
}
|
||||
|
||||
private static MqttConnectMessage createConnectMessage(String clientID, int keepAlive) {
|
||||
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(
|
||||
MqttMessageType.CONNECT,
|
||||
false,
|
||||
MqttQoS.AT_MOST_ONCE,
|
||||
false,
|
||||
0);
|
||||
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT, false, MqttQoS.AT_MOST_ONCE,
|
||||
false, 0);
|
||||
MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader(
|
||||
MqttVersion.MQTT_3_1.protocolName(),
|
||||
MqttVersion.MQTT_3_1.protocolLevel(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
false,
|
||||
true,
|
||||
keepAlive);
|
||||
MqttVersion.MQTT_3_1.protocolName(), MqttVersion.MQTT_3_1.protocolLevel(), false, false, false, 1, false,
|
||||
true, keepAlive);
|
||||
MqttConnectPayload mqttConnectPayload = new MqttConnectPayload(clientID, null, null, null, null);
|
||||
return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,6 @@ public class ServerRestartIntegrationTest {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
String dbPath = IntegrationUtils.localMapDBPath();
|
||||
IntegrationUtils.cleanPersistenceFile(dbPath);
|
||||
startServer();
|
||||
|
||||
m_subscriber = new MqttClient("tcp://localhost:1883", "Subscriber", s_dataStore);
|
||||
@@ -85,7 +84,6 @@ public class ServerRestartIntegrationTest {
|
||||
}
|
||||
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,7 +95,6 @@ public class ServerRestartIntegrationTest {
|
||||
|
||||
// shutdown the server
|
||||
m_server.stopServer();
|
||||
IntegrationUtils.cleanPersistenceFile(m_config);
|
||||
|
||||
// restart the server
|
||||
m_server.startServer(IntegrationUtils.prepareTestProperties());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
package io.moquette.spi;
|
||||
|
||||
import io.moquette.spi.impl.MemoryStorageService;
|
||||
import io.moquette.persistence.MemoryStorageService;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.impl.subscriptions.SubscriptionsStore;
|
||||
import io.moquette.spi.impl.subscriptions.Topic;
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.IMatchingCondition;
|
||||
import io.moquette.spi.MessageGUID;
|
||||
import io.moquette.spi.impl.subscriptions.Topic;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.util.*;
|
||||
import static io.moquette.spi.impl.Utils.defaultGet;
|
||||
|
||||
/**
|
||||
* @author andrea
|
||||
*/
|
||||
public class MemoryMessagesStore implements IMessagesStore {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MemoryMessagesStore.class);
|
||||
|
||||
private Map<Topic, MessageGUID> m_retainedStore = new HashMap<>();
|
||||
private Map<MessageGUID, StoredMessage> m_persistentMessageStore = new HashMap<>();
|
||||
private Map<String, Map<Integer, MessageGUID>> m_messageToGuids = new HashMap<>();
|
||||
|
||||
MemoryMessagesStore() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeRetained(Topic topic, MessageGUID guid) {
|
||||
m_retainedStore.put(topic, guid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<StoredMessage> searchMatching(IMatchingCondition condition) {
|
||||
LOG.debug("searchMatching scanning all retained messages, presents are {}", m_retainedStore.size());
|
||||
|
||||
List<StoredMessage> results = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<Topic, MessageGUID> entry : m_retainedStore.entrySet()) {
|
||||
final MessageGUID guid = entry.getValue();
|
||||
StoredMessage storedMsg = m_persistentMessageStore.get(guid);
|
||||
if (condition.match(entry.getKey())) {
|
||||
results.add(storedMsg);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageGUID storePublishForFuture(StoredMessage storedMessage) {
|
||||
LOG.debug("storePublishForFuture store evt {}", storedMessage);
|
||||
MessageGUID guid = new MessageGUID(UUID.randomUUID().toString());
|
||||
storedMessage.setGuid(guid);
|
||||
m_persistentMessageStore.put(guid, storedMessage);
|
||||
HashMap<Integer, MessageGUID> guids = (HashMap<Integer, MessageGUID>) defaultGet(
|
||||
m_messageToGuids,
|
||||
storedMessage.getClientID(),
|
||||
new HashMap<Integer, MessageGUID>());
|
||||
guids.put(storedMessage.getMessageID(), guid);
|
||||
return guid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropMessagesInSession(String clientID) {
|
||||
Map<Integer, MessageGUID> messageGUIDMap = m_messageToGuids.get(clientID);
|
||||
if (messageGUIDMap == null || messageGUIDMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (MessageGUID guid : messageGUIDMap.values()) {
|
||||
m_persistentMessageStore.remove(guid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredMessage getMessageByGuid(MessageGUID guid) {
|
||||
return m_persistentMessageStore.get(guid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanRetained(Topic topic) {
|
||||
m_retainedStore.remove(topic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingPublishMessages(String clientID) {
|
||||
Map<Integer, MessageGUID> messageToGuids = m_messageToGuids.get(clientID);
|
||||
if (messageToGuids == null)
|
||||
return 0;
|
||||
else
|
||||
return messageToGuids.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageGUID mapToGuid(String clientID, int messageID) {
|
||||
HashMap<Integer, MessageGUID> guids = (HashMap<Integer, MessageGUID>) Utils
|
||||
.defaultGet(m_messageToGuids, clientID, new HashMap<Integer, MessageGUID>());
|
||||
return guids.get(messageID);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.IStore;
|
||||
import io.moquette.spi.persistence.MemorySessionStore;
|
||||
|
||||
public class MemoryStorageService implements IStore {
|
||||
|
||||
private MemorySessionStore m_sessionsStore;
|
||||
private MemoryMessagesStore m_messagesStore;
|
||||
|
||||
public MemoryStorageService() {
|
||||
m_messagesStore = new MemoryMessagesStore();
|
||||
m_sessionsStore = new MemorySessionStore(m_messagesStore);
|
||||
m_messagesStore.initStore();
|
||||
m_sessionsStore.initStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMessagesStore messagesStore() {
|
||||
return m_messagesStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISessionsStore sessionsStore() {
|
||||
return m_sessionsStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.interception.InterceptHandler;
|
||||
import io.moquette.persistence.MemoryStorageService;
|
||||
import io.moquette.server.netty.MessageBuilder;
|
||||
import io.moquette.server.netty.NettyUtils;
|
||||
import io.moquette.spi.IMatchingCondition;
|
||||
@@ -98,14 +99,8 @@ public class ProtocolProcessorTest {
|
||||
subscriptions = new SubscriptionsStore();
|
||||
subscriptions.init(memStorage.sessionsStore());
|
||||
m_processor = new ProtocolProcessor();
|
||||
m_processor.init(
|
||||
subscriptions,
|
||||
m_messagesStore,
|
||||
m_sessionStore,
|
||||
m_mockAuthenticator,
|
||||
true,
|
||||
new PermitAllAuthorizator(),
|
||||
NO_OBSERVERS_INTERCEPTOR);
|
||||
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true,
|
||||
new PermitAllAuthorizator(), NO_OBSERVERS_INTERCEPTOR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -176,13 +171,7 @@ public class ProtocolProcessorTest {
|
||||
// simulate a connect that register a clientID to an IoSession
|
||||
MemoryStorageService storageService = new MemoryStorageService();
|
||||
subs.init(storageService.sessionsStore());
|
||||
m_processor.init(
|
||||
subs,
|
||||
m_messagesStore,
|
||||
m_sessionStore,
|
||||
null,
|
||||
true,
|
||||
new PermitAllAuthorizator(),
|
||||
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
|
||||
NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
EmbeddedChannel firstReceiverChannel = new EmbeddedChannel();
|
||||
@@ -228,10 +217,8 @@ public class ProtocolProcessorTest {
|
||||
|
||||
// Verify
|
||||
assertTrue(m_channel.readOutbound() instanceof MqttSubAckMessage);
|
||||
Subscription expectedSubscription = new Subscription(
|
||||
FAKE_CLIENT_ID,
|
||||
new Topic(FAKE_TOPIC),
|
||||
MqttQoS.AT_MOST_ONCE);
|
||||
Subscription expectedSubscription = new Subscription(FAKE_CLIENT_ID, new Topic(FAKE_TOPIC),
|
||||
MqttQoS.AT_MOST_ONCE);
|
||||
assertTrue(subscriptions.contains(expectedSubscription));
|
||||
}
|
||||
|
||||
@@ -244,13 +231,7 @@ public class ProtocolProcessorTest {
|
||||
when(mockAuthorizator.canRead(eq(new Topic(FAKE_TOPIC)), eq(fakeUserName), eq(FAKE_CLIENT_ID)))
|
||||
.thenReturn(false);
|
||||
|
||||
m_processor.init(
|
||||
subscriptions,
|
||||
m_messagesStore,
|
||||
m_sessionStore,
|
||||
m_mockAuthenticator,
|
||||
true,
|
||||
mockAuthorizator,
|
||||
m_processor.init(subscriptions, m_messagesStore, m_sessionStore, m_mockAuthenticator, true, mockAuthorizator,
|
||||
NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
// Exercise
|
||||
@@ -288,10 +269,8 @@ public class ProtocolProcessorTest {
|
||||
|
||||
// Verify
|
||||
assertEquals(1, subscriptions.size());
|
||||
Subscription expectedSubscription = new Subscription(
|
||||
FAKE_CLIENT_ID,
|
||||
new Topic(FAKE_TOPIC),
|
||||
MqttQoS.AT_MOST_ONCE);
|
||||
Subscription expectedSubscription = new Subscription(FAKE_CLIENT_ID, new Topic(FAKE_TOPIC),
|
||||
MqttQoS.AT_MOST_ONCE);
|
||||
|
||||
assertTrue(subscriptions.contains(expectedSubscription));
|
||||
}
|
||||
@@ -332,10 +311,7 @@ public class ProtocolProcessorTest {
|
||||
@Test
|
||||
public void testPublishOfRetainedMessage_afterNewSubscription() throws Exception {
|
||||
// simulate a connect that register a clientID to an IoSession
|
||||
final Subscription subscription = new Subscription(
|
||||
FAKE_PUBLISHER_ID,
|
||||
new Topic(FAKE_TOPIC),
|
||||
MqttQoS.AT_MOST_ONCE);
|
||||
final Subscription subscription = new Subscription(FAKE_PUBLISHER_ID, new Topic(FAKE_TOPIC), MqttQoS.AT_MOST_ONCE);
|
||||
|
||||
// subscriptions.matches(topic) redefine the method to return true
|
||||
SubscriptionsStore subs = new SubscriptionsStore() {
|
||||
@@ -353,13 +329,7 @@ public class ProtocolProcessorTest {
|
||||
subs.init(storageService.sessionsStore());
|
||||
|
||||
// simulate a connect that register a clientID to an IoSession
|
||||
m_processor.init(
|
||||
subs,
|
||||
m_messagesStore,
|
||||
m_sessionStore,
|
||||
null,
|
||||
true,
|
||||
new PermitAllAuthorizator(),
|
||||
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
|
||||
NO_OBSERVERS_INTERCEPTOR);
|
||||
MqttConnectMessage connectMessage = MessageBuilder.connect().clientId(FAKE_PUBLISHER_ID)
|
||||
.protocolVersion(MqttVersion.MQTT_3_1).cleanSession(true).build();
|
||||
@@ -398,13 +368,7 @@ public class ProtocolProcessorTest {
|
||||
retainedMessage.setClientID(FAKE_PUBLISHER_ID);
|
||||
m_messagesStore.storePublishForFuture(retainedMessage);
|
||||
|
||||
m_processor.init(
|
||||
subs,
|
||||
m_messagesStore,
|
||||
m_sessionStore,
|
||||
null,
|
||||
true,
|
||||
new PermitAllAuthorizator(),
|
||||
m_processor.init(subs, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
|
||||
NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
MqttConnectMessage connectMessage = MessageBuilder.connect().clientId(FAKE_PUBLISHER_ID)
|
||||
@@ -427,13 +391,7 @@ public class ProtocolProcessorTest {
|
||||
List<Subscription> inactiveSubscriptions = Collections.singletonList(inactiveSub);
|
||||
when(mockedSubscriptions.matches(eq(new Topic("/topic")))).thenReturn(inactiveSubscriptions);
|
||||
m_processor = new ProtocolProcessor();
|
||||
m_processor.init(
|
||||
mockedSubscriptions,
|
||||
m_messagesStore,
|
||||
m_sessionStore,
|
||||
null,
|
||||
true,
|
||||
new PermitAllAuthorizator(),
|
||||
m_processor.init(mockedSubscriptions, m_messagesStore, m_sessionStore, null, true, new PermitAllAuthorizator(),
|
||||
NO_OBSERVERS_INTERCEPTOR);
|
||||
|
||||
// Exercise
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package io.moquette.spi.impl;
|
||||
|
||||
import io.moquette.persistence.MemoryStorageService;
|
||||
import io.moquette.server.netty.MessageBuilder;
|
||||
import io.moquette.server.netty.NettyUtils;
|
||||
import io.moquette.spi.ClientSession;
|
||||
|
||||
@@ -19,7 +19,7 @@ package io.moquette.spi.impl.subscriptions;
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.ISessionsStore.ClientTopicCouple;
|
||||
import io.moquette.spi.impl.MemoryStorageService;
|
||||
import io.moquette.persistence.MemoryStorageService;
|
||||
import io.netty.handler.codec.mqtt.MqttQoS;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
|
||||
package io.moquette.spi.persistence;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import io.moquette.BrokerConstants;
|
||||
import io.moquette.server.IntegrationUtils;
|
||||
import io.moquette.server.config.IConfig;
|
||||
import io.moquette.server.config.MemoryConfig;
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.ISessionsStore.ClientTopicCouple;
|
||||
import io.moquette.spi.MessageGUID;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.impl.subscriptions.Topic;
|
||||
import io.netty.handler.codec.mqtt.MqttQoS;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class MapDBPersistentStoreTest {
|
||||
|
||||
public static final String TEST_CLIENT = "TestClient";
|
||||
MapDBPersistentStore m_storageService;
|
||||
ISessionsStore m_sessionsStore;
|
||||
IMessagesStore m_messagesStore;
|
||||
|
||||
private ScheduledExecutorService scheduler;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
IntegrationUtils.cleanPersistenceFile(BrokerConstants.DEFAULT_PERSISTENT_PATH);
|
||||
Properties props = new Properties();
|
||||
props.setProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, BrokerConstants.DEFAULT_PERSISTENT_PATH);
|
||||
IConfig conf = new MemoryConfig(props);
|
||||
m_storageService = new MapDBPersistentStore(conf, scheduler);
|
||||
m_storageService.initStore();
|
||||
m_messagesStore = m_storageService.messagesStore();
|
||||
m_sessionsStore = m_storageService.sessionsStore();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (m_storageService != null) {
|
||||
m_storageService.close();
|
||||
}
|
||||
|
||||
scheduler.shutdown();
|
||||
IntegrationUtils.cleanPersistenceFile(BrokerConstants.DEFAULT_PERSISTENT_PATH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overridingSubscriptions() {
|
||||
ClientSession session1 = m_sessionsStore.createNewSession("SESSION_ID_1", true);
|
||||
|
||||
// Subscribe on /topic with QOSType.MOST_ONE
|
||||
Subscription oldSubscription = new Subscription(session1.clientID, new Topic("/topic"), MqttQoS.AT_MOST_ONCE);
|
||||
session1.subscribe(oldSubscription);
|
||||
|
||||
// Subscribe on /topic again that overrides the previous subscription.
|
||||
Subscription overridingSubscription = new Subscription(
|
||||
session1.clientID,
|
||||
new Topic("/topic"),
|
||||
MqttQoS.EXACTLY_ONCE);
|
||||
session1.subscribe(overridingSubscription);
|
||||
|
||||
// Verify
|
||||
List<ClientTopicCouple> subscriptions = m_sessionsStore.listAllSubscriptions();
|
||||
assertEquals(1, subscriptions.size());
|
||||
Subscription sub = m_sessionsStore.getSubscription(subscriptions.get(0));
|
||||
assertEquals(overridingSubscription.getRequestedQos(), sub.getRequestedQos());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextPacketID_notExistingClientSession() {
|
||||
int packetId = m_sessionsStore.nextPacketID("NOT_EXISTING_CLI");
|
||||
assertEquals(1, packetId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextPacketID_existingClientSession() {
|
||||
// Force creation of inflight map for the CLIENT session
|
||||
int packetId = m_sessionsStore.nextPacketID("CLIENT");
|
||||
assertEquals(1, packetId);
|
||||
|
||||
// request a second packetID
|
||||
packetId = m_sessionsStore.nextPacketID("CLIENT");
|
||||
assertEquals(2, packetId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextPacketID() {
|
||||
// request a first ID
|
||||
|
||||
int packetId = m_sessionsStore.nextPacketID("CLIENT");
|
||||
m_sessionsStore.inFlight("CLIENT", packetId, new MessageGUID("ABCDE")); // simulate an
|
||||
// inflight
|
||||
assertEquals(1, packetId);
|
||||
|
||||
// release the ID
|
||||
m_sessionsStore.inFlightAck("CLIENT", packetId);
|
||||
|
||||
// request a second packetID, counter restarts from 0
|
||||
packetId = m_sessionsStore.nextPacketID("CLIENT");
|
||||
assertEquals(1, packetId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseShutdownCommitTask() throws InterruptedException {
|
||||
m_storageService.close();
|
||||
|
||||
// verify the executor is shutdown
|
||||
assertTrue(
|
||||
"Storage service scheduler can't be stopped in 3 seconds",
|
||||
m_storageService.m_scheduler.awaitTermination(3, TimeUnit.SECONDS));
|
||||
assertTrue(m_storageService.m_scheduler.isTerminated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDropMessagesInSessionCleanAllNotRetainedStoredMessages() {
|
||||
m_sessionsStore.createNewSession("TestClient", true);
|
||||
IMessagesStore.StoredMessage publishToStore = new IMessagesStore.StoredMessage(
|
||||
"Hello".getBytes(),
|
||||
MqttQoS.EXACTLY_ONCE,
|
||||
"/topic");
|
||||
publishToStore.setClientID(TEST_CLIENT);
|
||||
publishToStore.setMessageID(1);
|
||||
publishToStore.setRetained(false);
|
||||
MessageGUID guid = m_messagesStore.storePublishForFuture(publishToStore);
|
||||
|
||||
// Exercise
|
||||
m_messagesStore.dropMessagesInSession("TestClient");
|
||||
|
||||
// Verify the message store for session is empty.
|
||||
IMessagesStore.StoredMessage storedPublish = m_messagesStore.getMessageByGuid(guid);
|
||||
assertNull("The stored message must'n be present anymore", storedPublish);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDropMessagesInSessionDoesntCleanAnyRetainedStoredMessages() {
|
||||
m_sessionsStore.createNewSession("TestClient", true);
|
||||
IMessagesStore.StoredMessage publishToStore = new IMessagesStore.StoredMessage(
|
||||
"Hello".getBytes(),
|
||||
MqttQoS.EXACTLY_ONCE,
|
||||
"/topic");
|
||||
publishToStore.setClientID(TEST_CLIENT);
|
||||
publishToStore.setMessageID(1);
|
||||
publishToStore.setRetained(true);
|
||||
MessageGUID guid = m_messagesStore.storePublishForFuture(publishToStore);
|
||||
|
||||
// Exercise
|
||||
m_messagesStore.dropMessagesInSession("TestClient");
|
||||
|
||||
// Verify the message store for session is empty.
|
||||
IMessagesStore.StoredMessage storedPublish = m_messagesStore.getMessageByGuid(guid);
|
||||
assertNotNull("The stored retained message must be present after client's session drop", storedPublish);
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012-2017 The original author or authors
|
||||
* ------------------------------------------------------
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* and Apache License v2.0 which accompanies this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* The Apache License v2.0 is available at
|
||||
* http://www.opensource.org/licenses/apache2.0.php
|
||||
*
|
||||
* You may elect to redistribute this code under either of these licenses.
|
||||
*/
|
||||
|
||||
package io.moquette.spi.persistence;
|
||||
|
||||
import io.moquette.server.Constants;
|
||||
import io.moquette.spi.ClientSession;
|
||||
import io.moquette.spi.IMessagesStore;
|
||||
import io.moquette.spi.IMessagesStore.StoredMessage;
|
||||
import io.moquette.spi.ISessionsStore;
|
||||
import io.moquette.spi.MessageGUID;
|
||||
import io.moquette.spi.impl.Utils;
|
||||
import io.moquette.spi.impl.subscriptions.Subscription;
|
||||
import io.moquette.spi.impl.subscriptions.Topic;
|
||||
import io.moquette.spi.persistence.MapDBPersistentStore.PersistentSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class MemorySessionStore implements ISessionsStore {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MemorySessionStore.class);
|
||||
|
||||
private Map<String, Map<Topic, Subscription>> m_persistentSubscriptions = new HashMap<>();
|
||||
|
||||
private Map<String, MapDBPersistentStore.PersistentSession> m_persistentSessions = new HashMap<>();
|
||||
|
||||
// maps clientID->[MessageId -> guid]
|
||||
private Map<String, Map<Integer, MessageGUID>> m_inflightStore = new HashMap<>();
|
||||
// private Map<String, Set<Integer>> m_inflightIDs = new HashMap<>();
|
||||
// maps clientID->BlockingQueue
|
||||
private Map<String, BlockingQueue<StoredMessage>> queues = new HashMap<>();
|
||||
// maps clientID->[MessageId -> guid]
|
||||
private Map<String, Map<Integer, MessageGUID>> m_secondPhaseStore = new HashMap<>();
|
||||
|
||||
private final IMessagesStore m_messagesStore;
|
||||
|
||||
public MemorySessionStore(IMessagesStore messagesStore) {
|
||||
this.m_messagesStore = messagesStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubscription(Topic topic, String clientID) {
|
||||
LOG.debug("removeSubscription topic filter: {} for clientID: {}", topic, clientID);
|
||||
if (!m_persistentSubscriptions.containsKey(clientID)) {
|
||||
return;
|
||||
}
|
||||
Map<Topic, Subscription> clientSubscriptions = m_persistentSubscriptions.get(clientID);
|
||||
clientSubscriptions.remove(topic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initStore() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNewSubscription(Subscription newSubscription) {
|
||||
final String clientID = newSubscription.getClientId();
|
||||
if (!m_persistentSubscriptions.containsKey(clientID)) {
|
||||
m_persistentSubscriptions.put(clientID, new HashMap<Topic, Subscription>());
|
||||
}
|
||||
|
||||
m_persistentSubscriptions.get(clientID).put(newSubscription.getTopicFilter(), newSubscription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipeSubscriptions(String clientID) {
|
||||
m_persistentSubscriptions.remove(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String clientID) {
|
||||
return m_persistentSubscriptions.containsKey(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession createNewSession(String clientID, boolean cleanSession) {
|
||||
LOG.debug("createNewSession for client <{}>", clientID);
|
||||
if (m_persistentSessions.containsKey(clientID)) {
|
||||
LOG.error("already exists a session for client <{}>, bad condition", clientID);
|
||||
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
|
||||
}
|
||||
LOG.debug("clientID {} is a newcome, creating it's empty subscriptions set", clientID);
|
||||
m_persistentSubscriptions.put(clientID, new HashMap<Topic, Subscription>());
|
||||
m_persistentSessions.put(clientID, new MapDBPersistentStore.PersistentSession(cleanSession));
|
||||
return new ClientSession(clientID, m_messagesStore, this, cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSession sessionForClient(String clientID) {
|
||||
if (!m_persistentSessions.containsKey(clientID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MapDBPersistentStore.PersistentSession storedSession = m_persistentSessions.get(clientID);
|
||||
return new ClientSession(clientID, m_messagesStore, this, storedSession.cleanSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ClientSession> getAllSessions() {
|
||||
Collection<ClientSession> result = new ArrayList<>();
|
||||
for (Map.Entry<String, PersistentSession> entry : m_persistentSessions.entrySet()) {
|
||||
result.add(new ClientSession(entry.getKey(), m_messagesStore, this, entry.getValue().cleanSession));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCleanStatus(String clientID, boolean cleanSession) {
|
||||
m_persistentSessions.put(clientID, new MapDBPersistentStore.PersistentSession(cleanSession));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientTopicCouple> listAllSubscriptions() {
|
||||
List<ClientTopicCouple> allSubscriptions = new ArrayList<>();
|
||||
for (Map.Entry<String, Map<Topic, Subscription>> entry : m_persistentSubscriptions.entrySet()) {
|
||||
for (Subscription sub : entry.getValue().values()) {
|
||||
allSubscriptions.add(sub.asClientTopicCouple());
|
||||
}
|
||||
}
|
||||
return allSubscriptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription getSubscription(ClientTopicCouple couple) {
|
||||
Map<Topic, Subscription> subscriptions = m_persistentSubscriptions.get(couple.clientID);
|
||||
if (subscriptions == null || subscriptions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return subscriptions.get(couple.topicFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Subscription> getSubscriptions() {
|
||||
List<Subscription> subscriptions = new ArrayList<>();
|
||||
for (Map.Entry<String, Map<Topic, Subscription>> entry : m_persistentSubscriptions.entrySet()) {
|
||||
subscriptions.addAll(entry.getValue().values());
|
||||
}
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inFlightAck(String clientID, int messageID) {
|
||||
Map<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
|
||||
if (m == null) {
|
||||
LOG.error("Can't find the inFlight record for client <{}>", clientID);
|
||||
return;
|
||||
}
|
||||
m.remove(messageID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inFlight(String clientID, int messageID, MessageGUID guid) {
|
||||
Map<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
|
||||
if (m == null) {
|
||||
m = new HashMap<>();
|
||||
}
|
||||
m.put(messageID, guid);
|
||||
this.m_inflightStore.put(clientID, m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next valid packetIdentifier for the given client session.
|
||||
*/
|
||||
@Override
|
||||
public int nextPacketID(String clientID) {
|
||||
Map<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
|
||||
if (m == null) {
|
||||
m = new HashMap<>();
|
||||
int nextPacketId = 1;
|
||||
m.put(nextPacketId, null);
|
||||
return nextPacketId;
|
||||
}
|
||||
int maxId = Collections.max(m.keySet());
|
||||
int nextPacketId = (maxId + 1) % 0xFFFF;
|
||||
m.put(nextPacketId, null);
|
||||
return nextPacketId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockingQueue<StoredMessage> queue(String clientID) {
|
||||
return Utils.defaultGet(queues, clientID, new ArrayBlockingQueue<StoredMessage>(Constants.MAX_MESSAGE_QUEUE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropQueue(String clientID) {
|
||||
queues.remove(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveInFlightToSecondPhaseAckWaiting(String clientID, int messageID) {
|
||||
LOG.info("acknowledging inflight clientID <{}> messageID {}", clientID, messageID);
|
||||
Map<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
|
||||
if (m == null) {
|
||||
LOG.error("Can't find the inFlight record for client <{}>", clientID);
|
||||
return;
|
||||
}
|
||||
MessageGUID guid = m.remove(messageID);
|
||||
|
||||
LOG.info("Moving to second phase store");
|
||||
Map<Integer, MessageGUID> messageIDs = Utils
|
||||
.defaultGet(m_secondPhaseStore, clientID, new HashMap<Integer, MessageGUID>());
|
||||
messageIDs.put(messageID, guid);
|
||||
m_secondPhaseStore.put(clientID, messageIDs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageGUID secondPhaseAcknowledged(String clientID, int messageID) {
|
||||
Map<Integer, MessageGUID> messageIDs = Utils
|
||||
.defaultGet(m_secondPhaseStore, clientID, new HashMap<Integer, MessageGUID>());
|
||||
MessageGUID guid = messageIDs.remove(messageID);
|
||||
m_secondPhaseStore.put(clientID, messageIDs);
|
||||
return guid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredMessage getInflightMessage(String clientID, int messageID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInflightMessagesNo(String clientID) {
|
||||
Map<Integer, MessageGUID> inflightMessages = m_inflightStore.get(clientID);
|
||||
if (inflightMessages == null)
|
||||
return 0;
|
||||
else
|
||||
return inflightMessages.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingPublishMessagesNo(String clientID) {
|
||||
return m_messagesStore.getPendingPublishMessages(clientID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSecondPhaseAckPendingMessages(String clientID) {
|
||||
Map<Integer, MessageGUID> pendingAcks = m_secondPhaseStore.get(clientID);
|
||||
if (pendingAcks == null)
|
||||
return 0;
|
||||
else
|
||||
return pendingAcks.size();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user