/**
* 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.zookeeper_voltpatches.server;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Semaphore;
import javax.management.InstanceAlreadyExistsException;
import org.apache.jute_voltpatches.BinaryInputArchive;
import org.apache.jute_voltpatches.Record;
import org.apache.zookeeper_voltpatches.CreateMode;
import org.apache.zookeeper_voltpatches.KeeperException;
import org.apache.zookeeper_voltpatches.KeeperException.Code;
import org.apache.zookeeper_voltpatches.KeeperException.SessionMovedException;
import org.apache.zookeeper_voltpatches.ZooDefs;
import org.apache.zookeeper_voltpatches.ZooDefs.OpCode;
import org.apache.zookeeper_voltpatches.common.PathUtils;
import org.apache.zookeeper_voltpatches.data.ACL;
import org.apache.zookeeper_voltpatches.data.Id;
import org.apache.zookeeper_voltpatches.data.Stat;
import org.apache.zookeeper_voltpatches.data.StatPersisted;
import org.apache.zookeeper_voltpatches.jmx.MBeanRegistry;
import org.apache.zookeeper_voltpatches.proto.CreateRequest;
import org.apache.zookeeper_voltpatches.proto.CreateResponse;
import org.apache.zookeeper_voltpatches.proto.DeleteRequest;
import org.apache.zookeeper_voltpatches.proto.ExistsRequest;
import org.apache.zookeeper_voltpatches.proto.ExistsResponse;
import org.apache.zookeeper_voltpatches.proto.GetACLRequest;
import org.apache.zookeeper_voltpatches.proto.GetACLResponse;
import org.apache.zookeeper_voltpatches.proto.GetChildren2Request;
import org.apache.zookeeper_voltpatches.proto.GetChildren2Response;
import org.apache.zookeeper_voltpatches.proto.GetChildrenRequest;
import org.apache.zookeeper_voltpatches.proto.GetChildrenResponse;
import org.apache.zookeeper_voltpatches.proto.GetDataRequest;
import org.apache.zookeeper_voltpatches.proto.GetDataResponse;
import org.apache.zookeeper_voltpatches.proto.ReplyHeader;
import org.apache.zookeeper_voltpatches.proto.RequestHeader;
import org.apache.zookeeper_voltpatches.proto.SetACLRequest;
import org.apache.zookeeper_voltpatches.proto.SetACLResponse;
import org.apache.zookeeper_voltpatches.proto.SetDataRequest;
import org.apache.zookeeper_voltpatches.proto.SetDataResponse;
import org.apache.zookeeper_voltpatches.proto.SetWatches;
import org.apache.zookeeper_voltpatches.proto.SyncRequest;
import org.apache.zookeeper_voltpatches.proto.SyncResponse;
import org.apache.zookeeper_voltpatches.server.DataTree.ProcessTxnResult;
import org.apache.zookeeper_voltpatches.server.NIOServerCnxn.CnxnStats;
import org.apache.zookeeper_voltpatches.server.NIOServerCnxn.Factory;
import org.apache.zookeeper_voltpatches.server.SessionTracker.SessionExpirer;
import org.apache.zookeeper_voltpatches.server.auth.AuthenticationProvider;
import org.apache.zookeeper_voltpatches.server.auth.ProviderRegistry;
import org.apache.zookeeper_voltpatches.txn.CreateSessionTxn;
import org.apache.zookeeper_voltpatches.txn.CreateTxn;
import org.apache.zookeeper_voltpatches.txn.DeleteTxn;
import org.apache.zookeeper_voltpatches.txn.ErrorTxn;
import org.apache.zookeeper_voltpatches.txn.SetACLTxn;
import org.apache.zookeeper_voltpatches.txn.SetDataTxn;
import org.apache.zookeeper_voltpatches.txn.TxnHeader;
import org.voltcore.logging.VoltLogger;
/**
* This class implements a simple standalone ZooKeeperServer. It sets up the
* following chain of RequestProcessors to process requests:
* PrepRequestProcessor -> SyncRequestProcessor -> FinalRequestProcessor
*/
public class ZooKeeperServer implements SessionExpirer, ServerStats.Provider,
Runnable {
protected static final VoltLogger LOG;
static boolean skipACL;
static {
LOG = new VoltLogger("ZK-SERVER");
//Environment.logEnv("Server environment:", LOG);
skipACL = System.getProperty("zookeeper.skipACL", "no").equals("yes");
if (skipACL) {
LOG.info("zookeeper.skipACL==\"yes\", ACL checks will be skipped");
}
}
protected ZooKeeperServerBean jmxServerBean;
protected DataTreeBean jmxDataTreeBean;
/**
* The server delegates loading of the tree to an instance of the interface
*/
public interface DataTreeBuilder {
public DataTree build();
}
static public class BasicDataTreeBuilder implements DataTreeBuilder {
@Override
public DataTree build() {
return new DataTree();
}
}
public interface Callout {
public void run();
public void request(Request r);
public Semaphore createSession(final ServerCnxn cnxn,
final byte[] passwd, final int timeout);
}
public static final int DEFAULT_TICK_TIME = 3000;
protected int tickTime = DEFAULT_TICK_TIME;
/** value of -1 indicates unset, use default */
protected int minSessionTimeout = -1;
/** value of -1 indicates unset, use default */
protected int maxSessionTimeout = -1;
protected SessionTracker sessionTracker;
private ZKDatabase zkDb;
public final static Exception ok = new Exception("No prob");
protected volatile boolean running;
private final Callout m_callout;
/**
* This is the secret that we use to generate passwords, for the moment it
* is more of a sanity check.
*/
public static final long superSecret = 0XB3415C00L;
int requestsInProcess;
final List<ChangeRecord> outstandingChanges = new ArrayList<ChangeRecord>();
// this data structure must be accessed under the outstandingChanges lock
final HashMap<String, ChangeRecord> outstandingChangesForPath = new HashMap<String, ChangeRecord>();
private NIOServerCnxn.Factory serverCnxnFactory;
private final ServerStats serverStats;
void removeCnxn(ServerCnxn cnxn) {
this.closeSession(cnxn.getSessionId());
}
/**
* Creates a ZooKeeperServer instance. It sets everything up, but doesn't
* actually start listening for clients until run() is invoked.
*
* @param dataDir
* the directory to put the data
* @throws IOException
*/
public ZooKeeperServer(Callout callout, int tickTime,
int minSessionTimeout, int maxSessionTimeout) throws IOException {
serverStats = new ServerStats(this);
this.zkDb = new ZKDatabase();
this.tickTime = tickTime;
this.minSessionTimeout = minSessionTimeout;
this.maxSessionTimeout = maxSessionTimeout;
this.m_callout = callout;
LOG.info("Created server with tickTime " + tickTime
+ " minSessionTimeout " + getMinSessionTimeout()
+ " maxSessionTimeout " + getMaxSessionTimeout());
}
public ServerStats serverStats() {
return serverStats;
}
public void dumpConf(PrintWriter pwriter) {
pwriter.print("clientPort=");
pwriter.println(getClientPort());
pwriter.print("tickTime=");
pwriter.println(getTickTime());
pwriter.print("maxClientCnxns=");
pwriter.println(serverCnxnFactory.getMaxClientCnxns());
pwriter.print("minSessionTimeout=");
pwriter.println(getMinSessionTimeout());
pwriter.print("maxSessionTimeout=");
pwriter.println(getMaxSessionTimeout());
}
/**
* Default constructor, relies on the config for its agrument values
*
* @throws IOException
*/
public ZooKeeperServer(Callout callout) throws IOException {
this(callout, DEFAULT_TICK_TIME, -1, -1);
// this(callout, 999999, 999999, 999999);
}
/**
* get the zookeeper database for this server
*
* @return the zookeeper database for this server
*/
public ZKDatabase getZKDatabase() {
return this.zkDb;
}
/**
* set the zkdatabase for this zookeeper server
*
* @param zkDb
*/
public void setZKDatabase(ZKDatabase zkDb) {
this.zkDb = zkDb;
}
long getTime() {
return System.currentTimeMillis();
}
private void close(long sessionId) {
submitRequest(null, sessionId, OpCode.closeSession, 0, null, null);
}
public void closeSession(long sessionId) {
LOG.debug("Closing session 0x" + Long.toHexString(sessionId));
// we do not want to wait for a session close. send it as soon as we
// detect it!
close(sessionId);
}
protected void killSession(long sessionId, long zxid) {
zkDb.killSession(sessionId, zxid);
if (LOG.isTraceEnabled()) {
ZooTrace.logTraceMessage(
LOG,
ZooTrace.SESSION_TRACE_MASK,
"ZooKeeperServer --- killSession: 0x"
+ Long.toHexString(sessionId));
}
if (sessionTracker != null) {
sessionTracker.removeSession(sessionId);
}
}
@Override
public void expire(long sessionId) {
LOG.info("Initiating close of session 0x" + Long.toHexString(sessionId));
close(sessionId);
}
public static class MissingSessionException extends IOException {
private static final long serialVersionUID = 7467414635467261007L;
public MissingSessionException(String msg) {
super(msg);
}
}
protected void registerJMX() {
// register with JMX
try {
jmxServerBean = new ZooKeeperServerBean(this);
MBeanRegistry.getInstance().register(jmxServerBean, null);
try {
jmxDataTreeBean = new DataTreeBean(zkDb.getDataTree());
MBeanRegistry.getInstance().register(jmxDataTreeBean, jmxServerBean);
}
catch (Exception e) {
LOG.error("Failed to register with JMX.", e);
jmxDataTreeBean = null;
}
}
catch (InstanceAlreadyExistsException e) {
LOG.error("Failed to register ZooKeeper with JMX due to a conflict. " +
"You are likely running two VoltDB instances on one system. " +
"This will only affect JMX management.");
jmxServerBean = null;
}
catch (Exception e) {
LOG.error("Failed to register with JMX.", e);
jmxServerBean = null;
}
}
public Thread startup() {
createSessionTracker();
Thread thisThread = new Thread(this, "ZooKeeperServer");
thisThread.start();
registerJMX();
synchronized (this) {
running = true;
notifyAll();
}
return thisThread;
}
public void createSessionTracker() {
sessionTracker = new SessionTrackerImpl(this,
zkDb.getSessionWithTimeOuts());
}
public void closeSessions(long owner) {
sessionTracker.expireSessionsWithOwner(owner);
}
public boolean isRunning() {
return running;
}
public void shutdown() {
unregisterJMX();
}
protected void unregisterJMX() {
// unregister from JMX
try {
if (jmxDataTreeBean != null) {
MBeanRegistry.getInstance().unregister(jmxDataTreeBean);
}
} catch (Exception e) {
LOG.warn("Failed to unregister with JMX", e);
}
try {
if (jmxServerBean != null) {
MBeanRegistry.getInstance().unregister(jmxServerBean);
}
} catch (Exception e) {
LOG.warn("Failed to unregister with JMX", e);
}
jmxServerBean = null;
jmxDataTreeBean = null;
}
synchronized public void incInProcess() {
requestsInProcess++;
}
synchronized public void decInProcess() {
requestsInProcess--;
}
public int getInProcess() {
return requestsInProcess;
}
/**
* This structure is used to facilitate information sharing between PrepRP
* and FinalRP.
*/
static class ChangeRecord {
ChangeRecord(long zxid, String path, StatPersisted stat,
int childCount, List<ACL> acl) {
this.zxid = zxid;
this.path = path;
this.stat = stat;
this.childCount = childCount;
this.acl = acl;
}
long zxid;
String path;
StatPersisted stat; /* Make sure to create a new object when changing */
int childCount;
List<ACL> acl; /* Make sure to create a new object when changing */
@SuppressWarnings("unchecked")
ChangeRecord duplicate(long zxid) {
StatPersisted stat = new StatPersisted();
if (this.stat != null) {
DataTree.copyStatPersisted(this.stat, stat);
}
return new ChangeRecord(zxid, path, stat, childCount,
acl == null ? new ArrayList<ACL>() : new ArrayList(acl));
}
}
byte[] generatePasswd(long id) {
Random r = new Random(id ^ superSecret);
byte p[] = new byte[16];
r.nextBytes(p);
return p;
}
protected boolean checkPasswd(long sessionId, byte[] passwd) {
return sessionId != 0
&& Arrays.equals(passwd, generatePasswd(sessionId));
}
void createSession(ServerCnxn cnxn, byte passwd[], int timeout)
throws InterruptedException {
Semaphore sessionIdCreated = m_callout.createSession(cnxn, passwd,
timeout);
sessionIdCreated.acquire();
}
public void closeSession(ServerCnxn cnxn, RequestHeader requestHeader) {
closeSession(cnxn.getSessionId());
}
/**
* @param cnxn
* @param sessionId
* @param xid
* @param bb
*/
public void submitRequest(ServerCnxn cnxn, long sessionId, int type,
int xid, ByteBuffer bb, List<Id> authInfo) {
Request si = new Request(cnxn, sessionId, xid, type, bb, authInfo);
submitRequest(si);
}
public void submitRequest(Request si) {
boolean validpacket = Request.isValid(si.type);
if (validpacket) {
m_callout.request(si);
if (si.cnxn != null) {
incInProcess();
}
} else {
LOG.warn("Dropping packet at server of type " + si.type);
// if invalid packet drop the packet.
}
}
static public void byteBuffer2Record(ByteBuffer bb, Record record)
throws IOException {
BinaryInputArchive ia;
ia = BinaryInputArchive.getArchive(new ByteBufferInputStream(bb));
record.deserialize(ia, "request");
}
public static int getSnapCount() {
String sc = System.getProperty("zookeeper.snapCount");
try {
return Integer.parseInt(sc);
} catch (Exception e) {
return 100000;
}
}
public int getGlobalOutstandingLimit() {
String sc = System.getProperty("zookeeper.globalOutstandingLimit");
int limit;
try {
limit = Integer.parseInt(sc);
} catch (Exception e) {
limit = 1000;
}
return limit;
}
public void setServerCnxnFactory(NIOServerCnxn.Factory factory) {
serverCnxnFactory = factory;
}
public NIOServerCnxn.Factory getServerCnxnFactory() {
return serverCnxnFactory;
}
/**
* return the last proceesed id from the datatree
*/
@Override
public long getLastProcessedZxid() {
return zkDb.getDataTreeLastProcessedZxid();
}
/**
* return the outstanding requests in the queue, which havent been processed
* yet
*/
@Override
public long getOutstandingRequests() {
return getInProcess();
}
public int getTickTime() {
return tickTime;
}
public void setTickTime(int tickTime) {
LOG.info("tickTime set to " + tickTime);
this.tickTime = tickTime;
}
public int getMinSessionTimeout() {
return minSessionTimeout == -1 ? tickTime * 2 : minSessionTimeout;
}
public void setMinSessionTimeout(int min) {
LOG.info("minSessionTimeout set to " + min);
this.minSessionTimeout = min;
}
public int getMaxSessionTimeout() {
return maxSessionTimeout == -1 ? tickTime * 20 : maxSessionTimeout;
}
public void setMaxSessionTimeout(int max) {
LOG.info("maxSessionTimeout set to " + max);
this.maxSessionTimeout = max;
}
public int getClientPort() {
return serverCnxnFactory != null ? serverCnxnFactory.ss.socket()
.getLocalPort() : -1;
}
@Override
public String getState() {
return "standalone";
}
public void dumpEphemerals(PrintWriter pwriter) {
zkDb.dumpEphemerals(pwriter);
}
void addChangeRecord(ChangeRecord c) {
synchronized (outstandingChanges) {
outstandingChanges.add(c);
outstandingChangesForPath.put(c.path, c);
}
}
ChangeRecord getRecordForPath(String path)
throws KeeperException.NoNodeException {
ChangeRecord lastChange = null;
synchronized (outstandingChanges) {
lastChange = outstandingChangesForPath.get(path);
/*
* for (int i = 0; i < zks.outstandingChanges.size(); i++) {
* ChangeRecord c = zks.outstandingChanges.get(i); if
* (c.path.equals(path)) { lastChange = c; } }
*/
if (lastChange == null) {
DataNode n = getZKDatabase().getNode(path);
if (n != null) {
Long acl;
Set<String> children;
synchronized (n) {
acl = n.acl;
children = n.getChildren();
}
lastChange = new ChangeRecord(-1, path, n.stat,
children != null ? children.size() : 0,
getZKDatabase().convertLong(acl));
}
}
}
if (lastChange == null || lastChange.stat == null) {
throw new KeeperException.NoNodeException(path);
}
return lastChange;
}
static void checkACL(List<ACL> acl, int perm, List<Id> ids)
throws KeeperException.NoAuthException {
if (skipACL) {
return;
}
if (acl == null || acl.size() == 0) {
return;
}
for (Id authId : ids) {
if (authId.getScheme().equals("super")) {
return;
}
}
for (ACL a : acl) {
Id id = a.getId();
if ((a.getPerms() & perm) != 0) {
if (id.getScheme().equals("world")
&& id.getId().equals("anyone")) {
return;
}
AuthenticationProvider ap = ProviderRegistry.getProvider(id
.getScheme());
if (ap != null) {
for (Id authId : ids) {
if (authId.getScheme().equals(id.getScheme())
&& ap.matches(authId.getId(), id.getId())) {
return;
}
}
}
}
}
throw new KeeperException.NoAuthException();
}
/**
* This method will be called inside the ProcessRequestThread, which is a
* singleton, so there will be a single thread calling this code.
*
* @param request
*/
public void prepRequest(Request request, long txnId) {
// LOG.info("Prep>>> cxid = " + request.cxid + " type = " +
// request.type + " id = 0x" + Long.toHexString(request.sessionId));
TxnHeader txnHeader = null;
Record txn = null;
try {
switch (request.type) {
case OpCode.create:
txnHeader = new TxnHeader(request.sessionId, request.cxid,
txnId, getTime(), OpCode.create);
CreateRequest createRequest = new CreateRequest();
ZooKeeperServer.byteBuffer2Record(request.request,
createRequest);
String path = createRequest.getPath();
int lastSlash = path.lastIndexOf('/');
if (lastSlash == -1 || path.indexOf('\0') != -1) {
LOG.info("Invalid path " + path + " with session 0x"
+ Long.toHexString(request.sessionId));
throw new KeeperException.BadArgumentsException(path);
}
if (!fixupACL(request.authInfo, createRequest.getAcl())) {
throw new KeeperException.InvalidACLException(path);
}
String parentPath = path.substring(0, lastSlash);
ChangeRecord parentRecord = getRecordForPath(parentPath);
checkACL(parentRecord.acl, ZooDefs.Perms.CREATE,
request.authInfo);
int parentCVersion = parentRecord.stat.getCversion();
CreateMode createMode = CreateMode.fromFlag(createRequest
.getFlags());
if (createMode.isSequential()) {
path = path + String.format("%010d", parentCVersion);
}
try {
PathUtils.validatePath(path);
} catch (IllegalArgumentException ie) {
LOG.info("Invalid path " + path + " with session 0x"
+ Long.toHexString(request.sessionId));
throw new KeeperException.BadArgumentsException(path);
}
try {
if (getRecordForPath(path) != null) {
throw new KeeperException.NodeExistsException(path);
}
} catch (KeeperException.NoNodeException e) {
// ignore this one
}
boolean ephemeralParent = parentRecord.stat.getEphemeralOwner() != 0;
if (ephemeralParent) {
throw new KeeperException.NoChildrenForEphemeralsException(
path);
}
txn = new CreateTxn(path, createRequest.getData(),
createRequest.getAcl(), createMode.isEphemeral());
StatPersisted s = new StatPersisted();
if (createMode.isEphemeral()) {
s.setEphemeralOwner(request.sessionId);
}
parentRecord = parentRecord.duplicate(txnHeader.getZxid());
parentRecord.childCount++;
parentRecord.stat
.setCversion(parentRecord.stat.getCversion() + 1);
addChangeRecord(parentRecord);
addChangeRecord(new ChangeRecord(txnHeader.getZxid(), path, s,
0, createRequest.getAcl()));
break;
case OpCode.delete:
txnHeader = new TxnHeader(request.sessionId, request.cxid,
txnId, getTime(), OpCode.delete);
DeleteRequest deleteRequest = new DeleteRequest();
ZooKeeperServer.byteBuffer2Record(request.request,
deleteRequest);
path = deleteRequest.getPath();
lastSlash = path.lastIndexOf('/');
if (lastSlash == -1 || path.indexOf('\0') != -1
|| getZKDatabase().isSpecialPath(path)) {
throw new KeeperException.BadArgumentsException(path);
}
parentPath = path.substring(0, lastSlash);
parentRecord = getRecordForPath(parentPath);
ChangeRecord nodeRecord = getRecordForPath(path);
checkACL(parentRecord.acl, ZooDefs.Perms.DELETE,
request.authInfo);
int version = deleteRequest.getVersion();
if (version != -1 && nodeRecord.stat.getVersion() != version) {
throw new KeeperException.BadVersionException(path);
}
if (nodeRecord.childCount > 0) {
throw new KeeperException.NotEmptyException(path);
}
txn = new DeleteTxn(path);
parentRecord = parentRecord.duplicate(txnHeader.getZxid());
parentRecord.childCount--;
parentRecord.stat
.setCversion(parentRecord.stat.getCversion() + 1);
addChangeRecord(parentRecord);
addChangeRecord(new ChangeRecord(txnHeader.getZxid(), path,
null, -1, null));
break;
case OpCode.setData:
txnHeader = new TxnHeader(request.sessionId, request.cxid,
txnId, getTime(), OpCode.setData);
SetDataRequest setDataRequest = new SetDataRequest();
ZooKeeperServer.byteBuffer2Record(request.request,
setDataRequest);
path = setDataRequest.getPath();
nodeRecord = getRecordForPath(path);
checkACL(nodeRecord.acl, ZooDefs.Perms.WRITE, request.authInfo);
version = setDataRequest.getVersion();
int currentVersion = nodeRecord.stat.getVersion();
if (version != -1 && version != currentVersion) {
throw new KeeperException.BadVersionException(path);
}
version = currentVersion + 1;
txn = new SetDataTxn(path, setDataRequest.getData(), version);
nodeRecord = nodeRecord.duplicate(txnHeader.getZxid());
nodeRecord.stat.setVersion(version);
addChangeRecord(nodeRecord);
break;
case OpCode.setACL:
txnHeader = new TxnHeader(request.sessionId, request.cxid,
txnId, getTime(), OpCode.setACL);
SetACLRequest setAclRequest = new SetACLRequest();
ZooKeeperServer.byteBuffer2Record(request.request,
setAclRequest);
path = setAclRequest.getPath();
if (!fixupACL(request.authInfo, setAclRequest.getAcl())) {
throw new KeeperException.InvalidACLException(path);
}
nodeRecord = getRecordForPath(path);
checkACL(nodeRecord.acl, ZooDefs.Perms.ADMIN, request.authInfo);
version = setAclRequest.getVersion();
currentVersion = nodeRecord.stat.getAversion();
if (version != -1 && version != currentVersion) {
throw new KeeperException.BadVersionException(path);
}
version = currentVersion + 1;
txn = new SetACLTxn(path, setAclRequest.getAcl(), version);
nodeRecord = nodeRecord.duplicate(txnHeader.getZxid());
nodeRecord.stat.setAversion(version);
addChangeRecord(nodeRecord);
break;
case OpCode.createSession:
txnHeader = new TxnHeader(request.sessionId, request.cxid,
txnId, getTime(), OpCode.createSession);
request.request.rewind();
int to = request.request.getInt();
txn = new CreateSessionTxn((Long)request.getOwner());
request.request.rewind();
sessionTracker.addSession(request.sessionId,
(Long) request.getOwner());
break;
case OpCode.closeSession:
txnHeader = new TxnHeader(request.sessionId, request.cxid,
txnId, getTime(), OpCode.closeSession);
// We don't want to do this check since the session expiration
// thread
// queues up this operation without being the session owner.
// this request is the last of the session so it should be ok
// zks.sessionTracker.checkSession(request.sessionId,
// request.getOwner());
HashSet<String> es = getZKDatabase().getEphemerals(
request.sessionId);
synchronized (outstandingChanges) {
for (ChangeRecord c : outstandingChanges) {
if (c.stat == null) {
// Doing a delete
es.remove(c.path);
} else if (c.stat.getEphemeralOwner() == request.sessionId) {
es.add(c.path);
}
}
for (String path2Delete : es) {
addChangeRecord(new ChangeRecord(txnHeader.getZxid(),
path2Delete, null, 0, null));
}
}
sessionTracker.removeSession(request.sessionId);
LOG.info("Processed session termination for sessionid: 0x"
+ Long.toHexString(request.sessionId));
break;
case OpCode.sync:
case OpCode.exists:
case OpCode.getData:
case OpCode.getACL:
case OpCode.getChildren:
case OpCode.getChildren2:
case OpCode.ping:
case OpCode.setWatches:
default:
}
} catch (KeeperException e) {
if (txnHeader != null) {
txnHeader.setType(OpCode.error);
txn = new ErrorTxn(e.code().intValue());
}
LOG.debug("Got user-level KeeperException when processing "
+ request.toString() + " Error Path:" + e.getPath()
+ " Error:" + e.getMessage());
request.setException(e);
} catch (Exception e) {
// log at error level as we are returning a marshalling
// error to the user
LOG.error("Failed to process " + request, e);
StringBuilder sb = new StringBuilder();
ByteBuffer bb = request.request;
if (bb != null) {
bb.rewind();
while (bb.hasRemaining()) {
sb.append(Integer.toHexString(bb.get() & 0xff));
}
} else {
sb.append("request buffer is null");
}
LOG.error("Dumping request buffer: 0x" + sb.toString());
if (txnHeader != null) {
txnHeader.setType(OpCode.error);
txn = new ErrorTxn(Code.MARSHALLINGERROR.intValue());
}
}
request.hdr = txnHeader;
request.txn = txn;
request.zxid = txnId;
executeRequest(request);
}
private void executeRequest(Request request) {
if (LOG.isDebugEnabled()) {
LOG.debug("Processing request:: " + request);
}
// request.addRQRec(">final");
long traceMask = ZooTrace.CLIENT_REQUEST_TRACE_MASK;
if (request.type == OpCode.ping) {
traceMask = ZooTrace.SERVER_PING_TRACE_MASK;
}
if (LOG.isTraceEnabled()) {
ZooTrace.logRequest(LOG, traceMask, 'E', request, "");
}
ProcessTxnResult rc = null;
synchronized (outstandingChanges) {
while (!outstandingChanges.isEmpty()
&& outstandingChanges.get(0).zxid <= request.zxid) {
ChangeRecord cr = outstandingChanges.remove(0);
if (cr.zxid < request.zxid) {
LOG.warn("Zxid outstanding " + cr.zxid
+ " is less than current " + request.zxid);
}
if (outstandingChangesForPath.get(cr.path) == cr) {
outstandingChangesForPath.remove(cr.path);
}
}
if (request.hdr != null) {
rc = getZKDatabase().processTxn(request.hdr, request.txn);
}
}
if (request.hdr != null && request.hdr.getType() == OpCode.closeSession) {
Factory scxn = getServerCnxnFactory();
// this might be possible since
// we might just be playing diffs from the leader
if (scxn != null && request.cnxn == null) {
// calling this if we have the cnxn results in the client's
// close session response being lost - we've already closed
// the session/socket here before we can send the closeSession
// in the switch block below
scxn.closeSession(request.sessionId);
return;
}
}
if (request.cnxn == null) {
return;
}
ServerCnxn cnxn = request.cnxn;
String lastOp = "NA";
decInProcess();
Code err = Code.OK;
Record rsp = null;
boolean closeSession = false;
try {
if (request.hdr != null && request.hdr.getType() == OpCode.error) {
throw KeeperException.create(KeeperException.Code
.get(((ErrorTxn) request.txn).getErr()));
}
KeeperException ke = request.getException();
if (ke != null) {
throw ke;
}
if (LOG.isDebugEnabled()) {
LOG.debug(request);
}
switch (request.type) {
case OpCode.ping: {
serverStats().updateLatency(request.createTime);
lastOp = "PING";
((CnxnStats) cnxn.getStats()).updateForResponse(request.cxid,
request.zxid, lastOp, request.createTime,
System.currentTimeMillis());
cnxn.sendResponse(new ReplyHeader(-2, getZKDatabase()
.getDataTreeLastProcessedZxid(), 0), null, "response");
return;
}
case OpCode.createSession: {
serverStats().updateLatency(request.createTime);
lastOp = "SESS";
((CnxnStats) cnxn.getStats()).updateForResponse(request.cxid,
request.zxid, lastOp, request.createTime,
System.currentTimeMillis());
cnxn.finishSessionInit(true);
return;
}
case OpCode.create: {
lastOp = "CREA";
rsp = new CreateResponse(rc.path);
err = Code.get(rc.err);
break;
}
case OpCode.delete: {
lastOp = "DELE";
err = Code.get(rc.err);
break;
}
case OpCode.setData: {
lastOp = "SETD";
rsp = new SetDataResponse(rc.stat);
err = Code.get(rc.err);
break;
}
case OpCode.setACL: {
lastOp = "SETA";
rsp = new SetACLResponse(rc.stat);
err = Code.get(rc.err);
break;
}
case OpCode.closeSession: {
lastOp = "CLOS";
closeSession = true;
err = Code.get(rc.err);
break;
}
case OpCode.sync: {
lastOp = "SYNC";
SyncRequest syncRequest = new SyncRequest();
ZooKeeperServer.byteBuffer2Record(request.request, syncRequest);
rsp = new SyncResponse(syncRequest.getPath());
break;
}
case OpCode.exists: {
lastOp = "EXIS";
// TODO we need to figure out the security requirement for this!
ExistsRequest existsRequest = new ExistsRequest();
ZooKeeperServer.byteBuffer2Record(request.request,
existsRequest);
String path = existsRequest.getPath();
if (path.indexOf('\0') != -1) {
throw new KeeperException.BadArgumentsException();
}
Stat stat = getZKDatabase().statNode(path,
existsRequest.getWatch() ? cnxn : null);
rsp = new ExistsResponse(stat);
break;
}
case OpCode.getData: {
lastOp = "GETD";
GetDataRequest getDataRequest = new GetDataRequest();
ZooKeeperServer.byteBuffer2Record(request.request,
getDataRequest);
DataNode n = getZKDatabase().getNode(getDataRequest.getPath());
if (n == null) {
throw new KeeperException.NoNodeException();
}
Long aclL;
synchronized (n) {
aclL = n.acl;
}
checkACL(getZKDatabase().convertLong(aclL), ZooDefs.Perms.READ,
request.authInfo);
Stat stat = new Stat();
byte b[] = getZKDatabase().getData(getDataRequest.getPath(),
stat, getDataRequest.getWatch() ? cnxn : null);
rsp = new GetDataResponse(b, stat);
break;
}
case OpCode.setWatches: {
lastOp = "SETW";
SetWatches setWatches = new SetWatches();
// XXX We really should NOT need this!!!!
request.request.rewind();
ZooKeeperServer.byteBuffer2Record(request.request, setWatches);
long relativeZxid = setWatches.getRelativeZxid();
getZKDatabase().setWatches(relativeZxid,
setWatches.getDataWatches(),
setWatches.getExistWatches(),
setWatches.getChildWatches(), cnxn);
break;
}
case OpCode.getACL: {
lastOp = "GETA";
GetACLRequest getACLRequest = new GetACLRequest();
ZooKeeperServer.byteBuffer2Record(request.request,
getACLRequest);
Stat stat = new Stat();
List<ACL> acl = getZKDatabase().getACL(getACLRequest.getPath(),
stat);
rsp = new GetACLResponse(acl, stat);
break;
}
case OpCode.getChildren: {
lastOp = "GETC";
GetChildrenRequest getChildrenRequest = new GetChildrenRequest();
ZooKeeperServer.byteBuffer2Record(request.request,
getChildrenRequest);
DataNode n = getZKDatabase().getNode(
getChildrenRequest.getPath());
if (n == null) {
throw new KeeperException.NoNodeException();
}
Long aclG;
synchronized (n) {
aclG = n.acl;
}
checkACL(getZKDatabase().convertLong(aclG), ZooDefs.Perms.READ,
request.authInfo);
List<String> children = getZKDatabase().getChildren(
getChildrenRequest.getPath(), null,
getChildrenRequest.getWatch() ? cnxn : null);
rsp = new GetChildrenResponse(children);
break;
}
case OpCode.getChildren2: {
lastOp = "GETC";
GetChildren2Request getChildren2Request = new GetChildren2Request();
ZooKeeperServer.byteBuffer2Record(request.request,
getChildren2Request);
Stat stat = new Stat();
DataNode n = getZKDatabase().getNode(
getChildren2Request.getPath());
if (n == null) {
throw new KeeperException.NoNodeException();
}
Long aclG;
synchronized (n) {
aclG = n.acl;
}
checkACL(getZKDatabase().convertLong(aclG), ZooDefs.Perms.READ,
request.authInfo);
List<String> children = getZKDatabase().getChildren(
getChildren2Request.getPath(), stat,
getChildren2Request.getWatch() ? cnxn : null);
rsp = new GetChildren2Response(children, stat);
break;
}
}
} catch (SessionMovedException e) {
// session moved is a connection level error, we need to tear
// down the connection otw ZOOKEEPER-710 might happen
// ie client on slow follower starts to renew session, fails
// before this completes, then tries the fast follower (leader)
// and is successful, however the initial renew is then
// successfully fwd/processed by the leader and as a result
// the client and leader disagree on where the client is most
// recently attached (and therefore invalid SESSION MOVED generated)
cnxn.sendCloseSession();
return;
} catch (KeeperException e) {
err = e.code();
} catch (Exception e) {
// log at error level as we are returning a marshalling
// error to the user
LOG.error("Failed to process " + request, e);
StringBuilder sb = new StringBuilder();
ByteBuffer bb = request.request;
bb.rewind();
while (bb.hasRemaining()) {
sb.append(Integer.toHexString(bb.get() & 0xff));
}
LOG.error("Dumping request buffer: 0x" + sb.toString());
err = Code.MARSHALLINGERROR;
}
ReplyHeader hdr = new ReplyHeader(request.cxid, request.zxid,
err.intValue());
serverStats().updateLatency(request.createTime);
((CnxnStats) cnxn.getStats()).updateForResponse(request.cxid,
request.zxid, lastOp, request.createTime,
System.currentTimeMillis());
try {
cnxn.sendResponse(hdr, rsp, "response");
if (closeSession) {
cnxn.sendCloseSession();
}
} catch (IOException e) {
LOG.error("FIXMSG", e);
}
}
/**
* This method checks out the acl making sure it isn't null or empty, it has
* valid schemes and ids, and expanding any relative ids that depend on the
* requestor's authentication information.
*
* @param authInfo
* list of ACL IDs associated with the client connection
* @param acl
* list of ACLs being assigned to the node (create or setACL
* operation)
* @return
*/
private boolean fixupACL(List<Id> authInfo, List<ACL> acl) {
if (skipACL) {
return true;
}
if (acl == null || acl.size() == 0) {
return false;
}
Iterator<ACL> it = acl.iterator();
LinkedList<ACL> toAdd = null;
while (it.hasNext()) {
ACL a = it.next();
Id id = a.getId();
if (id.getScheme().equals("world") && id.getId().equals("anyone")) {
// wide open
} else if (id.getScheme().equals("auth")) {
// This is the "auth" id, so we have to expand it to the
// authenticated ids of the requestor
it.remove();
if (toAdd == null) {
toAdd = new LinkedList<ACL>();
}
boolean authIdValid = false;
for (Id cid : authInfo) {
AuthenticationProvider ap = ProviderRegistry
.getProvider(cid.getScheme());
if (ap == null) {
LOG.error("Missing AuthenticationProvider for "
+ cid.getScheme());
} else if (ap.isAuthenticated()) {
authIdValid = true;
toAdd.add(new ACL(a.getPerms(), cid));
}
}
if (!authIdValid) {
return false;
}
} else {
AuthenticationProvider ap = ProviderRegistry.getProvider(id
.getScheme());
if (ap == null) {
return false;
}
if (!ap.isValid(id.getId())) {
return false;
}
}
}
if (toAdd != null) {
for (ACL a : toAdd) {
acl.add(a);
}
}
return acl.size() > 0;
}
@Override
public void run() {
try {
m_callout.run();
} finally {
running = false;
}
}
}