All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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