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:
Terry Weymouth
2017-06-10 17:07:33 -04:00
parent c009b77d2a
commit 49cdac62a2
7 changed files with 596 additions and 48 deletions

View File

@ -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>

View File

@ -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);
}

View 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;
}
}
}

View File

@ -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());
}
}