GOSSIP-78 refactor into a multi-module maven project

* add *.ipr to .gitignore
* modify existing pom to be a parent. create new pom for gossip-core.
* I left all properties and dependencies in the parent, as they seemed to be a fairly general set of dependencies.
move all the code.
* rename parent module: gossip -> gossip-parent
* move dependencies into child module
This commit is contained in:
Gary Dusbabek
2017-04-13 08:20:06 -05:00
parent 6a4d50cae7
commit 298b1ae3ae
86 changed files with 162 additions and 137 deletions

91
gossip-base/pom.xml Normal file
View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.gossip</groupId>
<artifactId>gossip-parent</artifactId>
<version>0.1.3-incubating-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Gossip Base</name>
<artifactId>gossip-base</artifactId>
<version>0.1.3-incubating-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>commons-math</groupId>
<artifactId>commons-math</artifactId>
<version>${commons-math.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>${metrics.version}</version></dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.vintage.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>${junit.platform.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.teknek</groupId>
<artifactId>tunit</artifactId>
<version>${tunit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
<type>jar</type>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,226 @@
/*
* 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;
import java.util.HashMap;
import java.util.Map;
/**
* In this object the settings used by the GossipService are held.
*
*/
public class GossipSettings {
/** Time between gossip'ing in ms. Default is 1 second. */
private int gossipInterval = 10;
/** Time between cleanups in ms. Default is 10 seconds. */
private int cleanupInterval = 5000;
/** the minimum samples needed before reporting a result */
private int minimumSamples = 5;
/** the number of samples to keep per host */
private int windowSize = 5000;
/** the threshold for the detector */
private double convictThreshold = 10;
private String distribution = "normal";
private String activeGossipClass = "org.apache.gossip.manager.SimpleActiveGossipper";
private Map<String,String> activeGossipProperties = new HashMap<>();
private String pathToRingState = "./";
private boolean persistRingState = true;
private String pathToDataState = "./";
private boolean persistDataState = true;
private String pathToKeyStore = "./keys";
private boolean signMessages = false;
/**
* Construct GossipSettings with default settings.
*/
public GossipSettings() {
}
/**
* Construct GossipSettings with given settings.
*
* @param gossipInterval
* The gossip interval in ms.
* @param cleanupInterval
* The cleanup interval in ms.
*/
public GossipSettings(int gossipInterval, int cleanupInterval, int windowSize,
int minimumSamples, double convictThreshold, String distribution) {
this.gossipInterval = gossipInterval;
this.cleanupInterval = cleanupInterval;
this.windowSize = windowSize;
this.minimumSamples = minimumSamples;
this.convictThreshold = convictThreshold;
this.distribution = distribution;
}
/**
* Set the gossip interval. This is the time between a gossip message is send.
*
* @param gossipInterval
* The gossip interval in ms.
*/
public void setGossipTimeout(int gossipInterval) {
this.gossipInterval = gossipInterval;
}
/**
* Set the cleanup interval. This is the time between the last heartbeat received from a member
* and when it will be marked as dead.
*
* @param cleanupInterval
* The cleanup interval in ms.
*/
public void setCleanupInterval(int cleanupInterval) {
this.cleanupInterval = cleanupInterval;
}
/**
* Get the gossip interval.
*
* @return The gossip interval in ms.
*/
public int getGossipInterval() {
return gossipInterval;
}
/**
* Get the clean interval.
*
* @return The cleanup interval.
*/
public int getCleanupInterval() {
return cleanupInterval;
}
public int getMinimumSamples() {
return minimumSamples;
}
public void setMinimumSamples(int minimumSamples) {
this.minimumSamples = minimumSamples;
}
public int getWindowSize() {
return windowSize;
}
public void setWindowSize(int windowSize) {
this.windowSize = windowSize;
}
public double getConvictThreshold() {
return convictThreshold;
}
public void setConvictThreshold(double convictThreshold) {
this.convictThreshold = convictThreshold;
}
public void setGossipInterval(int gossipInterval) {
this.gossipInterval = gossipInterval;
}
public String getDistribution() {
return distribution;
}
public void setDistribution(String distribution) {
this.distribution = distribution;
}
public String getActiveGossipClass() {
return activeGossipClass;
}
public void setActiveGossipClass(String activeGossipClass) {
this.activeGossipClass = activeGossipClass;
}
public Map<String, String> getActiveGossipProperties() {
return activeGossipProperties;
}
public void setActiveGossipProperties(Map<String, String> activeGossipProperties) {
this.activeGossipProperties = activeGossipProperties;
}
public String getPathToRingState() {
return pathToRingState;
}
public void setPathToRingState(String pathToRingState) {
this.pathToRingState = pathToRingState;
}
public boolean isPersistRingState() {
return persistRingState;
}
public void setPersistRingState(boolean persistRingState) {
this.persistRingState = persistRingState;
}
public String getPathToDataState() {
return pathToDataState;
}
public void setPathToDataState(String pathToDataState) {
this.pathToDataState = pathToDataState;
}
public boolean isPersistDataState() {
return persistDataState;
}
public void setPersistDataState(boolean persistDataState) {
this.persistDataState = persistDataState;
}
public String getPathToKeyStore() {
return pathToKeyStore;
}
public void setPathToKeyStore(String pathToKeyStore) {
this.pathToKeyStore = pathToKeyStore;
}
public boolean isSignMessages() {
return signMessages;
}
public void setSignMessages(boolean signMessages) {
this.signMessages = signMessages;
}
}

View File

@ -0,0 +1,71 @@
/*
* 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;
import java.net.URI;
import java.util.Map;
import org.apache.gossip.accrual.FailureDetector;
/**
* This object represent a gossip member with the properties known locally. These objects are stored
* in the local list of gossip members.
*
*/
public class LocalMember extends Member {
/** The failure detector for this member */
private transient FailureDetector detector;
/**
*
* @param uri
* The uri of the member
* @param id
* id of the node
* @param heartbeat
* The current heartbeat
*/
public LocalMember(String clusterName, URI uri, String id,
long heartbeat, Map<String,String> properties, int windowSize, int minSamples, String distribution) {
super(clusterName, uri, id, heartbeat, properties );
detector = new FailureDetector(minSamples, windowSize, distribution);
}
protected LocalMember(){
}
public void recordHeartbeat(long now){
detector.recordHeartbeat(now);
}
public Double detect(long now) {
return detector.computePhiMeasure(now);
}
@Override
public String toString() {
Double d = null;
try {
d = detect(System.nanoTime());
} catch (RuntimeException ex) {}
return "LocalGossipMember [uri=" + uri + ", heartbeat=" + heartbeat + ", clusterName="
+ clusterName + ", id=" + id + ", currentdetect=" + d +" ]";
}
}

View File

@ -0,0 +1,166 @@
/*
* 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;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Map;
/**
* A abstract class representing a gossip member.
*
*/
public abstract class Member implements Comparable<Member> {
protected URI uri;
protected volatile long heartbeat;
protected String clusterName;
/**
* The purpose of the id field is to be able for nodes to identify themselves beyond their
* host/port. For example an application might generate a persistent id so if they rejoin the
* cluster at a different host and port we are aware it is the same node.
*/
protected String id;
/* properties provided at startup time */
protected Map<String,String> properties;
/**
* Constructor.
*
* @param clusterName
* The name of the cluster
* @param uri
* A URI object containing IP/hostname and port
* @param heartbeat
* The current heartbeat
* @param id
* An id that may be replaced after contact
*/
public Member(String clusterName, URI uri, String id, long heartbeat, Map<String,String> properties) {
this.clusterName = clusterName;
this.id = id;
this.heartbeat = heartbeat;
this.uri = uri;
this.properties = properties;
}
protected Member(){}
/**
* Get the name of the cluster the member belongs to.
*
* @return The cluster name
*/
public String getClusterName() {
return clusterName;
}
/**
* @return The member address in the form IP/host:port Similar to the toString in
* {@link InetSocketAddress}
*/
public String computeAddress() {
return uri.getHost() + ":" + uri.getPort();
}
/**
* Get the heartbeat of this gossip member.
*
* @return The current heartbeat.
*/
public long getHeartbeat() {
return heartbeat;
}
/**
* Set the heartbeat of this gossip member.
*
* @param heartbeat
* The new heartbeat.
*/
public void setHeartbeat(long heartbeat) {
this.heartbeat = heartbeat;
}
public String getId() {
return id;
}
public void setId(String _id) {
this.id = _id;
}
public Map<String, String> getProperties() {
return properties;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
public String toString() {
return "Member [address=" + computeAddress() + ", id=" + id + ", heartbeat=" + heartbeat + "]";
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
String address = computeAddress();
result = prime * result + ((address == null) ? 0 : address.hashCode()) + (clusterName == null ? 0
: clusterName.hashCode());
return result;
}
public URI getUri() {
return uri;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
System.err.println("equals(): obj is null.");
return false;
}
if (!(obj instanceof Member)) {
System.err.println("equals(): obj is not of type GossipMember.");
return false;
}
// The object is the same of they both have the same address (hostname and port).
return computeAddress().equals(((LocalMember) obj).computeAddress())
&& getClusterName().equals(((LocalMember) obj).getClusterName());
}
public int compareTo(Member other) {
return this.computeAddress().compareTo(other.computeAddress());
}
}

View File

@ -0,0 +1,47 @@
/*
* 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;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
/**
* The object represents a gossip member with the properties as received from a remote gossip
* member.
*
*/
public class RemoteMember extends Member {
/**
* Constructor.
*
* @param uri
* A URI object containing IP/hostname and port
* @param heartbeat
* The current heartbeat
*/
public RemoteMember(String clusterName, URI uri, String id, long heartbeat, Map<String,String> properties) {
super(clusterName, uri, id, heartbeat, properties);
}
public RemoteMember(String clusterName, URI uri, String id) {
super(clusterName, uri, id, System.nanoTime(), new HashMap<String,String>());
}
}

View File

@ -0,0 +1,207 @@
/*
* 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;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* This object represents the settings used when starting the gossip service.
*
*/
public class StartupSettings {
private static final Logger log = Logger.getLogger(StartupSettings.class);
/** The id to use fo the service */
private String id;
private URI uri;
private String cluster;
/** The gossip settings used at startup. */
private final GossipSettings gossipSettings;
/** The list with gossip members to start with. */
private final List<Member> gossipMembers;
/**
* Constructor.
*
* @param id
* The id to be used for this service
* @param uri
* A URI object containing IP/hostname and port
* @param logLevel
* unused
*/
public StartupSettings(String id, URI uri, int logLevel, String cluster) {
this(id, uri, new GossipSettings(), cluster);
}
public URI getUri() {
return uri;
}
public void setUri(URI uri) {
this.uri = uri;
}
/**
* Constructor.
*
* @param id
* The id to be used for this service
* @param uri
* A URI object containing IP/hostname and port
*/
public StartupSettings(String id, URI uri, GossipSettings gossipSettings, String cluster) {
this.id = id;
this.uri = uri;
this.gossipSettings = gossipSettings;
this.setCluster(cluster);
gossipMembers = new ArrayList<>();
}
public void setCluster(String cluster) {
this.cluster = cluster;
}
public String getCluster() {
return cluster;
}
/**
* Set the id to be used for this service.
*
* @param id
* The id for this service.
*/
public void setId(String id) {
this.id = id;
}
/**
* Get the id for this service.
*
* @return the service's id.
*/
public String getId() {
return id;
}
/**
* Get the GossipSettings.
*
* @return The GossipSettings object.
*/
public GossipSettings getGossipSettings() {
return gossipSettings;
}
/**
* Add a gossip member to the list of members to start with.
*
* @param member
* The member to add.
*/
public void addGossipMember(Member member) {
gossipMembers.add(member);
}
/**
* Get the list with gossip members.
*
* @return The gossip members.
*/
public List<Member> getGossipMembers() {
return gossipMembers;
}
/**
* Parse the settings for the gossip service from a JSON file.
*
* @param jsonFile
* The file object which refers to the JSON config file.
* @return The StartupSettings object with the settings from the config file.
* @throws FileNotFoundException
* Thrown when the file cannot be found.
* @throws IOException
* Thrown when reading the file gives problems.
* @throws URISyntaxException
*/
public static StartupSettings fromJSONFile(File jsonFile) throws
FileNotFoundException, IOException, URISyntaxException {
ObjectMapper om = new ObjectMapper();
JsonNode root = om.readTree(jsonFile);
JsonNode jsonObject = root.get(0);
String uri = jsonObject.get("uri").textValue();
String id = jsonObject.get("id").textValue();
Map<String,String> properties = new HashMap<String,String>();
JsonNode n = jsonObject.get("properties");
Iterator<Entry<String, JsonNode>> l = n.fields();
while (l.hasNext()){
Entry<String, JsonNode> i = l.next();
properties.put(i.getKey(), i.getValue().asText());
}
//TODO constants as defaults?
int gossipInterval = jsonObject.get("gossip_interval").intValue();
int cleanupInterval = jsonObject.get("cleanup_interval").intValue();
int windowSize = jsonObject.get("window_size").intValue();
int minSamples = jsonObject.get("minimum_samples").intValue();
double convictThreshold = jsonObject.get("convict_threshold").asDouble();
String cluster = jsonObject.get("cluster").textValue();
String distribution = jsonObject.get("distribution").textValue();
if (cluster == null){
throw new IllegalArgumentException("cluster was null. It is required");
}
URI uri2 = new URI(uri);
StartupSettings settings = new StartupSettings(id, uri2,
new GossipSettings(gossipInterval, cleanupInterval, windowSize,
minSamples, convictThreshold, distribution), cluster);
String configMembersDetails = "Config-members [";
JsonNode membersJSON = jsonObject.get("members");
Iterator<JsonNode> it = membersJSON.iterator();
while (it.hasNext()){
JsonNode child = it.next();
URI uri3 = new URI(child.get("uri").textValue());
RemoteMember member = new RemoteMember(child.get("cluster").asText(),
uri3, "", 0, new HashMap<String,String>());
settings.addGossipMember(member);
configMembersDetails += member.computeAddress();
configMembersDetails += ", ";
}
log.info(configMembersDetails + "]");
return settings;
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.accrual;
import org.apache.commons.math.MathException;
import org.apache.commons.math.distribution.ExponentialDistributionImpl;
import org.apache.commons.math.distribution.NormalDistributionImpl;
import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
import org.apache.log4j.Logger;
public class FailureDetector {
public static final Logger LOGGER = Logger.getLogger(FailureDetector.class);
private final DescriptiveStatistics descriptiveStatistics;
private final long minimumSamples;
private volatile long latestHeartbeatMs = -1;
private final String distribution;
public FailureDetector(long minimumSamples, int windowSize, String distribution) {
descriptiveStatistics = new DescriptiveStatistics(windowSize);
this.minimumSamples = minimumSamples;
this.distribution = distribution;
}
/**
* Updates the statistics based on the delta between the last
* heartbeat and supplied time
*
* @param now the time of the heartbeat in milliseconds
*/
public synchronized void recordHeartbeat(long now) {
if (now <= latestHeartbeatMs) {
return;
}
if (latestHeartbeatMs != -1) {
descriptiveStatistics.addValue(now - latestHeartbeatMs);
}
latestHeartbeatMs = now;
}
public synchronized Double computePhiMeasure(long now) {
if (latestHeartbeatMs == -1 || descriptiveStatistics.getN() < minimumSamples) {
return null;
}
long delta = now - latestHeartbeatMs;
try {
double probability;
if (distribution.equals("normal")) {
double standardDeviation = descriptiveStatistics.getStandardDeviation();
standardDeviation = standardDeviation < 0.1 ? 0.1 : standardDeviation;
probability = new NormalDistributionImpl(descriptiveStatistics.getMean(), standardDeviation).cumulativeProbability(delta);
} else {
probability = new ExponentialDistributionImpl(descriptiveStatistics.getMean()).cumulativeProbability(delta);
}
final double eps = 1e-12;
if (1 - probability < eps) {
probability = 1.0;
}
return -1.0d * Math.log10(1.0d - probability);
} catch (MathException | IllegalArgumentException e) {
LOGGER.debug(e);
return null;
}
}
}

View File

@ -0,0 +1,39 @@
/*
* 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;
/**
*
* Immutable type
*
* @param <SetType>
* @param <MergeReturnType>
*/
public interface Crdt<SetType, MergeReturnType extends Crdt<SetType, MergeReturnType>> {
MergeReturnType merge(MergeReturnType other);
SetType value();
/**
* Called to self optimize. Some CRDTs may use some mechanism to clean up be
* removing obsolete data outside the scope of merging. IE this could clean up
* temporal values, old copies etc.
* @return the Crdt structure optimized
*/
MergeReturnType optimize();
}

View File

@ -0,0 +1,55 @@
/*
* 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.function.BiFunction;
@SuppressWarnings("rawtypes")
public class CrdtBiFunctionMerge implements BiFunction<Crdt,Crdt,Crdt> {
@SuppressWarnings("unchecked")
@Override
public Crdt apply(Crdt t, Crdt u) {
if (t == null && u == null){
return null;
} else if (t == null){
return u;
} else if (u == null){
return t;
}
if (! u.getClass().equals(t.getClass())){
throw new IllegalArgumentException( "Can not merge " + t.getClass() + " "+ u.getClass());
}
return t.merge(u);
}
@SuppressWarnings("unchecked")
public static Crdt applyStatic(Crdt t, Crdt u){
if (t == null && u == null){
return null;
} else if (t == null){
return u;
} else if (u == null){
return t;
}
if (! u.getClass().equals(t.getClass())){
throw new IllegalArgumentException( "Can not merge " + t.getClass() + " "+ u.getClass());
}
return t.merge(u);
}
}

View File

@ -0,0 +1,24 @@
/*
* 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;
public interface CrdtCounter<ValueType extends Number, R extends CrdtCounter<ValueType, R>>
extends Crdt<ValueType, R> {
}

View File

@ -0,0 +1,69 @@
/*
* 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 java.util.Set;
import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.module.SimpleModule;
abstract class OrSetMixin<E> {
@JsonCreator
OrSetMixin(@JsonProperty("elements") Map<E, Set<UUID>> w, @JsonProperty("tombstones") Map<E, Set<UUID>> h) { }
@JsonProperty("elements") abstract Map<E, Set<UUID>> getElements();
@JsonProperty("tombstones") abstract Map<E, Set<UUID>> getTombstones();
@JsonIgnore abstract boolean isEmpty();
}
abstract class GrowOnlySetMixin<E>{
@JsonCreator
GrowOnlySetMixin(@JsonProperty("elements") Set<E> elements){ }
@JsonProperty("elements") abstract Set<E> getElements();
@JsonIgnore abstract boolean isEmpty();
}
abstract class GrowOnlyCounterMixin {
@JsonCreator
GrowOnlyCounterMixin(@JsonProperty("counters") Map<String, Long> counters) { }
@JsonProperty("counters") abstract Map<String, Long> getCounters();
}
//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 {
private static final long serialVersionUID = 6134836523275023418L;
public CrdtModule() {
super("CrdtModule", new Version(0, 0, 0, "0.0.0", "org.apache.gossip", "gossip"));
}
@Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(OrSet.class, OrSetMixin.class);
context.setMixInAnnotations(GrowOnlySet.class, GrowOnlySetMixin.class);
context.setMixInAnnotations(GrowOnlyCounter.class, GrowOnlyCounterMixin.class);
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.Set;
public interface CrdtSet<ElementType, SetType extends Set<ElementType>, R extends CrdtSet<ElementType, SetType, R>>
extends Crdt<SetType, R> {
}

View File

@ -0,0 +1,119 @@
/*
* 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 org.apache.gossip.manager.GossipManager;
import java.util.HashMap;
import java.util.Map;
public class GrowOnlyCounter implements CrdtCounter<Long, GrowOnlyCounter> {
private final Map<String, Long> counters = new HashMap<>();
GrowOnlyCounter(Map<String, Long> counters) {
this.counters.putAll(counters);
}
public GrowOnlyCounter(GrowOnlyCounter growOnlyCounter, Builder builder) {
counters.putAll(growOnlyCounter.counters);
if (counters.containsKey(builder.myId)) {
Long newValue = counters.get(builder.myId) + builder.counter;
counters.replace(builder.myId, newValue);
} else {
counters.put(builder.myId, builder.counter);
}
}
public GrowOnlyCounter(Builder builder) {
counters.put(builder.myId, builder.counter);
}
public GrowOnlyCounter(GossipManager manager) {
counters.put(manager.getMyself().getId(), 0L);
}
public GrowOnlyCounter(GrowOnlyCounter growOnlyCounter, GrowOnlyCounter other) {
counters.putAll(growOnlyCounter.counters);
for (Map.Entry<String, Long> entry : other.counters.entrySet()) {
String otherKey = entry.getKey();
Long otherValue = entry.getValue();
if (counters.containsKey(otherKey)) {
Long newValue = Math.max(counters.get(otherKey), otherValue);
counters.replace(otherKey, newValue);
} else {
counters.put(otherKey, otherValue);
}
}
}
@Override
public GrowOnlyCounter merge(GrowOnlyCounter other) {
return new GrowOnlyCounter(this, other);
}
@Override
public Long value() {
Long globalCount = 0L;
for (Long increment : counters.values()) {
globalCount += increment;
}
return globalCount;
}
@Override
public GrowOnlyCounter optimize() {
return new GrowOnlyCounter(counters);
}
@Override
public boolean equals(Object obj) {
if (getClass() != obj.getClass())
return false;
GrowOnlyCounter other = (GrowOnlyCounter) obj;
return value().longValue() == other.value().longValue();
}
@Override
public String toString() {
return "GrowOnlyCounter [counters= " + counters + ", Value=" + value() + "]";
}
Map<String, Long> getCounters() {
return counters;
}
public static class Builder {
private final String myId;
private Long counter;
public Builder(GossipManager gossipManager) {
myId = gossipManager.getMyself().getId();
counter = 0L;
}
public GrowOnlyCounter.Builder increment(Long count) {
counter += count;
return this;
}
}
}

View File

@ -0,0 +1,157 @@
/*
* 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.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
public class GrowOnlySet<ElementType> implements CrdtSet<ElementType, Set<ElementType>, GrowOnlySet<ElementType>>{
private final Set<ElementType> hidden = new LinkedHashSet<>();
@SuppressWarnings("unused")
/*
* Used by SerDe
*/
private GrowOnlySet(){
}
public GrowOnlySet(Set<ElementType> c){
hidden.addAll(c);
}
public GrowOnlySet(Collection<ElementType> c){
hidden.addAll(c);
}
public GrowOnlySet(GrowOnlySet<ElementType> first, GrowOnlySet<ElementType> second){
hidden.addAll(first.value());
hidden.addAll(second.value());
}
@Override
public GrowOnlySet<ElementType> merge(GrowOnlySet<ElementType> other) {
return new GrowOnlySet<>(this, other);
}
@Override
public Set<ElementType> value() {
Set<ElementType> copy = new LinkedHashSet<>();
copy.addAll(hidden);
return Collections.unmodifiableSet(copy);
}
@Override
public GrowOnlySet<ElementType> optimize() {
return new GrowOnlySet<>(hidden);
}
public int size() {
return hidden.size();
}
public boolean isEmpty() {
return hidden.isEmpty();
}
public boolean contains(Object o) {
return hidden.contains(o);
}
public Iterator<ElementType> iterator() {
Set<ElementType> copy = new HashSet<>();
copy.addAll(hidden);
return copy.iterator();
}
public Object[] toArray() {
return hidden.toArray();
}
public <T> T[] toArray(T[] a) {
return hidden.toArray(a);
}
public boolean add(ElementType e) {
throw new UnsupportedOperationException();
}
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
public boolean containsAll(Collection<?> c) {
return hidden.containsAll(c);
}
public boolean addAll(Collection<? extends ElementType> c) {
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return "GrowOnlySet [hidden=" + hidden + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((hidden == null) ? 0 : hidden.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
@SuppressWarnings("rawtypes")
GrowOnlySet other = (GrowOnlySet) obj;
if (hidden == null) {
if (other.hidden != null)
return false;
} else if (!hidden.equals(other.hidden))
return false;
return true;
}
Set<ElementType> getElements(){
return hidden;
}
}

View File

@ -0,0 +1,304 @@
/*
* 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.*;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
import org.apache.gossip.crdt.OrSet.Builder.Operation;
/*
* A immutable set
*/
public class OrSet<E> implements Crdt<Set<E>, OrSet<E>> {
private final Map<E, Set<UUID>> elements = new HashMap<>();
private final Map<E, Set<UUID>> tombstones = new HashMap<>();
private final transient Set<E> val;
public OrSet(){
val = computeValue();
}
OrSet(Map<E, Set<UUID>> elements, Map<E, Set<UUID>> tombstones){
this.elements.putAll(elements);
this.tombstones.putAll(tombstones);
val = computeValue();
}
@SafeVarargs
public OrSet(E ... elements){
for (E e: elements){
internalAdd(e);
}
val = computeValue();
}
public OrSet(Builder<E>builder){
for (Builder<E>.OrSetElement<E> e: builder.elements){
if (e.operation == Operation.ADD){
internalAdd(e.element);
} else {
internalRemove(e.element);
}
}
val = computeValue();
}
/**
* This constructor is the way to remove elements from an existing set
* @param set
* @param builder
*/
public OrSet(OrSet<E> set, Builder<E> builder){
elements.putAll(set.elements);
tombstones.putAll(set.tombstones);
for (Builder<E>.OrSetElement<E> e: builder.elements){
if (e.operation == Operation.ADD){
internalAdd(e.element);
} else {
internalRemove(e.element);
}
}
val = computeValue();
}
static Set<UUID> mergeSets(Set<UUID> a, Set<UUID> b) {
if ((a == null || a.isEmpty()) && (b == null || b.isEmpty())) {
return null;
}
Set<UUID> res = new HashSet<>(a);
res.addAll(b);
return res;
}
private void internalSetMerge(Map<E, Set<UUID>> map, E key, Set<UUID> value) {
if (value == null) {
return;
}
map.merge(key, value, OrSet::mergeSets);
}
public OrSet(OrSet<E> left, OrSet<E> right){
BiConsumer<Map<E, Set<UUID>>, Map<E, Set<UUID>>> internalMerge = (items, other) -> {
for (Entry<E, Set<UUID>> l : other.entrySet()){
internalSetMerge(items, l.getKey(), l.getValue());
}
};
internalMerge.accept(elements, left.elements);
internalMerge.accept(elements, right.elements);
internalMerge.accept(tombstones, left.tombstones);
internalMerge.accept(tombstones, right.tombstones);
val = computeValue();
}
public OrSet.Builder<E> builder(){
return new OrSet.Builder<>();
}
@Override
public OrSet<E> merge(OrSet<E> other) {
return new OrSet<E>(this, other);
}
private void internalAdd(E element) {
Set<UUID> toMerge = new HashSet<>();
toMerge.add(UUID.randomUUID());
internalSetMerge(elements, element, toMerge);
}
private void internalRemove(E element){
internalSetMerge(tombstones, element, elements.get(element));
}
/*
* Computes the live values by analyzing the elements and tombstones
*/
private Set<E> computeValue(){
Set<E> values = new HashSet<>();
for (Entry<E, Set<UUID>> entry: elements.entrySet()){
Set<UUID> deleteIds = tombstones.get(entry.getKey());
// if not all tokens for current element are in tombstones
if (deleteIds == null || !deleteIds.containsAll(entry.getValue())) {
values.add(entry.getKey());
}
}
return values;
}
@Override
public Set<E> value() {
return val;
}
@Override
public OrSet<E> optimize() {
return this;
}
public static class Builder<E> {
public static enum Operation {
ADD, REMOVE
};
private class OrSetElement<EL> {
EL element;
Operation operation;
private OrSetElement(EL element, Operation operation) {
this.element = element;
this.operation = operation;
}
}
private List<OrSetElement<E>> elements = new ArrayList<>();
public Builder<E> add(E element) {
elements.add(new OrSetElement<E>(element, Operation.ADD));
return this;
}
public Builder<E> remove(E element) {
elements.add(new OrSetElement<E>(element, Operation.REMOVE));
return this;
}
public Builder<E> mutate(E element, Operation operation) {
elements.add(new OrSetElement<E>(element, operation));
return this;
}
}
public int size() {
return value().size();
}
public boolean isEmpty() {
return value().size() == 0;
}
public boolean contains(Object o) {
return value().contains(o);
}
public Iterator<E> iterator() {
Iterator<E> managed = value().iterator();
return new Iterator<E>() {
@Override
public void remove() {
throw new IllegalArgumentException();
}
@Override
public boolean hasNext() {
return managed.hasNext();
}
@Override
public E next() {
return managed.next();
}
};
}
public Object[] toArray() {
return value().toArray();
}
public <T> T[] toArray(T[] a) {
return value().toArray(a);
}
public boolean add(E e) {
throw new IllegalArgumentException("Can not add");
}
public boolean remove(Object o) {
throw new IllegalArgumentException();
}
public boolean containsAll(Collection<?> c) {
return this.value().containsAll(c);
}
public boolean addAll(Collection<? extends E> c) {
throw new IllegalArgumentException();
}
public boolean retainAll(Collection<?> c) {
throw new IllegalArgumentException();
}
public boolean removeAll(Collection<?> c) {
throw new IllegalArgumentException();
}
public void clear() {
throw new IllegalArgumentException();
}
@Override
public String toString() {
return "OrSet [elements=" + elements + ", tombstones=" + tombstones + "]" ;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((value() == null) ? 0 : value().hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
@SuppressWarnings("rawtypes")
OrSet other = (OrSet) obj;
if (elements == null) {
if (other.elements != null)
return false;
} else if (!value().equals(other.value()))
return false;
return true;
}
Map<E, Set<UUID>> getElements() {
return elements;
}
Map<E, Set<UUID>> getTombstones() {
return tombstones;
}
}

View File

@ -0,0 +1,24 @@
/*
* 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.event;
import org.apache.gossip.Member;
public interface GossipListener {
void gossipEvent(Member member, GossipState state);
}

View File

@ -0,0 +1,28 @@
/*
* 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.event;
public enum GossipState {
UP("up"), DOWN("down");
@SuppressWarnings("unused")
private final String state;
private GossipState(String state) {
this.state = state;
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.net.URI;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.apache.gossip.GossipSettings;
import org.apache.gossip.RemoteMember;
import org.apache.gossip.manager.DatacenterRackAwareActiveGossiper;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.manager.GossipManagerBuilder;
public class StandAloneDatacenterAndRack {
public static void main (String [] args) throws UnknownHostException, InterruptedException {
GossipSettings s = new GossipSettings();
s.setWindowSize(1000);
s.setGossipInterval(100);
s.setActiveGossipClass(DatacenterRackAwareActiveGossiper.class.getName());
Map<String, String> gossipProps = new HashMap<>();
gossipProps.put("sameRackGossipIntervalMs", "2000");
gossipProps.put("differentDatacenterGossipIntervalMs", "10000");
s.setActiveGossipProperties(gossipProps);
Map<String, String> props = new HashMap<>();
props.put(DatacenterRackAwareActiveGossiper.DATACENTER, args[4]);
props.put(DatacenterRackAwareActiveGossiper.RACK, args[5]);
GossipManager manager = GossipManagerBuilder.newBuilder()
.cluster("mycluster")
.uri(URI.create(args[0]))
.id(args[1])
.gossipSettings(s)
.gossipMembers(Arrays.asList(new RemoteMember("mycluster", URI.create(args[2]), args[3])))
.properties(props)
.build();
manager.init();
while (true){
System.out.println("Live: " + manager.getLiveMembers());
System.out.println("Dead: " + manager.getDeadMembers());
Thread.sleep(2000);
}
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.net.URI;
import java.net.UnknownHostException;
import java.util.Arrays;
import org.apache.gossip.GossipSettings;
import org.apache.gossip.RemoteMember;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.manager.GossipManagerBuilder;
public class StandAloneNode {
public static void main (String [] args) throws UnknownHostException, InterruptedException{
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();
while (true){
System.out.println("Live: " + gossipService.getLiveMembers());
System.out.println("Dead: " + gossipService.getDeadMembers());
Thread.sleep(2000);
}
}
}

View File

@ -0,0 +1,115 @@
/*
* 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 org.apache.gossip.GossipSettings;
import org.apache.gossip.RemoteMember;
import org.apache.gossip.crdt.GrowOnlyCounter;
import org.apache.gossip.crdt.OrSet;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.manager.GossipManagerBuilder;
import org.apache.gossip.model.SharedDataMessage;
public class StandAloneNodeCrdtOrSet {
public static void main (String [] args) throws InterruptedException, IOException{
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){
System.out.println("Live: " + gossipService.getLiveMembers());
System.out.println("Dead: " + gossipService.getDeadMembers());
System.out.println("---------- " + (gossipService.findCrdt("abc") == null ? "":
gossipService.findCrdt("abc").value()));
System.out.println("********** " + gossipService.findCrdt("abc"));
System.out.println("^^^^^^^^^^ " + (gossipService.findCrdt("def") == null ? "":
gossipService.findCrdt("def").value()));
System.out.println("$$$$$$$$$$ " + gossipService.findCrdt("def"));
try {
Thread.sleep(2000);
} catch (Exception e) {}
}
}).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);
String val = line.substring(2);
if (op == 'a'){
addData(val, gossipService);
} else if (op == 'r') {
removeData(val, gossipService);
} else if (op == 'g'){
gcount(val, gossipService);
}
}
}
}
private static void gcount(String val, GossipManager gossipManager){
GrowOnlyCounter c = (GrowOnlyCounter) gossipManager.findCrdt("def");
Long l = Long.valueOf(val);
if (c == null){
c = new GrowOnlyCounter(new GrowOnlyCounter.Builder(gossipManager).increment((l)));
} else {
c = new GrowOnlyCounter(c, new GrowOnlyCounter.Builder(gossipManager).increment((l)));
}
SharedDataMessage m = new SharedDataMessage();
m.setExpireAt(Long.MAX_VALUE);
m.setKey("def");
m.setPayload(c);
m.setTimestamp(System.currentTimeMillis());
gossipManager.merge(m);
}
private static void removeData(String val, GossipManager gossipService){
@SuppressWarnings("unchecked")
OrSet<String> s = (OrSet<String>) gossipService.findCrdt("abc");
SharedDataMessage m = new SharedDataMessage();
m.setExpireAt(Long.MAX_VALUE);
m.setKey("abc");
m.setPayload(new OrSet<String>(s , new OrSet.Builder<String>().remove(val)));
m.setTimestamp(System.currentTimeMillis());
gossipService.merge(m);
}
private static void addData(String val, GossipManager gossipService){
SharedDataMessage m = new SharedDataMessage();
m.setExpireAt(Long.MAX_VALUE);
m.setKey("abc");
m.setPayload(new OrSet<String>(val));
m.setTimestamp(System.currentTimeMillis());
gossipService.merge(m);
}
}

View File

@ -0,0 +1,171 @@
/*
* 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.manager;
import java.util.Map.Entry;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.MetricRegistry;
import org.apache.gossip.LocalMember;
import org.apache.gossip.model.ActiveGossipOk;
import org.apache.gossip.model.PerNodeDataMessage;
import org.apache.gossip.model.Member;
import org.apache.gossip.model.Response;
import org.apache.gossip.model.SharedDataMessage;
import org.apache.gossip.model.ShutdownMessage;
import org.apache.gossip.udp.UdpActiveGossipMessage;
import org.apache.gossip.udp.UdpPerNodeDataMessage;
import org.apache.gossip.udp.UdpSharedDataMessage;
import org.apache.log4j.Logger;
import static com.codahale.metrics.MetricRegistry.name;
/**
* The ActiveGossipThread is sends information. Pick a random partner and send the membership list to that partner
*/
public abstract class AbstractActiveGossiper {
protected static final Logger LOGGER = Logger.getLogger(AbstractActiveGossiper.class);
protected final GossipManager gossipManager;
protected final GossipCore gossipCore;
private final Histogram sharedDataHistogram;
private final Histogram sendPerNodeDataHistogram;
private final Histogram sendMembershipHistorgram;
private final Random random;
public AbstractActiveGossiper(GossipManager gossipManager, GossipCore gossipCore, MetricRegistry registry) {
this.gossipManager = gossipManager;
this.gossipCore = gossipCore;
sharedDataHistogram = registry.histogram(name(AbstractActiveGossiper.class, "sharedDataHistogram-time"));
sendPerNodeDataHistogram = registry.histogram(name(AbstractActiveGossiper.class, "sendPerNodeDataHistogram-time"));
sendMembershipHistorgram = registry.histogram(name(AbstractActiveGossiper.class, "sendMembershipHistorgram-time"));
random = new Random();
}
public void init() {
}
public void shutdown() {
}
public final void sendShutdownMessage(LocalMember me, LocalMember target){
if (target == null){
return;
}
ShutdownMessage m = new ShutdownMessage();
m.setNodeId(me.getId());
m.setShutdownAtNanos(gossipManager.getClock().nanoTime());
gossipCore.sendOneWay(m, target.getUri());
}
public final void sendSharedData(LocalMember me, LocalMember member){
if (member == null){
return;
}
long startTime = System.currentTimeMillis();
for (Entry<String, SharedDataMessage> innerEntry : gossipCore.getSharedData().entrySet()){
UdpSharedDataMessage message = new UdpSharedDataMessage();
message.setUuid(UUID.randomUUID().toString());
message.setUriFrom(me.getId());
message.setExpireAt(innerEntry.getValue().getExpireAt());
message.setKey(innerEntry.getValue().getKey());
message.setNodeId(innerEntry.getValue().getNodeId());
message.setTimestamp(innerEntry.getValue().getTimestamp());
message.setPayload(innerEntry.getValue().getPayload());
gossipCore.sendOneWay(message, member.getUri());
}
sharedDataHistogram.update(System.currentTimeMillis() - startTime);
}
public final void sendPerNodeData(LocalMember me, LocalMember member){
if (member == null){
return;
}
long startTime = System.currentTimeMillis();
for (Entry<String, ConcurrentHashMap<String, PerNodeDataMessage>> entry : gossipCore.getPerNodeData().entrySet()){
for (Entry<String, PerNodeDataMessage> innerEntry : entry.getValue().entrySet()){
UdpPerNodeDataMessage message = new UdpPerNodeDataMessage();
message.setUuid(UUID.randomUUID().toString());
message.setUriFrom(me.getId());
message.setExpireAt(innerEntry.getValue().getExpireAt());
message.setKey(innerEntry.getValue().getKey());
message.setNodeId(innerEntry.getValue().getNodeId());
message.setTimestamp(innerEntry.getValue().getTimestamp());
message.setPayload(innerEntry.getValue().getPayload());
gossipCore.sendOneWay(message, member.getUri());
}
}
sendPerNodeDataHistogram.update(System.currentTimeMillis() - startTime);
}
/**
* Performs the sending of the membership list, after we have incremented our own heartbeat.
*/
protected void sendMembershipList(LocalMember me, LocalMember member) {
if (member == null){
return;
}
long startTime = System.currentTimeMillis();
me.setHeartbeat(System.nanoTime());
UdpActiveGossipMessage message = new UdpActiveGossipMessage();
message.setUriFrom(gossipManager.getMyself().getUri().toASCIIString());
message.setUuid(UUID.randomUUID().toString());
message.getMembers().add(convert(me));
for (LocalMember other : gossipManager.getMembers().keySet()) {
message.getMembers().add(convert(other));
}
Response r = gossipCore.send(message, member.getUri());
if (r instanceof ActiveGossipOk){
//maybe count metrics here
} else {
LOGGER.debug("Message " + message + " generated response " + r);
}
sendMembershipHistorgram.update(System.currentTimeMillis() - startTime);
}
protected final Member convert(LocalMember member){
Member gm = new Member();
gm.setCluster(member.getClusterName());
gm.setHeartbeat(member.getHeartbeat());
gm.setUri(member.getUri().toASCIIString());
gm.setId(member.getId());
gm.setProperties(member.getProperties());
return gm;
}
/**
*
* @param memberList
* An immutable list
* @return The chosen LocalGossipMember to gossip with.
*/
protected LocalMember selectPartner(List<LocalMember> memberList) {
LocalMember member = null;
if (memberList.size() > 0) {
int randomNeighborIndex = random.nextInt(memberList.size());
member = memberList.get(randomNeighborIndex);
}
return member;
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.manager;
public interface Clock {
long currentTimeMillis();
long nanoTime();
}

View File

@ -0,0 +1,85 @@
/*
* 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.manager;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.gossip.model.PerNodeDataMessage;
import org.apache.gossip.model.SharedDataMessage;
/**
* We wish to periodically sweep user data and remove entries past their timestamp. This
* implementation periodically sweeps through the data and removes old entries. While it might make
* sense to use a more specific high performance data-structure to handle eviction, keep in mind
* that we are not looking to store a large quantity of data as we currently have to transmit this
* data cluster wide.
*/
public class DataReaper {
private final GossipCore gossipCore;
private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);
private final Clock clock;
public DataReaper(GossipCore gossipCore, Clock clock){
this.gossipCore = gossipCore;
this.clock = clock;
}
public void init(){
Runnable reapPerNodeData = () -> {
runPerNodeOnce();
runSharedOnce();
};
scheduledExecutor.scheduleAtFixedRate(reapPerNodeData, 0, 5, TimeUnit.SECONDS);
}
void runSharedOnce(){
for (Entry<String, SharedDataMessage> entry : gossipCore.getSharedData().entrySet()){
if (entry.getValue().getExpireAt() < clock.currentTimeMillis()){
gossipCore.getSharedData().remove(entry.getKey(), entry.getValue());
}
}
}
void runPerNodeOnce(){
for (Entry<String, ConcurrentHashMap<String, PerNodeDataMessage>> node : gossipCore.getPerNodeData().entrySet()){
reapData(node.getValue());
}
}
void reapData(ConcurrentHashMap<String, PerNodeDataMessage> concurrentHashMap){
for (Entry<String, PerNodeDataMessage> entry : concurrentHashMap.entrySet()){
if (entry.getValue().getExpireAt() < clock.currentTimeMillis()){
concurrentHashMap.remove(entry.getKey(), entry.getValue());
}
}
}
public void close(){
scheduledExecutor.shutdown();
try {
scheduledExecutor.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
}
}

View File

@ -0,0 +1,244 @@
/*
* 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.manager;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.gossip.LocalMember;
import com.codahale.metrics.MetricRegistry;
/**
* Sends gossip traffic at different rates to other racks and data-centers.
* This implementation controls the rate at which gossip traffic is shared.
* There are two constructs Datacenter and Rack. It is assumed that bandwidth and latency is higher
* in the rack than in the the datacenter. We can adjust the rate at which we send messages to each group.
*
*/
public class DatacenterRackAwareActiveGossiper extends AbstractActiveGossiper {
public static final String DATACENTER = "datacenter";
public static final String RACK = "rack";
private int sameRackGossipIntervalMs = 100;
private int sameDcGossipIntervalMs = 500;
private int differentDatacenterGossipIntervalMs = 1000;
private int randomDeadMemberSendIntervalMs = 250;
private ScheduledExecutorService scheduledExecutorService;
private final BlockingQueue<Runnable> workQueue;
private ThreadPoolExecutor threadService;
public DatacenterRackAwareActiveGossiper(GossipManager gossipManager, GossipCore gossipCore,
MetricRegistry registry) {
super(gossipManager, gossipCore, registry);
scheduledExecutorService = Executors.newScheduledThreadPool(2);
workQueue = new ArrayBlockingQueue<Runnable>(1024);
threadService = new ThreadPoolExecutor(1, 30, 1, TimeUnit.SECONDS, workQueue,
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
sameRackGossipIntervalMs = Integer.parseInt(gossipManager.getSettings()
.getActiveGossipProperties().get("sameRackGossipIntervalMs"));
} catch (RuntimeException ex) { }
try {
sameDcGossipIntervalMs = Integer.parseInt(gossipManager.getSettings()
.getActiveGossipProperties().get("sameDcGossipIntervalMs"));
} catch (RuntimeException ex) { }
try {
differentDatacenterGossipIntervalMs = Integer.parseInt(gossipManager.getSettings()
.getActiveGossipProperties().get("differentDatacenterGossipIntervalMs"));
} catch (RuntimeException ex) { }
try {
randomDeadMemberSendIntervalMs = Integer.parseInt(gossipManager.getSettings()
.getActiveGossipProperties().get("randomDeadMemberSendIntervalMs"));
} catch (RuntimeException ex) { }
}
@Override
public void init() {
super.init();
//same rack
scheduledExecutorService.scheduleAtFixedRate(() ->
threadService.execute(() -> sendToSameRackMember()),
0, sameRackGossipIntervalMs, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(() ->
threadService.execute(() -> sendToSameRackMemberPerNode()),
0, sameRackGossipIntervalMs, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(() ->
threadService.execute(() -> sendToSameRackShared()),
0, sameRackGossipIntervalMs, TimeUnit.MILLISECONDS);
//same dc different rack
scheduledExecutorService.scheduleAtFixedRate(() ->
threadService.execute(() -> sameDcDiffernetRackMember()),
0, sameDcGossipIntervalMs, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(() ->
threadService.execute(() -> sameDcDiffernetRackPerNode()),
0, sameDcGossipIntervalMs, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(() ->
threadService.execute(() -> sameDcDiffernetRackShared()),
0, sameDcGossipIntervalMs, TimeUnit.MILLISECONDS);
//different dc
scheduledExecutorService.scheduleAtFixedRate(() ->
threadService.execute(() -> differentDcMember()),
0, differentDatacenterGossipIntervalMs, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(() ->
threadService.execute(() -> differentDcPerNode()),
0, differentDatacenterGossipIntervalMs, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(() ->
threadService.execute(() -> differentDcShared()),
0, differentDatacenterGossipIntervalMs, TimeUnit.MILLISECONDS);
//the dead
scheduledExecutorService.scheduleAtFixedRate(() ->
threadService.execute(() -> sendToDeadMember()),
0, randomDeadMemberSendIntervalMs, TimeUnit.MILLISECONDS);
}
private void sendToDeadMember() {
sendMembershipList(gossipManager.getMyself(), selectPartner(gossipManager.getDeadMembers()));
}
private List<LocalMember> differentDataCenter(){
String myDc = gossipManager.getMyself().getProperties().get(DATACENTER);
String rack = gossipManager.getMyself().getProperties().get(RACK);
if (myDc == null|| rack == null){
return Collections.emptyList();
}
List<LocalMember> notMyDc = new ArrayList<LocalMember>(10);
for (LocalMember i : gossipManager.getLiveMembers()){
if (!myDc.equals(i.getProperties().get(DATACENTER))){
notMyDc.add(i);
}
}
return notMyDc;
}
private List<LocalMember> sameDatacenterDifferentRack(){
String myDc = gossipManager.getMyself().getProperties().get(DATACENTER);
String rack = gossipManager.getMyself().getProperties().get(RACK);
if (myDc == null|| rack == null){
return Collections.emptyList();
}
List<LocalMember> notMyDc = new ArrayList<LocalMember>(10);
for (LocalMember i : gossipManager.getLiveMembers()){
if (myDc.equals(i.getProperties().get(DATACENTER)) && !rack.equals(i.getProperties().get(RACK))){
notMyDc.add(i);
}
}
return notMyDc;
}
private List<LocalMember> sameRackNodes(){
String myDc = gossipManager.getMyself().getProperties().get(DATACENTER);
String rack = gossipManager.getMyself().getProperties().get(RACK);
if (myDc == null|| rack == null){
return Collections.emptyList();
}
List<LocalMember> sameDcAndRack = new ArrayList<LocalMember>(10);
for (LocalMember i : gossipManager.getLiveMembers()){
if (myDc.equals(i.getProperties().get(DATACENTER))
&& rack.equals(i.getProperties().get(RACK))){
sameDcAndRack.add(i);
}
}
return sameDcAndRack;
}
private void sendToSameRackMember() {
LocalMember i = selectPartner(sameRackNodes());
sendMembershipList(gossipManager.getMyself(), i);
}
private void sendToSameRackMemberPerNode() {
sendPerNodeData(gossipManager.getMyself(), selectPartner(sameRackNodes()));
}
private void sendToSameRackShared() {
sendSharedData(gossipManager.getMyself(), selectPartner(sameRackNodes()));
}
private void differentDcMember() {
sendMembershipList(gossipManager.getMyself(), selectPartner(differentDataCenter()));
}
private void differentDcPerNode() {
sendPerNodeData(gossipManager.getMyself(), selectPartner(differentDataCenter()));
}
private void differentDcShared() {
sendSharedData(gossipManager.getMyself(), selectPartner(differentDataCenter()));
}
private void sameDcDiffernetRackMember() {
sendMembershipList(gossipManager.getMyself(), selectPartner(sameDatacenterDifferentRack()));
}
private void sameDcDiffernetRackPerNode() {
sendPerNodeData(gossipManager.getMyself(), selectPartner(sameDatacenterDifferentRack()));
}
private void sameDcDiffernetRackShared() {
sendSharedData(gossipManager.getMyself(), selectPartner(sameDatacenterDifferentRack()));
}
@Override
public void shutdown() {
super.shutdown();
scheduledExecutorService.shutdown();
try {
scheduledExecutorService.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.debug("Issue during shutdown", e);
}
sendShutdownMessage();
threadService.shutdown();
try {
threadService.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.debug("Issue during shutdown", e);
}
}
/**
* sends an optimistic shutdown message to several clusters nodes
*/
protected void sendShutdownMessage(){
List<LocalMember> l = gossipManager.getLiveMembers();
int sendTo = l.size() < 3 ? 1 : l.size() / 3;
for (int i = 0; i < sendTo; i++) {
threadService.execute(() -> sendShutdownMessage(gossipManager.getMyself(), selectPartner(l)));
}
}
}

View File

@ -0,0 +1,387 @@
/*
* 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.manager;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import org.apache.gossip.Member;
import org.apache.gossip.LocalMember;
import org.apache.gossip.RemoteMember;
import org.apache.gossip.crdt.Crdt;
import org.apache.gossip.event.GossipState;
import org.apache.gossip.model.*;
import org.apache.gossip.udp.Trackable;
import org.apache.log4j.Logger;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.URI;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.*;
public class GossipCore implements GossipCoreConstants {
class LatchAndBase {
private final CountDownLatch latch;
private volatile Base base;
LatchAndBase(){
latch = new CountDownLatch(1);
}
}
public static final Logger LOGGER = Logger.getLogger(GossipCore.class);
private final GossipManager gossipManager;
private ConcurrentHashMap<String, LatchAndBase> requests;
private ThreadPoolExecutor service;
private final ConcurrentHashMap<String, ConcurrentHashMap<String, PerNodeDataMessage>> perNodeData;
private final ConcurrentHashMap<String, SharedDataMessage> sharedData;
private final BlockingQueue<Runnable> workQueue;
private final PKCS8EncodedKeySpec privKeySpec;
private final PrivateKey privKey;
private final Meter messageSerdeException;
private final Meter tranmissionException;
private final Meter tranmissionSuccess;
public GossipCore(GossipManager manager, MetricRegistry metrics){
this.gossipManager = manager;
requests = new ConcurrentHashMap<>();
workQueue = new ArrayBlockingQueue<>(1024);
service = new ThreadPoolExecutor(1, 5, 1, TimeUnit.SECONDS, workQueue, new ThreadPoolExecutor.DiscardOldestPolicy());
perNodeData = new ConcurrentHashMap<>();
sharedData = new ConcurrentHashMap<>();
metrics.register(WORKQUEUE_SIZE, (Gauge<Integer>)() -> workQueue.size());
metrics.register(PER_NODE_DATA_SIZE, (Gauge<Integer>)() -> perNodeData.size());
metrics.register(SHARED_DATA_SIZE, (Gauge<Integer>)() -> sharedData.size());
metrics.register(REQUEST_SIZE, (Gauge<Integer>)() -> requests.size());
metrics.register(THREADPOOL_ACTIVE, (Gauge<Integer>)() -> service.getActiveCount());
metrics.register(THREADPOOL_SIZE, (Gauge<Integer>)() -> service.getPoolSize());
messageSerdeException = metrics.meter(MESSAGE_SERDE_EXCEPTION);
tranmissionException = metrics.meter(MESSAGE_TRANSMISSION_EXCEPTION);
tranmissionSuccess = metrics.meter(MESSAGE_TRANSMISSION_SUCCESS);
if (manager.getSettings().isSignMessages()){
File privateKey = new File(manager.getSettings().getPathToKeyStore(), manager.getMyself().getId());
File publicKey = new File(manager.getSettings().getPathToKeyStore(), manager.getMyself().getId() + ".pub");
if (!privateKey.exists()){
throw new IllegalArgumentException("private key not found " + privateKey);
}
if (!publicKey.exists()){
throw new IllegalArgumentException("public key not found " + publicKey);
}
try (FileInputStream keyfis = new FileInputStream(privateKey)) {
byte[] encKey = new byte[keyfis.available()];
keyfis.read(encKey);
keyfis.close();
privKeySpec = new PKCS8EncodedKeySpec(encKey);
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
privKey = keyFactory.generatePrivate(privKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) {
throw new RuntimeException("failed hard", e);
}
} else {
privKeySpec = null;
privKey = null;
}
}
private byte [] sign(byte [] bytes){
Signature dsa;
try {
dsa = Signature.getInstance("SHA1withDSA", "SUN");
dsa.initSign(privKey);
dsa.update(bytes);
return dsa.sign();
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | SignatureException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void addSharedData(SharedDataMessage message) {
while (true){
SharedDataMessage previous = sharedData.putIfAbsent(message.getKey(), message);
if (previous == null){
return;
}
if (message.getPayload() instanceof Crdt){
SharedDataMessage merged = new SharedDataMessage();
merged.setExpireAt(message.getExpireAt());
merged.setKey(message.getKey());
merged.setNodeId(message.getNodeId());
merged.setTimestamp(message.getTimestamp());
Crdt mergedCrdt = ((Crdt) previous.getPayload()).merge((Crdt) message.getPayload());
merged.setPayload(mergedCrdt);
boolean replaced = sharedData.replace(message.getKey(), previous, merged);
if (replaced){
return;
}
} else {
if (previous.getTimestamp() < message.getTimestamp()){
boolean result = sharedData.replace(message.getKey(), previous, message);
if (result){
return;
}
} else {
return;
}
}
}
}
public void addPerNodeData(PerNodeDataMessage message){
ConcurrentHashMap<String,PerNodeDataMessage> nodeMap = new ConcurrentHashMap<>();
nodeMap.put(message.getKey(), message);
nodeMap = perNodeData.putIfAbsent(message.getNodeId(), nodeMap);
if (nodeMap != null){
PerNodeDataMessage current = nodeMap.get(message.getKey());
if (current == null){
nodeMap.putIfAbsent(message.getKey(), message);
} else {
if (current.getTimestamp() < message.getTimestamp()){
nodeMap.replace(message.getKey(), current, message);
}
}
}
}
public ConcurrentHashMap<String, ConcurrentHashMap<String, PerNodeDataMessage>> getPerNodeData(){
return perNodeData;
}
public ConcurrentHashMap<String, SharedDataMessage> getSharedData() {
return sharedData;
}
public void shutdown(){
service.shutdown();
try {
service.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.warn(e);
}
service.shutdownNow();
}
public void receive(Base base) {
if (!gossipManager.getMessageInvoker().invoke(this, gossipManager, base)) {
LOGGER.warn("received message can not be handled");
}
}
/**
* Sends a blocking message.
* @param message
* @param uri
* @throws RuntimeException if data can not be serialized or in transmission error
*/
private void sendInternal(Base message, URI uri){
byte[] json_bytes;
try {
if (privKey == null){
json_bytes = gossipManager.getObjectMapper().writeValueAsBytes(message);
} else {
SignedPayload p = new SignedPayload();
p.setData(gossipManager.getObjectMapper().writeValueAsString(message).getBytes());
p.setSignature(sign(p.getData()));
json_bytes = gossipManager.getObjectMapper().writeValueAsBytes(p);
}
} catch (IOException e) {
messageSerdeException.mark();
throw new RuntimeException(e);
}
try (DatagramSocket socket = new DatagramSocket()) {
socket.setSoTimeout(gossipManager.getSettings().getGossipInterval() * 2);
InetAddress dest = InetAddress.getByName(uri.getHost());
DatagramPacket datagramPacket = new DatagramPacket(json_bytes, json_bytes.length, dest, uri.getPort());
socket.send(datagramPacket);
tranmissionSuccess.mark();
} catch (IOException e) {
tranmissionException.mark();
throw new RuntimeException(e);
}
}
public Response send(Base message, URI uri){
if (LOGGER.isDebugEnabled()){
LOGGER.debug("Sending " + message);
LOGGER.debug("Current request queue " + requests);
}
final Trackable t;
LatchAndBase latchAndBase = null;
if (message instanceof Trackable){
t = (Trackable) message;
latchAndBase = new LatchAndBase();
requests.put(t.getUuid() + "/" + t.getUriFrom(), latchAndBase);
} else {
t = null;
}
sendInternal(message, uri);
if (latchAndBase == null){
return null;
}
try {
boolean complete = latchAndBase.latch.await(1, TimeUnit.SECONDS);
if (complete){
return (Response) latchAndBase.base;
} else{
return null;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (latchAndBase != null){
requests.remove(t.getUuid() + "/" + t.getUriFrom());
}
}
}
/**
* Sends a message across the network while blocking. Catches and ignores IOException in transmission. Used
* when the protocol for the message is not to wait for a response
* @param message the message to send
* @param u the uri to send it to
*/
public void sendOneWay(Base message, URI u){
byte[] json_bytes;
try {
if (privKey == null){
json_bytes = gossipManager.getObjectMapper().writeValueAsBytes(message);
} else {
SignedPayload p = new SignedPayload();
p.setData(gossipManager.getObjectMapper().writeValueAsString(message).getBytes());
p.setSignature(sign(p.getData()));
json_bytes = gossipManager.getObjectMapper().writeValueAsBytes(p);
}
} catch (IOException e) {
messageSerdeException.mark();
throw new RuntimeException(e);
}
try (DatagramSocket socket = new DatagramSocket()) {
socket.setSoTimeout(gossipManager.getSettings().getGossipInterval() * 2);
InetAddress dest = InetAddress.getByName(u.getHost());
DatagramPacket datagramPacket = new DatagramPacket(json_bytes, json_bytes.length, dest, u.getPort());
socket.send(datagramPacket);
tranmissionSuccess.mark();
} catch (IOException ex) {
tranmissionException.mark();
LOGGER.debug("Send one way failed", ex);
}
}
public void handleResponse(String k, Base v) {
LatchAndBase latch = requests.get(k);
latch.base = v;
latch.latch.countDown();
}
/**
* Merge lists from remote members and update heartbeats
*
* @param gossipManager
* @param senderMember
* @param remoteList
*
*/
public void mergeLists(GossipManager gossipManager, RemoteMember senderMember,
List<Member> remoteList) {
if (LOGGER.isDebugEnabled()){
debugState(senderMember, remoteList);
}
for (LocalMember i : gossipManager.getDeadMembers()) {
if (i.getId().equals(senderMember.getId())) {
LOGGER.debug(gossipManager.getMyself() + " contacted by dead member " + senderMember.getUri());
i.recordHeartbeat(senderMember.getHeartbeat());
i.setHeartbeat(senderMember.getHeartbeat());
//TODO consider forcing an UP here
}
}
for (Member remoteMember : remoteList) {
if (remoteMember.getId().equals(gossipManager.getMyself().getId())) {
continue;
}
LocalMember aNewMember = new LocalMember(remoteMember.getClusterName(),
remoteMember.getUri(),
remoteMember.getId(),
remoteMember.getHeartbeat(),
remoteMember.getProperties(),
gossipManager.getSettings().getWindowSize(),
gossipManager.getSettings().getMinimumSamples(),
gossipManager.getSettings().getDistribution());
aNewMember.recordHeartbeat(remoteMember.getHeartbeat());
Object result = gossipManager.getMembers().putIfAbsent(aNewMember, GossipState.UP);
if (result != null){
for (Entry<LocalMember, GossipState> localMember : gossipManager.getMembers().entrySet()){
if (localMember.getKey().getId().equals(remoteMember.getId())){
localMember.getKey().recordHeartbeat(remoteMember.getHeartbeat());
localMember.getKey().setHeartbeat(remoteMember.getHeartbeat());
localMember.getKey().setProperties(remoteMember.getProperties());
}
}
}
}
if (LOGGER.isDebugEnabled()){
debugState(senderMember, remoteList);
}
}
private void debugState(RemoteMember senderMember,
List<Member> remoteList){
LOGGER.warn(
"-----------------------\n" +
"Me " + gossipManager.getMyself() + "\n" +
"Sender " + senderMember + "\n" +
"RemoteList " + remoteList + "\n" +
"Live " + gossipManager.getLiveMembers()+ "\n" +
"Dead " + gossipManager.getDeadMembers()+ "\n" +
"=======================");
}
@SuppressWarnings("rawtypes")
public Crdt merge(SharedDataMessage message) {
for (;;){
SharedDataMessage previous = sharedData.putIfAbsent(message.getKey(), message);
if (previous == null){
return (Crdt) message.getPayload();
}
SharedDataMessage copy = new SharedDataMessage();
copy.setExpireAt(message.getExpireAt());
copy.setKey(message.getKey());
copy.setNodeId(message.getNodeId());
copy.setTimestamp(message.getTimestamp());
@SuppressWarnings("unchecked")
Crdt merged = ((Crdt) previous.getPayload()).merge((Crdt) message.getPayload());
copy.setPayload(merged);
boolean replaced = sharedData.replace(message.getKey(), previous, copy);
if (replaced){
return merged;
}
}
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.manager;
public interface GossipCoreConstants {
String WORKQUEUE_SIZE = "gossip.core.workqueue.size";
String PER_NODE_DATA_SIZE = "gossip.core.pernodedata.size";
String SHARED_DATA_SIZE = "gossip.core.shareddata.size";
String REQUEST_SIZE = "gossip.core.requests.size";
String THREADPOOL_ACTIVE = "gossip.core.threadpool.active";
String THREADPOOL_SIZE = "gossip.core.threadpool.size";
String MESSAGE_SERDE_EXCEPTION = "gossip.core.message_serde_exception";
String MESSAGE_TRANSMISSION_EXCEPTION = "gossip.core.message_transmission_exception";
String MESSAGE_TRANSMISSION_SUCCESS = "gossip.core.message_transmission_success";
}

View File

@ -0,0 +1,319 @@
/*
* 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.manager;
import com.codahale.metrics.MetricRegistry;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.gossip.GossipSettings;
import org.apache.gossip.LocalMember;
import org.apache.gossip.Member;
import org.apache.gossip.crdt.Crdt;
import org.apache.gossip.event.GossipListener;
import org.apache.gossip.event.GossipState;
import org.apache.gossip.manager.handlers.MessageInvoker;
import org.apache.gossip.manager.impl.OnlyProcessReceivedPassiveGossipThread;
import org.apache.gossip.model.PerNodeDataMessage;
import org.apache.gossip.model.SharedDataMessage;
import org.apache.log4j.Logger;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
public abstract class GossipManager {
public static final Logger LOGGER = Logger.getLogger(GossipManager.class);
private final ConcurrentSkipListMap<LocalMember, GossipState> members;
private final LocalMember me;
private final GossipSettings settings;
private final AtomicBoolean gossipServiceRunning;
private AbstractActiveGossiper activeGossipThread;
private PassiveGossipThread passiveGossipThread;
private ExecutorService gossipThreadExecutor;
private final GossipCore gossipCore;
private final DataReaper dataReaper;
private final Clock clock;
private final ScheduledExecutorService scheduledServiced;
private final MetricRegistry registry;
private final RingStatePersister ringState;
private final UserDataPersister userDataState;
private final GossipMemberStateRefresher memberStateRefresher;
private final ObjectMapper objectMapper;
private final MessageInvoker messageInvoker;
public GossipManager(String cluster,
URI uri, String id, Map<String, String> properties, GossipSettings settings,
List<Member> gossipMembers, GossipListener listener, MetricRegistry registry,
ObjectMapper objectMapper, MessageInvoker messageInvoker) {
this.settings = settings;
this.messageInvoker = messageInvoker;
clock = new SystemClock();
me = new LocalMember(cluster, uri, id, clock.nanoTime(), properties,
settings.getWindowSize(), settings.getMinimumSamples(), settings.getDistribution());
gossipCore = new GossipCore(this, registry);
dataReaper = new DataReaper(gossipCore, clock);
members = new ConcurrentSkipListMap<>();
for (Member startupMember : gossipMembers) {
if (!startupMember.equals(me)) {
LocalMember member = new LocalMember(startupMember.getClusterName(),
startupMember.getUri(), startupMember.getId(),
clock.nanoTime(), startupMember.getProperties(), settings.getWindowSize(),
settings.getMinimumSamples(), settings.getDistribution());
//TODO should members start in down state?
members.put(member, GossipState.DOWN);
}
}
gossipThreadExecutor = Executors.newCachedThreadPool();
gossipServiceRunning = new AtomicBoolean(true);
this.scheduledServiced = Executors.newScheduledThreadPool(1);
this.registry = registry;
this.ringState = new RingStatePersister(this);
this.userDataState = new UserDataPersister(this, this.gossipCore);
this.memberStateRefresher = new GossipMemberStateRefresher(members, settings, listener, this::findPerNodeGossipData);
this.objectMapper = objectMapper;
readSavedRingState();
readSavedDataState();
}
public MessageInvoker getMessageInvoker() {
return messageInvoker;
}
public ConcurrentSkipListMap<LocalMember, GossipState> getMembers() {
return members;
}
public GossipSettings getSettings() {
return settings;
}
/**
* @return a read only list of members found in the DOWN state.
*/
public List<LocalMember> getDeadMembers() {
return Collections.unmodifiableList(
members.entrySet()
.stream()
.filter(entry -> GossipState.DOWN.equals(entry.getValue()))
.map(Entry::getKey).collect(Collectors.toList()));
}
/**
*
* @return a read only list of members found in the UP state
*/
public List<LocalMember> getLiveMembers() {
return Collections.unmodifiableList(
members.entrySet()
.stream()
.filter(entry -> GossipState.UP.equals(entry.getValue()))
.map(Entry::getKey).collect(Collectors.toList()));
}
public LocalMember getMyself() {
return me;
}
private AbstractActiveGossiper constructActiveGossiper(){
try {
Constructor<?> c = Class.forName(settings.getActiveGossipClass()).getConstructor(GossipManager.class, GossipCore.class, MetricRegistry.class);
return (AbstractActiveGossiper) c.newInstance(this, gossipCore, registry);
} catch (NoSuchMethodException | SecurityException | ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Starts the client. Specifically, start the various cycles for this protocol. Start the gossip
* thread and start the receiver thread.
*/
public void init() {
passiveGossipThread = new OnlyProcessReceivedPassiveGossipThread(this, gossipCore);
gossipThreadExecutor.execute(passiveGossipThread);
activeGossipThread = constructActiveGossiper();
activeGossipThread.init();
dataReaper.init();
scheduledServiced.scheduleAtFixedRate(ringState, 60, 60, TimeUnit.SECONDS);
scheduledServiced.scheduleAtFixedRate(userDataState, 60, 60, TimeUnit.SECONDS);
scheduledServiced.scheduleAtFixedRate(memberStateRefresher, 0, 100, TimeUnit.MILLISECONDS);
LOGGER.debug("The GossipManager is started.");
}
private void readSavedRingState() {
for (LocalMember l : ringState.readFromDisk()){
LocalMember member = new LocalMember(l.getClusterName(),
l.getUri(), l.getId(),
clock.nanoTime(), l.getProperties(), settings.getWindowSize(),
settings.getMinimumSamples(), settings.getDistribution());
members.putIfAbsent(member, GossipState.DOWN);
}
}
private void readSavedDataState() {
for (Entry<String, ConcurrentHashMap<String, PerNodeDataMessage>> l : userDataState.readPerNodeFromDisk().entrySet()){
for (Entry<String, PerNodeDataMessage> j : l.getValue().entrySet()){
gossipCore.addPerNodeData(j.getValue());
}
}
for (Entry<String, SharedDataMessage> l: userDataState.readSharedDataFromDisk().entrySet()){
gossipCore.addSharedData(l.getValue());
}
}
/**
* Shutdown the gossip service.
*/
public void shutdown() {
gossipServiceRunning.set(false);
gossipThreadExecutor.shutdown();
gossipCore.shutdown();
dataReaper.close();
if (passiveGossipThread != null) {
passiveGossipThread.shutdown();
}
if (activeGossipThread != null) {
activeGossipThread.shutdown();
}
try {
boolean result = gossipThreadExecutor.awaitTermination(10, TimeUnit.MILLISECONDS);
if (!result) {
LOGGER.error("executor shutdown timed out");
}
} catch (InterruptedException e) {
LOGGER.error(e);
}
gossipThreadExecutor.shutdownNow();
scheduledServiced.shutdown();
try {
scheduledServiced.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.error(e);
}
scheduledServiced.shutdownNow();
}
public void gossipPerNodeData(PerNodeDataMessage message){
Objects.nonNull(message.getKey());
Objects.nonNull(message.getTimestamp());
Objects.nonNull(message.getPayload());
message.setNodeId(me.getId());
gossipCore.addPerNodeData(message);
}
public void gossipSharedData(SharedDataMessage message){
Objects.nonNull(message.getKey());
Objects.nonNull(message.getTimestamp());
Objects.nonNull(message.getPayload());
message.setNodeId(me.getId());
gossipCore.addSharedData(message);
}
@SuppressWarnings("rawtypes")
public Crdt findCrdt(String key){
SharedDataMessage l = gossipCore.getSharedData().get(key);
if (l == null){
return null;
}
if (l.getExpireAt() < clock.currentTimeMillis()){
return null;
} else {
return (Crdt) l.getPayload();
}
}
@SuppressWarnings("rawtypes")
public Crdt merge(SharedDataMessage message){
Objects.nonNull(message.getKey());
Objects.nonNull(message.getTimestamp());
Objects.nonNull(message.getPayload());
message.setNodeId(me.getId());
if (! (message.getPayload() instanceof Crdt)){
throw new IllegalArgumentException("Not a subclass of CRDT " + message.getPayload());
}
return gossipCore.merge(message);
}
public PerNodeDataMessage findPerNodeGossipData(String nodeId, String key){
ConcurrentHashMap<String, PerNodeDataMessage> j = gossipCore.getPerNodeData().get(nodeId);
if (j == null){
return null;
} else {
PerNodeDataMessage l = j.get(key);
if (l == null){
return null;
}
if (l.getExpireAt() != null && l.getExpireAt() < clock.currentTimeMillis()) {
return null;
}
return l;
}
}
public SharedDataMessage findSharedGossipData(String key){
SharedDataMessage l = gossipCore.getSharedData().get(key);
if (l == null){
return null;
}
if (l.getExpireAt() < clock.currentTimeMillis()){
return null;
} else {
return l;
}
}
public DataReaper getDataReaper() {
return dataReaper;
}
public RingStatePersister getRingState() {
return ringState;
}
public UserDataPersister getUserDataState() {
return userDataState;
}
public GossipMemberStateRefresher getMemberStateRefresher() {
return memberStateRefresher;
}
public Clock getClock() {
return clock;
}
public ObjectMapper getObjectMapper() {
return objectMapper;
}
public MetricRegistry getRegistry() {
return registry;
}
}

View File

@ -0,0 +1,152 @@
/*
* 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.manager;
import com.codahale.metrics.MetricRegistry;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.gossip.Member;
import org.apache.gossip.GossipSettings;
import org.apache.gossip.StartupSettings;
import org.apache.gossip.crdt.CrdtModule;
import org.apache.gossip.event.GossipListener;
import org.apache.gossip.manager.handlers.DefaultMessageInvoker;
import org.apache.gossip.manager.handlers.MessageInvoker;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GossipManagerBuilder {
public static ManagerBuilder newBuilder() {
return new ManagerBuilder();
}
public static final class ManagerBuilder {
private String cluster;
private URI uri;
private String id;
private GossipSettings settings;
private List<Member> gossipMembers;
private GossipListener listener;
private MetricRegistry registry;
private Map<String,String> properties;
private ObjectMapper objectMapper;
private MessageInvoker messageInvoker;
private ManagerBuilder() {}
private void checkArgument(boolean check, String msg) {
if (!check) {
throw new IllegalArgumentException(msg);
}
}
public ManagerBuilder cluster(String cluster) {
this.cluster = cluster;
return this;
}
public ManagerBuilder properties(Map<String,String> properties) {
this.properties = properties;
return this;
}
public ManagerBuilder id(String id) {
this.id = id;
return this;
}
public ManagerBuilder gossipSettings(GossipSettings settings) {
this.settings = settings;
return this;
}
public ManagerBuilder startupSettings(StartupSettings startupSettings) {
this.cluster = startupSettings.getCluster();
this.id = startupSettings.getId();
this.settings = startupSettings.getGossipSettings();
this.gossipMembers = startupSettings.getGossipMembers();
this.uri = startupSettings.getUri();
return this;
}
public ManagerBuilder gossipMembers(List<Member> members) {
this.gossipMembers = members;
return this;
}
public ManagerBuilder listener(GossipListener listener) {
this.listener = listener;
return this;
}
public ManagerBuilder registry(MetricRegistry registry) {
this.registry = registry;
return this;
}
public ManagerBuilder uri(URI uri){
this.uri = uri;
return this;
}
public ManagerBuilder mapper(ObjectMapper objectMapper){
this.objectMapper = objectMapper;
return this;
}
public ManagerBuilder messageInvoker(MessageInvoker messageInvoker) {
this.messageInvoker = messageInvoker;
return this;
}
public GossipManager build() {
checkArgument(id != null, "You must specify an id");
checkArgument(cluster != null, "You must specify a cluster name");
checkArgument(settings != null, "You must specify gossip settings");
checkArgument(uri != null, "You must specify a uri");
if (registry == null){
registry = new MetricRegistry();
}
if (properties == null){
properties = new HashMap<String,String>();
}
if (listener == null){
listener((a,b) -> {});
}
if (gossipMembers == null) {
gossipMembers = new ArrayList<>();
}
if (objectMapper == null) {
objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping();
objectMapper.registerModule(new CrdtModule());
objectMapper.configure(Feature.WRITE_NUMBERS_AS_STRINGS, false);
}
if (messageInvoker == null) {
messageInvoker = new DefaultMessageInvoker();
}
return new GossipManager(cluster, uri, id, properties, settings, gossipMembers, listener, registry, objectMapper, messageInvoker) {} ;
}
}
}

View File

@ -0,0 +1,121 @@
/*
* 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.manager;
import org.apache.gossip.GossipSettings;
import org.apache.gossip.LocalMember;
import org.apache.gossip.event.GossipListener;
import org.apache.gossip.event.GossipState;
import org.apache.gossip.model.PerNodeDataMessage;
import org.apache.gossip.model.ShutdownMessage;
import org.apache.log4j.Logger;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
public class GossipMemberStateRefresher implements Runnable {
public static final Logger LOGGER = Logger.getLogger(GossipMemberStateRefresher.class);
private final Map<LocalMember, GossipState> members;
private final GossipSettings settings;
private final GossipListener listener;
private final Clock clock;
private final BiFunction<String, String, PerNodeDataMessage> findPerNodeGossipData;
public GossipMemberStateRefresher(Map<LocalMember, GossipState> members, GossipSettings settings,
GossipListener listener, BiFunction<String, String, PerNodeDataMessage> findPerNodeGossipData) {
this.members = members;
this.settings = settings;
this.listener = listener;
this.findPerNodeGossipData = findPerNodeGossipData;
clock = new SystemClock();
}
public void run() {
try {
runOnce();
} catch (RuntimeException ex) {
LOGGER.warn("scheduled state had exception", ex);
}
}
public void runOnce() {
for (Entry<LocalMember, GossipState> entry : members.entrySet()) {
boolean userDown = processOptimisticShutdown(entry);
if (userDown)
continue;
Double phiMeasure = entry.getKey().detect(clock.nanoTime());
GossipState requiredState;
if (phiMeasure != null) {
requiredState = calcRequiredState(phiMeasure);
} else {
requiredState = calcRequiredStateCleanupInterval(entry.getKey(), entry.getValue());
}
if (entry.getValue() != requiredState) {
members.put(entry.getKey(), requiredState);
listener.gossipEvent(entry.getKey(), requiredState);
}
}
}
public GossipState calcRequiredState(Double phiMeasure) {
if (phiMeasure > settings.getConvictThreshold())
return GossipState.DOWN;
else
return GossipState.UP;
}
public GossipState calcRequiredStateCleanupInterval(LocalMember member, GossipState state) {
long now = clock.nanoTime();
long nowInMillis = TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
if (nowInMillis - settings.getCleanupInterval() > member.getHeartbeat()) {
return GossipState.DOWN;
} else {
return state;
}
}
/**
* If we have a special key the per-node data that means that the node has sent us
* a pre-emptive shutdown message. We process this so node is seen down sooner
*
* @param l member to consider
* @return true if node forced down
*/
public boolean processOptimisticShutdown(Entry<LocalMember, GossipState> l) {
PerNodeDataMessage m = findPerNodeGossipData.apply(l.getKey().getId(), ShutdownMessage.PER_NODE_KEY);
if (m == null) {
return false;
}
ShutdownMessage s = (ShutdownMessage) m.getPayload();
if (s.getShutdownAtNanos() > l.getKey().getHeartbeat()) {
members.put(l.getKey(), GossipState.DOWN);
if (l.getValue() == GossipState.UP) {
listener.gossipEvent(l.getKey(), GossipState.DOWN);
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.manager;
public interface PassiveGossipConstants {
String SIGNED_MESSAGE = "gossip.passive.signed_message";
String UNSIGNED_MESSAGE = "gossip.passive.unsigned_message";
}

View File

@ -0,0 +1,114 @@
/*
* 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.manager;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.gossip.model.Base;
import org.apache.gossip.model.SignedPayload;
import org.apache.log4j.Logger;
import com.codahale.metrics.Meter;
/**
* This class handles the passive cycle,
* where this client has received an incoming message.
*/
abstract public class PassiveGossipThread implements Runnable {
public static final Logger LOGGER = Logger.getLogger(PassiveGossipThread.class);
/** The socket used for the passive thread of the gossip service. */
private final DatagramSocket server;
private final AtomicBoolean keepRunning;
private final GossipCore gossipCore;
private final GossipManager gossipManager;
private final Meter signed;
private final Meter unsigned;
public PassiveGossipThread(GossipManager gossipManager, GossipCore gossipCore) {
this.gossipManager = gossipManager;
this.gossipCore = gossipCore;
if (gossipManager.getMyself().getClusterName() == null){
throw new IllegalArgumentException("Cluster was null");
}
try {
SocketAddress socketAddress = new InetSocketAddress(gossipManager.getMyself().getUri().getHost(),
gossipManager.getMyself().getUri().getPort());
server = new DatagramSocket(socketAddress);
} catch (SocketException ex) {
LOGGER.warn(ex);
throw new RuntimeException(ex);
}
keepRunning = new AtomicBoolean(true);
signed = gossipManager.getRegistry().meter(PassiveGossipConstants.SIGNED_MESSAGE);
unsigned = gossipManager.getRegistry().meter(PassiveGossipConstants.UNSIGNED_MESSAGE);
}
@Override
public void run() {
while (keepRunning.get()) {
try {
byte[] buf = new byte[server.getReceiveBufferSize()];
DatagramPacket p = new DatagramPacket(buf, buf.length);
server.receive(p);
debug(p.getData());
try {
Base activeGossipMessage = gossipManager.getObjectMapper().readValue(p.getData(), Base.class);
if (activeGossipMessage instanceof SignedPayload){
SignedPayload s = (SignedPayload) activeGossipMessage;
Base nested = gossipManager.getObjectMapper().readValue(s.getData(), Base.class);
gossipCore.receive(nested);
signed.mark();
} else {
gossipCore.receive(activeGossipMessage);
unsigned.mark();
}
gossipManager.getMemberStateRefresher().run();
} catch (RuntimeException ex) {//TODO trap json exception
LOGGER.error("Unable to process message", ex);
}
} catch (IOException e) {
LOGGER.error(e);
keepRunning.set(false);
}
}
shutdown();
}
private void debug(byte[] jsonBytes) {
if (LOGGER.isDebugEnabled()){
String receivedMessage = new String(jsonBytes);
LOGGER.debug("Received message ( bytes): " + receivedMessage);
}
}
public void shutdown() {
try {
server.close();
} catch (RuntimeException ex) {
}
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.manager;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import org.apache.gossip.LocalMember;
import org.apache.log4j.Logger;
public class RingStatePersister implements Runnable {
private static final Logger LOGGER = Logger.getLogger(RingStatePersister.class);
private GossipManager parent;
public RingStatePersister(GossipManager parent){
this.parent = parent;
}
@Override
public void run() {
writeToDisk();
}
File computeTarget(){
return new File(parent.getSettings().getPathToRingState(), "ringstate." + parent.getMyself().getClusterName() + "."
+ parent.getMyself().getId() + ".json");
}
void writeToDisk(){
if (!parent.getSettings().isPersistRingState()){
return;
}
NavigableSet<LocalMember> i = parent.getMembers().keySet();
try (FileOutputStream fos = new FileOutputStream(computeTarget())){
parent.getObjectMapper().writeValue(fos, i);
} catch (IOException e) {
LOGGER.debug(e);
}
}
@SuppressWarnings("unchecked")
List<LocalMember> readFromDisk(){
if (!parent.getSettings().isPersistRingState()){
return Collections.emptyList();
}
try (FileInputStream fos = new FileInputStream(computeTarget())){
return parent.getObjectMapper().readValue(fos, ArrayList.class);
} catch (IOException e) {
LOGGER.debug(e);
}
return Collections.emptyList();
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.manager;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.gossip.LocalMember;
import com.codahale.metrics.MetricRegistry;
/**
* Base implementation gossips randomly to live nodes periodically gossips to dead ones
*
*/
public class SimpleActiveGossipper extends AbstractActiveGossiper {
private ScheduledExecutorService scheduledExecutorService;
private final BlockingQueue<Runnable> workQueue;
private ThreadPoolExecutor threadService;
public SimpleActiveGossipper(GossipManager gossipManager, GossipCore gossipCore,
MetricRegistry registry) {
super(gossipManager, gossipCore, registry);
scheduledExecutorService = Executors.newScheduledThreadPool(2);
workQueue = new ArrayBlockingQueue<Runnable>(1024);
threadService = new ThreadPoolExecutor(1, 30, 1, TimeUnit.SECONDS, workQueue,
new ThreadPoolExecutor.DiscardOldestPolicy());
}
@Override
public void init() {
super.init();
scheduledExecutorService.scheduleAtFixedRate(() -> {
threadService.execute(() -> {
sendToALiveMember();
});
}, 0, gossipManager.getSettings().getGossipInterval(), TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(() -> {
sendToDeadMember();
}, 0, gossipManager.getSettings().getGossipInterval(), TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(
() -> sendPerNodeData(gossipManager.getMyself(),
selectPartner(gossipManager.getLiveMembers())),
0, gossipManager.getSettings().getGossipInterval(), TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(
() -> sendSharedData(gossipManager.getMyself(),
selectPartner(gossipManager.getLiveMembers())),
0, gossipManager.getSettings().getGossipInterval(), TimeUnit.MILLISECONDS);
}
@Override
public void shutdown() {
super.shutdown();
scheduledExecutorService.shutdown();
try {
scheduledExecutorService.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.debug("Issue during shutdown", e);
}
sendShutdownMessage();
threadService.shutdown();
try {
threadService.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.debug("Issue during shutdown", e);
}
}
protected void sendToALiveMember(){
LocalMember member = selectPartner(gossipManager.getLiveMembers());
sendMembershipList(gossipManager.getMyself(), member);
}
protected void sendToDeadMember(){
LocalMember member = selectPartner(gossipManager.getDeadMembers());
sendMembershipList(gossipManager.getMyself(), member);
}
/**
* sends an optimistic shutdown message to several clusters nodes
*/
protected void sendShutdownMessage(){
List<LocalMember> l = gossipManager.getLiveMembers();
int sendTo = l.size() < 3 ? 1 : l.size() / 2;
for (int i = 0; i < sendTo; i++) {
threadService.execute(() -> sendShutdownMessage(gossipManager.getMyself(), selectPartner(l)));
}
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.manager;
public class SystemClock implements Clock {
@Override
public long currentTimeMillis() {
return System.currentTimeMillis();
}
@Override
public long nanoTime() {
return System.nanoTime();
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.manager;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.gossip.model.PerNodeDataMessage;
import org.apache.gossip.model.SharedDataMessage;
import org.apache.log4j.Logger;
public class UserDataPersister implements Runnable {
private static final Logger LOGGER = Logger.getLogger(UserDataPersister.class);
private final GossipManager parent;
private final GossipCore gossipCore;
UserDataPersister(GossipManager parent, GossipCore gossipCore){
this.parent = parent;
this.gossipCore = gossipCore;
}
File computeSharedTarget(){
return new File(parent.getSettings().getPathToDataState(), "shareddata."
+ parent.getMyself().getClusterName() + "." + parent.getMyself().getId() + ".json");
}
File computePerNodeTarget() {
return new File(parent.getSettings().getPathToDataState(), "pernodedata."
+ parent.getMyself().getClusterName() + "." + parent.getMyself().getId() + ".json");
}
@SuppressWarnings("unchecked")
ConcurrentHashMap<String, ConcurrentHashMap<String, PerNodeDataMessage>> readPerNodeFromDisk(){
if (!parent.getSettings().isPersistDataState()){
return new ConcurrentHashMap<String, ConcurrentHashMap<String, PerNodeDataMessage>>();
}
try (FileInputStream fos = new FileInputStream(computePerNodeTarget())){
return parent.getObjectMapper().readValue(fos, ConcurrentHashMap.class);
} catch (IOException e) {
LOGGER.debug(e);
}
return new ConcurrentHashMap<String, ConcurrentHashMap<String, PerNodeDataMessage>>();
}
void writePerNodeToDisk(){
if (!parent.getSettings().isPersistDataState()){
return;
}
try (FileOutputStream fos = new FileOutputStream(computePerNodeTarget())){
parent.getObjectMapper().writeValue(fos, gossipCore.getPerNodeData());
} catch (IOException e) {
LOGGER.warn(e);
}
}
void writeSharedToDisk(){
if (!parent.getSettings().isPersistDataState()){
return;
}
try (FileOutputStream fos = new FileOutputStream(computeSharedTarget())){
parent.getObjectMapper().writeValue(fos, gossipCore.getSharedData());
} catch (IOException e) {
LOGGER.warn(e);
}
}
@SuppressWarnings("unchecked")
ConcurrentHashMap<String, SharedDataMessage> readSharedDataFromDisk(){
if (!parent.getSettings().isPersistRingState()){
return new ConcurrentHashMap<String, SharedDataMessage>();
}
try (FileInputStream fos = new FileInputStream(computeSharedTarget())){
return parent.getObjectMapper().readValue(fos, ConcurrentHashMap.class);
} catch (IOException e) {
LOGGER.debug(e);
}
return new ConcurrentHashMap<String, SharedDataMessage>();
}
/**
* Writes all pernode and shared data to disk
*/
@Override
public void run() {
writePerNodeToDisk();
writeSharedToDisk();
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.manager.handlers;
import org.apache.gossip.Member;
import org.apache.gossip.RemoteMember;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.Base;
import org.apache.gossip.udp.UdpActiveGossipMessage;
import org.apache.gossip.udp.UdpActiveGossipOk;
import org.apache.gossip.udp.UdpNotAMemberFault;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
public class ActiveGossipMessageHandler implements MessageHandler {
@Override
public void invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) {
List<Member> remoteGossipMembers = new ArrayList<>();
RemoteMember senderMember = null;
UdpActiveGossipMessage activeGossipMessage = (UdpActiveGossipMessage) base;
for (int i = 0; i < activeGossipMessage.getMembers().size(); i++) {
URI u;
try {
u = new URI(activeGossipMessage.getMembers().get(i).getUri());
} catch (URISyntaxException e) {
GossipCore.LOGGER.debug("Gossip message with faulty URI", e);
continue;
}
RemoteMember member = new RemoteMember(
activeGossipMessage.getMembers().get(i).getCluster(),
u,
activeGossipMessage.getMembers().get(i).getId(),
activeGossipMessage.getMembers().get(i).getHeartbeat(),
activeGossipMessage.getMembers().get(i).getProperties());
if (i == 0) {
senderMember = member;
}
if (!(member.getClusterName().equals(gossipManager.getMyself().getClusterName()))) {
UdpNotAMemberFault f = new UdpNotAMemberFault();
f.setException("Not a member of this cluster " + i);
f.setUriFrom(activeGossipMessage.getUriFrom());
f.setUuid(activeGossipMessage.getUuid());
GossipCore.LOGGER.warn(f);
gossipCore.sendOneWay(f, member.getUri());
continue;
}
remoteGossipMembers.add(member);
}
UdpActiveGossipOk o = new UdpActiveGossipOk();
o.setUriFrom(activeGossipMessage.getUriFrom());
o.setUuid(activeGossipMessage.getUuid());
gossipCore.sendOneWay(o, senderMember.getUri());
gossipCore.mergeLists(gossipManager, senderMember, remoteGossipMembers);
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.manager.handlers;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.*;
public class DefaultMessageInvoker implements MessageInvoker {
private final MessageInvokerCombiner mic;
public DefaultMessageInvoker() {
mic = new MessageInvokerCombiner();
mic.add(new SimpleMessageInvoker(Response.class, new ResponseHandler()));
mic.add(new SimpleMessageInvoker(ShutdownMessage.class, new ShutdownMessageHandler()));
mic.add(new SimpleMessageInvoker(PerNodeDataMessage.class, new PerNodeDataMessageHandler()));
mic.add(new SimpleMessageInvoker(SharedDataMessage.class, new SharedDataMessageHandler()));
mic.add(new SimpleMessageInvoker(ActiveGossipMessage.class, new ActiveGossipMessageHandler()));
}
public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) {
return mic.invoke(gossipCore, gossipManager, base);
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.manager.handlers;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.Base;
public interface MessageHandler {
void invoke(GossipCore gossipCore, GossipManager gossipManager, Base base);
}

View File

@ -0,0 +1,33 @@
/*
* 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.manager.handlers;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.Base;
public interface MessageInvoker {
/**
*
* @param gossipCore
* @param gossipManager
* @param base
* @return true if the invoker processed the message type
*/
boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base);
}

View File

@ -0,0 +1,48 @@
/*
* 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.manager.handlers;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.Base;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class MessageInvokerCombiner implements MessageInvoker {
private final List<MessageInvoker> invokers = new CopyOnWriteArrayList<>();
public MessageInvokerCombiner() {
}
public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) {
return invokers.stream().filter((mi) -> mi.invoke(gossipCore, gossipManager, base)).count() > 0;
}
public void clear() {
invokers.clear();
}
public void add(MessageInvoker mi) {
if (mi == null) {
throw new NullPointerException();
}
invokers.add(mi);
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.manager.handlers;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.Base;
import org.apache.gossip.udp.UdpPerNodeDataMessage;
public class PerNodeDataMessageHandler implements MessageHandler {
@Override
public void invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) {
UdpPerNodeDataMessage message = (UdpPerNodeDataMessage) base;
gossipCore.addPerNodeData(message);
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.manager.handlers;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.Base;
import org.apache.gossip.udp.Trackable;
public class ResponseHandler implements MessageHandler {
@Override
public void invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) {
if (base instanceof Trackable) {
Trackable t = (Trackable) base;
gossipCore.handleResponse(t.getUuid() + "/" + t.getUriFrom(), (Base) t);
}
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.manager.handlers;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.Base;
import org.apache.gossip.udp.UdpSharedDataMessage;
public class SharedDataMessageHandler implements MessageHandler{
@Override
public void invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) {
UdpSharedDataMessage message = (UdpSharedDataMessage) base;
gossipCore.addSharedData(message);
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.manager.handlers;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.Base;
import org.apache.gossip.model.PerNodeDataMessage;
import org.apache.gossip.model.ShutdownMessage;
public class ShutdownMessageHandler implements MessageHandler {
@Override
public void invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) {
ShutdownMessage s = (ShutdownMessage) base;
PerNodeDataMessage m = new PerNodeDataMessage();
m.setKey(ShutdownMessage.PER_NODE_KEY);
m.setNodeId(s.getNodeId());
m.setPayload(base);
m.setTimestamp(System.currentTimeMillis());
m.setExpireAt(System.currentTimeMillis() + 30L * 1000L);
gossipCore.addPerNodeData(m);
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.manager.handlers;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.Base;
public class SimpleMessageInvoker implements MessageInvoker {
final private Class<?> messageClass;
final private MessageHandler messageHandler;
public SimpleMessageInvoker(Class<?> messageClass, MessageHandler messageHandler) {
if (messageClass == null || messageHandler == null) {
throw new NullPointerException();
}
this.messageClass = messageClass;
this.messageHandler = messageHandler;
}
@Override
public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) {
if (messageClass.isAssignableFrom(base.getClass())) {
messageHandler.invoke(gossipCore, gossipManager, base);
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.manager.impl;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.manager.PassiveGossipThread;
import org.apache.log4j.Logger;
public class OnlyProcessReceivedPassiveGossipThread extends PassiveGossipThread {
public static final Logger LOGGER = Logger.getLogger(OnlyProcessReceivedPassiveGossipThread.class);
public OnlyProcessReceivedPassiveGossipThread(GossipManager gossipManager, GossipCore gossipCore) {
super(gossipManager, gossipCore);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.model;
import java.util.ArrayList;
import java.util.List;
public class ActiveGossipMessage extends Base {
private List<Member> members = new ArrayList<>();
public ActiveGossipMessage(){
}
public List<Member> getMembers() {
return members;
}
public void setMembers(List<Member> members) {
this.members = members;
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.model;
public class ActiveGossipOk extends Response {
}

View File

@ -0,0 +1,49 @@
/*
* 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.model;
import org.apache.gossip.udp.UdpActiveGossipMessage;
import org.apache.gossip.udp.UdpActiveGossipOk;
import org.apache.gossip.udp.UdpPerNodeDataMessage;
import org.apache.gossip.udp.UdpNotAMemberFault;
import org.apache.gossip.udp.UdpSharedDataMessage;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
@JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@Type(value = ActiveGossipMessage.class, name = "ActiveGossipMessage"),
@Type(value = Fault.class, name = "Fault"),
@Type(value = ActiveGossipOk.class, name = "ActiveGossipOk"),
@Type(value = UdpActiveGossipOk.class, name = "UdpActiveGossipOk"),
@Type(value = UdpActiveGossipMessage.class, name = "UdpActiveGossipMessage"),
@Type(value = UdpNotAMemberFault.class, name = "UdpNotAMemberFault"),
@Type(value = PerNodeDataMessage.class, name = "PerNodeDataMessage"),
@Type(value = UdpPerNodeDataMessage.class, name = "UdpPerNodeDataMessage"),
@Type(value = SharedDataMessage.class, name = "SharedDataMessage"),
@Type(value = UdpSharedDataMessage.class, name = "UdpSharedDataMessage")
})
public class Base {
}

View File

@ -0,0 +1,40 @@
/*
* 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.model;
public abstract class Fault extends Response {
private String exception;
public Fault(){}
public String getException() {
return exception;
}
public void setException(String exception) {
this.exception = exception;
}
@Override
public String toString() {
return "Fault [exception=" + exception + "]";
}
}

View File

@ -0,0 +1,87 @@
/*
* 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.model;
import java.util.Map;
public class Member {
private String cluster;
private String uri;
private String id;
private Long heartbeat;
private Map<String,String> properties;
public Member(){
}
public Member(String cluster, String uri, String id, Long heartbeat){
this.cluster = cluster;
this.uri = uri;
this.id = id;
this.heartbeat = heartbeat;
}
public String getCluster() {
return cluster;
}
public void setCluster(String cluster) {
this.cluster = cluster;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Long getHeartbeat() {
return heartbeat;
}
public void setHeartbeat(Long heartbeat) {
this.heartbeat = heartbeat;
}
public Map<String, String> getProperties() {
return properties;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
@Override
public String toString() {
return "Member [cluster=" + cluster + ", uri=" + uri + ", id=" + id + ", heartbeat="
+ heartbeat + ", properties=" + properties + "]";
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.model;
public class Message extends Base {
}

View File

@ -0,0 +1,29 @@
/*
* 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.model;
public class NotAMemberFault extends Fault {
public NotAMemberFault(){
}
public NotAMemberFault(String message){
this.setException(message);
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.model;
public class PerNodeDataMessage extends Base {
private String nodeId;
private String key;
private Object payload;
private Long timestamp;
private Long expireAt;
public String getNodeId() {
return nodeId;
}
public void setNodeId(String nodeId) {
this.nodeId = nodeId;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Object getPayload() {
return payload;
}
public void setPayload(Object payload) {
this.payload = payload;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public Long getExpireAt() {
return expireAt;
}
public void setExpireAt(Long expireAt) {
this.expireAt = expireAt;
}
@Override
public String toString() {
return "GossipDataMessage [nodeId=" + nodeId + ", key=" + key + ", payload=" + payload
+ ", timestamp=" + timestamp + ", expireAt=" + expireAt + "]";
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.model;
public abstract class Response extends Base {
}

View File

@ -0,0 +1,64 @@
/*
* 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.model;
public class SharedDataMessage extends Base {
private String nodeId;
private String key;
private Object payload;
private Long timestamp;
private Long expireAt;
public String getNodeId() {
return nodeId;
}
public void setNodeId(String nodeId) {
this.nodeId = nodeId;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Object getPayload() {
return payload;
}
public void setPayload(Object payload) {
this.payload = payload;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public Long getExpireAt() {
return expireAt;
}
public void setExpireAt(Long expireAt) {
this.expireAt = expireAt;
}
@Override
public String toString() {
return "SharedGossipDataMessage [nodeId=" + nodeId + ", key=" + key + ", payload=" + payload
+ ", timestamp=" + timestamp + ", expireAt=" + expireAt + "]";
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.model;
public class ShutdownMessage extends Message {
public static final String PER_NODE_KEY = "gossipcore.shutdowmessage";
private long shutdownAtNanos;
private String nodeId;
public ShutdownMessage(){
}
public String getNodeId() {
return nodeId;
}
public void setNodeId(String nodeId) {
this.nodeId = nodeId;
}
public long getShutdownAtNanos() {
return shutdownAtNanos;
}
public void setShutdownAtNanos(long shutdownAtNanos) {
this.shutdownAtNanos = shutdownAtNanos;
}
@Override
public String toString() {
return "ShutdownMessage [shutdownAtNanos=" + shutdownAtNanos + ", nodeId=" + nodeId + "]";
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.model;
public class SignedPayload extends Base{
private byte [] data;
private byte [] signature;
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
public byte[] getSignature() {
return signature;
}
public void setSignature(byte[] signature) {
this.signature = signature;
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.secure;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
public class KeyTool {
public static void generatePubandPrivateKeyFiles(String path, String id)
throws NoSuchAlgorithmException, NoSuchProviderException, IOException{
SecureRandom r = new SecureRandom();
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA", "SUN");
keyGen.initialize(1024, r);
KeyPair pair = keyGen.generateKeyPair();
PrivateKey priv = pair.getPrivate();
PublicKey pub = pair.getPublic();
{
FileOutputStream sigfos = new FileOutputStream(new File(path, id));
sigfos.write(priv.getEncoded());
sigfos.close();
}
{
FileOutputStream sigfos = new FileOutputStream(new File(path, id + ".pub"));
sigfos.write(pub.getEncoded());
sigfos.close();
}
}
public static void main (String [] args) throws
NoSuchAlgorithmException, NoSuchProviderException, IOException{
generatePubandPrivateKeyFiles(args[0], args[1]);
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.udp;
public interface Trackable {
String getUriFrom();
void setUriFrom(String uriFrom);
String getUuid();
void setUuid(String uuid);
}

View File

@ -0,0 +1,49 @@
/*
* 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.udp;
import org.apache.gossip.model.ActiveGossipMessage;
public class UdpActiveGossipMessage extends ActiveGossipMessage implements Trackable {
private String uriFrom;
private String uuid;
public String getUriFrom() {
return uriFrom;
}
public void setUriFrom(String uriFrom) {
this.uriFrom = uriFrom;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "UdpActiveGossipMessage [uriFrom=" + uriFrom + ", uuid=" + uuid + ", getMembers()="
+ getMembers() + "]";
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.udp;
import org.apache.gossip.model.ActiveGossipOk;
public class UdpActiveGossipOk extends ActiveGossipOk implements Trackable {
private String uriFrom;
private String uuid;
public String getUriFrom() {
return uriFrom;
}
public void setUriFrom(String uriFrom) {
this.uriFrom = uriFrom;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.udp;
import org.apache.gossip.model.NotAMemberFault;
public class UdpNotAMemberFault extends NotAMemberFault implements Trackable{
public UdpNotAMemberFault(){
}
private String uriFrom;
private String uuid;
public String getUriFrom() {
return uriFrom;
}
public void setUriFrom(String uriFrom) {
this.uriFrom = uriFrom;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.udp;
import org.apache.gossip.model.PerNodeDataMessage;
public class UdpPerNodeDataMessage extends PerNodeDataMessage implements Trackable {
private String uriFrom;
private String uuid;
public String getUriFrom() {
return uriFrom;
}
public void setUriFrom(String uriFrom) {
this.uriFrom = uriFrom;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "UdpGossipDataMessage [uriFrom=" + uriFrom + ", uuid=" + uuid + "]";
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.udp;
import org.apache.gossip.model.SharedDataMessage;
public class UdpSharedDataMessage extends SharedDataMessage implements Trackable {
private String uriFrom;
private String uuid;
public String getUriFrom() {
return uriFrom;
}
public void setUriFrom(String uriFrom) {
this.uriFrom = uriFrom;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
@Override
public String toString() {
return "UdpSharedGossipDataMessage [uriFrom=" + uriFrom + ", uuid=" + uuid + ", getNodeId()="
+ getNodeId() + ", getKey()=" + getKey() + ", getPayload()=" + getPayload()
+ ", getTimestamp()=" + getTimestamp() + ", getExpireAt()=" + getExpireAt() + "]";
}
}

View File

@ -0,0 +1,20 @@
# Licensed 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.
log4j.rootLogger=INFO,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %d{HH:mm:ss,SSS} %m%n
log4j.logger.io.teknek=DEBUG
log4j.logger.com.google.code.gossip=INFO

View File

@ -0,0 +1,50 @@
/*
* 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;
import java.util.ArrayList;
import java.util.List;
import org.apache.gossip.manager.GossipManager;
import org.junit.After;
import org.junit.Before;
public abstract class AbstractIntegrationBase {
List <GossipManager> nodes = new ArrayList<GossipManager>();
public void register(GossipManager manager){
nodes.add(manager);
}
@Before
public void before(){
nodes = new ArrayList<GossipManager>();
}
@After
public void after(){
for (GossipManager node: nodes){
if (node !=null){
node.shutdown();
}
}
}
}

View File

@ -0,0 +1,238 @@
/*
* 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;
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.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.gossip.crdt.GrowOnlyCounter;
import org.apache.gossip.crdt.GrowOnlySet;
import org.apache.gossip.crdt.OrSet;
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.Test;
import io.teknek.tunit.TUnit;
public class DataTest extends AbstractIntegrationBase {
private String orSetKey = "cror";
private String gCounterKey = "crdtgc";
@Test
public void dataTest() throws InterruptedException, UnknownHostException, URISyntaxException{
GossipSettings settings = new GossipSettings();
settings.setPersistRingState(false);
settings.setPersistDataState(false);
String cluster = UUID.randomUUID().toString();
int seedNodes = 1;
List<Member> startupMembers = new ArrayList<>();
for (int i = 1; i < seedNodes+1; ++i) {
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (50000 + i));
startupMembers.add(new RemoteMember(cluster, uri, i + ""));
}
final List<GossipManager> clients = new ArrayList<>();
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();
clients.add(gossipService);
gossipService.init();
register(gossipService);
}
TUnit.assertThat(() -> {
int total = 0;
for (int i = 0; i < clusterMembers; ++i) {
total += clients.get(i).getLiveMembers().size();
}
return total;
}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo(2);
clients.get(0).gossipPerNodeData(msg());
clients.get(0).gossipSharedData(sharedMsg());
TUnit.assertThat(()-> {
PerNodeDataMessage x = clients.get(1).findPerNodeGossipData(1 + "", "a");
if (x == null)
return "";
else
return x.getPayload();
}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo("b");
TUnit.assertThat(() -> {
SharedDataMessage x = clients.get(1).findSharedGossipData("a");
if (x == null)
return "";
else
return x.getPayload();
}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo("c");
givenDifferentDatumsInSet(clients);
assertThatListIsMerged(clients);
givenOrs(clients);
assertThatOrSetIsMerged(clients);
dropIt(clients);
assertThatOrSetDelIsMerged(clients);
// test g counter
givenDifferentIncrement(clients);
assertThatCountIsUpdated(clients, 3);
givenIncreaseOther(clients);
assertThatCountIsUpdated(clients, 7);
for (int i = 0; i < clusterMembers; ++i) {
clients.get(i).shutdown();
}
}
private void givenDifferentIncrement(final List<GossipManager> clients) {
{
SharedDataMessage d = new SharedDataMessage();
d.setKey(gCounterKey);
d.setPayload(new GrowOnlyCounter(new GrowOnlyCounter.Builder(clients.get(0)).increment(1L)));
d.setExpireAt(Long.MAX_VALUE);
d.setTimestamp(System.currentTimeMillis());
clients.get(0).merge(d);
}
{
SharedDataMessage d = new SharedDataMessage();
d.setKey(gCounterKey);
d.setPayload(new GrowOnlyCounter(new GrowOnlyCounter.Builder(clients.get(1)).increment(2L)));
d.setExpireAt(Long.MAX_VALUE);
d.setTimestamp(System.currentTimeMillis());
clients.get(1).merge(d);
}
}
private void givenIncreaseOther(final List<GossipManager> clients) {
GrowOnlyCounter gc = (GrowOnlyCounter) clients.get(1).findCrdt(gCounterKey);
GrowOnlyCounter gc2 = new GrowOnlyCounter(gc,
new GrowOnlyCounter.Builder(clients.get(1)).increment(4L));
SharedDataMessage d = new SharedDataMessage();
d.setKey(gCounterKey);
d.setPayload(gc2);
d.setExpireAt(Long.MAX_VALUE);
d.setTimestamp(System.currentTimeMillis());
clients.get(1).merge(d);
}
private void givenOrs(List<GossipManager> clients) {
{
SharedDataMessage d = new SharedDataMessage();
d.setKey(orSetKey);
d.setPayload(new OrSet<String>("1", "2"));
d.setExpireAt(Long.MAX_VALUE);
d.setTimestamp(System.currentTimeMillis());
clients.get(0).merge(d);
}
{
SharedDataMessage d = new SharedDataMessage();
d.setKey(orSetKey);
d.setPayload(new OrSet<String>("3", "4"));
d.setExpireAt(Long.MAX_VALUE);
d.setTimestamp(System.currentTimeMillis());
clients.get(1).merge(d);
}
}
private void dropIt(List<GossipManager> clients) {
@SuppressWarnings("unchecked")
OrSet<String> o = (OrSet<String>) clients.get(0).findCrdt(orSetKey);
OrSet<String> o2 = new OrSet<String>(o, new OrSet.Builder<String>().remove("3"));
SharedDataMessage d = new SharedDataMessage();
d.setKey(orSetKey);
d.setPayload(o2);
d.setExpireAt(Long.MAX_VALUE);
d.setTimestamp(System.currentTimeMillis());
clients.get(0).merge(d);
}
private void assertThatOrSetIsMerged(final List<GossipManager> clients){
TUnit.assertThat(() -> {
return clients.get(0).findCrdt(orSetKey).value();
}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(new OrSet<String>("1", "2", "3", "4").value());
TUnit.assertThat(() -> {
return clients.get(1).findCrdt(orSetKey).value();
}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(new OrSet<String>("1", "2", "3", "4").value());
}
private void assertThatOrSetDelIsMerged(final List<GossipManager> clients){
TUnit.assertThat(() -> {
return clients.get(0).findCrdt(orSetKey);
}).afterWaitingAtMost(10, TimeUnit.SECONDS).equals(new OrSet<String>("1", "2", "4"));
}
private void givenDifferentDatumsInSet(final List<GossipManager> clients){
clients.get(0).merge(CrdtMessage("1"));
clients.get(1).merge(CrdtMessage("2"));
}
private void assertThatCountIsUpdated(final List<GossipManager> clients, long finalCount) {
TUnit.assertThat(() -> {
return clients.get(0).findCrdt(gCounterKey);
}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(new GrowOnlyCounter(
new GrowOnlyCounter.Builder(clients.get(0)).increment(finalCount)));
}
private void assertThatListIsMerged(final List<GossipManager> clients){
TUnit.assertThat(() -> {
return clients.get(0).findCrdt("cr");
}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(new GrowOnlySet<String>(Arrays.asList("1","2")));
}
private SharedDataMessage CrdtMessage(String item){
SharedDataMessage d = new SharedDataMessage();
d.setKey("cr");
d.setPayload(new GrowOnlySet<String>( Arrays.asList(item)));
d.setExpireAt(Long.MAX_VALUE);
d.setTimestamp(System.currentTimeMillis());
return d;
}
private PerNodeDataMessage msg(){
PerNodeDataMessage g = new PerNodeDataMessage();
g.setExpireAt(Long.MAX_VALUE);
g.setKey("a");
g.setPayload("b");
g.setTimestamp(System.currentTimeMillis());
return g;
}
private SharedDataMessage sharedMsg(){
SharedDataMessage g = new SharedDataMessage();
g.setExpireAt(Long.MAX_VALUE);
g.setKey("a");
g.setPayload("c");
g.setTimestamp(System.currentTimeMillis());
return g;
}
}

View File

@ -0,0 +1,91 @@
/*
* 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;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.gossip.manager.DatacenterRackAwareActiveGossiper;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.manager.GossipManagerBuilder;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import io.teknek.tunit.TUnit;
@RunWith(JUnitPlatform.class)
public class IdAndPropertyTest extends AbstractIntegrationBase {
@Test
public void testDatacenterRackGossiper() throws URISyntaxException, UnknownHostException, InterruptedException {
GossipSettings settings = new GossipSettings();
settings.setActiveGossipClass(DatacenterRackAwareActiveGossiper.class.getName());
List<Member> startupMembers = new ArrayList<>();
Map<String, String> x = new HashMap<>();
x.put("a", "b");
x.put("datacenter", "dc1");
x.put("rack", "rack1");
GossipManager gossipService1 = GossipManagerBuilder.newBuilder()
.cluster("a")
.uri(new URI("udp://" + "127.0.0.1" + ":" + (29000 + 0)))
.id("0")
.properties(x)
.gossipMembers(startupMembers)
.gossipSettings(settings).build();
gossipService1.init();
register(gossipService1);
Map<String, String> y = new HashMap<>();
y.put("a", "c");
y.put("datacenter", "dc2");
y.put("rack", "rack2");
GossipManager gossipService2 = GossipManagerBuilder.newBuilder().cluster("a")
.uri( new URI("udp://" + "127.0.0.1" + ":" + (29000 + 10)))
.id("1")
.properties(y)
.gossipMembers(Arrays.asList(new RemoteMember("a",
new URI("udp://" + "127.0.0.1" + ":" + (29000 + 0)), "0")))
.gossipSettings(settings).build();
gossipService2.init();
register(gossipService2);
TUnit.assertThat(() -> {
String value = "";
try {
value = gossipService1.getLiveMembers().get(0).getProperties().get("a");
} catch (RuntimeException e){ }
return value;
}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo("c");
TUnit.assertThat(() -> {
String value = "";
try {
value = gossipService2.getLiveMembers().get(0).getProperties().get("a");
} catch (RuntimeException e){ }
return value;
}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo("b");
}
}

View File

@ -0,0 +1,40 @@
/*
* 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;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
@RunWith(JUnitPlatform.class)
public class MemberTest {
@Test
public void testHashCodeFromGossip40() throws URISyntaxException {
Assert.assertNotEquals(
new LocalMember("mycluster", new URI("udp://4.4.4.4:1000"), "myid", 1, new HashMap<String,String>(), 10, 5, "exponential")
.hashCode(),
new LocalMember("mycluster", new URI("udp://4.4.4.5:1005"), "yourid", 11, new HashMap<String,String>(), 11, 6, "exponential")
.hashCode());
}
}

View File

@ -0,0 +1,146 @@
/*
* 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;
import io.teknek.tunit.TUnit;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.manager.GossipManagerBuilder;
import org.apache.log4j.Logger;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
@RunWith(JUnitPlatform.class)
public class ShutdownDeadtimeTest {
private static final Logger log = Logger.getLogger(ShutdownDeadtimeTest.class);
@Test
public void DeadNodesDoNotComeAliveAgain()
throws InterruptedException, UnknownHostException, URISyntaxException {
GossipSettings settings = new GossipSettings(100, 10000, 1000, 1, 10.0, "normal");
settings.setPersistRingState(false);
settings.setPersistDataState(false);
String cluster = UUID.randomUUID().toString();
int seedNodes = 3;
List<Member> startupMembers = new ArrayList<>();
for (int i = 1; i < seedNodes + 1; ++i) {
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (30300 + i));
startupMembers.add(new RemoteMember(cluster, uri, i + ""));
}
final List<GossipManager> clients = Collections.synchronizedList(new ArrayList<GossipManager>());
final int clusterMembers = 5;
for (int i = 1; i < clusterMembers + 1; ++i) {
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (30300 + i));
GossipManager gossipService = GossipManagerBuilder.newBuilder()
.cluster(cluster)
.uri(uri)
.id(i + "")
.gossipMembers(startupMembers)
.gossipSettings(settings)
.build();
clients.add(gossipService);
gossipService.init();
}
TUnit.assertThat(new Callable<Integer>() {
public Integer call() throws Exception {
int total = 0;
for (int i = 0; i < clusterMembers; ++i) {
total += clients.get(i).getLiveMembers().size();
}
return total;
}
}).afterWaitingAtMost(40, TimeUnit.SECONDS).isEqualTo(20);
// shutdown one client and verify that one client is lost.
Random r = new Random();
int randomClientId = r.nextInt(clusterMembers);
log.info("shutting down " + randomClientId);
final int shutdownPort = clients.get(randomClientId).getMyself().getUri()
.getPort();
final String shutdownId = clients.get(randomClientId).getMyself().getId();
clients.get(randomClientId).shutdown();
TUnit.assertThat(new Callable<Integer>() {
public Integer call() throws Exception {
int total = 0;
for (int i = 0; i < clusterMembers; ++i) {
total += clients.get(i).getLiveMembers().size();
}
return total;
}
}).afterWaitingAtMost(40, TimeUnit.SECONDS).isEqualTo(16);
clients.remove(randomClientId);
TUnit.assertThat(new Callable<Integer>() {
public Integer call() throws Exception {
int total = 0;
for (int i = 0; i < clusterMembers - 1; ++i) {
total += clients.get(i).getDeadMembers().size();
}
return total;
}
}).afterWaitingAtMost(50, TimeUnit.SECONDS).isEqualTo(4);
URI uri = new URI("udp://" + "127.0.0.1" + ":" + shutdownPort);
// start client again
GossipManager gossipService = GossipManagerBuilder.newBuilder()
.gossipSettings(settings)
.cluster(cluster)
.uri(uri)
.id(shutdownId+"")
.gossipMembers(startupMembers)
.build();
clients.add(gossipService);
gossipService.init();
// verify that the client is alive again for every node
TUnit.assertThat(new Callable<Integer>() {
public Integer call() throws Exception {
int total = 0;
for (int i = 0; i < clusterMembers; ++i) {
total += clients.get(i).getLiveMembers().size();
}
return total;
}
}).afterWaitingAtMost(60, TimeUnit.SECONDS).isEqualTo(20);
for (int i = 0; i < clusterMembers; ++i) {
final int j = i;
new Thread() {
public void run(){
clients.get(j).shutdown();
}
}.start();
}
}
}

View File

@ -0,0 +1,135 @@
/*
* 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;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.manager.GossipManagerBuilder;
import org.apache.gossip.manager.PassiveGossipConstants;
import org.apache.gossip.secure.KeyTool;
import org.junit.Assert;
import org.junit.Test;
import io.teknek.tunit.TUnit;
public class SignedMessageTest extends AbstractIntegrationBase {
@Test(expected = IllegalArgumentException.class)
public void ifSignMustHaveKeys()
throws URISyntaxException, UnknownHostException, InterruptedException {
String cluster = UUID.randomUUID().toString();
GossipSettings settings = gossiperThatSigns();
List<Member> startupMembers = new ArrayList<>();
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (30000 + 1));
GossipManager gossipService = GossipManagerBuilder.newBuilder()
.cluster(cluster)
.uri(uri)
.id(1 + "")
.gossipMembers(startupMembers)
.gossipSettings(settings)
.build();
gossipService.init();
}
private GossipSettings gossiperThatSigns(){
GossipSettings settings = new GossipSettings();
settings.setPersistRingState(false);
settings.setPersistDataState(false);
settings.setSignMessages(true);
return settings;
}
private GossipSettings gossiperThatSigns(String keysDir){
GossipSettings settings = gossiperThatSigns();
settings.setPathToKeyStore(Objects.requireNonNull(keysDir));
return settings;
}
@Test
public void dataTest() throws InterruptedException, URISyntaxException, NoSuchAlgorithmException, NoSuchProviderException, IOException {
final String keys = System.getProperty("java.io.tmpdir") + "/keys";
GossipSettings settings = gossiperThatSigns(keys);
setup(keys);
String cluster = UUID.randomUUID().toString();
List<Member> startupMembers = new ArrayList<>();
for (int i = 1; i < 2; ++i) {
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (30000 + i));
startupMembers.add(new RemoteMember(cluster, uri, i + ""));
}
final List<GossipManager> clients = new ArrayList<>();
for (int i = 1; i < 3; ++i) {
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (30000 + i));
GossipManager gossipService = GossipManagerBuilder.newBuilder()
.cluster(cluster)
.uri(uri)
.id(i + "")
.gossipMembers(startupMembers)
.gossipSettings(settings)
.build();
gossipService.init();
clients.add(gossipService);
}
assertTwoAlive(clients);
assertOnlySignedMessages(clients);
cleanup(keys, clients);
}
private void assertTwoAlive(List<GossipManager> clients){
TUnit.assertThat(() -> {
int total = 0;
for (int i = 0; i < clients.size(); ++i) {
total += clients.get(i).getLiveMembers().size();
}
return total;
}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo(2);
}
private void assertOnlySignedMessages(List<GossipManager> clients){
Assert.assertEquals(0, clients.get(0).getRegistry()
.meter(PassiveGossipConstants.UNSIGNED_MESSAGE).getCount());
Assert.assertTrue(clients.get(0).getRegistry()
.meter(PassiveGossipConstants.SIGNED_MESSAGE).getCount() > 0);
}
private void cleanup(String keys, List<GossipManager> clients){
new File(keys, "1").delete();
new File(keys, "2").delete();
new File(keys).delete();
for (int i = 0; i < clients.size(); ++i) {
clients.get(i).shutdown();
}
}
private void setup(String keys) throws NoSuchAlgorithmException, NoSuchProviderException, IOException {
new File(keys).mkdir();
KeyTool.generatePubandPrivateKeyFiles(keys, "1");
KeyTool.generatePubandPrivateKeyFiles(keys, "2");
}
}

View File

@ -0,0 +1,86 @@
/*
* 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;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.manager.GossipManagerBuilder;
import org.apache.log4j.Logger;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
/**
* Tests support of using {@code StartupSettings} and thereby reading
* setup config from file.
*/
@RunWith(JUnitPlatform.class)
public class StartupSettingsTest {
private static final Logger log = Logger.getLogger(StartupSettingsTest.class);
private static final String CLUSTER = UUID.randomUUID().toString();
@Test
public void testUsingSettingsFile() throws IOException, InterruptedException, URISyntaxException {
File settingsFile = File.createTempFile("gossipTest",".json");
settingsFile.deleteOnExit();
writeSettingsFile(settingsFile);
URI uri = new URI("udp://" + "127.0.0.1" + ":" + 50000);
GossipManager firstService = GossipManagerBuilder.newBuilder()
.cluster(CLUSTER)
.uri(uri)
.id("1")
.gossipSettings(new GossipSettings()).build();
firstService.init();
GossipManager manager = GossipManagerBuilder.newBuilder()
.startupSettings(StartupSettings.fromJSONFile(settingsFile)).build();
manager.init();
firstService.shutdown();
manager.shutdown();
}
private void writeSettingsFile( File target ) throws IOException {
String settings =
"[{\n" + // It is odd that this is meant to be in an array, but oh well.
" \"cluster\":\"" + CLUSTER + "\",\n" +
" \"id\":\"" + "2" + "\",\n" +
" \"uri\":\"udp://127.0.0.1:50001\",\n" +
" \"gossip_interval\":1000,\n" +
" \"window_size\":1000,\n" +
" \"minimum_samples\":5,\n" +
" \"cleanup_interval\":10000,\n" +
" \"convict_threshold\":2.6,\n" +
" \"distribution\":\"exponential\",\n" +
" \"properties\":{},\n" +
" \"members\":[\n" +
" {\"cluster\": \"" + CLUSTER + "\",\"uri\":\"udp://127.0.0.1:5000\"}\n" +
" ]\n" +
"}]";
log.info( "Using settings file with contents of:\n---\n" + settings + "\n---" );
FileOutputStream output = new FileOutputStream(target);
output.write(settings.getBytes());
output.close();
}
}

View File

@ -0,0 +1,92 @@
/*
* 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;
import io.teknek.tunit.TUnit;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.manager.GossipManagerBuilder;
import org.junit.jupiter.api.Test;
@RunWith(JUnitPlatform.class)
public class TenNodeThreeSeedTest {
@Test
public void test() throws UnknownHostException, InterruptedException, URISyntaxException {
abc(30150);
}
@Test
public void testAgain() throws UnknownHostException, InterruptedException, URISyntaxException {
abc(30100);
}
public void abc(int base) throws InterruptedException, UnknownHostException, URISyntaxException {
GossipSettings settings = new GossipSettings(1000, 10000, 1000, 1, 1.6, "exponential");
settings.setPersistRingState(false);
settings.setPersistDataState(false);
String cluster = UUID.randomUUID().toString();
int seedNodes = 3;
List<Member> startupMembers = new ArrayList<>();
for (int i = 1; i < seedNodes+1; ++i) {
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (base + i));
startupMembers.add(new RemoteMember(cluster, uri, i + ""));
}
final List<GossipManager> clients = new ArrayList<>();
final int clusterMembers = 5;
for (int i = 1; i < clusterMembers+1; ++i) {
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (base + i));
GossipManager gossipService = GossipManagerBuilder.newBuilder()
.cluster(cluster)
.uri(uri)
.id(i + "")
.gossipSettings(settings)
.gossipMembers(startupMembers)
.build();
gossipService.init();
clients.add(gossipService);
}
TUnit.assertThat(new Callable<Integer> (){
public Integer call() throws Exception {
int total = 0;
for (int i = 0; i < clusterMembers; ++i) {
total += clients.get(i).getLiveMembers().size();
}
return total;
}}).afterWaitingAtMost(40, TimeUnit.SECONDS).isEqualTo(20);
for (int i = 0; i < clusterMembers; ++i) {
int j = i;
new Thread(){
public void run(){
clients.get(j).shutdown();
}
}.start();
}
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.accrual;
import org.apache.gossip.GossipSettings;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@RunWith(JUnitPlatform.class)
public class FailureDetectorTest {
@FunctionalInterface
interface TriConsumer<A, B, C> {
void accept(A a, B b, C c);
}
static final Double failureThreshold = new GossipSettings().getConvictThreshold();
List<Integer> generateTimeList(int begin, int end, int step) {
List<Integer> values = new ArrayList<>();
Random rand = new Random();
for (int i = begin; i < end; i += step) {
int delta = (int) ((rand.nextDouble() - 0.5) * step / 2);
values.add(i + delta);
}
return values;
}
@Test
public void normalDistribution() {
FailureDetector fd = new FailureDetector(1, 1000, "normal");
List<Integer> values = generateTimeList(0, 10000, 100);
Double deltaSum = 0.0;
Integer deltaCount = 0;
for (int i = 0; i < values.size() - 1; i++) {
fd.recordHeartbeat(values.get(i));
if (i != 0) {
deltaSum += values.get(i) - values.get(i - 1);
deltaCount++;
}
}
Integer lastRecorded = values.get(values.size() - 2);
//after "step" delay we need to be considered UP
Assert.assertTrue(fd.computePhiMeasure(values.get(values.size() - 1)) < failureThreshold);
//if we check phi-measure after mean delay we get value for 0.5 probability(normal distribution)
Assert.assertEquals(fd.computePhiMeasure(lastRecorded + Math.round(deltaSum / deltaCount)), -Math.log10(0.5), 0.1);
}
@Test
public void checkMinimumSamples() {
Integer minimumSamples = 5;
FailureDetector fd = new FailureDetector(minimumSamples, 1000, "normal");
for (int i = 0; i < minimumSamples + 1; i++) { // +1 because we don't place first heartbeat into structure
Assert.assertNull(fd.computePhiMeasure(100));
fd.recordHeartbeat(i);
}
Assert.assertNotNull(fd.computePhiMeasure(100));
}
@Test
public void checkMonotonicDead() {
final FailureDetector fd = new FailureDetector(5, 1000, "normal");
TriConsumer<Integer, Integer, Integer> checkAlive = (begin, end, step) -> {
List<Integer> times = generateTimeList(begin, end, step);
for (int i = 0; i < times.size(); i++) {
Double current = fd.computePhiMeasure(times.get(i));
if (current != null) {
Assert.assertTrue(current < failureThreshold);
}
fd.recordHeartbeat(times.get(i));
}
};
TriConsumer<Integer, Integer, Integer> checkDeadMonotonic = (begin, end, step) -> {
List<Integer> times = generateTimeList(begin, end, step);
Double prev = null;
for (int i = 0; i < times.size(); i++) {
Double current = fd.computePhiMeasure(times.get(i));
if (current != null && prev != null) {
Assert.assertTrue(current >= prev);
}
prev = current;
}
};
checkAlive.accept(0, 20000, 100);
checkDeadMonotonic.accept(20000, 20500, 5);
}
}

View File

@ -0,0 +1,54 @@
/*
* 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 org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
public class GrowOnlyCounterTest {
@Test
public void mergeTest() {
Map<String, Long> node1Counter = new HashMap<>();
node1Counter.put("1", 3L);
Map<String, Long> node2Counter = new HashMap<>();
node2Counter.put("2", 1L);
Map<String, Long> node3Counter = new HashMap<>();
node3Counter.put("3", 2L);
GrowOnlyCounter gCounter1 = new GrowOnlyCounter(node1Counter);
GrowOnlyCounter gCounter2 = new GrowOnlyCounter(node2Counter);
GrowOnlyCounter gCounter3 = new GrowOnlyCounter(node3Counter);
// After node 2 receive from node 1
gCounter2 = gCounter2.merge(gCounter1);
Assert.assertEquals(4, (long) gCounter2.value());
// After node 3 receive from node 1
gCounter3 = gCounter3.merge(gCounter1);
Assert.assertEquals(5, (long) gCounter3.value());
// After node 3 receive from node 2
gCounter3 = gCounter3.merge(gCounter2);
Assert.assertEquals(6, (long) gCounter3.value());
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import org.junit.Assert;
import org.junit.Test;
public class GrowOnlySetTest {
@SuppressWarnings("rawtypes")
@Test
public void mergeTest(){
ConcurrentHashMap<String, Crdt> a = new ConcurrentHashMap<>();
GrowOnlySet<String> gset = new GrowOnlySet<>(Arrays.asList("a", "b"));
Assert.assertEquals(gset, a.merge("a", gset, new CrdtBiFunctionMerge()));
GrowOnlySet<String> over = new GrowOnlySet<>(Arrays.asList("b", "d"));
Assert.assertEquals(new GrowOnlySet<>(Arrays.asList("a", "b", "d")),
a.merge("a", over, CrdtBiFunctionMerge::applyStatic));
}
}

View File

@ -0,0 +1,115 @@
/*
* 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.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.gossip.GossipSettings;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.manager.GossipManagerBuilder;
import org.junit.Assert;
import org.junit.Test;
public class OrSetTest {
@Test
public void atest() {
OrSet<Integer> i = new OrSet<>(new OrSet.Builder<Integer>().add(4).add(5).add(6).remove(5));
Assert.assertArrayEquals(Arrays.asList(4, 6).toArray(), i.value().toArray());
}
@Test
public void mergeTest(){
OrSet<Integer> i = new OrSet<>(new OrSet.Builder<Integer>().add(4).add(5).add(6).remove(5));
Assert.assertArrayEquals(Arrays.asList(4, 6).toArray(), i.value().toArray());
OrSet<Integer> j = new OrSet<>(new OrSet.Builder<Integer>().add(9).add(4).add(5).remove(6));
OrSet<Integer> h = i.merge(j);
Assert.assertEquals(new OrSet<Integer>(4,6,9,5), h);
}
@Test
public void mergeTest2(){
OrSet<Integer> i = new OrSet<>(new OrSet.Builder<Integer>().add(5).add(4).remove(4).add(6));
Assert.assertEquals(new OrSet<Integer>(5,6), i);
SortedSet<Integer> tree = new TreeSet<>();
for (Integer in: i.value()){
tree.add(in);
}
TreeSet<Integer> compare = new TreeSet<>();
compare.add(5);
compare.add(6);
Assert.assertEquals(tree, compare);
}
@Test
public void mergeTest4() {
Assert.assertArrayEquals(new Integer[] {},
new OrSet<Integer>(new OrSet.Builder<Integer>().add(1).remove(1)).toArray());
}
@Test
public void mergeTest3(){
OrSet<Integer> i = new OrSet<>(1);
OrSet<Integer> j = new OrSet<>(2);
OrSet<Integer> k = new OrSet<>(i.merge(j), new OrSet.Builder<Integer>().remove(1));
Assert.assertArrayEquals(new Integer[] { 2 }, i.merge(j).merge(k).toArray());
Assert.assertArrayEquals(new Integer[] { 2 }, j.merge(i).merge(k).toArray());
Assert.assertArrayEquals(new Integer[] { 2 }, k.merge(i).merge(j).toArray());
Assert.assertArrayEquals(new Integer[] { 2 }, k.merge(j).merge(i).toArray());
Assert.assertEquals(j , i.merge(j.merge(k)));
}
@Test
public void mergeTest9(){
OrSet<Integer> i = new OrSet<>(19);
OrSet<Integer> j = i.merge(i);
Assert.assertEquals(i.value(), j.value());
}
@Test
public void serialTest() throws InterruptedException, URISyntaxException, IOException {
GossipManager gossipService2 = GossipManagerBuilder.newBuilder()
.cluster("a")
.uri(new URI("udp://" + "127.0.0.1" + ":" + (29000 + 1)))
.id("1")
.gossipSettings(new GossipSettings())
.build();
OrSet<Integer> i = new OrSet<Integer>(new OrSet.Builder<Integer>().add(1).remove(1));
String s = gossipService2.getObjectMapper().writeValueAsString(i);
@SuppressWarnings("unchecked")
OrSet<Integer> back = gossipService2.getObjectMapper().readValue(s, OrSet.class);
Assert.assertEquals(back, i);
}
@Test
public void mergeTestSame() {
OrSet<Integer> i = new OrSet<>(19);
OrSet<Integer> j = new OrSet<>(19);
OrSet<Integer> k = i.merge(j);
Assert.assertEquals(2, k.getElements().get(19).size());
OrSet<Integer> y = new OrSet<>(k, new OrSet.Builder<Integer>().remove(19));
Assert.assertEquals(2, y.getTombstones().get(19).size());
Assert.assertEquals(2, y.getElements().get(19).size());
Assert.assertEquals(new OrSet<Integer>().value(), y.value());
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.manager;
import com.codahale.metrics.MetricRegistry;
import java.net.URI;
import org.apache.gossip.GossipSettings;
import org.apache.gossip.model.PerNodeDataMessage;
import org.apache.gossip.model.SharedDataMessage;
import org.junit.Assert;
import org.junit.Test;
import io.teknek.tunit.TUnit;
public class DataReaperTest {
private final MetricRegistry registry = new MetricRegistry();
String myId = "4";
String key = "key";
String value = "a";
@Test
public void testReaperOneShot() {
GossipSettings settings = new GossipSettings();
settings.setPersistRingState(false);
settings.setPersistDataState(false);
GossipManager gm = GossipManagerBuilder.newBuilder().cluster("abc").gossipSettings(settings)
.id(myId).uri(URI.create("udp://localhost:6000")).registry(registry).build();
gm.init();
gm.gossipPerNodeData(perNodeDatum(key, value));
gm.gossipSharedData(sharedDatum(key, value));
assertDataIsAtCorrectValue(gm);
gm.getDataReaper().runPerNodeOnce();
gm.getDataReaper().runSharedOnce();
assertDataIsRemoved(gm);
gm.shutdown();
}
private void assertDataIsAtCorrectValue(GossipManager gm){
Assert.assertEquals(value, gm.findPerNodeGossipData(myId, key).getPayload());
Assert.assertEquals(1, registry.getGauges().get(GossipCoreConstants.PER_NODE_DATA_SIZE).getValue());
Assert.assertEquals(value, gm.findSharedGossipData(key).getPayload());
Assert.assertEquals(1, registry.getGauges().get(GossipCoreConstants.SHARED_DATA_SIZE).getValue());
}
private void assertDataIsRemoved(GossipManager gm){
TUnit.assertThat(() -> gm.findPerNodeGossipData(myId, key)).equals(null);
TUnit.assertThat(() -> gm.findSharedGossipData(key)).equals(null);
}
private PerNodeDataMessage perNodeDatum(String key, String value) {
PerNodeDataMessage m = new PerNodeDataMessage();
m.setExpireAt(System.currentTimeMillis() + 5L);
m.setKey(key);
m.setPayload(value);
m.setTimestamp(System.currentTimeMillis());
return m;
}
private SharedDataMessage sharedDatum(String key, String value) {
SharedDataMessage m = new SharedDataMessage();
m.setExpireAt(System.currentTimeMillis() + 5L);
m.setKey(key);
m.setPayload(value);
m.setTimestamp(System.currentTimeMillis());
return m;
}
@Test
public void testHigherTimestampWins() {
String myId = "4";
String key = "key";
String value = "a";
GossipSettings settings = new GossipSettings();
GossipManager gm = GossipManagerBuilder.newBuilder().cluster("abc").gossipSettings(settings)
.id(myId).uri(URI.create("udp://localhost:7000")).registry(registry).build();
gm.init();
PerNodeDataMessage before = perNodeDatum(key, value);
PerNodeDataMessage after = perNodeDatum(key, "b");
after.setTimestamp(after.getTimestamp() - 1);
gm.gossipPerNodeData(before);
Assert.assertEquals(value, gm.findPerNodeGossipData(myId, key).getPayload());
gm.gossipPerNodeData(after);
Assert.assertEquals(value, gm.findPerNodeGossipData(myId, key).getPayload());
gm.shutdown();
}
}

View File

@ -0,0 +1,121 @@
/*
* 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.manager;
import com.codahale.metrics.MetricRegistry;
import org.apache.gossip.Member;
import org.apache.gossip.GossipSettings;
import org.apache.gossip.LocalMember;
import org.apache.gossip.manager.handlers.DefaultMessageInvoker;
import org.apache.gossip.manager.handlers.MessageInvoker;
import org.apache.gossip.manager.handlers.ResponseHandler;
import org.apache.gossip.manager.handlers.SimpleMessageInvoker;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import javax.xml.ws.Response;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.expectThrows;
@RunWith(JUnitPlatform.class)
public class GossipManagerBuilderTest {
@Test
public void idShouldNotBeNull() {
expectThrows(IllegalArgumentException.class,() -> {
GossipManagerBuilder.newBuilder().cluster("aCluster").build();
});
}
@Test
public void clusterShouldNotBeNull() {
expectThrows(IllegalArgumentException.class,() -> {
GossipManagerBuilder.newBuilder().id("id").build();
});
}
@Test
public void settingsShouldNotBeNull() {
expectThrows(IllegalArgumentException.class,() -> {
GossipManagerBuilder.newBuilder().id("id").cluster("aCluster").build();
});
}
@Test
public void createMembersListIfNull() throws URISyntaxException {
GossipManager gossipManager = GossipManagerBuilder.newBuilder()
.id("id")
.cluster("aCluster")
.uri(new URI("udp://localhost:2000"))
.gossipSettings(new GossipSettings())
.gossipMembers(null).registry(new MetricRegistry()).build();
assertNotNull(gossipManager.getLiveMembers());
}
@Test
public void createDefaultMessageInvokerIfNull() throws URISyntaxException {
GossipManager gossipManager = GossipManagerBuilder.newBuilder()
.id("id")
.cluster("aCluster")
.uri(new URI("udp://localhost:2000"))
.gossipSettings(new GossipSettings())
.messageInvoker(null).registry(new MetricRegistry()).build();
assertNotNull(gossipManager.getMessageInvoker());
Assert.assertEquals(gossipManager.getMessageInvoker().getClass(), new DefaultMessageInvoker().getClass());
}
@Test
public void testMessageInvokerKeeping() throws URISyntaxException {
MessageInvoker mi = new SimpleMessageInvoker(Response.class, new ResponseHandler());
GossipManager gossipManager = GossipManagerBuilder.newBuilder()
.id("id")
.cluster("aCluster")
.uri(new URI("udp://localhost:2000"))
.gossipSettings(new GossipSettings())
.messageInvoker(mi).registry(new MetricRegistry()).build();
assertNotNull(gossipManager.getMessageInvoker());
Assert.assertEquals(gossipManager.getMessageInvoker(), mi);
}
@Test
public void useMemberListIfProvided() throws URISyntaxException {
LocalMember member = new LocalMember(
"aCluster", new URI("udp://localhost:2000"), "aGossipMember",
System.nanoTime(), new HashMap<String, String>(), 1000, 1, "exponential");
List<Member> memberList = new ArrayList<>();
memberList.add(member);
GossipManager gossipManager = GossipManagerBuilder.newBuilder()
.id("id")
.cluster("aCluster")
.gossipSettings(new GossipSettings())
.uri(new URI("udp://localhost:8000"))
.gossipMembers(memberList).registry(new MetricRegistry()).build();
assertEquals(1, gossipManager.getDeadMembers().size());
assertEquals(member.getId(), gossipManager.getDeadMembers().get(0).getId());
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.manager;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Arrays;
import org.apache.gossip.GossipSettings;
import org.apache.gossip.RemoteMember;
import org.junit.Assert;
import org.junit.Test;
public class RingPersistenceTest {
@Test
public void givenThatRingIsPersisted() throws UnknownHostException, InterruptedException, URISyntaxException {
GossipSettings settings = new GossipSettings();
File f = aGossiperPersists(settings);
Assert.assertTrue(f.exists());
aNewInstanceGetsRingInfo(settings);
f.delete();
}
private File aGossiperPersists(GossipSettings settings) throws UnknownHostException, InterruptedException, URISyntaxException {
GossipManager gossipService = GossipManagerBuilder.newBuilder()
.cluster("a")
.uri(new URI("udp://" + "127.0.0.1" + ":" + (29000 + 1)))
.id("1")
.gossipSettings(settings)
.gossipMembers(
Arrays.asList(
new RemoteMember("a", new URI("udp://" + "127.0.0.1" + ":" + (29000 + 0)), "0"),
new RemoteMember("a", new URI("udp://" + "127.0.0.1" + ":" + (29000 + 2)), "2"))).build();
gossipService.getRingState().writeToDisk();
return gossipService.getRingState().computeTarget();
}
private void aNewInstanceGetsRingInfo(GossipSettings settings) throws UnknownHostException, InterruptedException, URISyntaxException {
GossipManager gossipService2 = GossipManagerBuilder.newBuilder()
.cluster("a")
.uri(new URI("udp://" + "127.0.0.1" + ":" + (29000 + 1)))
.id("1")
.gossipSettings(settings).build();
Assert.assertEquals(2, gossipService2.getMembers().size());
}
}

View File

@ -0,0 +1,114 @@
/*
* 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.manager;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.gossip.GossipSettings;
import org.apache.gossip.model.PerNodeDataMessage;
import org.apache.gossip.model.SharedDataMessage;
import org.junit.Assert;
import org.junit.Test;
public class UserDataPersistenceTest {
String nodeId = "1";
private GossipManager sameService() throws URISyntaxException {
GossipSettings settings = new GossipSettings();
return GossipManagerBuilder.newBuilder()
.cluster("a")
.uri(new URI("udp://" + "127.0.0.1" + ":" + (29000 + 1)))
.id(nodeId)
.gossipSettings(settings).build();
}
@Test
public void givenThatRingIsPersisted() throws UnknownHostException, InterruptedException, URISyntaxException {
{ //Create a gossip service and force it to persist its user data
GossipManager gossipService = sameService();
gossipService.init();
gossipService.gossipPerNodeData(getToothpick());
gossipService.gossipSharedData(getAnotherToothpick());
gossipService.getUserDataState().writePerNodeToDisk();
gossipService.getUserDataState().writeSharedToDisk();
{ //read the raw data and confirm
ConcurrentHashMap<String, ConcurrentHashMap<String, PerNodeDataMessage>> l = gossipService.getUserDataState().readPerNodeFromDisk();
Assert.assertEquals("red", ((AToothpick) l.get(nodeId).get("a").getPayload()).getColor());
}
{
ConcurrentHashMap<String, SharedDataMessage> l =
gossipService.getUserDataState().readSharedDataFromDisk();
Assert.assertEquals("blue", ((AToothpick) l.get("a").getPayload()).getColor());
}
gossipService.shutdown();
}
{ //recreate the service and see that the data is read back in
GossipManager gossipService = sameService();
gossipService.init();
Assert.assertEquals("red", ((AToothpick) gossipService.findPerNodeGossipData(nodeId, "a").getPayload()).getColor());
Assert.assertEquals("blue", ((AToothpick) gossipService.findSharedGossipData("a").getPayload()).getColor());
File f = gossipService.getUserDataState().computeSharedTarget();
File g = gossipService.getUserDataState().computePerNodeTarget();
gossipService.shutdown();
f.delete();
g.delete();
}
}
public PerNodeDataMessage getToothpick(){
AToothpick a = new AToothpick();
a.setColor("red");
PerNodeDataMessage d = new PerNodeDataMessage();
d.setExpireAt(Long.MAX_VALUE);
d.setKey("a");
d.setPayload(a);
d.setTimestamp(System.currentTimeMillis());
return d;
}
public SharedDataMessage getAnotherToothpick(){
AToothpick a = new AToothpick();
a.setColor("blue");
SharedDataMessage d = new SharedDataMessage();
d.setExpireAt(Long.MAX_VALUE);
d.setKey("a");
d.setPayload(a);
d.setTimestamp(System.currentTimeMillis());
return d;
}
public static class AToothpick {
private String color;
public AToothpick(){
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
}

View File

@ -0,0 +1,178 @@
/*
* 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.manager.handlers;
import org.apache.gossip.manager.GossipCore;
import org.apache.gossip.manager.GossipManager;
import org.apache.gossip.model.ActiveGossipMessage;
import org.apache.gossip.model.Base;
import org.apache.gossip.udp.UdpSharedDataMessage;
import org.junit.Assert;
import org.junit.Test;
public class MessageInvokerTest {
private class FakeMessage extends Base {
public FakeMessage() {
}
}
private class FakeMessageData extends Base {
public int data;
public FakeMessageData(int data) {
this.data = data;
}
}
private class FakeMessageDataHandler implements MessageHandler {
public int data;
public FakeMessageDataHandler() {
data = 0;
}
public void invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) {
data = ((FakeMessageData) base).data;
}
}
private class FakeMessageHandler implements MessageHandler {
public int counter;
public FakeMessageHandler() {
counter = 0;
}
public void invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) {
counter++;
}
}
@Test
public void testSimpleInvoker() {
MessageInvoker mi = new SimpleMessageInvoker(FakeMessage.class, new FakeMessageHandler());
Assert.assertTrue(mi.invoke(null, null, new FakeMessage()));
Assert.assertFalse(mi.invoke(null, null, new ActiveGossipMessage()));
}
@Test(expected = NullPointerException.class)
public void testSimpleInvokerNullClassConstructor() {
new SimpleMessageInvoker(null, new FakeMessageHandler());
}
@Test(expected = NullPointerException.class)
public void testSimpleInvokerNullHandlerConstructor() {
new SimpleMessageInvoker(FakeMessage.class, null);
}
@Test
public void testCallCountSimpleInvoker() {
FakeMessageHandler h = new FakeMessageHandler();
MessageInvoker mi = new SimpleMessageInvoker(FakeMessage.class, h);
mi.invoke(null, null, new FakeMessage());
Assert.assertEquals(1, h.counter);
mi.invoke(null, null, new ActiveGossipMessage());
Assert.assertEquals(1, h.counter);
mi.invoke(null, null, new FakeMessage());
Assert.assertEquals(2, h.counter);
}
@Test(expected = NullPointerException.class)
public void cantAddNullInvoker() {
MessageInvokerCombiner mi = new MessageInvokerCombiner();
mi.add(null);
}
@Test
public void testCombinerClear() {
MessageInvokerCombiner mi = new MessageInvokerCombiner();
mi.add(new SimpleMessageInvoker(FakeMessage.class, new FakeMessageHandler()));
Assert.assertTrue(mi.invoke(null, null, new FakeMessage()));
mi.clear();
Assert.assertFalse(mi.invoke(null, null, new FakeMessage()));
}
@Test
public void testMessageInvokerCombiner() {
//Empty combiner - false result
MessageInvokerCombiner mi = new MessageInvokerCombiner();
Assert.assertFalse(mi.invoke(null, null, new Base()));
FakeMessageHandler h = new FakeMessageHandler();
mi.add(new SimpleMessageInvoker(FakeMessage.class, h));
mi.add(new SimpleMessageInvoker(FakeMessage.class, h));
Assert.assertTrue(mi.invoke(null, null, new FakeMessage()));
Assert.assertFalse(mi.invoke(null, null, new ActiveGossipMessage()));
Assert.assertEquals(2, h.counter);
//Increase size in runtime. Should be 3 calls: 2+3 = 5
mi.add(new SimpleMessageInvoker(FakeMessage.class, h));
Assert.assertTrue(mi.invoke(null, null, new FakeMessage()));
Assert.assertEquals(5, h.counter);
}
@Test
public void testMessageInvokerCombiner2levels() {
MessageInvokerCombiner mi = new MessageInvokerCombiner();
FakeMessageHandler h = new FakeMessageHandler();
MessageInvokerCombiner mi1 = new MessageInvokerCombiner();
mi1.add(new SimpleMessageInvoker(FakeMessage.class, h));
mi1.add(new SimpleMessageInvoker(FakeMessage.class, h));
MessageInvokerCombiner mi2 = new MessageInvokerCombiner();
mi2.add(new SimpleMessageInvoker(FakeMessage.class, h));
mi2.add(new SimpleMessageInvoker(FakeMessage.class, h));
mi.add(mi1);
mi.add(mi2);
Assert.assertTrue(mi.invoke(null, null, new FakeMessage()));
Assert.assertEquals(4, h.counter);
}
@Test
public void testMessageInvokerCombinerDataShipping() {
MessageInvokerCombiner mi = new MessageInvokerCombiner();
FakeMessageDataHandler h = new FakeMessageDataHandler();
mi.add(new SimpleMessageInvoker(FakeMessageData.class, h));
Assert.assertTrue(mi.invoke(null, null, new FakeMessageData(101)));
Assert.assertEquals(101, h.data);
}
@Test
public void testCombiningDefaultInvoker() {
MessageInvokerCombiner mi = new MessageInvokerCombiner();
mi.add(new DefaultMessageInvoker());
mi.add(new SimpleMessageInvoker(FakeMessage.class, new FakeMessageHandler()));
//UdpSharedGossipDataMessage with null gossipCore -> exception
boolean thrown = false;
try {
mi.invoke(null, null, new UdpSharedDataMessage());
} catch (NullPointerException e) {
thrown = true;
}
Assert.assertTrue(thrown);
//DefaultInvoker skips FakeMessage and FakeHandler works ok
Assert.assertTrue(mi.invoke(null, null, new FakeMessage()));
}
}

View File

@ -0,0 +1,20 @@
# Licensed 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.
log4j.rootLogger=INFO,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %d{HH:mm:ss,SSS} %m%n
log4j.logger.io.teknek=DEBUG
log4j.logger.com.google.code.gossip=INFO