org.apache.sentry.service.thrift.LeaderStatusMonitor.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sentry.service.thrift.LeaderStatusMonitor.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.sentry.service.thrift;

import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListener;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
import org.apache.hadoop.conf.Configuration;
import org.apache.sentry.provider.db.service.persistent.HAContext;

import javax.annotation.concurrent.ThreadSafe;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static org.apache.sentry.service.thrift.ServiceConstants.ServerConfig.*;

/**
 * LeaderStatusMonitor participates in the distributed leader election protocol
 * and allows clients to access the global leaadership status.
 * <p>
 * LeaderStatusMonitor is a singleton that uses Curator framework via
 * {@link HAContext}.The leadership status can be accessed via the
 * {@link #isLeader()} method.<p>
 *
 * Usually leadership re-election is initiated by the Curator framework when one
 * of the nodes disconnects from ZooKeeper, but LeaderStatusMonitor also supports
 * voluntary release of the leadership via the {@link #deactivate()} method. This is
 * intended to be used for debugging purposes.
 * <p>
 * The class also simulates leader election in non-HA environments. In such cases its
 * {@link #isLeader()} method always returns True. The non-HA environment is determined
 * by the absence of the SENTRY_HA_ZOOKEEPER_QUORUM in the configuration.
 *
 * <h2>Implementation notes</h2>
 *
 * <h3>Initialization</h3>
 *
 * Class initialization is split between the constructor and the {@link #init()} method.
 * There are two reasons for it:
 * <ul>
 *     <li>We do not want to pass <strong>this</strong> reference to
 *     {@link HAContext#newLeaderSelector(String, LeaderSelectorListener)}
 *     until it is fully initialized</li>
 *     <li>We do not want to call {@link LeaderSelector#start()} method in constructor</li>
 * </ul>
 *
 * Since LeaderStatusMonitor is a singleton and an instance can only be obtained via the
 * {@link #getLeaderStatusMonitor(Configuration)} method, we hide this construction split
 * from the callers.
 *
 * <h3>Synchronization</h3>
 * Singleton synchronization is achieved using the synchronized class builder
 * {@link #getLeaderStatusMonitor(Configuration)}
 * <p>
 * Upon becoming a leader, the code loops in {@link #takeLeadership(CuratorFramework)}
 * until it receives a deactivation signal from {@link #deactivate()}. This is synchronized
 * using a {@link #lock} and condition variable {@link #cond}.
 * <p>
 * Access to the leadership status {@link #isLeader} is also protected by the {@link #lock}.
 * This isn't strictly necessary and a volatile field would be sufficient, but since we
 * already use the {@link #lock} this is more straightforward.
 */
@ThreadSafe
final class LeaderStatusMonitor extends LeaderSelectorListenerAdapter implements AutoCloseable {

    private static final Log LOG = LogFactory.getLog(LeaderStatusMonitor.class);

    private static final String LEADER_SELECTOR_SUFFIX = "leader";

    /** Unique instance of the singleton object */
    private static LeaderStatusMonitor leaderStatusMonitor = null;

    private final HAContext haContext;

    /** Unique string describing this instance */
    private final String defaultIncarnationId = generateIncarnationId();
    private String incarnationId;

    /** True when not using ZooKeeeper */
    private final boolean isSingleNodeMode;

    /** Lock and condition used to signal the leader to voluntary release leadership */
    private final Lock lock = new ReentrantLock();
    /** Condition variable used to synchronize voluntary leadership release */
    private final Condition cond = lock.newCondition();
    /** Leadership status - true if leader. */
    private boolean isLeader = false;

    /** Curator framework leader monitor */
    private LeaderSelector leaderSelector = null;

    /** The number of times this incarnation has become the leader. */
    private final AtomicLong leaderCount = new AtomicLong(0);

    /**
     * Constructor. Initialize state and create HA context if configuration
     * specifies ZooKeeper servers.
     * @param conf Configuration. The fields we are interested in are:
     *             <ul>
     *             <li>SENTRY_HA_ZOOKEEPER_QUORUM</li>
     *             </ul>
     *             Configuration is also passed to the
     *             {@link HAContext#newLeaderSelector(String, LeaderSelectorListener)}
     *             which uses more properties.
     * @throws Exception
     */

    @VisibleForTesting
    protected LeaderStatusMonitor(Configuration conf) throws Exception {
        // Only enable HA configuration if zookeeper is configured
        String zkServers = conf.get(SENTRY_HA_ZOOKEEPER_QUORUM, "");
        if (zkServers.isEmpty()) {
            isSingleNodeMode = true;
            haContext = null;
            isLeader = true;
            incarnationId = "";
            LOG.info("Leader election protocol disabled, assuming single active server");
            return;
        }
        isSingleNodeMode = false;
        incarnationId = defaultIncarnationId;
        haContext = HAContext.getHAServerContext(conf);

        LOG.info("Created LeaderStatusMonitor(incarnationId=" + incarnationId + ", zkServers='" + zkServers + "')");
    }

    /**
     * Tests may need to provide custm incarnation ID
     * @param conf confguration
     * @param incarnationId custom incarnation ID
     * @throws Exception
     */
    @VisibleForTesting
    protected LeaderStatusMonitor(Configuration conf, String incarnationId) throws Exception {
        this(conf);
        this.incarnationId = incarnationId;
    }

    /**
     * Second half of the constructor links this object with {@link HAContext} and
     * starts leader election protocol.
     */
    @VisibleForTesting
    protected void init() {
        if (isSingleNodeMode) {
            return;
        }

        leaderSelector = haContext.newLeaderSelector("/" + LEADER_SELECTOR_SUFFIX, this);
        leaderSelector.setId(incarnationId);
        leaderSelector.autoRequeue();
        leaderSelector.start();
    }

    /**
     *
     * @param conf Configuration. See {@link #LeaderStatusMonitor(Configuration)} for details.
     * @return A global LeaderStatusMonitor instance.
     * @throws Exception
     */
    @SuppressWarnings("LawOfDemeter")
    static synchronized LeaderStatusMonitor getLeaderStatusMonitor(Configuration conf) throws Exception {
        if (leaderStatusMonitor == null) {
            leaderStatusMonitor = new LeaderStatusMonitor(conf);
            leaderStatusMonitor.init();
        }
        return leaderStatusMonitor;
    }

    /**
     * @return number of times this leader was elected. Used for metrics.
     */
    long getLeaderCount() {
        return leaderCount.get();
    }

    /**
     * Shut down the LeaderStatusMonitor and wait for it to transition to
     * standby.
     */
    @Override
    public void close() {
        if (leaderSelector != null) {
            // Shut down our Curator hooks.
            leaderSelector.close();
        }
    }

    /**
     * Deactivate the current client, if it is active.
     * In non-HA case this is a no-op.
     */
    void deactivate() {
        if (isSingleNodeMode) {
            return;
        }
        lock.lock();
        try {
            cond.signal();
        } finally {
            lock.unlock();
        }
    }

    /**
     * @return true iff we are the leader.
     * In non-HA case always returns true
     */
    boolean isLeader() {
        if (isSingleNodeMode) {
            return true;
        }
        lock.lock();
        @SuppressWarnings("FieldAccessNotGuarded")
        boolean leader = isLeader;
        lock.unlock();
        return leader;
    }

    /**
     * Curator framework callback which is called when we become a leader.
     * Should return only when we decide to resign.
     */
    @Override
    public void takeLeadership(CuratorFramework client) throws Exception {
        leaderCount.incrementAndGet();
        LOG.info("LeaderStatusMonitor: becoming active. " + "leaderCount=" + leaderCount);
        lock.lock();
        try {
            isLeader = true;
            // Wait until we are interrupted or receive a signal
            cond.await();
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
            LOG.info("LeaderStatusMonitor: interrupted");
        } finally {
            isLeader = false;
            lock.unlock();
            LOG.info("LeaderStatusMonitor: becoming standby");
        }
    }

    /**
     * Generate ID for the activator. <p>
     *
     * Ideally we would like something like host@pid, but Java doesn't provide a good
     * way to determine pid value, so we use
     * {@link RuntimeMXBean#getName()} which usually contains host
     * name and pid.
     */
    private static String generateIncarnationId() {
        return ManagementFactory.getRuntimeMXBean().getName();
    }

}