/*
* Copyright (C) 2014 Indeed Inc.
*
* 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.indeed.imhotep.client;
import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.indeed.util.core.DataLoadingRunnable;
import com.indeed.util.zookeeper.ZooKeeperConnection;
import org.apache.log4j.Logger;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author jsgroth
*/
public class ZkHostsReloader extends DataLoadingRunnable implements HostsReloader {
private static final Logger log = Logger.getLogger(ZkHostsReloader.class);
private static final int TIMEOUT = 60000;
private final ZooKeeperConnection zkConnection;
private final String zkPath;
private volatile boolean closed;
private volatile List<Host> hosts;
public ZkHostsReloader(final String zkNodes, final boolean readHostsBeforeReturning) {
this(zkNodes, "/imhotep/daemons", readHostsBeforeReturning);
}
public ZkHostsReloader(final String zkNodes, final String zkPath, final boolean readHostsBeforeReturning) {
super("ZkHostsReloader");
zkConnection = new ZooKeeperConnection(zkNodes, TIMEOUT);
try {
zkConnection.connect();
} catch (Exception e) {
throw Throwables.propagate(e);
}
this.zkPath = trimEndingSlash(zkPath);
closed = false;
hosts = new ArrayList<Host>();
if (readHostsBeforeReturning) {
int retries = 0;
while (true) {
updateLastLoadCheck();
if (load()) {
loadComplete();
break;
}
try {
Thread.sleep(TIMEOUT);
} catch (InterruptedException e) {
log.error("interrupted", e);
}
if (++retries == 5) {
throw new RuntimeException("unable to connect to zookeeper");
}
}
}
}
private static String trimEndingSlash(final String s) {
if (s.endsWith("/")) {
return s.substring(0, s.length() - 1);
}
return s;
}
@Override
public boolean load() {
if (!closed) {
try {
try {
try {
final List<Host> newHosts = readHostsFromZK();
if (!newHosts.equals(hosts)) {
hosts = newHosts;
}
return true;
} catch (KeeperException e) {
log.error("zookeeper exception", e);
loadFailed();
zkConnection.close();
zkConnection.connect();
}
} catch (IOException e) {
loadFailed();
log.error("io exception", e);
}
} catch (InterruptedException e) {
log.error("interrupted", e);
loadFailed();
}
}
return false;
}
private List<Host> readHostsFromZK() throws KeeperException, InterruptedException {
final List<String> childNodes = zkConnection.getChildren(zkPath, false);
final List<Host> hosts = new ArrayList<Host>(childNodes.size());
for (final String childNode : childNodes) {
final byte[] data = zkConnection.getData(zkPath + "/" + childNode, false, new Stat());
final String hostString = new String(data, Charsets.UTF_8);
final String[] splitHostString = hostString.split(":");
hosts.add(new Host(splitHostString[0], Integer.parseInt(splitHostString[1])));
}
Collections.sort(hosts);
return hosts;
}
public List<Host> getHosts() {
return hosts;
}
@Override
public void shutdown() {
closed = true;
try {
zkConnection.close();
} catch (InterruptedException e) {
log.error(e);
}
}
}