org.apache.hadoop.hdfs.server.journalservice.JournalService.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hdfs.server.journalservice.JournalService.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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 org.apache.hadoop.hdfs.server.journalservice;

import java.io.IOException;
import java.net.InetSocketAddress;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdfs.NameNodeProxies;
import org.apache.hadoop.hdfs.protocol.LayoutVersion;
import org.apache.hadoop.hdfs.protocol.UnregisteredNodeException;
import org.apache.hadoop.hdfs.protocol.proto.JournalProtocolProtos.JournalProtocolService;
import org.apache.hadoop.hdfs.protocolPB.JournalProtocolPB;
import org.apache.hadoop.hdfs.protocolPB.JournalProtocolServerSideTranslatorPB;
import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.NamenodeRole;
import org.apache.hadoop.hdfs.server.common.StorageInfo;
import org.apache.hadoop.hdfs.server.protocol.FenceResponse;
import org.apache.hadoop.hdfs.server.protocol.FencedException;
import org.apache.hadoop.hdfs.server.protocol.JournalInfo;
import org.apache.hadoop.hdfs.server.protocol.JournalProtocol;
import org.apache.hadoop.hdfs.server.protocol.NamenodeProtocol;
import org.apache.hadoop.hdfs.server.protocol.NamenodeRegistration;
import org.apache.hadoop.hdfs.server.protocol.NamespaceInfo;
import org.apache.hadoop.ipc.ProtobufRpcEngine;
import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.UserGroupInformation;

import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.BlockingService;

/**
 * This class interfaces with the namenode using {@link JournalProtocol} over
 * RPC. It has two modes: <br>
 * <ul>
 * <li>Mode where an RPC.Server is provided from outside, on which it
 * {@link JournalProtocol} is registered. The RPC.Server stop and start is
 * managed outside by the application.</li>
 * <li>Stand alone mode where an RPC.Server is started and managed by the
 * JournalListener.</li>
 * </ul>
 * 
 * The received journal operations are sent to a listener over callbacks. The
 * listener implementation can handle the callbacks based on the application
 * requirement.
 */
public class JournalService implements JournalProtocol {
    public static final Log LOG = LogFactory.getLog(JournalService.class.getName());

    private final JournalListener listener;
    private final InetSocketAddress nnAddress;
    private final NamenodeRegistration registration;
    private final NamenodeProtocol namenode;
    private final StateHandler stateHandler = new StateHandler();
    private final RPC.Server rpcServer;
    private long epoch = 0;
    private String fencerInfo;

    enum State {
        /** The service is initialized and ready to start. */
        INIT(false, false),
        /**
         * RPC server is started.
         * The service is ready to receive requests from namenode.
         */
        STARTED(false, false),
        /** The service is fenced by a namenode and waiting for roll. */
        WAITING_FOR_ROLL(false, true),
        /**
         * The existing log is syncing with another source
         * and it accepts journal from Namenode.
         */
        SYNCING(true, true),
        /** The existing log is in sync and it accepts journal from Namenode. */
        IN_SYNC(true, true),
        /** The service is stopped. */
        STOPPED(false, false);

        final boolean isJournalAllowed;
        final boolean isStartLogSegmentAllowed;

        State(boolean isJournalAllowed, boolean isStartLogSegmentAllowed) {
            this.isJournalAllowed = isJournalAllowed;
            this.isStartLogSegmentAllowed = isStartLogSegmentAllowed;
        }
    }

    static class StateHandler {
        State current = State.INIT;

        synchronized void start() {
            if (current != State.INIT) {
                throw new IllegalStateException("Service cannot be started in " + current + " state.");
            }
            current = State.STARTED;
        }

        synchronized void waitForRoll() {
            if (current != State.STARTED) {
                throw new IllegalStateException("Cannot wait-for-roll in " + current + " state.");
            }
            current = State.WAITING_FOR_ROLL;
        }

        synchronized void startLogSegment() {
            if (current == State.WAITING_FOR_ROLL) {
                current = State.SYNCING;
            }
        }

        synchronized void isStartLogSegmentAllowed() throws IOException {
            if (!current.isStartLogSegmentAllowed) {
                throw new IOException("Cannot start log segment in " + current + " state.");
            }
        }

        synchronized void isJournalAllowed() throws IOException {
            if (!current.isJournalAllowed) {
                throw new IOException("Cannot journal in " + current + " state.");
            }
        }

        synchronized boolean isStopped() {
            if (current == State.STOPPED) {
                LOG.warn("Ignore stop request since the service is in " + current + " state.");
                return true;
            }
            current = State.STOPPED;
            return false;
        }
    }

    /**
     * Constructor to create {@link JournalService} where an RPC server is
     * created by this service.
     * @param conf Configuration
     * @param nnAddr host:port for the active Namenode's RPC server
     * @param serverAddress address to start RPC server to receive
     *          {@link JournalProtocol} requests. This can be null, if
     *          {@code server} is a valid server that is managed out side this
     *          service.
     * @param listener call-back interface to listen to journal activities
     * @throws IOException on error
     */
    JournalService(Configuration conf, InetSocketAddress nnAddr, InetSocketAddress serverAddress,
            JournalListener listener) throws IOException {
        this.nnAddress = nnAddr;
        this.listener = listener;
        this.namenode = NameNodeProxies
                .createNonHAProxy(conf, nnAddr, NamenodeProtocol.class, UserGroupInformation.getCurrentUser(), true)
                .getProxy();
        this.rpcServer = createRpcServer(conf, serverAddress, this);

        String addr = NetUtils.getHostPortString(rpcServer.getListenerAddress());
        StorageInfo storage = new StorageInfo(LayoutVersion.getCurrentLayoutVersion(), 0, "", 0);
        registration = new NamenodeRegistration(addr, "", storage, NamenodeRole.BACKUP);
    }

    /**
     * Start the service.
     */
    public void start() {
        stateHandler.start();

        // Start the RPC server
        LOG.info("Starting rpc server");
        rpcServer.start();

        for (boolean registered = false, handshakeComplete = false;;) {
            try {
                // Perform handshake
                if (!handshakeComplete) {
                    handshake();
                    handshakeComplete = true;
                    LOG.info("handshake completed");
                }

                // Register with the namenode
                if (!registered) {
                    registerWithNamenode();
                    registered = true;
                    LOG.info("Registration completed");
                    break;
                }
            } catch (IOException ioe) {
                LOG.warn("Encountered exception ", ioe);
            } catch (Exception e) {
                LOG.warn("Encountered exception ", e);
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException ie) {
                LOG.warn("Encountered exception ", ie);
            }
        }

        stateHandler.waitForRoll();
        try {
            namenode.rollEditLog();
        } catch (IOException e) {
            LOG.warn("Encountered exception ", e);
        }
    }

    /**
     * Stop the service. For application with RPC Server managed outside, the
     * RPC Server must be stopped the application.
     */
    public void stop() {
        if (!stateHandler.isStopped()) {
            rpcServer.stop();
        }
    }

    @Override
    public void journal(JournalInfo journalInfo, long epoch, long firstTxnId, int numTxns, byte[] records)
            throws IOException {
        if (LOG.isTraceEnabled()) {
            LOG.trace("Received journal " + firstTxnId + " " + numTxns);
        }
        stateHandler.isJournalAllowed();
        verify(epoch, journalInfo);
        listener.journal(this, firstTxnId, numTxns, records);
    }

    @Override
    public void startLogSegment(JournalInfo journalInfo, long epoch, long txid) throws IOException {
        if (LOG.isTraceEnabled()) {
            LOG.trace("Received startLogSegment " + txid);
        }
        stateHandler.isStartLogSegmentAllowed();
        verify(epoch, journalInfo);
        listener.startLogSegment(this, txid);
        stateHandler.startLogSegment();
    }

    @Override
    public FenceResponse fence(JournalInfo journalInfo, long epoch, String fencerInfo) throws IOException {
        LOG.info("Fenced by " + fencerInfo + " with epoch " + epoch);
        verifyFence(epoch, fencerInfo);
        verify(journalInfo);
        long previousEpoch = epoch;
        this.epoch = epoch;
        this.fencerInfo = fencerInfo;

        // TODO:HDFS-3092 set lastTransId and inSync
        return new FenceResponse(previousEpoch, 0, false);
    }

    /** Create an RPC server. */
    private static RPC.Server createRpcServer(Configuration conf, InetSocketAddress address, JournalProtocol impl)
            throws IOException {
        RPC.setProtocolEngine(conf, JournalProtocolPB.class, ProtobufRpcEngine.class);
        JournalProtocolServerSideTranslatorPB xlator = new JournalProtocolServerSideTranslatorPB(impl);
        BlockingService service = JournalProtocolService.newReflectiveBlockingService(xlator);
        return RPC.getServer(JournalProtocolPB.class, service, address.getHostName(), address.getPort(), 1, false,
                conf, null);
    }

    private void verifyEpoch(long e) throws FencedException {
        if (epoch != e) {
            String errorMsg = "Epoch " + e + " is not valid. " + "Resource has already been fenced by " + fencerInfo
                    + " with epoch " + epoch;
            LOG.warn(errorMsg);
            throw new FencedException(errorMsg);
        }
    }

    private void verifyFence(long e, String fencer) throws FencedException {
        if (e <= epoch) {
            String errorMsg = "Epoch " + e + " from fencer " + fencer + " is not valid. "
                    + "Resource has already been fenced by " + fencerInfo + " with epoch " + epoch;
            LOG.warn(errorMsg);
            throw new FencedException(errorMsg);
        }
    }

    /** 
     * Verifies a journal request
     */
    private void verify(JournalInfo journalInfo) throws IOException {
        String errorMsg = null;
        int expectedNamespaceID = registration.getNamespaceID();
        if (journalInfo.getNamespaceId() != expectedNamespaceID) {
            errorMsg = "Invalid namespaceID in journal request - expected " + expectedNamespaceID + " actual "
                    + journalInfo.getNamespaceId();
            LOG.warn(errorMsg);
            throw new UnregisteredNodeException(journalInfo);
        }
        if (!journalInfo.getClusterId().equals(registration.getClusterID())) {
            errorMsg = "Invalid clusterId in journal request - expected " + journalInfo.getClusterId() + " actual "
                    + registration.getClusterID();
            LOG.warn(errorMsg);
            throw new UnregisteredNodeException(journalInfo);
        }
    }

    /** 
     * Verifies a journal request
     */
    private void verify(long e, JournalInfo journalInfo) throws IOException {
        verifyEpoch(e);
        verify(journalInfo);
    }

    /**
     * Register this service with the active namenode.
     */
    private void registerWithNamenode() throws IOException {
        NamenodeRegistration nnReg = namenode.register(registration);
        String msg = null;
        if (nnReg == null) { // consider as a rejection
            msg = "Registration rejected by " + nnAddress;
        } else if (!nnReg.isRole(NamenodeRole.NAMENODE)) {
            msg = " Name-node " + nnAddress + " is not active";
        }
        if (msg != null) {
            LOG.error(msg);
            throw new IOException(msg); // stop the node
        }
    }

    private void handshake() throws IOException {
        NamespaceInfo nsInfo = namenode.versionRequest();
        listener.verifyVersion(this, nsInfo);
        registration.setStorageInfo(nsInfo);
    }

    @VisibleForTesting
    long getEpoch() {
        return epoch;
    }
}