com.qwazr.cluster.manager.ClusterManager.java Source code

Java tutorial

Introduction

Here is the source code for com.qwazr.cluster.manager.ClusterManager.java

Source

/**
 * Copyright 2015-2016 Emmanuel Keller / QWAZR
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 com.qwazr.cluster.manager;

import com.qwazr.cluster.client.ClusterMultiClient;
import com.qwazr.cluster.client.ClusterSingleClient;
import com.qwazr.cluster.manager.ClusterNodeSet.Cache;
import com.qwazr.cluster.service.*;
import com.qwazr.cluster.service.ClusterServiceStatusJson.StatusEnum;
import com.qwazr.utils.ArrayUtils;
import com.qwazr.utils.StringUtils;
import com.qwazr.utils.server.ServerException;
import com.qwazr.utils.threads.PeriodicThread;
import org.apache.commons.lang3.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.core.Response.Status;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicLong;

public class ClusterManager {

    public final static String SERVICE_NAME_CLUSTER = "cluster";

    private static final Logger logger = LoggerFactory.getLogger(ClusterManager.class);

    public static ClusterManager INSTANCE = null;

    public synchronized static Class<? extends ClusterServiceInterface> load(ExecutorService executor,
            String myAddress, Set<String> myGroups) throws IOException {
        if (INSTANCE != null)
            throw new IOException("Already loaded");
        try {
            INSTANCE = new ClusterManager(executor, myAddress, myGroups);
            if (INSTANCE.isMaster()) {
                // First, we get the node list from another master (if any)
                ClusterManager.INSTANCE.loadNodesFromOtherMaster();
                // All is set, let's start the monitoring
                INSTANCE.clusterMasterThread = (ClusterMasterThread) INSTANCE
                        .addPeriodicThread(new ClusterMasterThread(120));
                INSTANCE.clusterMonitoringThread = (ClusterMonitoringThread) INSTANCE
                        .addPeriodicThread(new ClusterMonitoringThread(60));
            }
            return ClusterServiceImpl.class;
        } catch (URISyntaxException e) {
            throw new IOException(e);
        }
    }

    private final ClusterNodeMap clusterNodeMap;

    private final String[] clusterMasterArray;

    private final ClusterMultiClient clusterClient;

    public final String myAddress;

    private final Set<String> myGroups;

    private List<PeriodicThread> periodicThreads = null;

    private ClusterMasterThread clusterMasterThread = null;

    private ClusterMonitoringThread clusterMonitoringThread = null;

    private volatile ClusterRegisteringThread clusterRegisteringThead = null;

    private Thread clusterNodeShutdownThread = null;

    private final ConcurrentHashMap<String, Long> checkTimeMap;

    private final AtomicLong lastTimeCheck;

    private final boolean isMaster;

    private final boolean isCluster;

    public final ExecutorService executor;

    private ClusterManager(ExecutorService executor, String publicAddress, Set<String> myGroups)
            throws IOException, URISyntaxException {
        myAddress = ClusterNode.toAddress(publicAddress);
        if (logger.isInfoEnabled())
            logger.info("Server: " + myAddress + " Groups: " + ArrayUtils.prettyPrint(myGroups));
        this.myGroups = myGroups;

        this.executor = executor;

        // Load the configuration
        String masters_env = System.getenv("QWAZR_MASTERS");

        // No configuration file ? Okay, we are a simple node
        if (StringUtils.isEmpty(masters_env)) {
            clusterMasterArray = null;
            clusterNodeMap = null;
            clusterClient = null;
            checkTimeMap = null;
            lastTimeCheck = null;
            isMaster = false;
            isCluster = false;
            if (logger.isInfoEnabled())
                logger.info("No QWAZR_MASTERS environment variable. This node is not part of a cluster.");
            return;
        }

        // Store the last time a master checked the node
        checkTimeMap = new ConcurrentHashMap<>();
        lastTimeCheck = new AtomicLong();

        // Build the master list and check if I am a master
        boolean isMaster = false;
        HashSet<String> masterSet = new HashSet<>();
        int i = 0;
        String[] masters = StringUtils.split(masters_env, ',');
        for (String master : masters) {
            String masterAddress = ClusterNode.toAddress(master.trim());
            logger.info("Add a master: " + masterAddress);
            masterSet.add(masterAddress);
            if (masterAddress == myAddress) {
                isMaster = true;
                if (logger.isInfoEnabled())
                    logger.info("I am a master!");
            }
        }
        isCluster = true;
        clusterMasterArray = masterSet.toArray(new String[masterSet.size()]);
        clusterClient = new ClusterMultiClient(executor, clusterMasterArray, 60000);
        this.isMaster = isMaster;
        if (!isMaster) {
            clusterNodeMap = null;
            return;
        }

        // We load the cluster node map
        clusterNodeMap = new ClusterNodeMap();
    }

    /**
     * Load the node list from another master
     */
    void loadNodesFromOtherMaster() {
        for (String master : clusterMasterArray) {
            if (master == myAddress)
                continue;
            try {
                logger.info("Get node list from  " + master);
                Map<String, ClusterNodeJson> nodesMap = new ClusterSingleClient(master, 60000).getNodes();
                if (nodesMap == null)
                    continue;
                for (ClusterNodeJson node : nodesMap.values())
                    upsertNode(node);
                break;
            } catch (Exception e) {
                logger.warn("Unable to load the node list from " + master, e);
            }
        }
    }

    /**
     * Start the periodic threads
     */
    private synchronized PeriodicThread addPeriodicThread(PeriodicThread periodicThread) {
        logger.info("Starting the periodic thread " + periodicThread.getName());
        if (periodicThreads == null)
            periodicThreads = new ArrayList<PeriodicThread>(3);
        periodicThreads.add(periodicThread);
        return periodicThread;
    }

    private ClusterNodeMap checkMaster() throws ServerException {
        if (clusterNodeMap == null)
            throw new ServerException(Status.NOT_ACCEPTABLE, "I am not a master");
        return clusterNodeMap;
    }

    public ClusterNode upsertNode(ClusterNodeJson clusterNodeJson) throws URISyntaxException, ServerException {
        ClusterNode clusterNode = checkMaster().upsert(clusterNodeJson);
        clusterMonitoringThread.checkNode(clusterNode);
        return clusterNode;
    }

    public boolean isGroup(String group) {
        if (group == null)
            return true;
        if (myGroups == null)
            return true;
        if (group.isEmpty())
            return true;
        return myGroups.contains(group);
    }

    void updateNodeStatus(ClusterNode node) throws ServerException {
        checkMaster().status(node);
    }

    public ClusterNode removeNode(String address) throws URISyntaxException, ServerException {
        return checkMaster().remove(address);
    }

    public List<ClusterNode> getNodeList() throws ServerException {
        return checkMaster().getNodeList();
    }

    public String[] getMasterArray() {
        return clusterMasterArray;
    }

    public boolean isLeader(String service, String group) throws ServerException {
        final Cache cache = getNodeSetCacheService(service, group);
        if (cache == null)
            return false;
        return myAddress.equals(cache.leader);
    }

    public boolean isMe(String address) {
        return myAddress.equals(address);
    }

    public boolean isMaster() {
        return isMaster;
    }

    public boolean isCluster() {
        return isCluster;
    }

    private static String[] buildArray(ClusterNode[]... nodesArray) {
        if (nodesArray == null)
            return ArrayUtils.EMPTY_STRING_ARRAY;
        int count = 0;
        for (ClusterNode[] nodes : nodesArray)
            if (nodes != null)
                count += nodes.length;
        if (count == 0)
            return ArrayUtils.EMPTY_STRING_ARRAY;

        String[] array = new String[count];
        int i = 0;
        for (ClusterNode[] nodes : nodesArray)
            for (ClusterNode node : nodes)
                array[i++] = node.address;
        return array;
    }

    public Cache getNodeSetCacheService(String service, String group) throws ServerException {
        ClusterNodeSet nodeSet = checkMaster().getNodeSetByService(service, group);
        if (nodeSet == null)
            return null;
        return nodeSet.getCache();
    }

    public static String[] getAllNodes(Cache cache) throws ServerException {
        if (cache == null)
            return ArrayUtils.EMPTY_STRING_ARRAY;
        return buildArray(cache.activeArray, cache.inactiveArray);
    }

    public static String[] getInactiveNodes(Cache cache) throws ServerException {
        if (cache == null)
            return ArrayUtils.EMPTY_STRING_ARRAY;
        return buildArray(cache.inactiveArray);
    }

    public static String[] getActiveNodes(Cache cache) throws ServerException {
        if (cache == null)
            return ArrayUtils.EMPTY_STRING_ARRAY;
        return buildArray(cache.activeArray);
    }

    /**
     * @param cache the service cache
     * @return a randomly choosen node
     * @throws ServerException if any error occurs
     */
    public static String getActiveNodeRandom(Cache cache) throws ServerException {
        if (cache == null)
            return null;
        final ClusterNode[] aa = cache.activeArray;
        if (aa == null || aa.length == 0)
            return null;
        return aa[RandomUtils.nextInt(0, aa.length)].address;
    }

    /**
     * Build a status of the given service. The list of active nodes and the
     * list of inactive nodes with their latest status.
     *
     * @param cache the name of the service
     * @return the status of the service
     * @throws ServerException if any error occurs
     */
    public static ClusterServiceStatusJson getStatus(Cache cache) throws ServerException {
        if (cache == null)
            return new ClusterServiceStatusJson();
        String[] activeList = buildArray(cache.activeArray);
        if (cache.inactiveArray == null)
            return new ClusterServiceStatusJson(cache.leader, activeList, Collections.emptyMap());
        Map<String, ClusterNodeStatusJson> inactiveMap = new LinkedHashMap<String, ClusterNodeStatusJson>();
        for (ClusterNode node : cache.inactiveArray)
            inactiveMap.put(node.address, node.getStatus());
        return new ClusterServiceStatusJson(cache.leader, activeList, inactiveMap);
    }

    public synchronized void registerMe(ClusterNodeJson clusterNodeDef) {
        if (clusterClient == null || clusterMasterArray == null)
            return;
        if (clusterRegisteringThead != null) {
            logger.error("Node already registering");
            return;
        }
        clusterRegisteringThead = (ClusterRegisteringThread) addPeriodicThread(
                new ClusterRegisteringThread(90, clusterClient, clusterNodeDef));
        if (clusterNodeShutdownThread == null) {
            clusterNodeShutdownThread = new Thread() {
                @Override
                public void run() {
                    try {
                        unregisterMe();
                    } catch (Exception e) {
                        logger.warn(e.getMessage(), e);
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(clusterNodeShutdownThread);
        }
    }

    public void unregisterMe() throws URISyntaxException {
        if (clusterClient == null)
            return;
        logger.info("Unregistering from masters");
        clusterClient.unregister(myAddress);
    }

    private static TreeMap<String, StatusEnum> getStatusMap(HashMap<String, ClusterNodeSet> nodeMap) {
        TreeMap<String, StatusEnum> statusMap = new TreeMap<String, StatusEnum>();
        if (nodeMap == null)
            return statusMap;
        for (Map.Entry<String, ClusterNodeSet> entry : nodeMap.entrySet()) {
            Cache cache = entry.getValue().getCache();
            StatusEnum status = ClusterServiceStatusJson.findStatus(cache.activeArray.length,
                    cache.inactiveArray.length);
            statusMap.put(entry.getKey(), status);
        }
        return statusMap;
    }

    public TreeMap<String, StatusEnum> getServicesStatusMap(String group) throws ServerException {
        return getStatusMap(checkMaster().getServicesMap(group == null ? StringUtils.EMPTY : group));
    }

    public Map<String, Date> getLastExecutions() {
        if (periodicThreads == null)
            return null;
        Map<String, Date> threadsMap = new HashMap<String, Date>();
        for (PeriodicThread thread : periodicThreads)
            threadsMap.put(thread.getName(), thread.getLastExecutionDate());
        return threadsMap;
    }

    /**
     * Called by a master when a master check the node
     *
     * @param masterAddress the public address of the master
     */
    public void check(String masterAddress) {
        long time = System.currentTimeMillis();
        if (lastTimeCheck != null) {
            if (lastTimeCheck.getAndSet(time) == 0)
                if (logger.isInfoEnabled())
                    logger.info("Initial check by master: " + masterAddress);
        }
        if (checkTimeMap != null && masterAddress != null)
            checkTimeMap.put(masterAddress, time);
    }

    /**
     * @return the last time the node was checked
     */
    public Long getLastCheck() {
        if (lastTimeCheck == null)
            return null;
        long res = lastTimeCheck.get();
        return res == 0 ? null : res;
    }

    public void removeOldCheck(long removeTime) {
        if (checkTimeMap == null)
            return;
        Iterator<Map.Entry<String, Long>> iterator = checkTimeMap.entrySet().iterator();
        while (iterator.hasNext()) {
            if (iterator.next().getValue() < removeTime)
                iterator.remove();
        }
    }

    public ClusterMultiClient getClusterClient() {
        return clusterClient;
    }

}