org.wso2.andes.server.cluster.HazelcastCoordinationStrategy.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.andes.server.cluster.HazelcastCoordinationStrategy.java

Source

/*
 * Copyright (c) 2016, WSO2 Inc. (http://wso2.com) All Rights Reserved.
 * 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.
 */

package org.wso2.andes.server.cluster;

import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.core.Member;
import com.hazelcast.core.MemberAttributeEvent;
import com.hazelcast.core.MembershipEvent;
import com.hazelcast.core.MembershipListener;
import org.apache.commons.lang.StringUtils;
import org.wso2.andes.kernel.AndesContext;
import org.wso2.andes.kernel.AndesException;
import org.wso2.andes.kernel.slot.SlotCoordinationConstants;
import org.wso2.andes.server.cluster.coordination.CoordinationConstants;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.apache.catalina.ha.session.DeltaRequest.log;

/**
 * In this strategy the oldest member is elected as the coordinator
 */
public class HazelcastCoordinationStrategy implements CoordinationStrategy, MembershipListener {
    /**
     * Used to query if current node is the oldest one
     */
    private final HazelcastInstance hazelcastInstance;

    /**
     * registration id for membership listener
     */
    private String listenerRegistrationId;

    /**
     * Used to communicate membership events;
     */
    private CoordinationConfigurableClusterAgent configurableClusterAgent;

    /**
     * Used to identify coordinator change event
     */
    private final AtomicBoolean isCoordinator;

    /**
     * Hold coordinator information
     */
    private IMap<String, String> coordinatorNodeDetailsMap;

    /**
     * This map is used to store thrift server host and thrift server port
     * map's key is port or host name.
     */
    private IMap<String, String> thriftServerDetailsMap;

    public HazelcastCoordinationStrategy(HazelcastInstance hazelcastInstance) {
        this.isCoordinator = new AtomicBoolean(false);
        this.hazelcastInstance = hazelcastInstance;
    }

    /*
    * ======================== Methods from CoordinationStrategy ============================
    */

    /**
     * {@inheritDoc}
     */
    @Override
    public void start(CoordinationConfigurableClusterAgent configurableClusterAgent, String nodeId,
            InetSocketAddress thriftAddress, InetSocketAddress hazelcastAddress) {
        thriftServerDetailsMap = hazelcastInstance.getMap(CoordinationConstants.THRIFT_SERVER_DETAILS_MAP_NAME);
        coordinatorNodeDetailsMap = hazelcastInstance
                .getMap(CoordinationConstants.COORDINATOR_NODE_DETAILS_MAP_NAME);

        // Register listener for membership changes
        listenerRegistrationId = hazelcastInstance.getCluster().addMembershipListener(this);

        this.configurableClusterAgent = configurableClusterAgent;
        checkAndNotifyCoordinatorChange();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isCoordinator() {
        Member oldestMember = hazelcastInstance.getCluster().getMembers().iterator().next();

        return oldestMember.localMember();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public InetSocketAddress getThriftAddressOfCoordinator() {
        String hostname = thriftServerDetailsMap.get(SlotCoordinationConstants.THRIFT_COORDINATOR_SERVER_IP);
        String portString = thriftServerDetailsMap.get(SlotCoordinationConstants.THRIFT_COORDINATOR_SERVER_PORT);

        InetSocketAddress coordinatorAddress = null;

        if ((null != hostname) && (null != portString)) {
            int port = Integer.parseInt(portString);
            coordinatorAddress = new InetSocketAddress(hostname, port);
        }

        return coordinatorAddress;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<String> getAllNodeIdentifiers() throws AndesException {
        Set<Member> members = hazelcastInstance.getCluster().getMembers();
        List<String> nodeIDList = new ArrayList<>();
        for (Member member : members) {
            nodeIDList.add(configurableClusterAgent.getIdOfNode(member));
        }

        return nodeIDList;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<NodeDetail> getAllNodeDetails() throws AndesException {
        List<NodeDetail> nodeDetails = new ArrayList<>();

        CoordinatorInformation coordinatorDetails = getCoordinatorDetails();
        InetSocketAddress coordinatorSocketAddress = new InetSocketAddress(coordinatorDetails.getHostname(),
                Integer.parseInt(coordinatorDetails.getPort()));

        for (Member member : hazelcastInstance.getCluster().getMembers()) {
            InetSocketAddress nodeSocketAddress = member.getSocketAddress();
            String nodeId = configurableClusterAgent.getIdOfNode(member);
            boolean isCoordinator = nodeSocketAddress.equals(coordinatorSocketAddress);

            nodeDetails.add(new NodeDetail(nodeId, nodeSocketAddress, isCoordinator));
        }

        return nodeDetails;
    }

    /**
     * Return current coordinator hostname and port
     *
     * @return coordinator details
     */
    private CoordinatorInformation getCoordinatorDetails() {
        String ipAddress = coordinatorNodeDetailsMap.get(SlotCoordinationConstants.CLUSTER_COORDINATOR_SERVER_IP);
        String port = coordinatorNodeDetailsMap.get(SlotCoordinationConstants.CLUSTER_COORDINATOR_SERVER_PORT);

        return new CoordinatorInformation(ipAddress, port);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void stop() {
        hazelcastInstance.getCluster().removeMembershipListener(listenerRegistrationId);
    }

    /*
    * ======================== Methods from MembershipListener ============================
    */

    /**
     * {@inheritDoc}
     */
    @Override
    public void memberAdded(MembershipEvent membershipEvent) {
        Member member = membershipEvent.getMember();
        log.info("Handling cluster gossip: New member joined to the cluster. Member Socket Address:"
                + member.getSocketAddress() + " UUID:" + member.getUuid());

        checkAndNotifyCoordinatorChange();

        int maximumNumOfTries = 3;
        String nodeId;
        int numberOfAttemptsTried = 0;
        /*
         * Try a few times until nodeId is read from distributed Hazelcast Map
         * and give up
         */
        nodeId = configurableClusterAgent.getIdOfNode(member);
        if (null == nodeId) {
            while (numberOfAttemptsTried < maximumNumOfTries) {
                try {
                    // Exponentially increase waiting time
                    long sleepTime = Math.round(Math.pow(2, (numberOfAttemptsTried)));
                    TimeUnit.SECONDS.sleep(sleepTime);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                nodeId = configurableClusterAgent.getIdOfNode(member);
                numberOfAttemptsTried = numberOfAttemptsTried + 1;
                if (!(StringUtils.isEmpty(nodeId))) {
                    break;
                }
            }
        }
        if (StringUtils.isEmpty(nodeId)) {
            log.warn("Node ID is not set for member " + member + " when newly joined");
        }
        configurableClusterAgent.memberAdded(nodeId);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void memberRemoved(MembershipEvent membershipEvent) {
        Member member = membershipEvent.getMember();
        log.info("Handling cluster gossip: A member left the cluster. Member Socket Address:"
                + member.getSocketAddress() + " UUID:" + member.getUuid());

        try {
            checkAndNotifyCoordinatorChange();
            configurableClusterAgent.memberRemoved(configurableClusterAgent.getIdOfNode(member));
        } catch (AndesException e) {
            log.error("Error while handling node removal, " + member.getSocketAddress(), e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void memberAttributeChanged(MemberAttributeEvent memberAttributeEvent) {
        // do nothing here, since member attributes are not used in the implementation
    }

    /**
     * Check if the current node is the new coordinator and notify cluster agent.
     */
    private void checkAndNotifyCoordinatorChange() {
        if (isCoordinator() && isCoordinator.compareAndSet(false, true)) {
            updateThriftCoordinatorDetailsToMap();
            updateCoordinatorNodeDetailMap();
            configurableClusterAgent.becameCoordinator();
        } else {
            isCoordinator.set(false);
        }
    }

    /**
     * Sets coordinator's hostname and port in {@link org.wso2.andes.server.cluster.coordination
     * .CoordinationConstants#COORDINATOR_NODE_DETAILS_MAP_NAME} hazelcast map.
     */
    private void updateCoordinatorNodeDetailMap() {
        // Adding cluster coordinator's node IP and port
        Member localMember = hazelcastInstance.getCluster().getLocalMember();
        coordinatorNodeDetailsMap.put(SlotCoordinationConstants.CLUSTER_COORDINATOR_SERVER_IP,
                localMember.getSocketAddress().getAddress().getHostAddress());
        coordinatorNodeDetailsMap.put(SlotCoordinationConstants.CLUSTER_COORDINATOR_SERVER_PORT,
                Integer.toString(localMember.getSocketAddress().getPort()));
    }

    /**
     * Set coordinator's thrift server IP and port in hazelcast map.
     */
    private void updateThriftCoordinatorDetailsToMap() {

        String thriftCoordinatorServerIP = AndesContext.getInstance().getThriftServerHost();
        int thriftCoordinatorServerPort = AndesContext.getInstance().getThriftServerPort();

        log.info("This node is elected as the Slot Coordinator. Registering " + thriftCoordinatorServerIP + ":"
                + thriftCoordinatorServerPort);
        thriftServerDetailsMap.put(SlotCoordinationConstants.THRIFT_COORDINATOR_SERVER_IP,
                thriftCoordinatorServerIP);
        thriftServerDetailsMap.put(SlotCoordinationConstants.THRIFT_COORDINATOR_SERVER_PORT,
                Integer.toString(thriftCoordinatorServerPort));
    }
}