edu.umass.cs.reconfiguration.ReconfigurableNode.java Source code

Java tutorial

Introduction

Here is the source code for edu.umass.cs.reconfiguration.ReconfigurableNode.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;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import org.json.JSONObject;

import edu.umass.cs.gigapaxos.AbstractPaxosLogger;
import edu.umass.cs.gigapaxos.PaxosConfig;
import edu.umass.cs.gigapaxos.PaxosConfig.PC;
import edu.umass.cs.gigapaxos.interfaces.ClientMessenger;
import edu.umass.cs.gigapaxos.interfaces.ClientRequest;
import edu.umass.cs.gigapaxos.interfaces.Replicable;
import edu.umass.cs.nio.AbstractJSONPacketDemultiplexer;
import edu.umass.cs.nio.AbstractPacketDemultiplexer;
import edu.umass.cs.nio.JSONMessenger;
import edu.umass.cs.nio.JSONNIOTransport;
import edu.umass.cs.nio.MessageNIOTransport;
import edu.umass.cs.nio.interfaces.IntegerPacketType;
import edu.umass.cs.nio.nioutils.NIOInstrumenter;
import edu.umass.cs.reconfiguration.ReconfigurationConfig.RC;
import edu.umass.cs.reconfiguration.interfaces.ReconfigurableNodeConfig;
import edu.umass.cs.reconfiguration.reconfigurationutils.ConsistentReconfigurableNodeConfig;
import edu.umass.cs.reconfiguration.reconfigurationutils.DefaultNodeConfig;
import edu.umass.cs.reconfiguration.reconfigurationutils.ReconfigurationPacketDemultiplexer;
import edu.umass.cs.utils.Config;
import edu.umass.cs.utils.Util;

/**
 * 
 * @author V. Arun
 *
 * @param <NodeIDType>
 *            A generic type for representing node identifiers. It must support
 *            an explicitly overridden toString() method that converts
 *            NodeIDType to String, and the NodeConfig object supplied to this
 *            class' constructor must support a valueOf(String) method that
 *            returns back the original NodeIDType. Thus, even though NodeIDType
 *            is generic, a one-to-one mapping between NodeIDType and String is
 *            necessary.
 */
public abstract class ReconfigurableNode<NodeIDType> {

    protected final NodeIDType myID;
    protected final ReconfigurableNodeConfig<NodeIDType> nodeConfig;
    protected final JSONMessenger<NodeIDType> messenger;

    protected abstract AbstractReplicaCoordinator<NodeIDType> createAppCoordinator();

    private final Set<ActiveReplica<NodeIDType>> activeReplicas = new HashSet<ActiveReplica<NodeIDType>>();
    private final Set<Reconfigurator<NodeIDType>> reconfigurators = new HashSet<Reconfigurator<NodeIDType>>();

    /**
     * @param id
     * @param nodeConfig
     * @throws IOException
     */
    public ReconfigurableNode(NodeIDType id, ReconfigurableNodeConfig<NodeIDType> nodeConfig) throws IOException {
        this(id, nodeConfig, null, false);
    }

    /**
     * Close gracefully.
     */
    public void close() {
        for (ActiveReplica<NodeIDType> node : this.activeReplicas) {
            node.close();
        }
        for (Reconfigurator<NodeIDType> node : this.reconfigurators) {
            node.close();
        }
        this.messenger.stop();
        Reconfigurator.getLogger().info("----------" + this + " closed----->||||");
    }

    private AbstractReplicaCoordinator<NodeIDType> createApp(String[] args,
            ReconfigurableNodeConfig<NodeIDType> nodeConfig) {
        if (ReconfigurationConfig.application != null) {
            Replicable app = ReconfigurationConfig.createApp(args);
            if (app instanceof ClientMessenger)
                ((ClientMessenger) app).setClientMessenger(messenger);
            else
                Reconfigurator.getLogger().info(app.getClass().getSimpleName() + " does not implement "
                        + ClientMessenger.class.getSimpleName() + ", which means the app should either rely on "
                        + ClientRequest.class.getSimpleName() + " or not expect to send "
                        + " responses back to clients or rely on alternate means for messaging.");
            PaxosReplicaCoordinator<NodeIDType> prc = new PaxosReplicaCoordinator<NodeIDType>(app, myID, nodeConfig,
                    messenger);
            Reconfigurator.getLogger().info("Creating default group with " + nodeConfig.getActiveReplicas());
            prc.createDefaultGroupNodes(app.getClass().getSimpleName() + "0", nodeConfig.getActiveReplicas(),
                    nodeConfig);
            return prc;
        } else {
            AbstractReplicaCoordinator<NodeIDType> appCoordinator = this.createAppCoordinator();
            if (appCoordinator instanceof PaxosReplicaCoordinator)
                Reconfigurator.getLogger()
                        .warning("Using createAppCoordinator() is discouraged for "
                                + "applications simply using paxos as the coordiantion protocol. Implement "
                                + "Application.createApp(String[]) or Application.createApp() to construct "
                                + "the application instance instead.");
            return appCoordinator;
        }
    }

    /**
     * @param id
     *            Node ID of this ReconfigurableNode being created.
     * @param nodeConfig
     *            Maps node IDs of active replicas and reconfigurators to their
     *            socket addresses.
     * @param args
     * @param startCleanSlate
     *            Used to join newly added nodes.
     * 
     * @throws IOException
     *             Thrown if networking functions can not be successfully
     *             initialized. A common reason for this exception is that the
     *             socket addresses corresponding to the supplied 'id' argument
     *             are not local, i.e., the node with this id should not be
     *             created on this machine in the first place, or if the id is
     *             not present at all in the supplied 'nodeConfig' argument.
     */
    public ReconfigurableNode(NodeIDType id, ReconfigurableNodeConfig<NodeIDType> nodeConfig, String[] args,
            boolean startCleanSlate) throws IOException {
        this.myID = id;
        this.nodeConfig = nodeConfig;

        AbstractPaxosLogger.fileLock(id);

        AbstractJSONPacketDemultiplexer pd;

        String err = null;
        if (!nodeConfig.getActiveReplicas().contains(id) && !nodeConfig.getReconfigurators().contains(id)) {
            Reconfigurator.getLogger().severe(err = "Node " + id + " not present in NodeConfig argument \n  "
                    + nodeConfig.getActiveReplicas() + "\n  " + nodeConfig.getReconfigurators());
            throw new IOException(err);
        }
        // else
        Reconfigurator.getLogger().info(this + ":" + this.myID + " listening on " + nodeConfig.getNodeAddress(myID)
                + ":" + nodeConfig.getNodePort(myID));
        MessageNIOTransport<NodeIDType, JSONObject> niot = null;
        InetSocketAddress isa = new InetSocketAddress(nodeConfig.getNodeAddress(myID),
                nodeConfig.getNodePort(myID));
        // else we have something to start
        messenger = (new JSONMessenger<NodeIDType>(
                (niot = new MessageNIOTransport<NodeIDType, JSONObject>(ReconfigurableNode.this.myID, nodeConfig,
                        (pd = new ReconfigurationPacketDemultiplexer()
                                .setThreadName(ReconfigurableNode.this.myID.toString())),
                        true, ReconfigurationConfig.getServerSSLMode()))));
        if (!niot.getListeningSocketAddress().equals(isa) && Config.getGlobalBoolean(PC.STRICT_ADDRESS_CHECKS)) {
            Reconfigurator.getLogger()
                    .severe(err = this + " unable to start ReconfigurableNode at socket address " + isa);
            throw new IOException(err);
        }
        // else created messenger, may still fail to create client messenger

        if (nodeConfig.getActiveReplicas().contains(id)) {
            // create active
            ActiveReplica<NodeIDType> activeReplica = new ActiveReplica<NodeIDType>(
                    // createAppCoordinator(),
                    createApp(args, nodeConfig), nodeConfig, messenger);
            this.activeReplicas.add(activeReplica);
            // getPacketTypes includes app's packets
            pd.register(activeReplica.getPacketTypes(), activeReplica);
        } else if (nodeConfig.getReconfigurators().contains(id)) {
            // create reconfigurator
            Reconfigurator<NodeIDType> reconfigurator = new Reconfigurator<NodeIDType>(nodeConfig, messenger,
                    startCleanSlate);
            pd.register(reconfigurator.getPacketTypes().toArray(new IntegerPacketType[0]), reconfigurator);
            this.reconfigurators.add(reconfigurator);

            // wrap reconfigurator in active to make it reconfigurable
            ActiveReplica<NodeIDType> activeReplica = reconfigurator
                    .getReconfigurableReconfiguratorAsActiveReplica();
            pd.register(activeReplica.getPacketTypes(), activeReplica);
            this.activeReplicas.add(activeReplica);
        }
    }

    // because ReconfigurableNode is abstract for backwards compatibility
    /**
     */
    public static class DefaultReconfigurableNode extends ReconfigurableNode<String> {

        /**
         * @param id
         * @param nodeConfig
         * @param args
         * @param startCleanSlate
         * @throws IOException
         */
        public DefaultReconfigurableNode(String id, ReconfigurableNodeConfig<String> nodeConfig, String[] args,
                boolean startCleanSlate) throws IOException {
            super(id, nodeConfig, args, startCleanSlate);
        }

        @Override
        protected AbstractReplicaCoordinator<String> createAppCoordinator() {
            return super.createApp(null, super.nodeConfig);
        }

        public String toString() {
            return super.toString();
        }
    }

    public String toString() {
        return "Node" + this.myID;
    }

    private static boolean clear = false;

    // get all nodes to be started via main
    private static Set<String> getAllNodes(String[] args) {
        Set<String> nodeIDs = new HashSet<String>();
        // search for START_ALL; only for backwards compatibility
        if (args[args.length - 1].equals(ReconfigurationConfig.CommandArgs.START_ALL.toString())
                // look for "start all" or "clear all" at the end
                || (args.length >= 2 && args[args.length - 1].equals(ReconfigurationConfig.CommandArgs.all)
                        && (args[args.length - 2].equals(ReconfigurationConfig.CommandArgs.start)
                                || args[args.length - 2].equals(ReconfigurationConfig.CommandArgs.clear)))) {
            nodeIDs.addAll(ReconfigurationConfig.getReconfiguratorIDs());
            nodeIDs.addAll(PaxosConfig.getActives().keySet());
        } else
            for (int i = args.length - 1; i >= 0; i--)
                if ((ReconfigurationConfig.getReconfiguratorIDs().contains(args[i])
                        || PaxosConfig.getActives().keySet().contains(args[i]))
                        && !args[i].equals(ReconfigurationConfig.CommandArgs.start.toString())
                        && !args[i].equals(ReconfigurationConfig.CommandArgs.clear.toString()))
                    nodeIDs.add(args[i]);
                else {
                    clear = clear || args[i].equals(ReconfigurationConfig.CommandArgs.clear.toString());
                    break;
                }
        return nodeIDs;
    }

    // only for backwards compatibility
    private static String getAppArgs(String[] args) {
        String argsAsString = "";
        for (String arg : args)
            argsAsString += " " + arg;
        argsAsString = argsAsString.replaceAll(ReconfigurationConfig.CommandArgs.START_ALL.toString(), "")
                .replaceAll(" " + ReconfigurationConfig.CommandArgs.start.toString() + " .*", "").trim();
        return argsAsString;
    }

    /**
     * {@code args} contains a list of app arguments followed by a list of
     * active or reconfigurator node IDs at the end. The string "start all" is
     * accepted as a proxy for the list of all nodes if the socket addresses of
     * all nodes are on the local machine.
     * 
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        Config.register(args);
        PaxosConfig.sanityCheck();
        ReconfigurationConfig.setConsoleHandler();
        if (Config.getGlobalBoolean(PC.EMULATE_DELAYS))
            AbstractPacketDemultiplexer.emulateDelays();

        if (args.length == 0)
            throw new RuntimeException(
                    "At least one node ID must be specified as a command-line argument for starting "
                            + ReconfigurableNode.class);
        ReconfigurableNodeConfig<String> nodeConfig = new DefaultNodeConfig<String>(PaxosConfig.getActives(),
                ReconfigurationConfig.getReconfigurators());
        PaxosConfig.sanityCheck(nodeConfig);
        Set<String> servers = getAllNodes(args);

        if (clear) {
            for (String id : servers)
                try {
                    SQLReconfiguratorDB.dropState(id,
                            new ConsistentReconfigurableNodeConfig<String>(new DefaultNodeConfig<String>(
                                    PaxosConfig.getActives(), ReconfigurationConfig.getReconfigurators())));
                } catch (Exception e) {
                    /* ignore all exceptions as they correspond to non-local
                     * nodes */
                    e.printStackTrace();
                }
            return;
        }

        String sysPropAppArgsAsString = System.getProperty(ReconfigurationConfig.CommandArgs.appArgs.toString());
        // append cmdline args to system property based args
        String cmdlineAppArgs = getAppArgs(args);
        String[] appArgs = ((sysPropAppArgsAsString != null ? sysPropAppArgsAsString : "") + " " + cmdlineAppArgs)
                .split("\\s");
        int numServers = servers.size();
        if (numServers == 0)
            throw new RuntimeException("No valid server names supplied");
        System.out.print("Initializing gigapaxos server" + (numServers > 1 ? "s" : "") + " [ ");
        for (String node : servers) {
            System.out
                    .print(node + ":" + nodeConfig.getNodeAddress(node) + ":" + nodeConfig.getNodePort(node) + " ");
            new DefaultReconfigurableNode(node,
                    // must use a different nodeConfig for each
                    new DefaultNodeConfig<String>(PaxosConfig.getActives(),
                            ReconfigurationConfig.getReconfigurators()),
                    appArgs, false);
        }
        System.out.println("]; server" + (numServers > 1 ? "s" : "") + servers + " ready");
    }
}