package org.apache.solr.common.cloud;
/**
* 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.
*/
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.concurrent.TimeoutException;
import org.apache.commons.io.FileUtils;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ZkClientConnectionStrategy.ZkUpdate;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* All Solr ZooKeeper interactions should go through this class rather than
* ZooKeeper. This class handles synchronous connects and reconnections.
*
*/
public class SolrZkClient {
static final String NEWL = System.getProperty("line.separator");
static final int DEFAULT_CLIENT_CONNECT_TIMEOUT = 30000;
private static final Logger log = LoggerFactory
.getLogger(SolrZkClient.class);
private ConnectionManager connManager;
private volatile SolrZooKeeper keeper;
/**
* @param zkServerAddress
* @param zkClientTimeout
* @throws InterruptedException
* @throws TimeoutException
* @throws IOException
*/
public SolrZkClient(String zkServerAddress, int zkClientTimeout) throws InterruptedException, TimeoutException, IOException {
this(zkServerAddress, zkClientTimeout, new DefaultConnectionStrategy(), null);
}
public SolrZkClient(String zkServerAddress, int zkClientTimeout, int zkClientConnectTimeout, OnReconnect onReonnect) throws InterruptedException, TimeoutException, IOException {
this(zkServerAddress, zkClientTimeout, new DefaultConnectionStrategy(), onReonnect, zkClientConnectTimeout);
}
/**
* @param zkServerAddress
* @param zkClientTimeout
* @param strat
* @param onReconnect
* @throws InterruptedException
* @throws TimeoutException
* @throws IOException
*/
public SolrZkClient(String zkServerAddress, int zkClientTimeout,
ZkClientConnectionStrategy strat, final OnReconnect onReconnect) throws InterruptedException,
TimeoutException, IOException {
this(zkServerAddress, zkClientTimeout, strat, onReconnect, DEFAULT_CLIENT_CONNECT_TIMEOUT);
}
/**
* @param zkServerAddress
* @param zkClientTimeout
* @param strat
* @param onReconnect
* @param clientConnectTimeout
* @throws InterruptedException
* @throws TimeoutException
* @throws IOException
*/
public SolrZkClient(String zkServerAddress, int zkClientTimeout,
ZkClientConnectionStrategy strat, final OnReconnect onReconnect, int clientConnectTimeout) throws InterruptedException,
TimeoutException, IOException {
connManager = new ConnectionManager("ZooKeeperConnection Watcher:"
+ zkServerAddress, this, zkServerAddress, zkClientTimeout, strat, onReconnect);
strat.connect(zkServerAddress, zkClientTimeout, connManager,
new ZkUpdate() {
@Override
public void update(SolrZooKeeper zooKeeper) {
SolrZooKeeper oldKeeper = keeper;
keeper = zooKeeper;
if (oldKeeper != null) {
try {
oldKeeper.close();
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
log.error("", e);
throw new ZooKeeperException(SolrException.ErrorCode.SERVER_ERROR,
"", e);
}
}
}
});
connManager.waitForConnected(clientConnectTimeout);
}
/**
* @return true if client is connected
*/
public boolean isConnected() {
return keeper != null && keeper.getState() == ZooKeeper.States.CONNECTED;
}
/**
* @param path
* @param version
* @throws InterruptedException
* @throws KeeperException
*/
public void delete(final String path, int version)
throws InterruptedException, KeeperException {
keeper.delete(path, version);
}
/**
* Return the stat of the node of the given path. Return null if no such a
* node exists.
* <p>
* If the watch is non-null and the call is successful (no exception is thrown),
* a watch will be left on the node with the given path. The watch will be
* triggered by a successful operation that creates/delete the node or sets
* the data on the node.
*
* @param path the node path
* @param watcher explicit watcher
* @return the stat of the node of the given path; return null if no such a
* node exists.
* @throws KeeperException If the server signals an error
* @throws InterruptedException If the server transaction is interrupted.
* @throws IllegalArgumentException if an invalid path is specified
*/
public Stat exists(final String path, Watcher watcher)
throws KeeperException, InterruptedException {
return keeper.exists(path, watcher);
}
/**
* @param path
* @return true if path exists
* @throws KeeperException
* @throws InterruptedException
*/
public boolean exists(final String path)
throws KeeperException, InterruptedException {
return keeper.exists(path, null) != null;
}
/**
* @param path
* @param data
* @param acl
* @param createMode
* @return path of created node
* @throws KeeperException
* @throws InterruptedException
*/
public String create(final String path, byte data[], List<ACL> acl,
CreateMode createMode) throws KeeperException, InterruptedException {
return keeper.create(path, data, acl, createMode);
}
/**
* @param path
* @param watcher
* @return children of the node at the path
* @throws KeeperException
* @throws InterruptedException
*/
public List<String> getChildren(final String path, Watcher watcher)
throws KeeperException, InterruptedException {
return keeper.getChildren(path, watcher);
}
/**
* @param path
* @param watcher
* @param stat
* @return node's data
* @throws KeeperException
* @throws InterruptedException
*/
public byte[] getData(final String path, Watcher watcher, Stat stat)
throws KeeperException, InterruptedException {
return keeper.getData(path, watcher, stat);
}
/**
* @param path
* @param data
* @param version
* @return node's state
* @throws KeeperException
* @throws InterruptedException
*/
public Stat setData(final String path, byte data[], int version)
throws KeeperException, InterruptedException {
return keeper.setData(path, data, version);
}
/**
*
* @param path
* @param data
* @param createMode
* @return path of created node
* @throws KeeperException
* @throws InterruptedException
*/
public String create(String path, byte[] data, CreateMode createMode) throws KeeperException, InterruptedException {
String zkPath = keeper.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, createMode);
return zkPath;
}
/**
* Creates the path in ZooKeeper, creating each node as necessary.
*
* e.g. If <code>path=/solr/group/node</code> and none of the nodes, solr,
* group, node exist, each will be created.
*
* @param path
* @throws KeeperException
* @throws InterruptedException
*/
public void makePath(String path) throws KeeperException,
InterruptedException {
makePath(path, null, CreateMode.PERSISTENT);
}
public void makePath(String path, CreateMode createMode) throws KeeperException,
InterruptedException {
makePath(path, null, createMode);
}
/**
* Creates the path in ZooKeeper, creating each node as necessary.
*
* @param path
* @param data to set on the last zkNode
* @throws KeeperException
* @throws InterruptedException
*/
public void makePath(String path, byte[] data) throws KeeperException,
InterruptedException {
makePath(path, data, CreateMode.PERSISTENT);
}
/**
* Creates the path in ZooKeeper, creating each node as necessary.
*
* e.g. If <code>path=/solr/group/node</code> and none of the nodes, solr,
* group, node exist, each will be created.
*
* @param path
* @param data to set on the last zkNode
* @param createMode
* @throws KeeperException
* @throws InterruptedException
*/
public void makePath(String path, byte[] data, CreateMode createMode)
throws KeeperException, InterruptedException {
makePath(path, data, createMode, null);
}
/**
* Creates the path in ZooKeeper, creating each node as necessary.
*
* e.g. If <code>path=/solr/group/node</code> and none of the nodes, solr,
* group, node exist, each will be created.
*
* @param path
* @param data to set on the last zkNode
* @param createMode
* @param watcher
* @throws KeeperException
* @throws InterruptedException
*/
public void makePath(String path, byte[] data, CreateMode createMode,
Watcher watcher) throws KeeperException, InterruptedException {
makePath(path, data, createMode, watcher, false);
}
/**
* Creates the path in ZooKeeper, creating each node as necessary.
*
* e.g. If <code>path=/solr/group/node</code> and none of the nodes, solr,
* group, node exist, each will be created.
*
* @param path
* @param data to set on the last zkNode
* @param createMode
* @param watcher
* @throws KeeperException
* @throws InterruptedException
*/
public void makePath(String path, byte[] data, CreateMode createMode,
Watcher watcher, boolean failOnExists) throws KeeperException, InterruptedException {
if (log.isInfoEnabled()) {
log.info("makePath: " + path);
}
if (path.startsWith("/")) {
path = path.substring(1, path.length());
}
String[] paths = path.split("/");
StringBuilder sbPath = new StringBuilder();
for (int i = 0; i < paths.length; i++) {
byte[] bytes = null;
String pathPiece = paths[i];
sbPath.append("/" + pathPiece);
String currentPath = sbPath.toString();
Object exists = exists(currentPath, watcher);
if (exists == null || ((i == paths.length -1) && failOnExists)) {
CreateMode mode = CreateMode.PERSISTENT;
if (i == paths.length - 1) {
mode = createMode;
bytes = data;
}
keeper.create(currentPath, bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, mode);
if(i == paths.length -1) {
// set new watch
exists(currentPath, watcher);
}
} else if (i == paths.length - 1) {
// TODO: version ? for now, don't worry about race
setData(currentPath, data, -1);
// set new watch
exists(currentPath, watcher);
}
}
}
/**
* @param zkPath
* @param createMode
* @param watcher
* @throws KeeperException
* @throws InterruptedException
*/
public void makePath(String zkPath, CreateMode createMode, Watcher watcher)
throws KeeperException, InterruptedException {
makePath(zkPath, null, createMode, watcher);
}
/**
* Write data to ZooKeeper.
*
* @param path
* @param data
* @throws KeeperException
* @throws InterruptedException
*/
public void setData(String path, byte[] data) throws KeeperException,
InterruptedException {
makePath(path);
Object exists = exists(path, null);
if (exists != null) {
setData(path, data, -1);
} else {
create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
/**
* Write file to ZooKeeper - default system encoding used.
*
* @param path path to upload file to e.g. /solr/conf/solrconfig.xml
* @param file path to file to be uploaded
* @throws IOException
* @throws KeeperException
* @throws InterruptedException
*/
public void setData(String path, File file) throws IOException,
KeeperException, InterruptedException {
if (log.isInfoEnabled()) {
log.info("Write to ZooKeepeer " + file.getAbsolutePath() + " to " + path);
}
String data = FileUtils.readFileToString(file);
setData(path, data.getBytes("UTF-8"));
}
/**
* Fills string with printout of current ZooKeeper layout.
*
* @param path
* @param indent
* @throws KeeperException
* @throws InterruptedException
*/
public void printLayout(String path, int indent, StringBuilder string)
throws KeeperException, InterruptedException {
byte[] data = getData(path, null, null);
List<String> children = getChildren(path, null);
StringBuilder dent = new StringBuilder();
for (int i = 0; i < indent; i++) {
dent.append(" ");
}
string.append(dent + path + " (" + children.size() + ")" + NEWL);
if (data != null) {
try {
String dataString = new String(data, "UTF-8");
if (!path.endsWith(".txt") && !path.endsWith(".xml")) {
string.append(dent + "DATA:\n" + dent + " "
+ dataString.replaceAll("\n", "\n" + dent + " ") + NEWL);
} else {
string.append(dent + "DATA: ...supressed..." + NEWL);
}
} catch (UnsupportedEncodingException e) {
// can't happen - UTF-8
throw new RuntimeException(e);
}
}
for (String child : children) {
if (!child.equals("quota")) {
printLayout(path + (path.equals("/") ? "" : "/") + child, indent + 1,
string);
}
}
}
/**
* Prints current ZooKeeper layout to stdout.
*
* @throws KeeperException
* @throws InterruptedException
*/
public void printLayoutToStdOut() throws KeeperException,
InterruptedException {
StringBuilder sb = new StringBuilder();
printLayout("/", 0, sb);
System.out.println(sb.toString());
}
/**
* @throws InterruptedException
*/
public void close() throws InterruptedException {
keeper.close();
}
/**
* Allows package private classes to update volatile ZooKeeper.
*
* @param keeper
* @throws InterruptedException
*/
void updateKeeper(SolrZooKeeper keeper) throws InterruptedException {
SolrZooKeeper oldKeeper = this.keeper;
this.keeper = keeper;
if (oldKeeper != null) {
oldKeeper.close();
}
}
public SolrZooKeeper getSolrZooKeeper() {
return keeper;
}
}