http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java b/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java new file mode 100644 index 0000000..d97d6c1 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java @@ -0,0 +1,422 @@ +/** + * 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; + +/** + * This class is responsible for refreshing Kerberos credentials for + * logins for both Zookeeper client and server. + * See ZooKeeperSaslServer for server-side usage. + * See ZooKeeperSaslClient for client-side usage. + */ + +import javax.security.auth.kerberos.KerberosPrincipal; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.security.auth.callback.CallbackHandler; + +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.server.ZooKeeperSaslServer; +import org.apache.zookeeper.common.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.kerberos.KerberosTicket; +import javax.security.auth.Subject; + +import java.util.Date; +import java.util.Random; +import java.util.Set; + +public class Login { + private static final String KINIT_COMMAND_DEFAULT = "/usr/bin/kinit"; + private static final Logger LOG = LoggerFactory.getLogger(Login.class); + public CallbackHandler callbackHandler; + + // LoginThread will sleep until 80% of time from last refresh to + // ticket's expiry has been reached, at which time it will wake + // and try to renew the ticket. + private static final float TICKET_RENEW_WINDOW = 0.80f; + + /** + * Percentage of random jitter added to the renewal time + */ + private static final float TICKET_RENEW_JITTER = 0.05f; + + // Regardless of TICKET_RENEW_WINDOW setting above and the ticket expiry time, + // thread will not sleep between refresh attempts any less than 1 minute (60*1000 milliseconds = 1 minute). + // Change the '1' to e.g. 5, to change this to 5 minutes. + private static final long MIN_TIME_BEFORE_RELOGIN = 1 * 60 * 1000L; + + private Subject subject = null; + private Thread t = null; + private boolean isKrbTicket = false; + private boolean isUsingTicketCache = false; + + /** Random number generator */ + private static Random rng = new Random(); + + private LoginContext login = null; + private String loginContextName = null; + private String principal = null; + + // Initialize 'lastLogin' to do a login at first time + private long lastLogin = Time.currentElapsedTime() - MIN_TIME_BEFORE_RELOGIN; + private final ZKConfig zkConfig; + + /** + * LoginThread constructor. The constructor starts the thread used to + * periodically re-login to the Kerberos Ticket Granting Server. + * + * @param loginContextName + * name of section in JAAS file that will be use to login. Passed + * as first param to javax.security.auth.login.LoginContext(). + * + * @param callbackHandler + * Passed as second param to + * javax.security.auth.login.LoginContext(). + * @param zkConfig + * client or server configurations + * @throws javax.security.auth.login.LoginException + * Thrown if authentication fails. + */ + public Login(final String loginContextName, CallbackHandler callbackHandler, final ZKConfig zkConfig) + throws LoginException { + this.zkConfig=zkConfig; + this.callbackHandler = callbackHandler; + login = login(loginContextName); + this.loginContextName = loginContextName; + subject = login.getSubject(); + isKrbTicket = !subject.getPrivateCredentials(KerberosTicket.class).isEmpty(); + AppConfigurationEntry entries[] = Configuration.getConfiguration().getAppConfigurationEntry(loginContextName); + for (AppConfigurationEntry entry: entries) { + // there will only be a single entry, so this for() loop will only be iterated through once. + if (entry.getOptions().get("useTicketCache") != null) { + String val = (String)entry.getOptions().get("useTicketCache"); + if (val.equals("true")) { + isUsingTicketCache = true; + } + } + if (entry.getOptions().get("principal") != null) { + principal = (String)entry.getOptions().get("principal"); + } + break; + } + + if (!isKrbTicket) { + // if no TGT, do not bother with ticket management. + return; + } + + // Refresh the Ticket Granting Ticket (TGT) periodically. How often to refresh is determined by the + // TGT's existing expiry date and the configured MIN_TIME_BEFORE_RELOGIN. For testing and development, + // you can decrease the interval of expiration of tickets (for example, to 3 minutes) by running : + // "modprinc -maxlife 3mins <principal>" in kadmin. + t = new Thread(new Runnable() { + public void run() { + LOG.info("TGT refresh thread started."); + while (true) { // renewal thread's main loop. if it exits from here, thread will exit. + KerberosTicket tgt = getTGT(); + long now = Time.currentWallTime(); + long nextRefresh; + Date nextRefreshDate; + if (tgt == null) { + nextRefresh = now + MIN_TIME_BEFORE_RELOGIN; + nextRefreshDate = new Date(nextRefresh); + LOG.warn("No TGT found: will try again at {}", nextRefreshDate); + } else { + nextRefresh = getRefreshTime(tgt); + long expiry = tgt.getEndTime().getTime(); + Date expiryDate = new Date(expiry); + if ((isUsingTicketCache) && (tgt.getEndTime().equals(tgt.getRenewTill()))) { + Object[] logPayload = {expiryDate, principal, principal}; + LOG.error("The TGT cannot be renewed beyond the next expiry date: {}." + + "This process will not be able to authenticate new SASL connections after that " + + "time (for example, it will not be authenticate a new connection with a Zookeeper " + + "Quorum member). Ask your system administrator to either increase the " + + "'renew until' time by doing : 'modprinc -maxrenewlife {}' within " + + "kadmin, or instead, to generate a keytab for {}. Because the TGT's " + + "expiry cannot be further extended by refreshing, exiting refresh thread now.", logPayload); + return; + } + // determine how long to sleep from looking at ticket's expiry. + // We should not allow the ticket to expire, but we should take into consideration + // MIN_TIME_BEFORE_RELOGIN. Will not sleep less than MIN_TIME_BEFORE_RELOGIN, unless doing so + // would cause ticket expiration. + if ((nextRefresh > expiry) || + ((now + MIN_TIME_BEFORE_RELOGIN) > expiry)) { + // expiry is before next scheduled refresh). + nextRefresh = now; + } else { + if (nextRefresh < (now + MIN_TIME_BEFORE_RELOGIN)) { + // next scheduled refresh is sooner than (now + MIN_TIME_BEFORE_LOGIN). + Date until = new Date(nextRefresh); + Date newuntil = new Date(now + MIN_TIME_BEFORE_RELOGIN); + Object[] logPayload = {until, newuntil, (MIN_TIME_BEFORE_RELOGIN / 1000)}; + LOG.warn("TGT refresh thread time adjusted from : {} to : {} since " + + "the former is sooner than the minimum refresh interval (" + + "{} seconds) from now.", logPayload); + } + nextRefresh = Math.max(nextRefresh, now + MIN_TIME_BEFORE_RELOGIN); + } + nextRefreshDate = new Date(nextRefresh); + if (nextRefresh > expiry) { + Object[] logPayload = {nextRefreshDate, expiryDate}; + LOG.error("next refresh: {} is later than expiry {}." + + " This may indicate a clock skew problem. Check that this host and the KDC's " + + "hosts' clocks are in sync. Exiting refresh thread.", logPayload); + return; + } + } + if (now == nextRefresh) { + LOG.info("refreshing now because expiry is before next scheduled refresh time."); + } else if (now < nextRefresh) { + Date until = new Date(nextRefresh); + LOG.info("TGT refresh sleeping until: {}", until.toString()); + try { + Thread.sleep(nextRefresh - now); + } catch (InterruptedException ie) { + LOG.warn("TGT renewal thread has been interrupted and will exit."); + break; + } + } + else { + LOG.error("nextRefresh:{} is in the past: exiting refresh thread. Check" + + " clock sync between this host and KDC - (KDC's clock is likely ahead of this host)." + + " Manual intervention will be required for this client to successfully authenticate." + + " Exiting refresh thread.", nextRefreshDate); + break; + } + if (isUsingTicketCache) { + String cmd = zkConfig.getProperty(ZKConfig.KINIT_COMMAND, KINIT_COMMAND_DEFAULT); + String kinitArgs = "-R"; + int retry = 1; + while (retry >= 0) { + try { + LOG.debug("running ticket cache refresh command: {} {}", cmd, kinitArgs); + Shell.execCommand(cmd, kinitArgs); + break; + } catch (Exception e) { + if (retry > 0) { + --retry; + // sleep for 10 seconds + try { + Thread.sleep(10 * 1000); + } catch (InterruptedException ie) { + LOG.error("Interrupted while renewing TGT, exiting Login thread"); + return; + } + } else { + Object[] logPayload = {cmd, kinitArgs, e.toString(), e}; + LOG.warn("Could not renew TGT due to problem running shell command: '{}" + + " {}'; exception was:{}. Exiting refresh thread.", logPayload); + return; + } + } + } + } + try { + int retry = 1; + while (retry >= 0) { + try { + reLogin(); + break; + } catch (LoginException le) { + if (retry > 0) { + --retry; + // sleep for 10 seconds. + try { + Thread.sleep(10 * 1000); + } catch (InterruptedException e) { + LOG.error("Interrupted during login retry after LoginException:", le); + throw le; + } + } else { + LOG.error("Could not refresh TGT for principal: {}.", principal, le); + } + } + } + } catch (LoginException le) { + LOG.error("Failed to refresh TGT: refresh thread exiting now.",le); + break; + } + } + } + }); + t.setDaemon(true); + } + + public void startThreadIfNeeded() { + // thread object 't' will be null if a refresh thread is not needed. + if (t != null) { + t.start(); + } + } + + public void shutdown() { + if ((t != null) && (t.isAlive())) { + t.interrupt(); + try { + t.join(); + } catch (InterruptedException e) { + LOG.warn("error while waiting for Login thread to shutdown: ", e); + } + } + } + + public Subject getSubject() { + return subject; + } + + public String getLoginContextName() { + return loginContextName; + } + + private synchronized LoginContext login(final String loginContextName) throws LoginException { + if (loginContextName == null) { + throw new LoginException("loginContext name (JAAS file section header) was null. " + + "Please check your java.security.login.auth.config (=" + + System.getProperty("java.security.login.auth.config") + + ") and your " + getLoginContextMessage()); + } + LoginContext loginContext = new LoginContext(loginContextName,callbackHandler); + loginContext.login(); + LOG.info("{} successfully logged in.", loginContextName); + return loginContext; + } + + private String getLoginContextMessage() { + if (zkConfig instanceof ZKClientConfig) { + return ZKClientConfig.LOGIN_CONTEXT_NAME_KEY + "(=" + zkConfig.getProperty( + ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT) + ")"; + } else { + return ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY + "(=" + System.getProperty( + ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY, ZooKeeperSaslServer.DEFAULT_LOGIN_CONTEXT_NAME) + ")"; + } + } + + // c.f. org.apache.hadoop.security.UserGroupInformation. + private long getRefreshTime(KerberosTicket tgt) { + long start = tgt.getStartTime().getTime(); + long expires = tgt.getEndTime().getTime(); + LOG.info("TGT valid starting at: {}", tgt.getStartTime().toString()); + LOG.info("TGT expires: {}", tgt.getEndTime().toString()); + long proposedRefresh = start + (long) ((expires - start) * + (TICKET_RENEW_WINDOW + (TICKET_RENEW_JITTER * rng.nextDouble()))); + if (proposedRefresh > expires) { + // proposedRefresh is too far in the future: it's after ticket expires: simply return now. + return Time.currentWallTime(); + } + else { + return proposedRefresh; + } + } + + private synchronized KerberosTicket getTGT() { + Set<KerberosTicket> tickets = subject.getPrivateCredentials(KerberosTicket.class); + for(KerberosTicket ticket: tickets) { + KerberosPrincipal server = ticket.getServer(); + if (server.getName().equals("krbtgt/" + server.getRealm() + "@" + server.getRealm())) { + LOG.debug("Client principal is \"" + ticket.getClient().getName() + "\"."); + LOG.debug("Server principal is \"" + ticket.getServer().getName() + "\"."); + return ticket; + } + } + return null; + } + + private boolean hasSufficientTimeElapsed() { + long now = Time.currentElapsedTime(); + if (now - getLastLogin() < MIN_TIME_BEFORE_RELOGIN ) { + LOG.warn("Not attempting to re-login since the last re-login was " + + "attempted less than {} seconds before.", + (MIN_TIME_BEFORE_RELOGIN / 1000)); + return false; + } + // register most recent relogin attempt + setLastLogin(now); + return true; + } + + /** + * Returns login object + * @return login + */ + private LoginContext getLogin() { + return login; + } + + /** + * Set the login object + * @param login + */ + private void setLogin(LoginContext login) { + this.login = login; + } + + /** + * Set the last login time. + * @param time the number of milliseconds since the beginning of time + */ + private void setLastLogin(long time) { + lastLogin = time; + } + + /** + * Get the time of the last login. + * @return the number of milliseconds since the beginning of time. + */ + private long getLastLogin() { + return lastLogin; + } + + /** + * Re-login a principal. This method assumes that {@link #login(String)} has happened already. + * @throws javax.security.auth.login.LoginException on a failure + */ + // c.f. HADOOP-6559 + private synchronized void reLogin() + throws LoginException { + if (!isKrbTicket) { + return; + } + LoginContext login = getLogin(); + if (login == null) { + throw new LoginException("login must be done first"); + } + if (!hasSufficientTimeElapsed()) { + return; + } + LOG.info("Initiating logout for {}", principal); + synchronized (Login.class) { + //clear up the kerberos state. But the tokens are not cleared! As per + //the Java kerberos login module code, only the kerberos credentials + //are cleared + login.logout(); + //login and also update the subject field of this instance to + //have the new credentials (pass it to the LoginContext constructor) + login = new LoginContext(loginContextName, getSubject()); + LOG.info("Initiating re-login for {}", principal); + login.login(); + setLogin(login); + } + } +}
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/MultiResponse.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/MultiResponse.java b/zookeeper-server/src/main/java/org/apache/zookeeper/MultiResponse.java new file mode 100644 index 0000000..5ac906a --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/MultiResponse.java @@ -0,0 +1,177 @@ +/* + * 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; + +import org.apache.jute.InputArchive; +import org.apache.jute.OutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.proto.Create2Response; +import org.apache.zookeeper.proto.CreateResponse; +import org.apache.zookeeper.proto.MultiHeader; +import org.apache.zookeeper.proto.SetDataResponse; +import org.apache.zookeeper.proto.ErrorResponse; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Handles the response from a multi request. Such a response consists of + * a sequence of responses each prefixed by a MultiResponse that indicates + * the type of the response. The end of the list is indicated by a MultiHeader + * with a negative type. Each individual response is in the same format as + * with the corresponding operation in the original request list. + */ +public class MultiResponse implements Record, Iterable<OpResult> { + private List<OpResult> results = new ArrayList<OpResult>(); + + public void add(OpResult x) { + results.add(x); + } + + @Override + public Iterator<OpResult> iterator() { + return results.iterator(); + } + + public int size() { + return results.size(); + } + + @Override + public void serialize(OutputArchive archive, String tag) throws IOException { + archive.startRecord(this, tag); + + for (OpResult result : results) { + int err = result.getType() == ZooDefs.OpCode.error ? ((OpResult.ErrorResult)result).getErr() : 0; + + new MultiHeader(result.getType(), false, err).serialize(archive, tag); + + switch (result.getType()) { + case ZooDefs.OpCode.create: + new CreateResponse(((OpResult.CreateResult) result).getPath()).serialize(archive, tag); + break; + case ZooDefs.OpCode.create2: + OpResult.CreateResult createResult = (OpResult.CreateResult) result; + new Create2Response(createResult.getPath(), + createResult.getStat()).serialize(archive, tag); + break; + case ZooDefs.OpCode.delete: + case ZooDefs.OpCode.check: + break; + case ZooDefs.OpCode.setData: + new SetDataResponse(((OpResult.SetDataResult) result).getStat()).serialize(archive, tag); + break; + case ZooDefs.OpCode.error: + new ErrorResponse(((OpResult.ErrorResult) result).getErr()).serialize(archive, tag); + break; + default: + throw new IOException("Invalid type " + result.getType() + " in MultiResponse"); + } + } + new MultiHeader(-1, true, -1).serialize(archive, tag); + archive.endRecord(this, tag); + } + + @Override + public void deserialize(InputArchive archive, String tag) throws IOException { + results = new ArrayList<OpResult>(); + + archive.startRecord(tag); + MultiHeader h = new MultiHeader(); + h.deserialize(archive, tag); + while (!h.getDone()) { + switch (h.getType()) { + case ZooDefs.OpCode.create: + CreateResponse cr = new CreateResponse(); + cr.deserialize(archive, tag); + results.add(new OpResult.CreateResult(cr.getPath())); + break; + + case ZooDefs.OpCode.create2: + Create2Response cr2 = new Create2Response(); + cr2.deserialize(archive, tag); + results.add(new OpResult.CreateResult(cr2.getPath(), cr2.getStat())); + break; + + case ZooDefs.OpCode.delete: + results.add(new OpResult.DeleteResult()); + break; + + case ZooDefs.OpCode.setData: + SetDataResponse sdr = new SetDataResponse(); + sdr.deserialize(archive, tag); + results.add(new OpResult.SetDataResult(sdr.getStat())); + break; + + case ZooDefs.OpCode.check: + results.add(new OpResult.CheckResult()); + break; + + case ZooDefs.OpCode.error: + //FIXME: need way to more cleanly serialize/deserialize exceptions + ErrorResponse er = new ErrorResponse(); + er.deserialize(archive, tag); + results.add(new OpResult.ErrorResult(er.getErr())); + break; + + default: + throw new IOException("Invalid type " + h.getType() + " in MultiResponse"); + } + h.deserialize(archive, tag); + } + archive.endRecord(tag); + } + + public List<OpResult> getResultList() { + return results; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MultiResponse)) return false; + + MultiResponse other = (MultiResponse) o; + + if (results != null) { + Iterator<OpResult> i = other.results.iterator(); + for (OpResult result : results) { + if (i.hasNext()) { + if (!result.equals(i.next())) { + return false; + } + } else { + return false; + } + } + return !i.hasNext(); + } + else return other.results == null; + } + + @Override + public int hashCode() { + int hash = results.size(); + for (OpResult result : results) { + hash = (hash * 35) + result.hashCode(); + } + return hash; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/MultiTransactionRecord.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/MultiTransactionRecord.java b/zookeeper-server/src/main/java/org/apache/zookeeper/MultiTransactionRecord.java new file mode 100644 index 0000000..336a677 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/MultiTransactionRecord.java @@ -0,0 +1,163 @@ +/* + * 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; + +import org.apache.jute.InputArchive; +import org.apache.jute.OutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.proto.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Encodes a composite transaction. In the wire format, each transaction + * consists of a single MultiHeader followed by the appropriate request. + * Each of these MultiHeaders has a type which indicates + * the type of the following transaction or a negative number if no more transactions + * are included. + */ +public class MultiTransactionRecord implements Record, Iterable<Op> { + private List<Op> ops = new ArrayList<Op>(); + + public MultiTransactionRecord() { + } + + public MultiTransactionRecord(Iterable<Op> ops) { + for (Op op : ops) { + add(op); + } + } + + @Override + public Iterator<Op> iterator() { + return ops.iterator() ; + } + + public void add(Op op) { + ops.add(op); + } + + public int size() { + return ops.size(); + } + + @Override + public void serialize(OutputArchive archive, String tag) throws IOException { + archive.startRecord(this, tag); + for (Op op : ops) { + MultiHeader h = new MultiHeader(op.getType(), false, -1); + h.serialize(archive, tag); + switch (op.getType()) { + case ZooDefs.OpCode.create: + case ZooDefs.OpCode.create2: + case ZooDefs.OpCode.createTTL: + case ZooDefs.OpCode.createContainer: + case ZooDefs.OpCode.delete: + case ZooDefs.OpCode.setData: + case ZooDefs.OpCode.check: + op.toRequestRecord().serialize(archive, tag); + break; + default: + throw new IOException("Invalid type of op"); + } + } + new MultiHeader(-1, true, -1).serialize(archive, tag); + archive.endRecord(this, tag); + } + + @Override + public void deserialize(InputArchive archive, String tag) throws IOException { + archive.startRecord(tag); + MultiHeader h = new MultiHeader(); + h.deserialize(archive, tag); + + while (!h.getDone()) { + switch (h.getType()) { + case ZooDefs.OpCode.create: + case ZooDefs.OpCode.create2: + case ZooDefs.OpCode.createContainer: + CreateRequest cr = new CreateRequest(); + cr.deserialize(archive, tag); + add(Op.create(cr.getPath(), cr.getData(), cr.getAcl(), cr.getFlags())); + break; + case ZooDefs.OpCode.createTTL: + CreateTTLRequest crTtl = new CreateTTLRequest(); + crTtl.deserialize(archive, tag); + add(Op.create(crTtl.getPath(), crTtl.getData(), crTtl.getAcl(), crTtl.getFlags(), crTtl.getTtl())); + break; + case ZooDefs.OpCode.delete: + DeleteRequest dr = new DeleteRequest(); + dr.deserialize(archive, tag); + add(Op.delete(dr.getPath(), dr.getVersion())); + break; + case ZooDefs.OpCode.setData: + SetDataRequest sdr = new SetDataRequest(); + sdr.deserialize(archive, tag); + add(Op.setData(sdr.getPath(), sdr.getData(), sdr.getVersion())); + break; + case ZooDefs.OpCode.check: + CheckVersionRequest cvr = new CheckVersionRequest(); + cvr.deserialize(archive, tag); + add(Op.check(cvr.getPath(), cvr.getVersion())); + break; + default: + throw new IOException("Invalid type of op"); + } + h.deserialize(archive, tag); + } + archive.endRecord(tag); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MultiTransactionRecord)) return false; + + MultiTransactionRecord that = (MultiTransactionRecord) o; + + if (ops != null) { + Iterator<Op> other = that.ops.iterator(); + for (Op op : ops) { + boolean hasMoreData = other.hasNext(); + if (!hasMoreData) { + return false; + } + Op otherOp = other.next(); + if (!op.equals(otherOp)) { + return false; + } + } + return !other.hasNext(); + } else { + return that.ops == null; + } + + } + + @Override + public int hashCode() { + int h = 1023; + for (Op op : ops) { + h = h * 25 + op.hashCode(); + } + return h; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/Op.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/Op.java b/zookeeper-server/src/main/java/org/apache/zookeeper/Op.java new file mode 100644 index 0000000..c73cc79 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/Op.java @@ -0,0 +1,452 @@ +/* + * 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; + +import org.apache.jute.Record; +import org.apache.zookeeper.common.PathUtils; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.proto.CheckVersionRequest; +import org.apache.zookeeper.proto.CreateRequest; +import org.apache.zookeeper.proto.CreateTTLRequest; +import org.apache.zookeeper.proto.DeleteRequest; +import org.apache.zookeeper.proto.SetDataRequest; +import org.apache.zookeeper.server.EphemeralType; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a single operation in a multi-operation transaction. Each operation can be a create, update + * or delete or can just be a version check. + * + * Sub-classes of Op each represent each detailed type but should not normally be referenced except via + * the provided factory methods. + * + * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode) + * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode, org.apache.zookeeper.AsyncCallback.StringCallback, Object) + * @see ZooKeeper#delete(String, int) + * @see ZooKeeper#setData(String, byte[], int) + */ +public abstract class Op { + private int type; + private String path; + + // prevent untyped construction + private Op(int type, String path) { + this.type = type; + this.path = path; + } + + /** + * Constructs a create operation. Arguments are as for the ZooKeeper method of the same name. + * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode) + * @see CreateMode#fromFlag(int) + * + * @param path + * the path for the node + * @param data + * the initial data for the node + * @param acl + * the acl for the node + * @param flags + * specifying whether the node to be created is ephemeral + * and/or sequential but using the integer encoding. + */ + public static Op create(String path, byte[] data, List<ACL> acl, int flags) { + return new Create(path, data, acl, flags); + } + + /** + * Constructs a create operation. Arguments are as for the ZooKeeper method of the same name + * but adding an optional ttl + * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode) + * @see CreateMode#fromFlag(int) + * + * @param path + * the path for the node + * @param data + * the initial data for the node + * @param acl + * the acl for the node + * @param flags + * specifying whether the node to be created is ephemeral + * and/or sequential but using the integer encoding. + * @param ttl + * optional ttl or 0 (flags must imply a TTL creation mode) + */ + public static Op create(String path, byte[] data, List<ACL> acl, int flags, long ttl) { + CreateMode createMode = CreateMode.fromFlag(flags, CreateMode.PERSISTENT); + if (createMode.isTTL()) { + return new CreateTTL(path, data, acl, createMode, ttl); + } + return new Create(path, data, acl, flags); + } + + /** + * Constructs a create operation. Arguments are as for the ZooKeeper method of the same name. + * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode) + * + * @param path + * the path for the node + * @param data + * the initial data for the node + * @param acl + * the acl for the node + * @param createMode + * specifying whether the node to be created is ephemeral + * and/or sequential + */ + public static Op create(String path, byte[] data, List<ACL> acl, CreateMode createMode) { + return new Create(path, data, acl, createMode); + } + + /** + * Constructs a create operation. Arguments are as for the ZooKeeper method of the same name + * but adding an optional ttl + * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode) + * + * @param path + * the path for the node + * @param data + * the initial data for the node + * @param acl + * the acl for the node + * @param createMode + * specifying whether the node to be created is ephemeral + * and/or sequential + * @param ttl + * optional ttl or 0 (createMode must imply a TTL) + */ + public static Op create(String path, byte[] data, List<ACL> acl, CreateMode createMode, long ttl) { + if (createMode.isTTL()) { + return new CreateTTL(path, data, acl, createMode, ttl); + } + return new Create(path, data, acl, createMode); + } + + /** + * Constructs a delete operation. Arguments are as for the ZooKeeper method of the same name. + * @see ZooKeeper#delete(String, int) + * + * @param path + * the path of the node to be deleted. + * @param version + * the expected node version. + */ + public static Op delete(String path, int version) { + return new Delete(path, version); + } + + /** + * Constructs an update operation. Arguments are as for the ZooKeeper method of the same name. + * @see ZooKeeper#setData(String, byte[], int) + * + * @param path + * the path of the node + * @param data + * the data to set + * @param version + * the expected matching version + */ + public static Op setData(String path, byte[] data, int version) { + return new SetData(path, data, version); + } + + + /** + * Constructs an version check operation. Arguments are as for the ZooKeeper.setData method except that + * no data is provided since no update is intended. The purpose for this is to allow read-modify-write + * operations that apply to multiple znodes, but where some of the znodes are involved only in the read, + * not the write. A similar effect could be achieved by writing the same data back, but that leads to + * way more version updates than are necessary and more writing in general. + * + * @param path + * the path of the node + * @param version + * the expected matching version + */ + public static Op check(String path, int version) { + return new Check(path, version); + } + + /** + * Gets the integer type code for an Op. This code should be as from ZooDefs.OpCode + * @see ZooDefs.OpCode + * @return The type code. + */ + public int getType() { + return type; + } + + /** + * Gets the path for an Op. + * @return The path. + */ + public String getPath() { + return path; + } + + /** + * Encodes an op for wire transmission. + * @return An appropriate Record structure. + */ + public abstract Record toRequestRecord() ; + + /** + * Reconstructs the transaction with the chroot prefix. + * @return transaction with chroot. + */ + abstract Op withChroot(String addRootPrefix); + + /** + * Performs client path validations. + * + * @throws IllegalArgumentException + * if an invalid path is specified + * @throws KeeperException.BadArgumentsException + * if an invalid create mode flag is specified + */ + void validate() throws KeeperException { + PathUtils.validatePath(path); + } + + ////////////////// + // these internal classes are public, but should not generally be referenced. + // + public static class Create extends Op { + protected byte[] data; + protected List<ACL> acl; + protected int flags; + + private Create(String path, byte[] data, List<ACL> acl, int flags) { + super(getOpcode(CreateMode.fromFlag(flags, CreateMode.PERSISTENT)), path); + this.data = data; + this.acl = acl; + this.flags = flags; + } + + private static int getOpcode(CreateMode createMode) { + if (createMode.isTTL()) { + return ZooDefs.OpCode.createTTL; + } + return createMode.isContainer() ? ZooDefs.OpCode.createContainer : ZooDefs.OpCode.create; + } + + private Create(String path, byte[] data, List<ACL> acl, CreateMode createMode) { + super(getOpcode(createMode), path); + this.data = data; + this.acl = acl; + this.flags = createMode.toFlag(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Create)) return false; + + Create op = (Create) o; + + boolean aclEquals = true; + Iterator<ACL> i = op.acl.iterator(); + for (ACL acl : op.acl) { + boolean hasMoreData = i.hasNext(); + if (!hasMoreData) { + aclEquals = false; + break; + } + ACL otherAcl = i.next(); + if (!acl.equals(otherAcl)) { + aclEquals = false; + break; + } + } + return !i.hasNext() && getType() == op.getType() && Arrays.equals(data, op.data) && flags == op.flags && aclEquals; + } + + @Override + public int hashCode() { + return getType() + getPath().hashCode() + Arrays.hashCode(data); + } + + @Override + public Record toRequestRecord() { + return new CreateRequest(getPath(), data, acl, flags); + } + + @Override + Op withChroot(String path) { + return new Create(path, data, acl, flags); + } + + @Override + void validate() throws KeeperException { + CreateMode createMode = CreateMode.fromFlag(flags); + PathUtils.validatePath(getPath(), createMode.isSequential()); + EphemeralType.validateTTL(createMode, -1); + } + } + + public static class CreateTTL extends Create { + private final long ttl; + + private CreateTTL(String path, byte[] data, List<ACL> acl, int flags, long ttl) { + super(path, data, acl, flags); + this.ttl = ttl; + } + + private CreateTTL(String path, byte[] data, List<ACL> acl, CreateMode createMode, long ttl) { + super(path, data, acl, createMode); + this.ttl = ttl; + } + + @Override + public boolean equals(Object o) { + return super.equals(o) && (o instanceof CreateTTL) && (ttl == ((CreateTTL)o).ttl); + } + + @Override + public int hashCode() { + return super.hashCode() + (int)(ttl ^ (ttl >>> 32)); + } + + @Override + public Record toRequestRecord() { + return new CreateTTLRequest(getPath(), data, acl, flags, ttl); + } + + @Override + Op withChroot(String path) { + return new CreateTTL(path, data, acl, flags, ttl); + } + + @Override + void validate() throws KeeperException { + CreateMode createMode = CreateMode.fromFlag(flags); + PathUtils.validatePath(getPath(), createMode.isSequential()); + EphemeralType.validateTTL(createMode, ttl); + } + } + + public static class Delete extends Op { + private int version; + + private Delete(String path, int version) { + super(ZooDefs.OpCode.delete, path); + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Delete)) return false; + + Delete op = (Delete) o; + + return getType() == op.getType() && version == op.version + && getPath().equals(op.getPath()); + } + + @Override + public int hashCode() { + return getType() + getPath().hashCode() + version; + } + + @Override + public Record toRequestRecord() { + return new DeleteRequest(getPath(), version); + } + + @Override + Op withChroot(String path) { + return new Delete(path, version); + } + } + + public static class SetData extends Op { + private byte[] data; + private int version; + + private SetData(String path, byte[] data, int version) { + super(ZooDefs.OpCode.setData, path); + this.data = data; + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SetData)) return false; + + SetData op = (SetData) o; + + return getType() == op.getType() && version == op.version + && getPath().equals(op.getPath()) && Arrays.equals(data, op.data); + } + + @Override + public int hashCode() { + return getType() + getPath().hashCode() + Arrays.hashCode(data) + version; + } + + @Override + public Record toRequestRecord() { + return new SetDataRequest(getPath(), data, version); + } + + @Override + Op withChroot(String path) { + return new SetData(path, data, version); + } + } + + public static class Check extends Op { + private int version; + + private Check(String path, int version) { + super(ZooDefs.OpCode.check, path); + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Check)) return false; + + Check op = (Check) o; + + return getType() == op.getType() && getPath().equals(op.getPath()) && version == op.version; + } + + @Override + public int hashCode() { + return getType() + getPath().hashCode() + version; + } + + @Override + public Record toRequestRecord() { + return new CheckVersionRequest(getPath(), version); + } + + @Override + Op withChroot(String path) { + return new Check(path, version); + } + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/OpResult.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/OpResult.java b/zookeeper-server/src/main/java/org/apache/zookeeper/OpResult.java new file mode 100644 index 0000000..d294b8f --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/OpResult.java @@ -0,0 +1,205 @@ +/* + * 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; + + +import org.apache.zookeeper.data.Stat; + +/** + * Encodes the result of a single part of a multiple operation commit. + */ +public abstract class OpResult { + private int type; + + private OpResult(int type) { + this.type = type; + } + + /** + * Encodes the return type as from ZooDefs.OpCode. Can be used + * to dispatch to the correct cast needed for getting the desired + * additional result data. + * @see ZooDefs.OpCode + * @return an integer identifying what kind of operation this result came from. + */ + public int getType() { + return type; + } + + /** + * A result from a create operation. This kind of result allows the + * path to be retrieved since the create might have been a sequential + * create. + */ + public static class CreateResult extends OpResult { + private String path; + private Stat stat; + + public CreateResult(String path) { + this(ZooDefs.OpCode.create, path, null); + } + + public CreateResult(String path, Stat stat) { + this(ZooDefs.OpCode.create2, path, stat); + } + + private CreateResult(int opcode, String path, Stat stat) { + super(opcode); + this.path = path; + this.stat = stat; + } + + public String getPath() { + return path; + } + + public Stat getStat() { + return stat; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CreateResult)) return false; + + CreateResult other = (CreateResult) o; + + boolean statsAreEqual = (stat == null && other.stat == null || + (stat != null && other.stat != null && + stat.getMzxid() == other.stat.getMzxid())); + return getType() == other.getType() && + path.equals(other.getPath()) && statsAreEqual; + } + + @Override + public int hashCode() { + return (int) (getType() * 35 + path.hashCode() + + (stat == null ? 0 : stat.getMzxid())); + } + } + + /** + * A result from a delete operation. No special values are available. + */ + public static class DeleteResult extends OpResult { + public DeleteResult() { + super(ZooDefs.OpCode.delete); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DeleteResult)) return false; + + DeleteResult opResult = (DeleteResult) o; + return getType() == opResult.getType(); + } + + @Override + public int hashCode() { + return getType(); + } + } + + /** + * A result from a setData operation. This kind of result provides access + * to the Stat structure from the update. + */ + public static class SetDataResult extends OpResult { + private Stat stat; + + public SetDataResult(Stat stat) { + super(ZooDefs.OpCode.setData); + this.stat = stat; + } + + public Stat getStat() { + return stat; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SetDataResult)) return false; + + SetDataResult other = (SetDataResult) o; + return getType() == other.getType() && stat.getMzxid() == other.stat.getMzxid(); + } + + @Override + public int hashCode() { + return (int) (getType() * 35 + stat.getMzxid()); + } + } + + /** + * A result from a version check operation. No special values are available. + */ + public static class CheckResult extends OpResult { + public CheckResult() { + super(ZooDefs.OpCode.check); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CheckResult)) return false; + + CheckResult other = (CheckResult) o; + return getType() == other.getType(); + } + + @Override + public int hashCode() { + return getType(); + } + } + + /** + * An error result from any kind of operation. The point of error results + * is that they contain an error code which helps understand what happened. + * @see KeeperException.Code + * + */ + public static class ErrorResult extends OpResult { + private int err; + + public ErrorResult(int err) { + super(ZooDefs.OpCode.error); + this.err = err; + } + + public int getErr() { + return err; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ErrorResult)) return false; + + ErrorResult other = (ErrorResult) o; + return getType() == other.getType() && err == other.getErr(); + } + + @Override + public int hashCode() { + return getType() * 35 + err; + } + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/Quotas.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/Quotas.java b/zookeeper-server/src/main/java/org/apache/zookeeper/Quotas.java new file mode 100644 index 0000000..b82e339 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/Quotas.java @@ -0,0 +1,68 @@ +/** + * 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; + +/** + * this class manages quotas + * and has many other utils + * for quota + */ +public class Quotas { + + /** the zookeeper nodes that acts as the management and status node **/ + public static final String procZookeeper = "/zookeeper"; + + /** the zookeeper quota node that acts as the quota + * management node for zookeeper */ + public static final String quotaZookeeper = "/zookeeper/quota"; + + /** + * the limit node that has the limit of + * a subtree + */ + public static final String limitNode = "zookeeper_limits"; + + /** + * the stat node that monitors the limit of + * a subtree. + */ + public static final String statNode = "zookeeper_stats"; + + /** + * return the quota path associated with this + * prefix + * @param path the actual path in zookeeper. + * @return the limit quota path + */ + public static String quotaPath(String path) { + return quotaZookeeper + path + + "/" + limitNode; + } + + /** + * return the stat quota path associated with this + * prefix. + * @param path the actual path in zookeeper + * @return the stat quota path + */ + public static String statPath(String path) { + return quotaZookeeper + path + "/" + + statNode; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/SaslClientCallbackHandler.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/SaslClientCallbackHandler.java b/zookeeper-server/src/main/java/org/apache/zookeeper/SaslClientCallbackHandler.java new file mode 100644 index 0000000..d6f5549 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/SaslClientCallbackHandler.java @@ -0,0 +1,104 @@ +/** + * 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; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.RealmCallback; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is used by the SASL mechanisms to get further information to complete + * the authentication. For example, a SASL mechanism might use this callback + * handler to do verification operation. The CallbackHandler interface here + * refers to javax.security.auth.callback.CallbackHandler. It should not be + * confused with ZooKeeper packet callbacks like + * org.apache.zookeeper.server.auth.SaslServerCallbackHandler. + */ +public class SaslClientCallbackHandler implements CallbackHandler { + private String password = null; + private static final Logger LOG = LoggerFactory.getLogger(SaslClientCallbackHandler.class); + private final String entity; + public SaslClientCallbackHandler(String password, String client) { + this.password = password; + this.entity = client; + } + + public void handle(Callback[] callbacks) throws UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback) { + NameCallback nc = (NameCallback) callback; + nc.setName(nc.getDefaultName()); + } + else { + if (callback instanceof PasswordCallback) { + PasswordCallback pc = (PasswordCallback)callback; + if (password != null) { + pc.setPassword(this.password.toCharArray()); + } else { + LOG.warn("Could not login: the {} is being asked for a password, but the ZooKeeper {}" + + " code does not currently support obtaining a password from the user." + + " Make sure that the {} is configured to use a ticket cache (using" + + " the JAAS configuration setting 'useTicketCache=true)' and restart the {}. If" + + " you still get this message after that, the TGT in the ticket cache has expired and must" + + " be manually refreshed. To do so, first determine if you are using a password or a" + + " keytab. If the former, run kinit in a Unix shell in the environment of the user who" + + " is running this Zookeeper {} using the command" + + " 'kinit <princ>' (where <princ> is the name of the {}'s Kerberos principal)." + + " If the latter, do" + + " 'kinit -k -t <keytab> <princ>' (where <princ> is the name of the Kerberos principal, and" + + " <keytab> is the location of the keytab file). After manually refreshing your cache," + + " restart this {}. If you continue to see this message after manually refreshing" + + " your cache, ensure that your KDC host's clock is in sync with this host's clock.", + new Object[]{entity, entity, entity, entity, entity, entity, entity}); + } + } + else { + if (callback instanceof RealmCallback) { + RealmCallback rc = (RealmCallback) callback; + rc.setText(rc.getDefaultText()); + } + else { + if (callback instanceof AuthorizeCallback) { + AuthorizeCallback ac = (AuthorizeCallback) callback; + String authid = ac.getAuthenticationID(); + String authzid = ac.getAuthorizationID(); + if (authid.equals(authzid)) { + ac.setAuthorized(true); + } else { + ac.setAuthorized(false); + } + if (ac.isAuthorized()) { + ac.setAuthorizedID(authzid); + } + } + else { + throw new UnsupportedCallbackException(callback, "Unrecognized SASL " + entity + "Callback"); + } + } + } + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/ServerAdminClient.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/ServerAdminClient.java b/zookeeper-server/src/main/java/org/apache/zookeeper/ServerAdminClient.java new file mode 100644 index 0000000..5efa53e --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/ServerAdminClient.java @@ -0,0 +1,280 @@ +/** + * 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; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; + +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@InterfaceAudience.Public +public class ServerAdminClient { + private static final Logger LOG = LoggerFactory.getLogger(ServerAdminClient.class); + + public static void ruok(String host, int port) { + Socket s = null; + try { + byte[] reqBytes = new byte[4]; + ByteBuffer req = ByteBuffer.wrap(reqBytes); + req.putInt(ByteBuffer.wrap("ruok".getBytes()).getInt()); + s = new Socket(); + s.setSoLinger(false, 10); + s.setSoTimeout(20000); + s.connect(new InetSocketAddress(host, port)); + + InputStream is = s.getInputStream(); + OutputStream os = s.getOutputStream(); + + os.write(reqBytes); + + byte[] resBytes = new byte[4]; + + int rc = is.read(resBytes); + String retv = new String(resBytes); + System.out.println("rc=" + rc + " retv=" + retv); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } finally { + if (s != null) { + try { + s.close(); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } + } + } + } + + public static void dump(String host, int port) { + Socket s = null; + try { + byte[] reqBytes = new byte[4]; + ByteBuffer req = ByteBuffer.wrap(reqBytes); + req.putInt(ByteBuffer.wrap("dump".getBytes()).getInt()); + s = new Socket(); + s.setSoLinger(false, 10); + s.setSoTimeout(20000); + s.connect(new InetSocketAddress(host, port)); + + InputStream is = s.getInputStream(); + OutputStream os = s.getOutputStream(); + + os.write(reqBytes); + + byte[] resBytes = new byte[1024]; + + int rc = is.read(resBytes); + String retv = new String(resBytes); + System.out.println("rc=" + rc + " retv=" + retv); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } finally { + if (s != null) { + try { + s.close(); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } + } + } + } + + public static void stat(String host, int port) { + Socket s = null; + try { + byte[] reqBytes = new byte[4]; + ByteBuffer req = ByteBuffer.wrap(reqBytes); + req.putInt(ByteBuffer.wrap("stat".getBytes()).getInt()); + s = new Socket(); + s.setSoLinger(false, 10); + s.setSoTimeout(20000); + s.connect(new InetSocketAddress(host, port)); + + InputStream is = s.getInputStream(); + OutputStream os = s.getOutputStream(); + + os.write(reqBytes); + + byte[] resBytes = new byte[1024]; + + int rc = is.read(resBytes); + String retv = new String(resBytes); + System.out.println("rc=" + rc + " retv=" + retv); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } finally { + if (s != null) { + try { + s.close(); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } + } + } + } + + public static void kill(String host, int port) { + Socket s = null; + try { + byte[] reqBytes = new byte[4]; + ByteBuffer req = ByteBuffer.wrap(reqBytes); + req.putInt(ByteBuffer.wrap("kill".getBytes()).getInt()); + s = new Socket(); + s.setSoLinger(false, 10); + s.setSoTimeout(20000); + s.connect(new InetSocketAddress(host, port)); + + InputStream is = s.getInputStream(); + OutputStream os = s.getOutputStream(); + + os.write(reqBytes); + byte[] resBytes = new byte[4]; + + int rc = is.read(resBytes); + String retv = new String(resBytes); + System.out.println("rc=" + rc + " retv=" + retv); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } finally { + if (s != null) { + try { + s.close(); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } + } + } + } + + public static void setTraceMask(String host, int port, String traceMaskStr) { + Socket s = null; + try { + byte[] reqBytes = new byte[12]; + ByteBuffer req = ByteBuffer.wrap(reqBytes); + long traceMask = Long.parseLong(traceMaskStr, 8); + req.putInt(ByteBuffer.wrap("stmk".getBytes()).getInt()); + req.putLong(traceMask); + + s = new Socket(); + s.setSoLinger(false, 10); + s.setSoTimeout(20000); + s.connect(new InetSocketAddress(host, port)); + + InputStream is = s.getInputStream(); + OutputStream os = s.getOutputStream(); + + os.write(reqBytes); + + byte[] resBytes = new byte[8]; + + int rc = is.read(resBytes); + ByteBuffer res = ByteBuffer.wrap(resBytes); + long retv = res.getLong(); + System.out.println("rc=" + rc + " retv=0" + + Long.toOctalString(retv) + " masks=0" + + Long.toOctalString(traceMask)); + assert (retv == traceMask); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } finally { + if (s != null) { + try { + s.close(); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } + } + } + } + + public static void getTraceMask(String host, int port) { + Socket s = null; + try { + byte[] reqBytes = new byte[12]; + ByteBuffer req = ByteBuffer.wrap(reqBytes); + req.putInt(ByteBuffer.wrap("gtmk".getBytes()).getInt()); + + s = new Socket(); + s.setSoLinger(false, 10); + s.setSoTimeout(20000); + s.connect(new InetSocketAddress(host, port)); + + InputStream is = s.getInputStream(); + OutputStream os = s.getOutputStream(); + + os.write(reqBytes); + + byte[] resBytes = new byte[8]; + + int rc = is.read(resBytes); + ByteBuffer res = ByteBuffer.wrap(resBytes); + long retv = res.getLong(); + System.out.println("rc=" + rc + " retv=0" + + Long.toOctalString(retv)); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } finally { + if (s != null) { + try { + s.close(); + } catch (IOException e) { + LOG.warn("Unexpected exception", e); + } + } + } + } + + private static void usage() { + System.out + .println("usage: java [-cp CLASSPATH] org.apache.zookeeper.ServerAdminClient " + + "host port op (ruok|stat|dump|kill|gettracemask|settracemask) [arguments]"); + + } + + public static void main(String[] args) { + if (args.length < 3) { + usage(); + return; + } + String host = args[0]; + int port = Integer.parseInt(args[1]); + String op = args[2]; + if (op.equalsIgnoreCase("gettracemask")) { + getTraceMask(host, port); + } else if (op.equalsIgnoreCase("settracemask")) { + setTraceMask(host, port, args[3]); + } else if (op.equalsIgnoreCase("ruok")) { + ruok(host, port); + } else if (op.equalsIgnoreCase("kill")) { + kill(host, port); + } else if (op.equalsIgnoreCase("stat")) { + stat(host, port); + } else if (op.equalsIgnoreCase("dump")) { + dump(host, port); + } else { + System.out.println("Unrecognized op: " + op); + } + } +}