edu.umass.cs.reconfiguration.reconfigurationutils.ConsistentReconfigurableNodeConfig.java Source code

Java tutorial

Introduction

Here is the source code for edu.umass.cs.reconfiguration.reconfigurationutils.ConsistentReconfigurableNodeConfig.java

Source

/* Copyright (c) 2015 University of Massachusetts
 * 
 * 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.
 * 
 * Initial developer(s): V. Arun */
package edu.umass.cs.reconfiguration.reconfigurationutils;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.json.JSONArray;
import org.json.JSONException;

import edu.umass.cs.nio.nioutils.RTTEstimator;
import edu.umass.cs.reconfiguration.ReconfigurationConfig;
import edu.umass.cs.reconfiguration.Reconfigurator;
import edu.umass.cs.reconfiguration.ReconfigurationConfig.RC;
import edu.umass.cs.reconfiguration.interfaces.ModifiableActiveConfig;
import edu.umass.cs.reconfiguration.interfaces.ModifiableRCConfig;
import edu.umass.cs.reconfiguration.interfaces.ReconfigurableNodeConfig;
import edu.umass.cs.utils.Config;

/**
 * @author arun
 *
 * @param <NodeIDType>
 * 
 *            * This class is a wrapper around NodeConfig to ensure that it is
 *            consistent, i.e., it returns consistent results even if it changes
 *            midway. In particular, it does not allow the use of a method like
 *            getNodeIDs().
 * 
 *            It also has consistent hashing utility methods.
 */
public class ConsistentReconfigurableNodeConfig<NodeIDType> extends ConsistentNodeConfig<NodeIDType>
        implements ModifiableActiveConfig<NodeIDType>, ModifiableRCConfig<NodeIDType>, InterfaceGetActiveIPs {
    private final SimpleReconfiguratorNodeConfig<NodeIDType> nodeConfig;
    private Set<NodeIDType> activeReplicas; // most recent cached copy
    private Set<NodeIDType> reconfigurators; // most recent cached copy

    // need to refresh when nodeConfig changes
    private final ConsistentHashing<NodeIDType> CH_RC;
    // need to refresh when nodeConfig changes
    private final ConsistentHashing<NodeIDType> CH_AR;

    /* We need to track reconfigurators slated for removal separately because we
     * still need ID to socket address mappings for deleted nodes (e.g., in
     * order to do networking for completing deletion operations) but not
     * include deleted nodes in the consistent hash ring. Thus, the ring
     * transitions immediately to the new ring when reconfigurators are added or
     * slated for removal, but socket addresses for removal-slated nodes are
     * maintained until explicitly told to garbage collect them. */
    private Set<NodeIDType> reconfiguratorsSlatedForRemoval = new HashSet<NodeIDType>();
    private Set<NodeIDType> activesSlatedForRemoval = new HashSet<NodeIDType>();

    /**
     * @param nc
     */
    public ConsistentReconfigurableNodeConfig(ReconfigurableNodeConfig<NodeIDType> nc) {
        super(nc);
        this.nodeConfig = new SimpleReconfiguratorNodeConfig<NodeIDType>(nc);
        this.activeReplicas = this.nodeConfig.getActiveReplicas();
        this.reconfigurators = this.nodeConfig.getReconfigurators();
        this.CH_RC = new ConsistentHashing<NodeIDType>(this.reconfigurators);
        /* The true flag means replicate_all, i.e., number of active replicas
         * chosen initially will be the set of all active replicas at that time. */
        this.CH_AR = new ConsistentHashing<NodeIDType>(this.activeReplicas,
                Config.getGlobalBoolean(RC.REPLICATE_ALL));
    }

    @Override
    public Set<NodeIDType> getValuesFromStringSet(Set<String> strNodes) {
        return this.nodeConfig.getValuesFromStringSet(strNodes);
    }

    @Override
    public Set<NodeIDType> getValuesFromJSONArray(JSONArray array) throws JSONException {
        return this.nodeConfig.getValuesFromJSONArray(array);
    }

    @Override
    public Set<NodeIDType> getNodeIDs() {
        throw new RuntimeException("The use of this method is not permitted");
    }

    @Override
    public Set<NodeIDType> getActiveReplicas() {
        return this.nodeConfig.getActiveReplicas();
    }

    /**
     * @return Socket addresses of active replicas. Used by reconfigurator to
     *         respond to broadcast queries.
     */
    public Set<InetSocketAddress> getActiveReplicaSocketAddresses() {
        return this.getActiveReplicaSocketAddresses(Integer.MAX_VALUE);
    }

    /**
     * This method tries to return a limited number of active replica socket
     * addresses that to the extent possible have distinct IPs. The socket
     * addresses are a randomly chosen subset of size up to limit from the set
     * of all active replicas.
     * 
     * @param limit
     * @return Socket addresses of active replicas. Used by reconfigurator to
     *         respond to broadcast queries.
     */
    public Set<InetSocketAddress> getActiveReplicaSocketAddresses(int limit) {
        Set<InetSocketAddress> actives = new HashSet<InetSocketAddress>();
        Set<Integer> activeIPs = new HashSet<Integer>();
        ArrayList<InetSocketAddress> activeNodes = new ArrayList<InetSocketAddress>();
        ArrayList<InetSocketAddress> duplicateIPs = new ArrayList<InetSocketAddress>();

        /* Create up to limit-sized ArrayList for shuffling. First add socket
         * addresses corresponding to distinct /24 IP prefixes. */
        for (NodeIDType node : this.getActiveReplicas()) {
            InetSocketAddress sockAddr = this.getNodeSocketAddress(node);
            if (!activeIPs.contains(RTTEstimator.addrToPrefixInt(sockAddr.getAddress()))) {
                activeNodes.add(sockAddr);
                activeIPs.add(RTTEstimator.addrToPrefixInt(sockAddr.getAddress()));
            } else
                duplicateIPs.add(sockAddr);
            if (activeNodes.size() >= limit)
                break;
        }
        /* Then check if we can add back some of the socket addresses with
         * duplicate /24 prefixes up to limit. */
        if (activeNodes.size() < limit) {
            for (InetSocketAddress duplicate : duplicateIPs) {
                activeNodes.add(duplicate);
                if (activeNodes.size() >= limit)
                    break;
            }
        }

        // shuffle to return actives in random order
        Collections.shuffle(activeNodes);
        for (InetSocketAddress sockAddr : activeNodes) {
            actives.add(sockAddr);
            if (actives.size() >= limit)
                break;
        }
        return actives;
    }

    @Override
    public Set<NodeIDType> getReconfigurators() {
        return this.nodeConfig.getReconfigurators();
    }

    /**
     * Consistent coz it always consults nodeConfig.
     * 
     * @param nodeIDs
     * @return Array of IP addresses corresponding to {@code nodeIDs}.
     */
    public ArrayList<InetAddress> getNodeIPs(Set<NodeIDType> nodeIDs) {
        ArrayList<InetAddress> addresses = new ArrayList<InetAddress>();
        for (NodeIDType id : nodeIDs) {
            addresses.add(this.nodeConfig.getNodeAddress(id));
        }
        assert (addresses != null);
        return addresses;
    }

    @Override
    public ArrayList<InetAddress> getActiveIPs() {
        return getNodeIPs(getActiveReplicas());
    }

    // refresh before returning
    /**
     * @param name
     * @return Set of consecutive reconfigurators to which {@code name} hashes
     *         on the consistent hash ring.
     */
    public Set<NodeIDType> getReplicatedReconfigurators(String name) {
        this.refreshReconfigurators();
        return this.CH_RC.getReplicatedServers(name);
    }

    /**
     * @param name
     * @return Reconfigurator addresses responsible for name.
     */
    public Set<InetSocketAddress> getReconfiguratorsAsAddresses(String name) {
        Set<NodeIDType> nodes = getReplicatedReconfigurators(name);
        Set<InetSocketAddress> addrs = new HashSet<InetSocketAddress>();
        for (NodeIDType node : nodes)
            addrs.add(this.getNodeSocketAddress(node));
        return addrs;
    }

    /**
     * @param name
     * @return First reconfigurator to which {@code name} hashes on the
     *         consistent hash ring.
     */
    // refresh before returning
    public NodeIDType getFirstReconfigurator(String name) {
        this.refreshReconfigurators();
        return this.CH_RC.getNode(name);
    }

    // NOT USED IN NEW APP. FOR BACKWARDS COMPATIBILITY WITH OLD APP.
    // WILL BE REMOVED AFTER NEW APP IS TESTED.
    /**
     * Returns the hash for this name.
     * 
     * @param name
     * @return Returns the node id to which name consistent-hashes.
     */
    @Deprecated
    public NodeIDType getReconfiguratorHash(String name) {
        return this.getFirstReconfigurator(name);
    }

    // refresh before returning
    /**
     * @param name
     * @return Set of active replica nodes to which {@code name} hashes on the
     *         consistent hash ring of all active replica nodes.
     */
    public Set<NodeIDType> getReplicatedActives(String name) {
        this.refreshActives();
        return this.CH_AR.getReplicatedServers(name);
    }

    /**
     * @param name
     * @return Set of active replica IPs to which {@code name} hashes on the
     *         consistent hash ring of all active replica nodes.
     */
    public ArrayList<InetAddress> getReplicatedActivesIPs(String name) {
        return this.getNodeIPs(this.getReplicatedActives(name));
    }

    /**
     * @param name
     * @param limit
     * @return Active replica addresses.
     */
    private Set<InetSocketAddress> getReplicatedActivesAddresses(String name, int limit) {
        Set<NodeIDType> actives = this.getReplicatedActives(name);
        Set<InetSocketAddress> addresses = new HashSet<InetSocketAddress>();
        int count = 0;
        for (NodeIDType node : actives) {
            addresses.add(this.getNodeSocketAddress(node));
            if (++count == limit)
                break;
        }
        return addresses;
    }

    /**
     * @return Random active replica address.
     */
    public Set<InetSocketAddress> getRandomActiveReplica() {
        return this.getReplicatedActivesAddresses("" + Math.random(), 1);
    }

    /**
     * This method maps a set of addresses, newAddresses, to a set of nodes such
     * that there is maximal overlap with the specified set of nodes, oldNodes.
     * It is somewhat nontrivial only because there is a many-to-one mapping
     * from nodes to addresses, so a simple reverse lookup is not meaningful.
     * 
     * @param newAddresses
     * @param oldNodes
     * @return Set of active replica IPs corresponding to {@code newAddresses}
     *         that have high overlap with the set of old active replica nodes
     *         {@code oldNodes}.
     */
    public Set<NodeIDType> getIPToActiveReplicaIDs(ArrayList<InetAddress> newAddresses, Set<NodeIDType> oldNodes) {
        Set<NodeIDType> newNodes = new HashSet<NodeIDType>(); // return value
        ArrayList<InetAddress> unassigned = new ArrayList<InetAddress>();
        for (InetAddress address : newAddresses)
            unassigned.add(address);
        // assign old nodes first if they match any new address
        for (NodeIDType oldNode : oldNodes) {
            InetAddress oldAddress = this.nodeConfig.getNodeAddress(oldNode);
            if (unassigned.contains(oldAddress)) {
                newNodes.add(oldNode);
                unassigned.remove(oldAddress);
            }
        }
        // assign any node to unassigned addresses
        for (NodeIDType node : this.nodeConfig.getActiveReplicas()) {
            if (this.activesSlatedForRemoval.contains(node))
                continue;
            InetAddress address = this.nodeConfig.getNodeAddress(node);
            if (unassigned.contains(address)) {
                newNodes.add(node);
                unassigned.remove(address);
            }
        }
        return newNodes;
    }

    /**
     * This method should not be used. Reconfigurator uses this at exactly one
     * place, namely, to encapsulate itself inside ActiveReplica so as to be
     * itself reconfigurable.
     * 
     * @return Underlying node config object.
     */
    public ReconfigurableNodeConfig<NodeIDType> getUnderlyingNodeConfig() {
        return this.nodeConfig;
    }

    // refresh consistent hash structure if changed
    private synchronized boolean refreshActives() {
        Set<NodeIDType> curActives = this.nodeConfig.getActiveReplicas();
        // remove those slated for removal for CH ring purposes
        curActives.removeAll(this.activesSlatedForRemoval);
        if (curActives.equals(this.getLastActives()))
            return false;
        this.setLastActives(curActives);
        this.CH_AR.refresh(curActives);
        return true;
    }

    // refresh consistent hash structure if changed
    private synchronized boolean refreshReconfigurators() {
        Set<NodeIDType> curReconfigurators = this.nodeConfig.getReconfigurators();
        // remove those slated for removal for CH ring purposes
        curReconfigurators.removeAll(this.reconfiguratorsSlatedForRemoval);
        if (curReconfigurators.equals(this.getLastReconfigurators()))
            return false;
        this.setLastReconfigurators(curReconfigurators);
        this.CH_RC.refresh(curReconfigurators);
        return true;
    }

    private synchronized Set<NodeIDType> getLastActives() {
        return this.activeReplicas;
    }

    private synchronized Set<NodeIDType> getLastReconfigurators() {
        return this.reconfigurators;
    }

    private synchronized Set<NodeIDType> setLastActives(Set<NodeIDType> curActives) {
        return this.activeReplicas = curActives;
    }

    private synchronized Set<NodeIDType> setLastReconfigurators(Set<NodeIDType> curReconfigurators) {
        return this.reconfigurators = curReconfigurators;
    }

    @Override
    public InetSocketAddress addReconfigurator(NodeIDType id, InetSocketAddress sockAddr) {
        InetSocketAddress isa = this.nodeConfig.addReconfigurator(id, sockAddr);
        assert (this.nodeConfig.getNodeAddress(id) != null);
        return isa;
    }

    /**
     * Returns the socket address of the public host corresponding to this id.
     * 
     * @param id
     * @return Socket address corresponding to node {@code id}.
     */
    public InetSocketAddress getNodeSocketAddress(NodeIDType id) {
        InetAddress ip = this.nodeConfig.getNodeAddress(id);
        int port = this.nodeConfig.getNodePort(id);
        if (port == -1 && !this.isSlatedForRemoval(id))
            Reconfigurator.getLogger().warning("No port found for nodeID " + id);
        return (ip != null ? new InetSocketAddress(ip, port) : null);
    }

    /**
     * Returns the bindable socket address of the public host corresponding to
     * this id. This code be a private address in the case of where we are
     * inside a NAT.
     * 
     * @param id
     * @return Bind socket address.
     */
    public InetSocketAddress getBindSocketAddress(NodeIDType id) {
        InetAddress ip = this.nodeConfig.getBindAddress(id);
        int port = this.nodeConfig.getNodePort(id);
        if (port == -1 && !this.isSlatedForRemoval(id))
            Reconfigurator.getLogger().warning("No port found for nodeID " + id);
        return (ip != null ? new InetSocketAddress(ip, port) : null);
    }

    private boolean isSlatedForRemoval(NodeIDType id) {
        return this.activesSlatedForRemoval.contains(id) || this.reconfiguratorsSlatedForRemoval.contains(id);
    }

    @Override
    public InetSocketAddress removeReconfigurator(NodeIDType id) {
        return this.nodeConfig.removeReconfigurator(id);
    }

    private InetSocketAddress removeActive(NodeIDType id) {
        return this.nodeConfig.removeActiveReplica(id);
    }

    /**
     * A utility method to split a collection of names into batches wherein
     * names in each batch map to the same reconfigurator group. The set of
     * reconfigurators can be specified either as a NodeIDType or a String set
     * but not as an InetSocketAddress set (unless NodeIDType is
     * InetSocketAddress).
     * 
     * @param names
     * @param reconfigurators
     * @return A set of batches of names wherein names in each batch map to the
     *         same reconfigurator group.
     */
    public static Collection<Set<String>> splitIntoRCGroups(Set<String> names, Set<?> reconfigurators) {
        if (reconfigurators.isEmpty())
            throw new RuntimeException("A nonempty set of reconfigurators must be specified.");
        ConsistentHashing<?> ch = new ConsistentHashing<>(reconfigurators);
        Map<String, Set<String>> batches = new HashMap<String, Set<String>>();
        for (String name : names) {
            String rc = ch.getNode(name).toString();
            if (!batches.containsKey(rc))
                batches.put(rc, new HashSet<String>());
            batches.get(rc).add(name); // no need to put again
        }
        return batches.values();
    }

    /**
     * @param names
     * @return True if all names map to the same reconfigurator group.
     */
    public boolean checkSameGroup(Set<String> names) {
        NodeIDType rc = null;
        for (String name : names)
            if (rc == null)
                rc = this.getFirstReconfigurator(name);
            else if (!rc.equals(this.getFirstReconfigurator(name)))
                return false;
        return true;
    }

    /**
     * @param id
     * @return IP address of id being slated for removal.
     */
    public InetSocketAddress slateForRemovalReconfigurator(NodeIDType id) {
        this.reconfiguratorsSlatedForRemoval.add(id);
        return this.getNodeSocketAddress(id);
    }

    /**
     * @param id
     * @return IP address of id being slated for removal.
     */
    public InetSocketAddress slateForRemovalActive(NodeIDType id) {
        this.activesSlatedForRemoval.add(id);
        return this.getNodeSocketAddress(id);
    }

    /**
     * @return True if all nodes slated for removal have been successfully
     *         removed.
     */
    public boolean removeReconfiguratorsSlatedForRemoval() {
        boolean allRemoved = true;
        for (Iterator<NodeIDType> nodeIter = this.reconfiguratorsSlatedForRemoval.iterator(); nodeIter.hasNext();) {
            NodeIDType slated = nodeIter.next();
            if (this.removeReconfigurator(slated) != null)
                nodeIter.remove();
            else
                allRemoved = false;
        }
        return allRemoved;
    }

    /**
     * @return True if all nodes slated for removal have been successfully
     *         removed.
     */
    public boolean removeActivesSlatedForRemoval() {
        boolean allRemoved = true;
        for (Iterator<NodeIDType> nodeIter = this.activesSlatedForRemoval.iterator(); nodeIter.hasNext();) {
            NodeIDType slated = nodeIter.next();
            if (this.removeActive(slated) != null) {
                assert (!this.nodeExists(slated)) : slated;
                nodeIter.remove();
            } else
                allRemoved = false;
        }
        return allRemoved;
    }

    @Override
    public InetSocketAddress addActiveReplica(NodeIDType id, InetSocketAddress sockAddr) {
        return this.nodeConfig.addActiveReplica(id, sockAddr);
    }

    @Override
    public InetSocketAddress removeActiveReplica(NodeIDType id) {
        return this.nodeConfig.removeActiveReplica(id);
    }

    @Override
    public long getVersion() {
        return this.nodeConfig.getVersion();
    }

    public String toString() {
        String s = "";
        for (NodeIDType id : this.nodeConfig.getActiveReplicas()) {
            s += id + ":" + this.getNodeSocketAddress(id) + " ";
        }
        s += "\n";
        for (NodeIDType id : this.nodeConfig.getReconfigurators()) {
            s += id + ":" + this.getNodeSocketAddress(id) + " ";
        }

        return s;
    }
}