/*
* Copyright 2016 Hortonworks.
*
* 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 com.hortonworks.registries.ha.zk;
import com.google.common.base.Preconditions;
import com.hortonworks.registries.common.ha.LeadershipParticipant;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.retry.BoundedExponentialBackoffRetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
* {@link LeadershipParticipant} implementation for ZooKeeper.
*/
public class ZKLeadershipParticipant implements LeadershipParticipant {
public static final String LEADER_LOCK_NODE_PATH = "-leader-lock";
public static final int DEFAULT_CONN_TIMOUT = 20_1000;
public static final int DEFAULT_SESSION_TIMEOUT = 30_1000;
public static final int DEFAULT_BASE_SLEEP_TIME = 1000;
public static final int DEFAULT_MAX_SLEEP_TIME = 5000;
public static final int DEFAULT_RETRY_LIMIT = 5;
private static final Logger LOG = LoggerFactory.getLogger(ZKLeadershipParticipant.class);
public static final String CONNECT_URL = "connect.url";
public static final String CONNECTION_TIMEOUT_MS = "connection.timeout.ms";
public static final String SESSION_TIMEOUT_MS = "session.timeout.ms";
public static final String RETRY_BASE_SLEEP_TIME_MS = "retry.base.sleep.time.ms";
public static final String RETRY_MAX_SLEEP_TIME_MS = "retry.max.sleep.time.ms";
public static final String RETRY_LIMIT = "retry.limit";
private CuratorFramework curatorFramework;
private Map<String, Object> conf;
private String serverUrl;
private LeaderLatchListener leaderLatchListener;
private AtomicReference<LeaderLatch> leaderLatchRef;
private String leaderLatchPath;
public void init(Map<String, Object> conf, String participantId) {
Preconditions.checkNotNull(participantId, "participantId can not be null");
Preconditions.checkNotNull(conf, "conf can not be null");
this.conf = conf;
this.serverUrl = participantId;
this.leaderLatchListener = createLeaderLatchListener();
LOG.info("Received configuration : [{}]", conf);
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder();
String url = (String) conf.get(CONNECT_URL);
String rootPrefix = (String) conf.get("root");
builder.connectString(url);
builder.connectionTimeoutMs((Integer) conf.getOrDefault(CONNECTION_TIMEOUT_MS, DEFAULT_CONN_TIMOUT));
builder.sessionTimeoutMs((Integer) conf.getOrDefault(SESSION_TIMEOUT_MS, DEFAULT_SESSION_TIMEOUT));
builder.retryPolicy(
new BoundedExponentialBackoffRetry(
(Integer) conf.getOrDefault(RETRY_BASE_SLEEP_TIME_MS, DEFAULT_BASE_SLEEP_TIME),
(Integer) conf.getOrDefault(RETRY_MAX_SLEEP_TIME_MS, DEFAULT_MAX_SLEEP_TIME),
(Integer) conf.getOrDefault(RETRY_LIMIT, DEFAULT_RETRY_LIMIT)
));
curatorFramework = builder.build();
leaderLatchPath = rootPrefix + LEADER_LOCK_NODE_PATH;
leaderLatchRef = new AtomicReference<>(createLeaderLatch());
curatorFramework.start();
}
private LeaderLatchListener createLeaderLatchListener() {
return new LeaderLatchListener() {
@Override
public void isLeader() {
LOG.info("This instance with id [{}] acquired leadership", serverUrl);
}
@Override
public void notLeader() {
LOG.info("This instance with id [{}] lost leadership", serverUrl);
}
};
}
private LeaderLatch createLeaderLatch() {
return new LeaderLatch(curatorFramework, leaderLatchPath, serverUrl);
}
public boolean checkLeaderLatchPathExists() {
try {
curatorFramework.getChildren().forPath(leaderLatchPath);
return true;
} catch (Exception e) {
return false;
}
}
/**
* Participates for leader lock with the given configuration.
*
* @throws Exception if any errors encountered.
*/
@Override
public void participateForLeadership() throws Exception {
// if the existing leader latch is closed, recreate and connect again
if (LeaderLatch.State.CLOSED.equals(leaderLatchRef.get().getState())) {
// remove listener from earlier closed leader latch
leaderLatchRef.get().removeListener(leaderLatchListener);
leaderLatchRef.set(createLeaderLatch());
leaderLatchRef.get().addListener(leaderLatchListener);
LOG.info("Existing leader latch is in CLOSED state, it is recreated.");
}
// if the existing leader latch is not yet started, start now!!
if (LeaderLatch.State.LATENT.equals(leaderLatchRef.get().getState())) {
leaderLatchRef.get().start();
LOG.info("Existing leader latch is in LATENT state, it is started. leader latch: [{}]", leaderLatchRef.get());
}
}
/**
* Returns the current leader's participant id.
*
* @throws Exception if any error occurs.
*/
@Override
public String getCurrentLeader() throws Exception {
return leaderLatchRef.get().getLeader().getId();
}
/**
* Exits the current leader latch by closing it. This may throw an Exception if the current latch is not yet started
* or it has already been closed.
*
* @throws IOException if any IO related errors occur.
*/
@Override
public void exitFromLeaderParticipation() throws IOException {
// close the current leader latch for removing from leader participation.
leaderLatchRef.get().close();
}
/**
* Returns true if the current participant is a leader
*/
@Override
public boolean isLeader() {
return leaderLatchRef.get().hasLeadership();
}
/**
* Closes the underlying ZK client resources.
*
* @throws IOException if any io errors occurred during this operation.
*/
@Override
public void close() throws IOException {
leaderLatchRef.get().close();
curatorFramework.close();
}
@Override
public String toString() {
return "ZKLeadershipParticipant{" +
"conf=" + conf +
", serverUrl='" + serverUrl + '\'' +
", leaderLatchPath='" + leaderLatchPath + '\'' +
'}'+super.toString();
}
}