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:
226
gossip-base/src/main/java/org/apache/gossip/GossipSettings.java
Normal file
226
gossip-base/src/main/java/org/apache/gossip/GossipSettings.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
71
gossip-base/src/main/java/org/apache/gossip/LocalMember.java
Normal file
71
gossip-base/src/main/java/org/apache/gossip/LocalMember.java
Normal 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 +" ]";
|
||||
}
|
||||
|
||||
}
|
166
gossip-base/src/main/java/org/apache/gossip/Member.java
Normal file
166
gossip-base/src/main/java/org/apache/gossip/Member.java
Normal 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());
|
||||
}
|
||||
}
|
@ -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>());
|
||||
}
|
||||
|
||||
}
|
207
gossip-base/src/main/java/org/apache/gossip/StartupSettings.java
Normal file
207
gossip-base/src/main/java/org/apache/gossip/StartupSettings.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
39
gossip-base/src/main/java/org/apache/gossip/crdt/Crdt.java
Normal file
39
gossip-base/src/main/java/org/apache/gossip/crdt/Crdt.java
Normal 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();
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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> {
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
304
gossip-base/src/main/java/org/apache/gossip/crdt/OrSet.java
Normal file
304
gossip-base/src/main/java/org/apache/gossip/crdt/OrSet.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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) {} ;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
49
gossip-base/src/main/java/org/apache/gossip/model/Base.java
Normal file
49
gossip-base/src/main/java/org/apache/gossip/model/Base.java
Normal 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 {
|
||||
|
||||
}
|
40
gossip-base/src/main/java/org/apache/gossip/model/Fault.java
Normal file
40
gossip-base/src/main/java/org/apache/gossip/model/Fault.java
Normal 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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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() + "]";
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
@ -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() + "]";
|
||||
}
|
||||
|
||||
}
|
20
gossip-base/src/main/resources/log4j.properties
Normal file
20
gossip-base/src/main/resources/log4j.properties
Normal 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
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
238
gossip-base/src/test/java/org/apache/gossip/DataTest.java
Normal file
238
gossip-base/src/test/java/org/apache/gossip/DataTest.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
40
gossip-base/src/test/java/org/apache/gossip/MemberTest.java
Normal file
40
gossip-base/src/test/java/org/apache/gossip/MemberTest.java
Normal 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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
115
gossip-base/src/test/java/org/apache/gossip/crdt/OrSetTest.java
Normal file
115
gossip-base/src/test/java/org/apache/gossip/crdt/OrSetTest.java
Normal 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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
20
gossip-base/src/test/resources/log4j.properties
Normal file
20
gossip-base/src/test/resources/log4j.properties
Normal 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
|
Reference in New Issue
Block a user