/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2014 Boundless
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.cluster.hazelcast;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createMockBuilder;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.easymock.Capture;
import org.easymock.CaptureType;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.event.CatalogListener;
import org.geoserver.cluster.ClusterConfig;
import org.geoserver.cluster.ClusterConfigWatcher;
import org.geoserver.cluster.Event;
import org.geoserver.config.ConfigurationListener;
import org.geoserver.config.GeoServer;
import org.geoserver.platform.resource.Files;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.Resources;
import org.junit.Before;
import com.google.common.collect.Sets;
import com.hazelcast.core.Cluster;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.ITopic;
import com.hazelcast.core.Member;
import com.hazelcast.core.Message;
import com.hazelcast.core.MessageListener;
public abstract class HzSynchronizerTest {
final public static String TOPIC_NAME = "geoserver.config";
final public static String ACK_TOPIC_NAME = "geoserver.config.ack";
final public static int SYNC_DELAY = 1;
//protected void setUpSpring(List<String> springContextLocations) {
// We're going to set up the synchronizer manually so ignore the spring context.
//}
public static Resource tmpDir() throws IOException {
Resource root = Files.asResource(new File(System.getProperty("java.io.tmpdir", ".")));
Resource directory = Resources.createRandom("tmp", "", root);
do {
FileUtils.forceDelete(directory.dir());
} while (Resources.exists(directory));
FileUtils.forceMkdir(directory.dir());
return Files.asResource(directory.dir());
}
@Before
@SuppressWarnings("unchecked")
public void setUp() throws Exception {
hz = createMock(HazelcastInstance.class);
// Create a "partial mock" of the HzCluster
cluster = createMockBuilder(HzCluster.class)
.addMockedMethods("getHz", "isEnabled", "getRawCatalog", "getAckTimeoutMillis")
.createMock();
topic = createMock(ITopic.class);
ackTopic = createMock(ITopic.class);
configWatcher = createMock(ClusterConfigWatcher.class);
clusterConfig = createMock(ClusterConfig.class);
captureTopicListener = new Capture<MessageListener<Event>>();
captureAckTopicListener = new Capture<MessageListener<UUID>>();
captureAckTopicPublish = new Capture<UUID>();
localAddress = new InetSocketAddress( localAddress(42) , 5000);
remoteAddress = new InetSocketAddress( localAddress(54) , 5000);
catalog = createMock(Catalog.class);
Cluster cluster = createMock(Cluster.class);
Member localMember = createMock(Member.class);
Member remoteMember = createMock(Member.class);
expect(this.cluster.getHz()).andStubReturn(hz);
expect(this.cluster.isEnabled()).andStubReturn(true);
expect(this.cluster.getRawCatalog()).andStubReturn(catalog);;
expect(this.cluster.getAckTimeoutMillis()).andStubReturn(100);
expect(hz.<Event>getTopic(TOPIC_NAME)).andStubReturn(topic);
expect(topic.addMessageListener(capture(captureTopicListener))).andReturn("fake-id");
expectLastCall().anyTimes();
expect(hz.<UUID>getTopic(ACK_TOPIC_NAME)).andStubReturn(ackTopic);
expect(ackTopic.addMessageListener(capture(captureAckTopicListener))).andReturn("fake-id");
expectLastCall().anyTimes();
ackTopic.publish(EasyMock.capture(captureAckTopicPublish));EasyMock.expectLastCall().andStubAnswer(new IAnswer<Object>() {
@Override
public Object answer() throws Throwable {
Message<UUID> message = createMock(Message.class);
expect(message.getMessageObject()).andStubReturn(captureAckTopicPublish.getValue());
EasyMock.replay(message);
for(MessageListener<UUID> listener: captureAckTopicListener.getValues()) {
listener.onMessage(message);
}
return null;
}
});
expect(cluster.getLocalMember()).andStubReturn(localMember);
expect(localMember.getSocketAddress()).andStubReturn(localAddress);
expect(remoteMember.getSocketAddress()).andStubReturn(remoteAddress);
expect(localMember.localMember()).andStubReturn(true);
expect(remoteMember.localMember()).andStubReturn(false);
expect(cluster.getMembers()).andStubReturn(Sets.newHashSet(localMember, remoteMember));
EasyMock.replay(cluster, localMember, remoteMember);
expect(hz.getCluster()).andStubReturn(cluster);
expect(configWatcher.get()).andStubReturn(clusterConfig);
expect(clusterConfig.getSyncDelay()).andStubReturn(SYNC_DELAY);
geoServer = createMock(GeoServer.class);
expect(geoServer.getCatalog()).andStubReturn(catalog);
gsListenerCapture = new Capture<ConfigurationListener>();
geoServer.addListener(capture(gsListenerCapture));expectLastCall().atLeastOnce();
catListenerCapture = new Capture<CatalogListener>();
catalog.addListener(capture(catListenerCapture));expectLastCall().atLeastOnce();
executor = createMock(ScheduledExecutorService.class);
captureExecutor = new Capture<Runnable>(CaptureType.ALL);
expect(executor.schedule(capture(captureExecutor), anyLong(), (TimeUnit)anyObject())).andStubReturn(null);
}
protected static InetAddress localAddress(int i) throws Exception {
return InetAddress.getByAddress(new byte[]{(byte) 192,(byte) 168,0,(byte) i});
}
MessageListener<Event> getListener() {
return captureTopicListener.getValue();
}
protected HazelcastInstance hz;
protected HzCluster cluster;
protected ITopic<Event> topic;
protected ITopic<UUID> ackTopic;
protected GeoServer geoServer;
protected Catalog catalog;
protected ClusterConfigWatcher configWatcher;
protected ClusterConfig clusterConfig;
protected ScheduledExecutorService executor;
protected InetSocketAddress localAddress;
protected InetSocketAddress remoteAddress;
protected Capture<ConfigurationListener> gsListenerCapture;
protected Capture<CatalogListener> catListenerCapture;
protected Capture<MessageListener<Event>> captureTopicListener;
protected Capture<MessageListener<UUID>> captureAckTopicListener;
protected Capture<Runnable> captureExecutor;
protected Capture<UUID> captureAckTopicPublish;
public List<Object> myMocks() {
return Arrays.asList(topic, ackTopic, configWatcher, clusterConfig, geoServer, catalog, hz, executor, cluster);
}
public HzSynchronizerTest() {
super();
}
protected ScheduledExecutorService getMockExecutor() {
return executor;
}
/**
* Return the HzSynchronizer instance to be tested. Override {@link HzSyncronizer#getExecutor}
* to return {@link #getMockExecutor}. Provide it with {@link #hz} and {@link #geoServer}.
*/
protected abstract HzSynchronizer getSynchronizer();
protected void initSynchronizer(HzSynchronizer sync) {
sync.initialize(configWatcher);
}
protected GeoServer getGeoServer(){
return geoServer;
}
protected Catalog getCatalog(){
return catalog;
}
/**
* Replay all the mocks on this test class, plus those specified
* @param mocks
*/
protected void replay(Object... mocks) {
EasyMock.replay(myMocks().toArray());
EasyMock.replay(mocks);
}
/**
* Reset all the mocks on this test class, plus those specified
* @param mocks
*/
protected void reset(Object... mocks) {
EasyMock.reset(myMocks().toArray());
EasyMock.reset(mocks);
}
/**
* Verify all the mocks on this test class, plus those specified
* @param mocks
*/
protected void verify(Object... mocks) {
EasyMock.verify(myMocks().toArray());
EasyMock.verify(mocks);
}
protected void waitForSync() throws Exception {
//Thread.sleep(SYNC_DELAY*1000+500); // Convert to millis, then add a little extra to be sure
List<Runnable> tasks = captureExecutor.getValues();
for (Iterator<Runnable> i = tasks.iterator(); i.hasNext();) {
Runnable task = i.next();
i.remove();
task.run();
}
}
void assertAcked(UUID... eventId) {
assertThat(captureAckTopicPublish.getValues(), hasItems(eventId));
}
}