huge rework, RAFT WORKING
This commit is contained in:
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@@ -9,6 +9,7 @@
|
|||||||
</option>
|
</option>
|
||||||
<option name="ignoredFiles">
|
<option name="ignoredFiles">
|
||||||
<set>
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$/SymphonyDiscovery/pom.xml" />
|
||||||
<option value="$PROJECT_DIR$/SymphonyFTP/pom.xml" />
|
<option value="$PROJECT_DIR$/SymphonyFTP/pom.xml" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
package io.github.lumijiez;
|
package io.github.lumijiez;
|
||||||
|
|
||||||
|
import io.github.lumijiez.raft.Raft;
|
||||||
import io.github.lumijiez.app.NodeManager;
|
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
|
public static final String HOST = System.getenv().getOrDefault("HOSTNAME", "localhost");
|
||||||
|
public static final int PORT = Integer.parseInt(System.getenv().getOrDefault("UDP_PORT", "8084"));
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
NodeManager manager = new NodeManager();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
Raft raft = new Raft();
|
||||||
Thread.currentThread().join();
|
Thread.currentThread().join();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
package io.github.lumijiez.app;
|
|
||||||
|
|
||||||
import io.github.lumijiez.data.models.NodeInfo;
|
|
||||||
import io.github.lumijiez.network.UdpListener;
|
|
||||||
import io.github.lumijiez.network.UdpMessageSender;
|
|
||||||
import io.github.lumijiez.network.WebSocketManager;
|
|
||||||
import io.github.lumijiez.raft.Raft;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class NodeManager {
|
|
||||||
private final Logger logger = LogManager.getLogger(NodeManager.class);
|
|
||||||
|
|
||||||
private final UdpListener listener;
|
|
||||||
private final UdpMessageSender sender;
|
|
||||||
private final WebSocketManager ws;
|
|
||||||
private final Raft raft;
|
|
||||||
private final List<NodeInfo> nodes = new ArrayList<>();
|
|
||||||
public static final String HOST = System.getenv().getOrDefault("HOSTNAME", "localhost");
|
|
||||||
public static final int PORT = Integer.parseInt(System.getenv().getOrDefault("UDP_PORT", "8084"));
|
|
||||||
|
|
||||||
public NodeManager() {
|
|
||||||
this.listener = new UdpListener(this);
|
|
||||||
this.sender = new UdpMessageSender(this);
|
|
||||||
this.ws = new WebSocketManager(this);
|
|
||||||
this.raft = new Raft(this, sender);
|
|
||||||
|
|
||||||
listener.startListening();
|
|
||||||
ws.connectAndListen();
|
|
||||||
raft.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleMessage(String message) {
|
|
||||||
raft.processMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<NodeInfo> getNodes() {
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerNode(NodeInfo node) {
|
|
||||||
if (!nodes.contains(node)) {
|
|
||||||
nodes.add(node);
|
|
||||||
sender.sendMessage(node, "NODE_REGISTERED");
|
|
||||||
// logger.info("New node registered, updating Raft peers");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeNode(NodeInfo node) {
|
|
||||||
nodes.remove(node);
|
|
||||||
// logger.info("Node removed, updating Raft peers");
|
|
||||||
}
|
|
||||||
|
|
||||||
public UdpMessageSender getSender() {
|
|
||||||
return sender;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package io.github.lumijiez.data.models;
|
|
||||||
|
|
||||||
public record NodeInfo(String hostname, int port) {
|
|
||||||
public static NodeInfo fromString(String nodeString) {
|
|
||||||
String[] parts = nodeString.split(":");
|
|
||||||
if (parts.length != 2) {
|
|
||||||
throw new IllegalArgumentException("Invalid node string format. Expected 'hostname:port'");
|
|
||||||
}
|
|
||||||
return new NodeInfo(parts[0], Integer.parseInt(parts[1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return hostname + ":" + port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package io.github.lumijiez.network;
|
package io.github.lumijiez.network;
|
||||||
|
|
||||||
import io.github.lumijiez.app.NodeManager;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@@ -11,26 +10,31 @@ import java.nio.channels.DatagramChannel;
|
|||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.Selector;
|
import java.nio.channels.Selector;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
public class UdpListener {
|
public class UdpListener {
|
||||||
private static final Logger logger = LogManager.getLogger(UdpListener.class);
|
private static final Logger logger = LogManager.getLogger(UdpListener.class);
|
||||||
|
private final int port;
|
||||||
|
private final Consumer<JsonMessage> messageHandler;
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
private final NodeManager nodeManager;
|
public UdpListener(int port, Consumer<JsonMessage> messageHandler) {
|
||||||
|
this.port = port;
|
||||||
public UdpListener(NodeManager nodeManager) {
|
this.messageHandler = messageHandler;
|
||||||
this.nodeManager = nodeManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startListening() {
|
public void startListening() {
|
||||||
Thread udpListenerThread = new Thread(() -> {
|
Thread listenerThread = new Thread(() -> {
|
||||||
try (Selector selector = Selector.open();
|
try (Selector selector = Selector.open();
|
||||||
DatagramChannel channel = DatagramChannel.open()) {
|
DatagramChannel channel = DatagramChannel.open()) {
|
||||||
|
|
||||||
channel.bind(new InetSocketAddress(NodeManager.PORT));
|
channel.bind(new InetSocketAddress(port));
|
||||||
channel.configureBlocking(false);
|
channel.configureBlocking(false);
|
||||||
channel.register(selector, SelectionKey.OP_READ);
|
channel.register(selector, SelectionKey.OP_READ);
|
||||||
|
|
||||||
logger.info("UDP listens on port {}", NodeManager.PORT);
|
logger.info("Listening for UDP messages on port {}", port);
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||||
|
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
@@ -48,15 +52,37 @@ public class UdpListener {
|
|||||||
buffer.flip();
|
buffer.flip();
|
||||||
|
|
||||||
String message = new String(buffer.array(), 0, buffer.limit()).trim();
|
String message = new String(buffer.array(), 0, buffer.limit()).trim();
|
||||||
// logger.info("Received UDP {}:{}: {}", sender.getHostName(), sender.getPort(), message);
|
logger.info("Received message from {}:{} - {}", sender.getHostName(), sender.getPort(), message);
|
||||||
nodeManager.handleMessage(message);
|
try {
|
||||||
|
JsonMessage jsonMessage = gson.fromJson(message, JsonMessage.class);
|
||||||
|
messageHandler.accept(jsonMessage);
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
logger.error("Invalid JSON received: {}", message, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error in UDP listener: {}", e.getMessage());
|
logger.error("Error in UDP listener: {}", e.getMessage(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
udpListenerThread.start();
|
|
||||||
|
listenerThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JsonMessage {
|
||||||
|
public String type;
|
||||||
|
public int term;
|
||||||
|
public String sender;
|
||||||
|
public String additionalData;
|
||||||
|
|
||||||
|
public JsonMessage() {}
|
||||||
|
|
||||||
|
public JsonMessage(String type, int term, String sender, String additionalData) {
|
||||||
|
this.type = type;
|
||||||
|
this.term = term;
|
||||||
|
this.sender = sender;
|
||||||
|
this.additionalData = additionalData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package io.github.lumijiez.network;
|
|
||||||
|
|
||||||
import io.github.lumijiez.app.NodeManager;
|
|
||||||
import io.github.lumijiez.data.models.NodeInfo;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.DatagramChannel;
|
|
||||||
|
|
||||||
public class UdpMessageSender {
|
|
||||||
private static final Logger logger = LogManager.getLogger(UdpMessageSender.class);
|
|
||||||
private final NodeManager nodeManager;
|
|
||||||
|
|
||||||
public UdpMessageSender(NodeManager nodeManager) {
|
|
||||||
this.nodeManager = nodeManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMessage(NodeInfo node, String message) {
|
|
||||||
try (DatagramChannel channel = DatagramChannel.open()) {
|
|
||||||
channel.configureBlocking(false);
|
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
|
|
||||||
channel.send(buffer, new InetSocketAddress(node.hostname(), node.port()));
|
|
||||||
// logger.info("Sent UDP to {}:{}: {}", node.hostname(), node.port(), message);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Error sending UDP message: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package io.github.lumijiez.network;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.DatagramChannel;
|
||||||
|
|
||||||
|
public class UdpSender {
|
||||||
|
private static final Logger logger = LogManager.getLogger(UdpSender.class);
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
|
public void sendMessage(String hostname, int port, Object message) {
|
||||||
|
try (DatagramChannel channel = DatagramChannel.open()) {
|
||||||
|
channel.configureBlocking(false);
|
||||||
|
String jsonMessage = gson.toJson(message);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(jsonMessage.getBytes());
|
||||||
|
channel.send(buffer, new InetSocketAddress(hostname, port));
|
||||||
|
logger.info("Sent message to {}:{} - {}", hostname, port, jsonMessage);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error sending UDP message: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package io.github.lumijiez.network;
|
|
||||||
|
|
||||||
import io.github.lumijiez.app.NodeManager;
|
|
||||||
import io.github.lumijiez.data.models.NodeInfo;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.WebSocket;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CompletionStage;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
|
|
||||||
public class WebSocketManager {
|
|
||||||
private static final Logger logger = LogManager.getLogger(WebSocketManager.class);
|
|
||||||
private static final Gson gson = new Gson();
|
|
||||||
private static final CountDownLatch waitForConnection = new CountDownLatch(1);
|
|
||||||
|
|
||||||
private WebSocket webSocket;
|
|
||||||
private final NodeManager nodeManager;
|
|
||||||
|
|
||||||
public WebSocketManager(NodeManager nodeManager) {
|
|
||||||
this.nodeManager = nodeManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connectAndListen() {
|
|
||||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
CompletableFuture<WebSocket> wsFuture = client
|
|
||||||
.newWebSocketBuilder()
|
|
||||||
.buildAsync(new URI("ws://symphony-discovery:8083/discovery"), new WebSocket.Listener() {
|
|
||||||
@Override
|
|
||||||
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
|
|
||||||
try {
|
|
||||||
Type nodeListType = new TypeToken<List<NodeInfo>>() {}.getType();
|
|
||||||
List<NodeInfo> nodes = gson.fromJson(data.toString(), nodeListType);
|
|
||||||
|
|
||||||
for (NodeInfo newNode : nodes) {
|
|
||||||
nodeManager.registerNode(newNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error processing WebSocket data: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
return WebSocket.Listener.super.onText(webSocket, data, last);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onOpen(WebSocket webSocket) {
|
|
||||||
NodeInfo nodeInfo = new NodeInfo(NodeManager.HOST, NodeManager.PORT);
|
|
||||||
webSocket.sendText(gson.toJson(nodeInfo), true);
|
|
||||||
logger.info("Successfully registered to Discovery");
|
|
||||||
waitForConnection.countDown();
|
|
||||||
WebSocket.Listener.super.onOpen(webSocket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
|
|
||||||
logger.info("Unregistered from Discovery: {}", reason);
|
|
||||||
return WebSocket.Listener.super.onClose(webSocket, statusCode, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(WebSocket webSocket, Throwable error) {
|
|
||||||
logger.error("WebSocket error: {}", error.getMessage());
|
|
||||||
WebSocket.Listener.super.onError(webSocket, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
webSocket = wsFuture.join();
|
|
||||||
waitForConnection.await();
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error in WebSocketManager: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,177 +1,234 @@
|
|||||||
package io.github.lumijiez.raft;
|
package io.github.lumijiez.raft;
|
||||||
|
|
||||||
import io.github.lumijiez.app.NodeManager;
|
import com.google.gson.Gson;
|
||||||
import io.github.lumijiez.data.models.NodeInfo;
|
import io.github.lumijiez.Main;
|
||||||
import io.github.lumijiez.network.UdpMessageSender;
|
import io.github.lumijiez.network.UdpListener;
|
||||||
|
import io.github.lumijiez.network.UdpSender;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.util.*;
|
import java.io.OutputStream;
|
||||||
import java.util.concurrent.Executors;
|
import java.net.HttpURLConnection;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.net.URL;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
public class Raft {
|
public class Raft {
|
||||||
private static final Random RANDOM = new Random();
|
private static final Logger logger = LogManager.getLogger(Raft.class);
|
||||||
private final Logger logger = LogManager.getLogger(Raft.class);
|
|
||||||
|
|
||||||
// Configuration Parameters
|
private enum State {
|
||||||
private static final int MIN_ELECTION_TIMEOUT = 300;
|
FOLLOWER, CANDIDATE, LEADER
|
||||||
private static final int MAX_ELECTION_TIMEOUT = 600;
|
}
|
||||||
private static final int HEARTBEAT_INTERVAL = 100;
|
|
||||||
private static final int QUORUM_FACTOR = 2;
|
|
||||||
|
|
||||||
// State Variables
|
private static final List<String> NODES = Arrays.asList(
|
||||||
private RaftStates state = RaftStates.FOLLOWER;
|
"node1:8105", "node2:8106", "node3:8107", "node4:8108", "node5:8109"
|
||||||
|
);
|
||||||
|
|
||||||
|
private final String selfAddress = Main.HOST;
|
||||||
|
private final int selfPort = Main.PORT;
|
||||||
|
|
||||||
|
private State currentState = State.FOLLOWER;
|
||||||
private int currentTerm = 0;
|
private int currentTerm = 0;
|
||||||
private String votedFor = null;
|
private String votedFor = null;
|
||||||
private Set<String> votesReceived = new HashSet<>();
|
private String currentLeader = null;
|
||||||
|
|
||||||
private final NodeManager nodeManager;
|
private final Set<String> receivedVotes = ConcurrentHashMap.newKeySet();
|
||||||
private final UdpMessageSender sender;
|
private final Random random = new Random();
|
||||||
private final ScheduledExecutorService electionExecutor = Executors.newSingleThreadScheduledExecutor();
|
private long electionTimeout = generateElectionTimeout();
|
||||||
|
private long lastHeartbeatTime = System.currentTimeMillis();
|
||||||
|
|
||||||
public Raft(NodeManager nodeManager, UdpMessageSender sender) {
|
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
|
||||||
this.nodeManager = nodeManager;
|
private final ExecutorService electionExecutor = Executors.newSingleThreadExecutor();
|
||||||
this.sender = sender;
|
|
||||||
nodeManager.getNodes().removeIf(node -> node.hostname().equals(NodeManager.HOST) && node.port() == NodeManager.PORT);
|
private final UdpListener listener;
|
||||||
|
private final UdpSender sender;
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
|
public Raft() {
|
||||||
|
this.listener = new UdpListener(selfPort, this::processMessage);
|
||||||
|
this.sender = new UdpSender();
|
||||||
|
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
private void start() {
|
||||||
logger.info("Raft initialization. Total peers: {}", nodeManager.getNodes().size());
|
executorService.scheduleAtFixedRate(this::checkElectionTimeout, 50, 50, TimeUnit.MILLISECONDS);
|
||||||
becomeFollower(1);
|
executorService.scheduleAtFixedRate(this::sendHeartbeats, 100, 100, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
listener.startListening();
|
||||||
|
logger.info("Node {} started", selfAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void becomeFollower(int term) {
|
private long generateElectionTimeout() {
|
||||||
if (term > currentTerm) {
|
return System.currentTimeMillis() + 150 + random.nextInt(150);
|
||||||
state = RaftStates.FOLLOWER;
|
}
|
||||||
currentTerm = term;
|
|
||||||
votedFor = null;
|
private void checkElectionTimeout() {
|
||||||
votesReceived.clear();
|
long currentTime = System.currentTimeMillis();
|
||||||
logger.debug("Transitioned to FOLLOWER. Term: {}", term);
|
|
||||||
|
if (currentState != State.LEADER && currentTime > electionTimeout) {
|
||||||
|
startElection();
|
||||||
}
|
}
|
||||||
scheduleElectionTimeout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void becomeCandidate() {
|
private void startElection() {
|
||||||
state = RaftStates.CANDIDATE;
|
currentState = State.CANDIDATE;
|
||||||
currentTerm++;
|
currentTerm++;
|
||||||
votedFor = NodeManager.HOST + ":" + NodeManager.PORT;
|
votedFor = selfAddress;
|
||||||
votesReceived = new HashSet<>(Set.of(votedFor)); // Self vote
|
receivedVotes.clear();
|
||||||
|
receivedVotes.add(selfAddress);
|
||||||
|
electionTimeout = generateElectionTimeout();
|
||||||
|
|
||||||
// logger.info("Starting election in Term {}. Attempting to collect votes.", currentTerm);
|
logger.info("Node {} starting election for term {}", selfAddress, currentTerm);
|
||||||
sendVoteRequests();
|
|
||||||
|
electionExecutor.submit(() -> {
|
||||||
|
for (String node : NODES) {
|
||||||
|
if (!node.equals(selfAddress)) {
|
||||||
|
UdpListener.JsonMessage message = new UdpListener.JsonMessage("VOTE_REQUEST", currentTerm, selfAddress + ":" + selfPort, null);
|
||||||
|
sendMessage(node, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
tallyVotes();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tallyVotes() {
|
||||||
|
long requiredVotes = (NODES.size() / 2) + 1;
|
||||||
|
|
||||||
|
if (receivedVotes.size() >= requiredVotes) {
|
||||||
|
becomeLeader();
|
||||||
|
} else {
|
||||||
|
currentState = State.FOLLOWER;
|
||||||
|
votedFor = null;
|
||||||
|
electionTimeout = generateElectionTimeout();
|
||||||
|
logger.info("Node {} failed to become leader for term {}", selfAddress, currentTerm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void becomeLeader() {
|
private void becomeLeader() {
|
||||||
if (state == RaftStates.CANDIDATE &&
|
if (currentState == State.CANDIDATE) {
|
||||||
votesReceived.size() >= (nodeManager.getNodes().size() / QUORUM_FACTOR + 1)) {
|
currentState = State.LEADER;
|
||||||
state = RaftStates.LEADER;
|
currentLeader = selfAddress;
|
||||||
logger.info("LEADER ELECTION SUCCESS: Becoming LEADER in Term {}", currentTerm);
|
logger.info("Node {} became leader for term {}", selfAddress, currentTerm);
|
||||||
votesReceived.clear();
|
|
||||||
sendHeartbeats();
|
sendHeartbeats();
|
||||||
|
notifyManager();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendVoteRequests() {
|
private void notifyManager() {
|
||||||
String voteRequest = String.format("REQUEST_VOTE|%d|%s:%d",
|
try {
|
||||||
currentTerm, NodeManager.HOST, NodeManager.PORT);
|
URL url = new URL("http://manager:8081/update_leader");
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
|
||||||
for (NodeInfo peer : nodeManager.getNodes()) {
|
String jsonBody = String.format(
|
||||||
sender.sendMessage(peer, voteRequest);
|
"{\"leaderHost\": \"%s\", \"leaderPort\": %d}",
|
||||||
|
selfAddress.split(":")[0], selfPort
|
||||||
|
);
|
||||||
|
|
||||||
|
try (OutputStream os = connection.getOutputStream()) {
|
||||||
|
os.write(jsonBody.getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to notify manager of leadership: {}", e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendHeartbeats() {
|
private void sendHeartbeats() {
|
||||||
if (state != RaftStates.LEADER) return;
|
if (currentState == State.LEADER) {
|
||||||
|
for (String node : NODES) {
|
||||||
String heartbeat = String.format("HEARTBEAT|%d|%s:%d",
|
if (!node.equals(selfAddress)) {
|
||||||
currentTerm, NodeManager.HOST, NodeManager.PORT);
|
UdpListener.JsonMessage message = new UdpListener.JsonMessage("HEARTBEAT", currentTerm, selfAddress, null);
|
||||||
|
sendMessage(node, message);
|
||||||
for (NodeInfo peer : nodeManager.getNodes()) {
|
}
|
||||||
sender.sendMessage(peer, heartbeat);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reschedule heartbeats
|
|
||||||
electionExecutor.schedule(this::sendHeartbeats, HEARTBEAT_INTERVAL, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleElectionTimeout() {
|
private void processMessage(UdpListener.JsonMessage message) {
|
||||||
int timeout = MIN_ELECTION_TIMEOUT + RANDOM.nextInt(MAX_ELECTION_TIMEOUT - MIN_ELECTION_TIMEOUT);
|
|
||||||
|
|
||||||
electionExecutor.schedule(() -> {
|
|
||||||
if (state != RaftStates.LEADER) {
|
|
||||||
logger.debug("Election timeout triggered. Starting new election.");
|
|
||||||
becomeCandidate();
|
|
||||||
}
|
|
||||||
}, timeout, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void processMessage(String message) {
|
|
||||||
String[] parts = message.split("\\|");
|
|
||||||
if (parts.length < 3) return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String type = parts[0];
|
switch (message.type) {
|
||||||
int messageTerm = Integer.parseInt(parts[1]);
|
case "VOTE_REQUEST":
|
||||||
String sender = parts[2];
|
handleVoteRequest(message.term, message.sender);
|
||||||
|
break;
|
||||||
// Term comparison and potential state change
|
case "VOTE_RESPONSE":
|
||||||
if (messageTerm > currentTerm) {
|
handleVoteResponse(message.term, message.sender, message.additionalData);
|
||||||
becomeFollower(messageTerm);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case "REQUEST_VOTE":
|
|
||||||
handleRequestVote(messageTerm, sender);
|
|
||||||
break;
|
break;
|
||||||
case "HEARTBEAT":
|
case "HEARTBEAT":
|
||||||
handleHeartbeat(messageTerm, sender);
|
handleHeartbeat(message.term, message.sender);
|
||||||
break;
|
|
||||||
case "VOTE_GRANTED":
|
|
||||||
handleVoteGranted(messageTerm, sender);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error processing message: {}", message);
|
logger.error("Error processing message: {}", gson.toJson(message), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRequestVote(int term, String candidate) {
|
private void handleVoteRequest(int term, String candidate) {
|
||||||
boolean voteGranted = false;
|
boolean voteGranted = false;
|
||||||
|
|
||||||
if (term >= currentTerm &&
|
if (term > currentTerm) {
|
||||||
(votedFor == null || votedFor.equals(candidate))) {
|
|
||||||
voteGranted = true;
|
|
||||||
votedFor = candidate;
|
|
||||||
currentTerm = term;
|
currentTerm = term;
|
||||||
scheduleElectionTimeout();
|
currentState = State.FOLLOWER;
|
||||||
|
votedFor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String response = String.format("VOTE_GRANTED|%d|%s", term, voteGranted);
|
if (term == currentTerm && (votedFor == null || votedFor.equals(candidate)) && currentState != State.LEADER) {
|
||||||
sender.sendMessage(NodeInfo.fromString(candidate), response);
|
voteGranted = true;
|
||||||
|
votedFor = candidate;
|
||||||
|
electionTimeout = generateElectionTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
UdpListener.JsonMessage response = new UdpListener.JsonMessage("VOTE_RESPONSE", term, selfAddress + ":" + selfPort, voteGranted ? "GRANTED" : "REJECTED");
|
||||||
|
sendMessage(candidate, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleVoteResponse(int term, String sender, String response) {
|
||||||
|
if (currentState != State.CANDIDATE || term != currentTerm) return;
|
||||||
|
|
||||||
|
if ("GRANTED".equals(response)) {
|
||||||
|
receivedVotes.add(sender);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleHeartbeat(int term, String leader) {
|
private void handleHeartbeat(int term, String leader) {
|
||||||
if (term >= currentTerm) {
|
if (term >= currentTerm) {
|
||||||
scheduleElectionTimeout();
|
currentTerm = term;
|
||||||
becomeFollower(term);
|
currentState = State.FOLLOWER;
|
||||||
|
currentLeader = leader;
|
||||||
|
lastHeartbeatTime = System.currentTimeMillis();
|
||||||
|
electionTimeout = generateElectionTimeout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleVoteGranted(int term, String candidate) {
|
private void sendMessage(String address, UdpListener.JsonMessage message) {
|
||||||
if (state == RaftStates.CANDIDATE && term == currentTerm) {
|
try {
|
||||||
votesReceived.add(candidate);
|
String[] parts = address.split(":");
|
||||||
logger.debug("Vote received from {}. Total votes: {}/{}",
|
if (parts.length != 2) {
|
||||||
candidate, votesReceived.size(), (nodeManager.getNodes().size() / QUORUM_FACTOR + 1));
|
logger.error("Invalid address format: {}", address);
|
||||||
|
return;
|
||||||
becomeLeader();
|
}
|
||||||
|
String host = parts[0];
|
||||||
|
int port = Integer.parseInt(parts[1]);
|
||||||
|
sender.sendMessage(host, port, message);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.error("Invalid port in address: {}", address, e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error sending message to {}: {}", address, e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
electionExecutor.shutdownNow();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package io.github.lumijiez.raft;
|
|
||||||
|
|
||||||
public enum RaftStates {
|
|
||||||
FOLLOWER,
|
|
||||||
CANDIDATE,
|
|
||||||
LEADER
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
FROM ubuntu:latest
|
|
||||||
LABEL authors="lumijiez"
|
|
||||||
|
|
||||||
FROM maven:3.9.9-eclipse-temurin-21 AS build
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY pom.xml .
|
|
||||||
|
|
||||||
RUN mvn dependency:go-offline
|
|
||||||
|
|
||||||
COPY src /app/src
|
|
||||||
|
|
||||||
RUN mvn clean package -DskipTests
|
|
||||||
|
|
||||||
FROM openjdk:21
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY --from=build /app/target/SymphonyDiscovery-1.0-SNAPSHOT.jar /app/SymphonyDiscovery.jar
|
|
||||||
|
|
||||||
EXPOSE 8083
|
|
||||||
|
|
||||||
ENTRYPOINT ["java", "-jar", "SymphonyDiscovery.jar"]
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>io.github.lumijiez</groupId>
|
|
||||||
<artifactId>SymphonyDiscovery</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.code.gson</groupId>
|
|
||||||
<artifactId>gson</artifactId>
|
|
||||||
<version>2.11.0</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>log4j-api</artifactId>
|
|
||||||
<version>2.24.2</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>log4j-core</artifactId>
|
|
||||||
<version>2.24.2</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.javalin</groupId>
|
|
||||||
<artifactId>javalin</artifactId>
|
|
||||||
<version>6.3.0</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-simple</artifactId>
|
|
||||||
<version>2.0.16</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ch.qos.logback</groupId>
|
|
||||||
<artifactId>logback-classic</artifactId>
|
|
||||||
<version>1.4.12</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>ch.qos.logback</groupId>
|
|
||||||
<artifactId>logback-core</artifactId>
|
|
||||||
<version>1.4.14</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<version>3.8.1</version>
|
|
||||||
<configuration>
|
|
||||||
<source>21</source>
|
|
||||||
<target>21</target>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
|
||||||
<version>3.2.1</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>shade</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
|
||||||
<version>3.1.0</version>
|
|
||||||
<configuration>
|
|
||||||
<archive>
|
|
||||||
<manifestEntries>
|
|
||||||
<Main-Class>io.github.lumijiez.Main</Main-Class>
|
|
||||||
</manifestEntries>
|
|
||||||
</archive>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package io.github.lumijiez;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import io.javalin.Javalin;
|
|
||||||
import io.javalin.websocket.WsContext;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class JavalinConfig {
|
|
||||||
private static final Map<String, NodeInfo> registeredNodes = new ConcurrentHashMap<>();
|
|
||||||
private static final Map<String, WsContext> nodes = new ConcurrentHashMap<>();
|
|
||||||
private static final Gson gson = new Gson();
|
|
||||||
private static final Logger logger = LogManager.getLogger(JavalinConfig.class);
|
|
||||||
|
|
||||||
public static void setup(Javalin app) {
|
|
||||||
app.ws("/discovery", ws -> {
|
|
||||||
ws.onConnect(ctx -> {
|
|
||||||
// ToDo
|
|
||||||
// A general notification
|
|
||||||
nodes.put(ctx.sessionId(), ctx);
|
|
||||||
});
|
|
||||||
|
|
||||||
ws.onMessage(ctx -> {
|
|
||||||
String message = ctx.message();
|
|
||||||
NodeInfo nodeInfo = gson.fromJson(message, NodeInfo.class);
|
|
||||||
registeredNodes.put(ctx.sessionId(), nodeInfo);
|
|
||||||
broadcastNodeCount();
|
|
||||||
});
|
|
||||||
|
|
||||||
ws.onClose(ctx -> {
|
|
||||||
registeredNodes.remove(ctx.sessionId());
|
|
||||||
broadcastNodeCount();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void broadcastNodeCount() {
|
|
||||||
List<NodeInfo> nodeInfoList = new ArrayList<>(registeredNodes.values());
|
|
||||||
String nodesJson = gson.toJson(nodeInfoList);
|
|
||||||
nodes.values().forEach(ctx -> ctx.send(nodesJson));
|
|
||||||
}
|
|
||||||
|
|
||||||
public record NodeInfo(String hostname, int port) { }
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package io.github.lumijiez;
|
|
||||||
|
|
||||||
import io.javalin.Javalin;
|
|
||||||
import io.javalin.json.JavalinGson;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
public class Main {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
Logger logger = LogManager.getLogger(Main.class);
|
|
||||||
|
|
||||||
Javalin app = Javalin.create(config -> {
|
|
||||||
config.jsonMapper(new JavalinGson());
|
|
||||||
config.jetty.modifyWebSocketServletFactory(wsFactoryConfig -> {
|
|
||||||
wsFactoryConfig.setIdleTimeout(Duration.ZERO);
|
|
||||||
});
|
|
||||||
}).start(8083);
|
|
||||||
|
|
||||||
JavalinConfig.setup(app);
|
|
||||||
|
|
||||||
logger.info("Discovery service up and running");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package io.github.lumijiez.models.requests;
|
|
||||||
|
|
||||||
public class RegisterRequest {
|
|
||||||
public String hostname;
|
|
||||||
public String port;
|
|
||||||
|
|
||||||
public RegisterRequest(String hostname, String port) {
|
|
||||||
this.hostname = hostname;
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHostname() {
|
|
||||||
return hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Configuration status="WARN">
|
|
||||||
<Appenders>
|
|
||||||
<Console name="Console" target="SYSTEM_OUT">
|
|
||||||
<PatternLayout>
|
|
||||||
<Pattern>
|
|
||||||
%d{yyyy-MM-dd HH:mm:ss} %highlight{%-5level}{FATAL=red, ERROR=red, WARN=yellow, INFO=green, DEBUG=blue}: %highlight{%msg}{FATAL=red, ERROR=red}%n
|
|
||||||
</Pattern>
|
|
||||||
</PatternLayout>
|
|
||||||
</Console>
|
|
||||||
</Appenders>
|
|
||||||
|
|
||||||
<Loggers>
|
|
||||||
<Logger name="io.javalin" level="off" additivity="false"/>
|
|
||||||
<Logger name="org.eclipse" level="off" additivity="false"/>
|
|
||||||
|
|
||||||
<Root level="info">
|
|
||||||
<AppenderRef ref="Console"/>
|
|
||||||
</Root>
|
|
||||||
</Loggers>
|
|
||||||
</Configuration>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
org.slf4j.simpleLogger.defaultLogLevel=off
|
|
||||||
|
|
||||||
org.slf4j.simpleLogger.log.io.javalin=off
|
|
||||||
|
|
||||||
org.slf4j.simpleLogger.log.org.eclipse.jetty=off
|
|
||||||
org.slf4j.simpleLogger.log.org.eclipse.jetty.server=off
|
|
||||||
org.slf4j.simpleLogger.log.org.eclipse.jetty.util=off
|
|
||||||
|
|
||||||
# org.slf4j.simpleLogger.log.org.springframework=off
|
|
||||||
# org.slf4j.simpleLogger.log.org.hibernate=off
|
|
||||||
|
|
||||||
org.slf4j.simpleLogger.showDateTime=true
|
|
||||||
|
|
||||||
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
|
|
||||||
|
|
||||||
org.slf4j.simpleLogger.showThreadName=true
|
|
||||||
|
|
||||||
org.slf4j.simpleLogger.showLogName=true
|
|
||||||
|
|
||||||
org.slf4j.simpleLogger.showShortLogName=false
|
|
||||||
@@ -22,30 +22,6 @@
|
|||||||
<version>2.11.0</version>
|
<version>2.11.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-api</artifactId>
|
|
||||||
<version>1.7.32</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>ch.qos.logback</groupId>
|
|
||||||
<artifactId>logback-classic</artifactId>
|
|
||||||
<version>1.5.12</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>ch.qos.logback</groupId>
|
|
||||||
<artifactId>logback-core</artifactId>
|
|
||||||
<version>1.5.12</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-simple</artifactId>
|
|
||||||
<version>1.7.32</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-api</artifactId>
|
<artifactId>log4j-api</artifactId>
|
||||||
@@ -56,6 +32,29 @@
|
|||||||
<artifactId>log4j-core</artifactId>
|
<artifactId>log4j-core</artifactId>
|
||||||
<version>2.24.2</version>
|
<version>2.24.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.javalin</groupId>
|
||||||
|
<artifactId>javalin</artifactId>
|
||||||
|
<version>6.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>2.0.16</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>1.4.12</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-core</artifactId>
|
||||||
|
<version>1.4.14</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package io.github.lumijiez;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
public class AddressHolder {
|
||||||
|
private String host;
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
private static AddressHolder INSTANCE;
|
||||||
|
|
||||||
|
private AddressHolder() {
|
||||||
|
// Timer timer = new Timer(true);
|
||||||
|
// timer.scheduleAtFixedRate(new TimerTask() {
|
||||||
|
// @Override
|
||||||
|
// public void run() {
|
||||||
|
// Main.logger.info("Host: {}, Port: {}", host, port);
|
||||||
|
// }
|
||||||
|
// }, 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AddressHolder getInstance() {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = new AddressHolder();
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPort(int port) {
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package io.github.lumijiez;
|
||||||
|
|
||||||
|
import io.github.lumijiez.requests.UpdateLeaderRequest;
|
||||||
|
import io.javalin.Javalin;
|
||||||
|
import io.javalin.http.Context;
|
||||||
|
|
||||||
|
public class JavalinHttpConfig {
|
||||||
|
public static void setup(Javalin app) {
|
||||||
|
app.post("/update_leader", JavalinHttpConfig::handleUpdateLeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handleUpdateLeader(Context ctx) {
|
||||||
|
UpdateLeaderRequest request = ctx.bodyAsClass(UpdateLeaderRequest.class);
|
||||||
|
|
||||||
|
AddressHolder.getInstance().setHost(request.getLeaderHost());
|
||||||
|
AddressHolder.getInstance().setPort(request.getLeaderPort());
|
||||||
|
|
||||||
|
Main.logger.info("Changed host to leader: {}:{}", request.getLeaderHost(), request.getLeaderPort());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,26 @@
|
|||||||
package io.github.lumijiez;
|
package io.github.lumijiez;
|
||||||
|
|
||||||
public class Main {
|
import io.javalin.Javalin;
|
||||||
|
import io.javalin.json.JavalinGson;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static Logger logger = LogManager.getLogger(Main.class);
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
BrokerConnector.connect();
|
new Thread(BrokerConnector::connect, "RabbitMQ-Connection").start();
|
||||||
|
|
||||||
|
Javalin app = Javalin.create(config -> {
|
||||||
|
config.jsonMapper(new JavalinGson());
|
||||||
|
config.jetty.modifyWebSocketServletFactory(wsFactoryConfig -> {
|
||||||
|
wsFactoryConfig.setIdleTimeout(Duration.ZERO);
|
||||||
|
});
|
||||||
|
}).start(8081);
|
||||||
|
|
||||||
|
JavalinHttpConfig.setup(app);
|
||||||
|
|
||||||
|
logger.info("Discovery service up and running");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package io.github.lumijiez.requests;
|
||||||
|
|
||||||
|
public class UpdateLeaderRequest {
|
||||||
|
private String leaderHost;
|
||||||
|
private int leaderPort;
|
||||||
|
|
||||||
|
public void setLeaderHost(String leaderHost) {
|
||||||
|
this.leaderHost = leaderHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeaderPort(int leaderPort) {
|
||||||
|
this.leaderPort = leaderPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLeaderPort() {
|
||||||
|
return leaderPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLeaderHost() {
|
||||||
|
return leaderHost;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
2024-12-12 20:43:45.086648+00:00 [error] <0.1439.0> closing AMQP 1.0 connection (172.18.0.1:54074 -> 172.18.0.3:5672, duration: '775ms'): RabbitMQ requires SASL security layer (expected protocol ID 3, but client sent protocol ID 2)
|
||||||
|
|||||||
@@ -23,18 +23,6 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- rabbitmq
|
- rabbitmq
|
||||||
|
|
||||||
symphony-discovery:
|
|
||||||
container_name: discovery
|
|
||||||
build:
|
|
||||||
context: ./SymphonyDiscovery
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
ports:
|
|
||||||
- "8083:8083"
|
|
||||||
networks:
|
|
||||||
- symphony-network
|
|
||||||
depends_on:
|
|
||||||
- postgres_db
|
|
||||||
|
|
||||||
symphony-smtp:
|
symphony-smtp:
|
||||||
container_name: smtp
|
container_name: smtp
|
||||||
build:
|
build:
|
||||||
@@ -59,7 +47,6 @@ services:
|
|||||||
- UDP_PORT=8105
|
- UDP_PORT=8105
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres_db
|
- postgres_db
|
||||||
- symphony-discovery
|
|
||||||
|
|
||||||
node2:
|
node2:
|
||||||
container_name: node2
|
container_name: node2
|
||||||
@@ -77,7 +64,6 @@ services:
|
|||||||
- UDP_PORT=8106
|
- UDP_PORT=8106
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres_db
|
- postgres_db
|
||||||
- symphony-discovery
|
|
||||||
|
|
||||||
node3:
|
node3:
|
||||||
container_name: node3
|
container_name: node3
|
||||||
@@ -95,7 +81,6 @@ services:
|
|||||||
- UDP_PORT=8107
|
- UDP_PORT=8107
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres_db
|
- postgres_db
|
||||||
- symphony-discovery
|
|
||||||
|
|
||||||
node4:
|
node4:
|
||||||
container_name: node4
|
container_name: node4
|
||||||
@@ -113,7 +98,6 @@ services:
|
|||||||
- UDP_PORT=8108
|
- UDP_PORT=8108
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres_db
|
- postgres_db
|
||||||
- symphony-discovery
|
|
||||||
|
|
||||||
node5:
|
node5:
|
||||||
container_name: node5
|
container_name: node5
|
||||||
@@ -131,7 +115,6 @@ services:
|
|||||||
- UDP_PORT=8109
|
- UDP_PORT=8109
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres_db
|
- postgres_db
|
||||||
- symphony-discovery
|
|
||||||
|
|
||||||
postgres_db:
|
postgres_db:
|
||||||
image: postgres:latest
|
image: postgres:latest
|
||||||
|
|||||||
1
pom.xml
1
pom.xml
@@ -13,7 +13,6 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>SymphonyManager</module>
|
<module>SymphonyManager</module>
|
||||||
<module>SymphonyProducer</module>
|
<module>SymphonyProducer</module>
|
||||||
<module>SymphonyDiscovery</module>
|
|
||||||
<module>SymphonyDatabaseNode</module>
|
<module>SymphonyDatabaseNode</module>
|
||||||
<module>SymphonySMTP</module>
|
<module>SymphonySMTP</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|||||||
Reference in New Issue
Block a user