io.hekate.cluster.internal.ClusterAcceptManager Maven / Gradle / Ivy
/*
* Copyright 2022 The Hekate Project
*
* The Hekate Project 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 io.hekate.cluster.internal;
import io.hekate.cluster.ClusterAcceptor;
import io.hekate.cluster.ClusterNode;
import io.hekate.cluster.ClusterNodeId;
import io.hekate.core.Hekate;
import io.hekate.util.StateGuard;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ClusterAcceptManager {
private static final Logger log = LoggerFactory.getLogger(ClusterAcceptManager.class);
private static final boolean DEBUG = log.isDebugEnabled();
private final StateGuard guard = new StateGuard(ClusterAcceptManager.class);
private final Map>> activeChecks = new ConcurrentHashMap<>();
private final List acceptors;
private final ExecutorService async;
public ClusterAcceptManager(List acceptors, ExecutorService async) {
this.async = async;
this.acceptors = new ArrayList<>(acceptors);
// Register default acceptor.
this.acceptors.add((joining, hekate) -> {
boolean locLoopback = hekate.localNode().socket().getAddress().isLoopbackAddress();
boolean remLoopback = joining.socket().getAddress().isLoopbackAddress();
if (locLoopback != remLoopback) {
if (locLoopback) {
return "Cluster is configured with loopback addresses while node is configured to use a non-loopback address "
+ "[rejected-by=" + hekate.localNode().address() + ']';
} else {
return "Cluster is configured with non-loopback addresses while node is configured to use a loopback address "
+ "[rejected-by=" + hekate.localNode().address() + ']';
}
}
return null;
});
// Make sure that guard is in the 'initialized' state.
guard.becomeInitialized(() -> { /* No-op. */ });
}
public void terminate() {
guard.becomeTerminated(() -> {
activeChecks.values().forEach(future -> future.cancel(false));
activeChecks.clear();
});
}
public CompletableFuture> check(ClusterNode joining, Hekate hekate) {
return guard.withReadLockAndStateCheck(() -> {
CompletableFuture> future = new CompletableFuture<>();
// Do not enqueueing multiple checks for the same node.
CompletableFuture> existing = activeChecks.putIfAbsent(joining.id(), future);
if (existing == null) {
// Schedule new check.
async.execute(() -> {
try {
guard.withReadLockIfInitialized(() -> {
String reject = doAccept(joining, hekate);
future.complete(Optional.ofNullable(reject));
});
} catch (Throwable e) {
log.error("Got an unexpected error while accepting the joining node [node={}]", joining, e);
} finally {
// Allow subsequent checks for this node.
activeChecks.remove(joining.id());
}
});
return future;
} else {
return existing;
}
});
}
private String doAccept(ClusterNode newNode, Hekate hekate) {
if (DEBUG) {
log.debug("Checking the join acceptors [node={}]", newNode);
}
String rejectReason = null;
for (ClusterAcceptor acceptor : acceptors) {
rejectReason = acceptor.acceptJoin(newNode, hekate);
if (rejectReason != null) {
if (DEBUG) {
log.debug("Rejected cluster join request [node={}, reason={}, acceptor={}]", newNode, rejectReason, acceptor);
}
break;
}
}
if (DEBUG) {
if (rejectReason == null) {
log.debug("New node accepted [node={}]", newNode);
} else {
log.debug("New node rejected [node={}, reason={}]", newNode, rejectReason);
}
}
return rejectReason;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy