/*
* 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.accumulo.server.zookeeper;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.SecurityPermission;
import java.util.List;
import org.apache.accumulo.core.Constants;
import org.apache.accumulo.core.conf.AccumuloConfiguration;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.util.UtilWaitThread;
import org.apache.accumulo.core.zookeeper.ZooReader;
import org.apache.accumulo.core.zookeeper.ZooUtil;
import org.apache.accumulo.core.zookeeper.ZooUtil.NodeExistsPolicy;
import org.apache.accumulo.core.zookeeper.ZooUtil.NodeMissingPolicy;
import org.apache.accumulo.server.conf.ServerConfiguration;
import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.KeeperException.BadVersionException;
import org.apache.zookeeper.KeeperException.NodeExistsException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
public class ZooReaderWriter extends ZooReader implements IZooReaderWriter {
private static SecurityPermission ZOOWRITER_PERMISSION = new SecurityPermission("zookeeperWriterPermission");
private static ZooReaderWriter instance = null;
private static IZooReaderWriter retryingInstance = null;
private final String auth;
@Override
public ZooKeeper getZooKeeper() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(ZOOWRITER_PERMISSION);
}
return getSession(keepers, timeout, auth);
}
public ZooReaderWriter(String string, int timeInMillis, String auth) {
super(string, timeInMillis);
this.auth = "accumulo:" + auth;
}
@Override
public void recursiveDelete(String zPath, NodeMissingPolicy policy) throws KeeperException, InterruptedException {
ZooUtil.recursiveDelete(getZooKeeper(), zPath, policy);
}
@Override
public void recursiveDelete(String zPath, int version, NodeMissingPolicy policy) throws KeeperException, InterruptedException {
ZooUtil.recursiveDelete(getZooKeeper(), zPath, version, policy);
}
/**
* Create a persistent node with the default ACL
*
* @return true if the node was created or altered; false if it was skipped
*/
@Override
public boolean putPersistentData(String zPath, byte[] data, NodeExistsPolicy policy) throws KeeperException, InterruptedException {
return ZooUtil.putPersistentData(getZooKeeper(), zPath, data, policy);
}
@Override
public boolean putPrivatePersistentData(String zPath, byte[] data, NodeExistsPolicy policy) throws KeeperException, InterruptedException {
return ZooUtil.putPrivatePersistentData(getZooKeeper(), zPath, data, policy);
}
@Override
public void putPersistentData(String zPath, byte[] data, int version, NodeExistsPolicy policy) throws KeeperException, InterruptedException {
ZooUtil.putPersistentData(getZooKeeper(), zPath, data, version, policy);
}
@Override
public String putPersistentSequential(String zPath, byte[] data) throws KeeperException, InterruptedException {
return ZooUtil.putPersistentSequential(getZooKeeper(), zPath, data);
}
@Override
public String putEphemeralData(String zPath, byte[] data) throws KeeperException, InterruptedException {
return ZooUtil.putEphemeralData(getZooKeeper(), zPath, data);
}
@Override
public String putEphemeralSequential(String zPath, byte[] data) throws KeeperException, InterruptedException {
return ZooUtil.putEphemeralSequential(getZooKeeper(), zPath, data);
}
@Override
public void recursiveCopyPersistent(String source, String destination, NodeExistsPolicy policy) throws KeeperException, InterruptedException {
ZooUtil.recursiveCopyPersistent(getZooKeeper(), source, destination, policy);
}
@Override
public void delete(String path, int version) throws InterruptedException, KeeperException {
getZooKeeper().delete(path, version);
}
public interface Mutator {
byte[] mutate(byte[] currentValue) throws Exception;
}
@Override
public byte[] mutate(String zPath, byte[] createValue, List<ACL> acl, Mutator mutator) throws Exception {
if (createValue != null) {
try {
getZooKeeper().create(zPath, createValue, acl, CreateMode.PERSISTENT);
return createValue;
} catch (NodeExistsException ex) {
// expected
}
}
do {
Stat stat = new Stat();
byte[] data = getZooKeeper().getData(zPath, false, stat);
data = mutator.mutate(data);
if (data == null)
return data;
try {
getZooKeeper().setData(zPath, data, stat.getVersion());
return data;
} catch (BadVersionException ex) {
//
}
} while (true);
}
public static synchronized ZooReaderWriter getInstance() {
if (instance == null) {
AccumuloConfiguration conf = ServerConfiguration.getSiteConfiguration();
instance = new ZooReaderWriter(conf.get(Property.INSTANCE_ZK_HOST), (int) conf.getTimeInMillis(Property.INSTANCE_ZK_TIMEOUT),
conf.get(Property.INSTANCE_SECRET));
}
return instance;
}
/**
* get an instance that retries when zookeeper connection errors occur
*
* @return an instance that retries when Zookeeper connection errors occur.
*/
public static synchronized IZooReaderWriter getRetryingInstance() {
if (retryingInstance == null) {
final IZooReaderWriter inst = getInstance();
InvocationHandler ih = new InvocationHandler() {
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
long retryTime = 250;
while (true) {
try {
return method.invoke(inst, args);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof KeeperException.ConnectionLossException) {
Logger.getLogger(ZooReaderWriter.class).warn("Error connecting to zookeeper, will retry in " + retryTime, e.getCause());
UtilWaitThread.sleep(retryTime);
retryTime = Math.min(5000, retryTime + 250);
} else {
throw e.getCause();
}
}
}
}
};
retryingInstance = (IZooReaderWriter) Proxy.newProxyInstance(ZooReaderWriter.class.getClassLoader(), new Class[] {IZooReaderWriter.class}, ih);
}
return retryingInstance;
}
@Override
public boolean isLockHeld(ZooUtil.LockID lockID) throws KeeperException, InterruptedException {
return ZooUtil.isLockHeld(getZooKeeper(), lockID);
}
@Override
public void mkdirs(String path) throws KeeperException, InterruptedException {
if (path.equals(""))
return;
if (!path.startsWith("/"))
throw new IllegalArgumentException(path + "does not start with /");
if (getZooKeeper().exists(path, false) != null)
return;
String parent = path.substring(0, path.lastIndexOf("/"));
mkdirs(parent);
putPersistentData(path, new byte[] {}, NodeExistsPolicy.SKIP);
}
public static void validateSecret() {
// Before we try anything, check to see if we can read users/root out of Zookeeper, as it should be guaranteed to exist and ACLed
// This is to ensure we have the right secret before we can muck around with anything
try {
ZooReaderWriter.getInstance().getStatus(Constants.ZROOT+Constants.ZUSERS+Constants.ZROOT+"/root");
} catch (KeeperException ke) {
switch (ke.code()) {
case NOAUTH:
case AUTHFAILED:
throw new RuntimeException("Could not read ACLed zookeeper data. Please make sure instance secret is correct.", ke);
default:
throw new RuntimeException("Had issues reading data from zookeeper.", ke);
}
} catch (InterruptedException ie) {
throw new RuntimeException("Interrupted from zookeeper, exitting.", ie);
}
}
}