GOSSIP-62 Implement Crdt PN-Counter
restored layout of pom.xml for minimal changes in PR Snapshot - I think I have the basic framework in place. No tests are passing and nothing works, but most of the calls and the builder are in place. capture working code starting working on example code Working examples GOSSIP-65 Implement crdt LWW-Element-Set LWWSet implemented + se/de + unit tests + jackson tests + DataTests GOSSIP-55 Added event handlers to notify share data and per node data changes Reformat code to match apache standard Fixed DataTest errors WRT PNCounter
This commit is contained in:
@ -73,6 +73,13 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -61,6 +61,13 @@ abstract class GrowOnlyCounterMixin {
|
||||
@JsonProperty("counters") abstract Map<String, Long> getCounters();
|
||||
}
|
||||
|
||||
abstract class PNCounterMixin {
|
||||
@JsonCreator
|
||||
PNCounterMixin(@JsonProperty("p-counters") Map<String, Long> up, @JsonProperty("n-counters") Map<String,Long> down) { }
|
||||
@JsonProperty("p-counters") abstract Map<String, Long> getPCounters();
|
||||
@JsonProperty("n-counters") abstract Map<String, Long> getNCounters();
|
||||
}
|
||||
|
||||
//If anyone wants to take a stab at this. please have at it
|
||||
//https://github.com/FasterXML/jackson-datatype-guava/blob/master/src/main/java/com/fasterxml/jackson/datatype/guava/ser/MultimapSerializer.java
|
||||
public class CrdtModule extends SimpleModule {
|
||||
@ -76,6 +83,7 @@ public class CrdtModule extends SimpleModule {
|
||||
context.setMixInAnnotations(OrSet.class, OrSetMixin.class);
|
||||
context.setMixInAnnotations(GrowOnlySet.class, GrowOnlySetMixin.class);
|
||||
context.setMixInAnnotations(GrowOnlyCounter.class, GrowOnlyCounterMixin.class);
|
||||
context.setMixInAnnotations(PNCounter.class, PNCounterMixin.class);
|
||||
context.setMixInAnnotations(LWWSet.class, LWWSetMixin.class);
|
||||
context.setMixInAnnotations(LWWSet.Timestamps.class, LWWSetTimestampsMixin.class);
|
||||
}
|
||||
|
139
gossip-base/src/main/java/org/apache/gossip/crdt/PNCounter.java
Normal file
139
gossip-base/src/main/java/org/apache/gossip/crdt/PNCounter.java
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.gossip.crdt;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.gossip.manager.GossipManager;
|
||||
|
||||
public class PNCounter implements CrdtCounter<Long, PNCounter> {
|
||||
|
||||
private final GrowOnlyCounter pCount;
|
||||
|
||||
private final GrowOnlyCounter nCount;
|
||||
|
||||
PNCounter(Map<String, Long> pCounters, Map<String, Long> nCounters) {
|
||||
pCount = new GrowOnlyCounter(pCounters);
|
||||
nCount = new GrowOnlyCounter(nCounters);
|
||||
}
|
||||
|
||||
public PNCounter(PNCounter starter, Builder builder) {
|
||||
GrowOnlyCounter.Builder pBuilder = builder.makeGrowOnlyCounterBuilder(builder.pCount());
|
||||
pCount = new GrowOnlyCounter(starter.pCount, pBuilder);
|
||||
GrowOnlyCounter.Builder nBuilder = builder.makeGrowOnlyCounterBuilder(builder.nCount());
|
||||
nCount = new GrowOnlyCounter(starter.nCount, nBuilder);
|
||||
}
|
||||
|
||||
public PNCounter(Builder builder) {
|
||||
GrowOnlyCounter.Builder pBuilder = builder.makeGrowOnlyCounterBuilder(builder.pCount());
|
||||
pCount = new GrowOnlyCounter(pBuilder);
|
||||
GrowOnlyCounter.Builder nBuilder = builder.makeGrowOnlyCounterBuilder(builder.nCount());
|
||||
nCount = new GrowOnlyCounter(nBuilder);
|
||||
}
|
||||
|
||||
public PNCounter(GossipManager manager) {
|
||||
pCount = new GrowOnlyCounter(manager);
|
||||
nCount = new GrowOnlyCounter(manager);
|
||||
}
|
||||
|
||||
public PNCounter(PNCounter starter, PNCounter other) {
|
||||
pCount = new GrowOnlyCounter(starter.pCount, other.pCount);
|
||||
nCount = new GrowOnlyCounter(starter.nCount, other.nCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PNCounter merge(PNCounter other) {
|
||||
return new PNCounter(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long value() {
|
||||
long pValue = (long) pCount.value();
|
||||
long nValue = (long) nCount.value();
|
||||
return pValue - nValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PNCounter optimize() {
|
||||
return new PNCounter(pCount.getCounters(), nCount.getCounters());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
PNCounter other = (PNCounter) obj;
|
||||
return value().longValue() == other.value().longValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PnCounter [pCount=" + pCount + ", nCount=" + nCount + ", value=" + value() + "]";
|
||||
}
|
||||
|
||||
Map<String, Long> getPCounters() {
|
||||
return pCount.getCounters();
|
||||
}
|
||||
|
||||
Map<String, Long> getNCounters() {
|
||||
return nCount.getCounters();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final GossipManager myManager;
|
||||
|
||||
private long value = 0L;
|
||||
|
||||
public Builder(GossipManager gossipManager) {
|
||||
myManager = gossipManager;
|
||||
}
|
||||
|
||||
public long pCount() {
|
||||
if (value > 0) {
|
||||
return value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long nCount() {
|
||||
if (value < 0) {
|
||||
return -value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public org.apache.gossip.crdt.GrowOnlyCounter.Builder makeGrowOnlyCounterBuilder(long value) {
|
||||
org.apache.gossip.crdt.GrowOnlyCounter.Builder ret = new org.apache.gossip.crdt.GrowOnlyCounter.Builder(
|
||||
myManager);
|
||||
ret.increment(value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public PNCounter.Builder increment(long delta) {
|
||||
value += delta;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PNCounter.Builder decrement(long delta) {
|
||||
value -= delta;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.gossip.crdt;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.gossip.LocalMember;
|
||||
import org.apache.gossip.manager.GossipManager;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PNCounterTest {
|
||||
|
||||
private List<GossipManager> mockManagers;
|
||||
|
||||
@Before
|
||||
public void setupMocks() {
|
||||
GossipManager manager1 = mock(GossipManager.class);
|
||||
LocalMember mockMember1 = mock(LocalMember.class);
|
||||
when(mockMember1.getId()).thenReturn("x");
|
||||
when(manager1.getMyself()).thenReturn(mockMember1);
|
||||
|
||||
GossipManager manager2 = mock(GossipManager.class);
|
||||
LocalMember mockMember2 = mock(LocalMember.class);
|
||||
when(mockMember2.getId()).thenReturn("y");
|
||||
when(manager2.getMyself()).thenReturn(mockMember2);
|
||||
|
||||
GossipManager manager3 = mock(GossipManager.class);
|
||||
LocalMember mockMember3 = mock(LocalMember.class);
|
||||
when(mockMember3.getId()).thenReturn("z");
|
||||
when(manager3.getMyself()).thenReturn(mockMember3);
|
||||
|
||||
mockManagers = new ArrayList<GossipManager>();
|
||||
mockManagers.add(manager1);
|
||||
mockManagers.add(manager2);
|
||||
mockManagers.add(manager3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existanceTest() {
|
||||
PNCounter counter = new PNCounter(mockManagers.get(0));
|
||||
Assert.assertEquals(0, (long) counter.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void localOperationTest() {
|
||||
PNCounter counter = new PNCounter(mockManagers.get(0));
|
||||
Assert.assertEquals(0, (long) counter.value());
|
||||
|
||||
counter = new PNCounter(counter, new PNCounter.Builder(mockManagers.get(0)).increment(5L));
|
||||
Assert.assertEquals(5, (long) counter.value());
|
||||
|
||||
counter = new PNCounter(counter, new PNCounter.Builder(mockManagers.get(0)).increment(4L));
|
||||
Assert.assertEquals(9, (long) counter.value());
|
||||
|
||||
counter = new PNCounter(counter, new PNCounter.Builder(mockManagers.get(0)).decrement(3L));
|
||||
Assert.assertEquals(6, (long) counter.value());
|
||||
|
||||
counter = new PNCounter(counter, new PNCounter.Builder(mockManagers.get(0)).decrement(12L));
|
||||
Assert.assertEquals(-6, (long) counter.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oddballLocalOperationTest() {
|
||||
PNCounter counter = new PNCounter(mockManagers.get(0));
|
||||
Assert.assertEquals(0, (long) counter.value());
|
||||
|
||||
counter = new PNCounter(counter, new PNCounter.Builder(mockManagers.get(0)).increment(-5L));
|
||||
Assert.assertEquals(-5, (long) counter.value());
|
||||
|
||||
counter = new PNCounter(counter, new PNCounter.Builder(mockManagers.get(0)).increment(4L));
|
||||
Assert.assertEquals(-1, (long) counter.value());
|
||||
|
||||
counter = new PNCounter(counter, new PNCounter.Builder(mockManagers.get(0)).decrement(-3L));
|
||||
Assert.assertEquals(2, (long) counter.value());
|
||||
|
||||
counter = new PNCounter(counter, new PNCounter.Builder(mockManagers.get(0)).decrement(-12L));
|
||||
Assert.assertEquals(14, (long) counter.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void networkLikeOperations() {
|
||||
PNCounter counter1 = new PNCounter(mockManagers.get(0));
|
||||
PNCounter counter2 = new PNCounter(mockManagers.get(1));
|
||||
PNCounter counter3 = new PNCounter(mockManagers.get(2));
|
||||
|
||||
Assert.assertEquals(0, (long) counter1.value());
|
||||
Assert.assertEquals(0, (long) counter2.value());
|
||||
Assert.assertEquals(0, (long) counter3.value());
|
||||
|
||||
counter1 = new PNCounter(counter1, new PNCounter.Builder(mockManagers.get(0)).increment(3L));
|
||||
Assert.assertEquals(3, (long) counter1.value());
|
||||
|
||||
counter2 = new PNCounter(counter2, new PNCounter.Builder(mockManagers.get(1)).increment(5L));
|
||||
Assert.assertEquals(5, (long) counter2.value());
|
||||
|
||||
counter3 = new PNCounter(counter3, new PNCounter.Builder(mockManagers.get(2)).decrement(7L));
|
||||
Assert.assertEquals(-7, (long) counter3.value());
|
||||
|
||||
// 2 becomes 2 and 1
|
||||
counter2 = counter2.merge(counter1);
|
||||
Assert.assertEquals(8, (long) counter2.value());
|
||||
|
||||
// 3 becomes 3 and 1
|
||||
counter3 = counter3.merge(counter1);
|
||||
Assert.assertEquals(-4, (long) counter3.value());
|
||||
|
||||
// 3 becomes all
|
||||
counter3 = counter3.merge(counter2);
|
||||
Assert.assertEquals(1, (long) counter3.value());
|
||||
|
||||
// 2 becomes all - different order
|
||||
counter2 = counter2.merge(counter3);
|
||||
Assert.assertEquals(1, (long) counter3.value());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.gossip.examples;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.gossip.GossipSettings;
|
||||
import org.apache.gossip.LocalMember;
|
||||
import org.apache.gossip.RemoteMember;
|
||||
import org.apache.gossip.crdt.PNCounter;
|
||||
import org.apache.gossip.manager.GossipManager;
|
||||
import org.apache.gossip.manager.GossipManagerBuilder;
|
||||
import org.apache.gossip.model.SharedDataMessage;
|
||||
|
||||
public class StandAlonePNCounter {
|
||||
private static ExampleCommon common = new ExampleCommon();
|
||||
private static String lastInput = "{None}";
|
||||
|
||||
public static void main(String[] args) throws InterruptedException, IOException {
|
||||
args = common.checkArgsForClearFlag(args);
|
||||
GossipSettings s = new GossipSettings();
|
||||
s.setWindowSize(1000);
|
||||
s.setGossipInterval(100);
|
||||
GossipManager gossipService = GossipManagerBuilder
|
||||
.newBuilder()
|
||||
.cluster("mycluster")
|
||||
.uri(URI.create(args[0])).id(args[1])
|
||||
.gossipMembers(
|
||||
Arrays.asList(new RemoteMember("mycluster", URI.create(args[2]), args[3])))
|
||||
.gossipSettings(s)
|
||||
.build();
|
||||
gossipService.init();
|
||||
|
||||
new Thread(() -> {
|
||||
while (true) {
|
||||
common.optionallyClearTerminal();
|
||||
printLiveMembers(gossipService);
|
||||
printDeadMambers(gossipService);
|
||||
printValues(gossipService);
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
String line = null;
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
|
||||
while ((line = br.readLine()) != null) {
|
||||
System.out.println(line);
|
||||
char op = line.charAt(0);
|
||||
char blank = line.charAt(1);
|
||||
String val = line.substring(2);
|
||||
Long l = null;
|
||||
boolean valid = true;
|
||||
try {
|
||||
l = Long.valueOf(val);
|
||||
} catch (NumberFormatException ex) {
|
||||
valid = false;
|
||||
}
|
||||
valid = valid &&
|
||||
(
|
||||
(blank == ' ') &&
|
||||
((op == 'i') || (op == 'd'))
|
||||
);
|
||||
if (valid) {
|
||||
if (op == 'i') {
|
||||
increment(l, gossipService);
|
||||
} else if (op == 'd') {
|
||||
decrement(l, gossipService);
|
||||
}
|
||||
}
|
||||
setLastInput(line,valid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void printValues(GossipManager gossipService) {
|
||||
System.out.println("Last Input: " + getLastInput());
|
||||
System.out.println("---------- " + (gossipService.findCrdt("myPNCounter") == null ? ""
|
||||
: gossipService.findCrdt("myPNCounter").value()));
|
||||
System.out.println("********** " + gossipService.findCrdt("myPNCounter"));
|
||||
}
|
||||
|
||||
private static void printDeadMambers(GossipManager gossipService) {
|
||||
List<LocalMember> members = gossipService.getDeadMembers();
|
||||
if (members.isEmpty()) {
|
||||
System.out.println("Dead: (none)");
|
||||
return;
|
||||
}
|
||||
System.out.println("Dead: " + members.get(0));
|
||||
for (int i = 1; i < members.size(); i++) {
|
||||
System.out.println(" : " + members.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static void printLiveMembers(GossipManager gossipService) {
|
||||
List<LocalMember> members = gossipService.getLiveMembers();
|
||||
if (members.isEmpty()) {
|
||||
System.out.println("Live: (none)");
|
||||
return;
|
||||
}
|
||||
System.out.println("Live: " + members.get(0));
|
||||
for (int i = 1; i < members.size(); i++) {
|
||||
System.out.println(" : " + members.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static void increment(Long l, GossipManager gossipManager) {
|
||||
PNCounter c = (PNCounter) gossipManager.findCrdt("myPNCounter");
|
||||
if (c == null) {
|
||||
c = new PNCounter(new PNCounter.Builder(gossipManager).increment((l)));
|
||||
} else {
|
||||
c = new PNCounter(c, new PNCounter.Builder(gossipManager).increment((l)));
|
||||
}
|
||||
SharedDataMessage m = new SharedDataMessage();
|
||||
m.setExpireAt(Long.MAX_VALUE);
|
||||
m.setKey("myPNCounter");
|
||||
m.setPayload(c);
|
||||
m.setTimestamp(System.currentTimeMillis());
|
||||
gossipManager.merge(m);
|
||||
}
|
||||
|
||||
private static void decrement(Long l, GossipManager gossipManager) {
|
||||
PNCounter c = (PNCounter) gossipManager.findCrdt("myPNCounter");
|
||||
if (c == null) {
|
||||
c = new PNCounter(new PNCounter.Builder(gossipManager).decrement((l)));
|
||||
} else {
|
||||
c = new PNCounter(c, new PNCounter.Builder(gossipManager).decrement((l)));
|
||||
}
|
||||
SharedDataMessage m = new SharedDataMessage();
|
||||
m.setExpireAt(Long.MAX_VALUE);
|
||||
m.setKey("myPNCounter");
|
||||
m.setPayload(c);
|
||||
m.setTimestamp(System.currentTimeMillis());
|
||||
gossipManager.merge(m);
|
||||
}
|
||||
|
||||
private static void setLastInput(String input, boolean valid) {
|
||||
lastInput = input;
|
||||
if (! valid) {
|
||||
lastInput += " (invalid)";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getLastInput() {
|
||||
return lastInput;
|
||||
}
|
||||
|
||||
}
|
@ -17,29 +17,35 @@
|
||||
*/
|
||||
package org.apache.gossip;
|
||||
|
||||
import io.teknek.tunit.TUnit;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.gossip.crdt.GrowOnlyCounter;
|
||||
import org.apache.gossip.crdt.GrowOnlySet;
|
||||
import org.apache.gossip.crdt.LWWSet;
|
||||
import org.apache.gossip.crdt.OrSet;
|
||||
import org.apache.gossip.crdt.PNCounter;
|
||||
import org.apache.gossip.manager.GossipManager;
|
||||
import org.apache.gossip.manager.GossipManagerBuilder;
|
||||
import org.apache.gossip.model.PerNodeDataMessage;
|
||||
import org.apache.gossip.model.SharedDataMessage;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import io.teknek.tunit.TUnit;
|
||||
|
||||
public class DataTest extends AbstractIntegrationBase {
|
||||
|
||||
private String orSetKey = "cror";
|
||||
private String lwwSetKey = "crlww";
|
||||
private String gCounterKey = "crdtgc";
|
||||
private String pnCounterKey = "crdtpn";
|
||||
|
||||
@Test
|
||||
public void dataTest() throws InterruptedException, UnknownHostException, URISyntaxException {
|
||||
@ -57,8 +63,12 @@ public class DataTest extends AbstractIntegrationBase {
|
||||
final int clusterMembers = 2;
|
||||
for (int i = 1; i < clusterMembers + 1; ++i) {
|
||||
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (50000 + i));
|
||||
GossipManager gossipService = GossipManagerBuilder.newBuilder().cluster(cluster).uri(uri)
|
||||
.id(i + "").gossipMembers(startupMembers).gossipSettings(settings).build();
|
||||
GossipManager gossipService = GossipManagerBuilder
|
||||
.newBuilder()
|
||||
.cluster(cluster).uri(uri)
|
||||
.id(i + "")
|
||||
.gossipMembers(startupMembers)
|
||||
.gossipSettings(settings).build();
|
||||
clients.add(gossipService);
|
||||
gossipService.init();
|
||||
register(gossipService);
|
||||
@ -89,18 +99,14 @@ public class DataTest extends AbstractIntegrationBase {
|
||||
return x.getPayload();
|
||||
}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo("c");
|
||||
|
||||
|
||||
givenDifferentDatumsInSet(clients);
|
||||
assertThatListIsMerged(clients);
|
||||
|
||||
testOrSet(clients);
|
||||
testLWWSet(clients);
|
||||
|
||||
// test g counter
|
||||
givenDifferentIncrement(clients);
|
||||
assertThatCountIsUpdated(clients, 3);
|
||||
givenIncreaseOther(clients);
|
||||
assertThatCountIsUpdated(clients, 7);
|
||||
testGrowOnlyCounter(clients);
|
||||
testPNCounter(clients);
|
||||
|
||||
for (int i = 0; i < clusterMembers; ++i) {
|
||||
clients.get(i).shutdown();
|
||||
@ -146,6 +152,30 @@ public class DataTest extends AbstractIntegrationBase {
|
||||
assertMerged(clients.get(1), lwwSetKey, new OrSet<>("1", "2", "4").value());
|
||||
}
|
||||
|
||||
private void testGrowOnlyCounter(List<GossipManager> clients) {
|
||||
givenDifferentIncrement(clients);
|
||||
assertThatCountIsUpdated(clients, 3);
|
||||
givenIncreaseOther(clients);
|
||||
assertThatCountIsUpdated(clients, 7);
|
||||
}
|
||||
|
||||
private void testPNCounter(List<GossipManager> clients) {
|
||||
givenPNCounter(clients);
|
||||
assertThatPNCounterSettlesAt(clients, 0);
|
||||
int[] delta1 = { 2, 3 };
|
||||
givenPNCounterUpdate(clients, delta1);
|
||||
assertThatPNCounterSettlesAt(clients, 5);
|
||||
int[] delta2 = { -3, 5 };
|
||||
givenPNCounterUpdate(clients, delta2);
|
||||
assertThatPNCounterSettlesAt(clients, 7);
|
||||
int[] delta3 = { 1, 1 };
|
||||
givenPNCounterUpdate(clients, delta3);
|
||||
assertThatPNCounterSettlesAt(clients, 9);
|
||||
int[] delta4 = { 1, -7 };
|
||||
givenPNCounterUpdate(clients, delta4);
|
||||
assertThatPNCounterSettlesAt(clients, 3);
|
||||
}
|
||||
|
||||
private void givenDifferentIncrement(final List<GossipManager> clients) {
|
||||
Object payload = new GrowOnlyCounter(new GrowOnlyCounter.Builder(clients.get(0)).increment(1L));
|
||||
clients.get(0).merge(generateSharedMsg(gCounterKey, payload));
|
||||
@ -162,8 +192,8 @@ public class DataTest extends AbstractIntegrationBase {
|
||||
}
|
||||
|
||||
private void assertMerged(final GossipManager client, String key, final Set<String> expected) {
|
||||
TUnit.assertThat(() -> client.findCrdt(key).value())
|
||||
.afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(expected);
|
||||
TUnit.assertThat(() -> client.findCrdt(key).value()).afterWaitingAtMost(10, TimeUnit.SECONDS)
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
|
||||
private void givenDifferentDatumsInSet(final List<GossipManager> clients) {
|
||||
@ -171,16 +201,64 @@ public class DataTest extends AbstractIntegrationBase {
|
||||
clients.get(1).merge(CrdtMessage("2"));
|
||||
}
|
||||
|
||||
|
||||
private void assertThatCountIsUpdated(final List<GossipManager> clients, long finalCount) {
|
||||
TUnit.assertThat(() -> clients.get(0).findCrdt(gCounterKey))
|
||||
.afterWaitingAtMost(10, TimeUnit.SECONDS)
|
||||
.isEqualTo(new GrowOnlyCounter(new GrowOnlyCounter.Builder(clients.get(0)).increment(finalCount)));
|
||||
.afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(new GrowOnlyCounter(
|
||||
new GrowOnlyCounter.Builder(clients.get(0)).increment(finalCount)));
|
||||
}
|
||||
|
||||
private void assertThatListIsMerged(final List<GossipManager> clients) {
|
||||
TUnit.assertThat(() -> clients.get(0).findCrdt("cr"))
|
||||
.afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(new GrowOnlySet<>(Arrays.asList("1", "2")));
|
||||
TUnit.assertThat(() -> clients.get(0).findCrdt("cr")).afterWaitingAtMost(10, TimeUnit.SECONDS)
|
||||
.isEqualTo(new GrowOnlySet<>(Arrays.asList("1", "2")));
|
||||
}
|
||||
|
||||
private void givenPNCounter(List<GossipManager> clients) {
|
||||
{
|
||||
SharedDataMessage d = new SharedDataMessage();
|
||||
d.setKey(pnCounterKey);
|
||||
d.setPayload(new PNCounter(new PNCounter.Builder(clients.get(0))));
|
||||
d.setExpireAt(Long.MAX_VALUE);
|
||||
d.setTimestamp(System.currentTimeMillis());
|
||||
clients.get(0).merge(d);
|
||||
}
|
||||
{
|
||||
SharedDataMessage d = new SharedDataMessage();
|
||||
d.setKey(pnCounterKey);
|
||||
d.setPayload(new PNCounter(new PNCounter.Builder(clients.get(1))));
|
||||
d.setExpireAt(Long.MAX_VALUE);
|
||||
d.setTimestamp(System.currentTimeMillis());
|
||||
clients.get(1).merge(d);
|
||||
}
|
||||
}
|
||||
|
||||
private void givenPNCounterUpdate(List<GossipManager> clients, int[] deltaArray) {
|
||||
int clientIndex = 0;
|
||||
for (int delta: deltaArray) {
|
||||
PNCounter c = (PNCounter) clients.get(clientIndex).findCrdt(pnCounterKey);
|
||||
c = new PNCounter(c, new PNCounter.Builder(clients.get(clientIndex)).increment(((long)delta)));
|
||||
SharedDataMessage d = new SharedDataMessage();
|
||||
d.setKey(pnCounterKey);
|
||||
d.setPayload(c);
|
||||
d.setExpireAt(Long.MAX_VALUE);
|
||||
d.setTimestamp(System.currentTimeMillis());
|
||||
clients.get(clientIndex).merge(d);
|
||||
clientIndex = (clientIndex + 1) % clients.size();
|
||||
}
|
||||
}
|
||||
|
||||
private void assertThatPNCounterSettlesAt(List<GossipManager> clients, long expectedValue) {
|
||||
for (GossipManager client: clients) {
|
||||
TUnit.assertThat(() -> {
|
||||
long value = 0;
|
||||
Object o = client.findCrdt(pnCounterKey);
|
||||
if (o != null) {
|
||||
PNCounter c = (PNCounter)o;
|
||||
value = c.value();
|
||||
}
|
||||
return value;
|
||||
}).afterWaitingAtMost(10, TimeUnit.SECONDS)
|
||||
.isEqualTo(expectedValue);
|
||||
}
|
||||
}
|
||||
|
||||
private SharedDataMessage CrdtMessage(String item) {
|
||||
|
9
pom.xml
9
pom.xml
@ -32,6 +32,7 @@
|
||||
<junit.vintage.version>4.12.0-M2</junit.vintage.version>
|
||||
<log4j.version>1.2.17</log4j.version>
|
||||
<tunit.version>0.0.0</tunit.version>
|
||||
<mockito.version>2.8.9</mockito.version>
|
||||
|
||||
<!-- plugins versions -->
|
||||
<maven-compiler-plugin.version>3.5.1</maven-compiler-plugin.version>
|
||||
@ -116,6 +117,12 @@
|
||||
<version>${tunit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@ -200,6 +207,8 @@
|
||||
<exclude>eclipse_template.xml</exclude>
|
||||
<!-- files generated by running the examples in gossip-examples -->
|
||||
<exclued>**/*.mycluster.*.json</exclued>
|
||||
<!-- e.g.npm-debug.log -->
|
||||
<exclued>**/*.log</exclued>
|
||||
</excludes>
|
||||
</configuration>
|
||||
<executions>
|
||||
|
Reference in New Issue
Block a user