You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zookeeper.apache.org by iv...@apache.org on 2012/12/03 17:59:06 UTC

svn commit: r1416590 - in /zookeeper/bookkeeper/trunk: ./ bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/ bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/ bookkeepe...

Author: ivank
Date: Mon Dec  3 16:59:04 2012
New Revision: 1416590

URL: http://svn.apache.org/viewvc?rev=1416590&view=rev
Log:
BOOKKEEPER-347: Provide mechanism to detect r-o bookie by the bookie clients (Vinay via ivank)

Modified:
    zookeeper/bookkeeper/trunk/CHANGES.txt
    zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Bookie.java
    zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BKException.java
    zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
    zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookieWatcher.java
    zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/PerChannelBookieClient.java
    zookeeper/bookkeeper/trunk/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/ReadOnlyBookieTest.java

Modified: zookeeper/bookkeeper/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/zookeeper/bookkeeper/trunk/CHANGES.txt?rev=1416590&r1=1416589&r2=1416590&view=diff
==============================================================================
--- zookeeper/bookkeeper/trunk/CHANGES.txt (original)
+++ zookeeper/bookkeeper/trunk/CHANGES.txt Mon Dec  3 16:59:04 2012
@@ -118,6 +118,8 @@ Trunk (unreleased changes)
 
         BOOKKEEPER-459: Rename metastore mock implementation to InMemory implementation (jiannan via ivank)
 
+        BOOKKEEPER-347: Provide mechanism to detect r-o bookie by the bookie clients (Vinay via ivank)
+
       hedwig-protocol:
 
         BOOKKEEPER-394: CompositeException message is not useful (Stu Hood via sijie)

Modified: zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Bookie.java
URL: http://svn.apache.org/viewvc/zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Bookie.java?rev=1416590&r1=1416589&r2=1416590&view=diff
==============================================================================
--- zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Bookie.java (original)
+++ zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Bookie.java Mon Dec  3 16:59:04 2012
@@ -65,6 +65,8 @@ import org.apache.zookeeper.ZooKeeper;
 import org.apache.zookeeper.ZooDefs.Ids;
 import org.apache.zookeeper.Watcher.Event.EventType;
 
+import com.google.common.annotations.VisibleForTesting;
+
 /**
  * Implements a bookie.
  *
@@ -657,7 +659,8 @@ public class Bookie extends Thread {
     /*
      * Transition the bookie to readOnly mode
      */
-    void transitionToReadOnlyMode() {
+    @VisibleForTesting
+    public void transitionToReadOnlyMode() {
         if (!readOnly.compareAndSet(false, true)) {
             return;
         }
@@ -682,11 +685,11 @@ public class Bookie extends Thread {
                     // this node is just now created by someone.
                 }
             }
+            // Create the readonly node
+            zk.create(this.bookieRegistrationPath + READONLY + "/" + getMyId(), new byte[0], Ids.OPEN_ACL_UNSAFE,
+                    CreateMode.EPHEMERAL);
             // Clear the current registered node
             zk.delete(zkBookieRegPath, -1);
-            // Create the readonly node
-            zk.create(this.bookieRegistrationPath + READONLY + "/" + getMyId(),
-                    new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
         } catch (IOException e) {
             LOG.error("Error in transition to ReadOnly Mode."
                     + " Shutting down", e);

Modified: zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BKException.java
URL: http://svn.apache.org/viewvc/zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BKException.java?rev=1416590&r1=1416589&r2=1416590&view=diff
==============================================================================
--- zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BKException.java (original)
+++ zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BKException.java Mon Dec  3 16:59:04 2012
@@ -84,6 +84,8 @@ public abstract class BKException extend
             return new BKUnauthorizedAccessException();
         case Code.UnclosedFragmentException:
             return new BKUnclosedFragmentException();
+        case Code.WriteOnReadOnlyBookieException:
+            return new BKWriteOnReadOnlyBookieException();
         default:
             return new BKIllegalOpException();
         }
@@ -117,6 +119,7 @@ public abstract class BKException extend
         int LedgerFencedException = -101;
         int UnauthorizedAccessException = -102;
         int UnclosedFragmentException = -103;
+        int WriteOnReadOnlyBookieException = -104;
     }
 
     public void setCode(int code) {
@@ -171,6 +174,8 @@ public abstract class BKException extend
             return "Attempted to access ledger using the wrong password";
         case Code.UnclosedFragmentException:
             return "Attempting to use an unclosed fragment; This is not safe";
+        case Code.WriteOnReadOnlyBookieException:
+            return "Attempting to write on ReadOnly bookie";
         default:
             return "Invalid operation";
         }
@@ -301,4 +306,10 @@ public abstract class BKException extend
             super(Code.UnclosedFragmentException);
         }
     }
+
+    public static class BKWriteOnReadOnlyBookieException extends BKException {
+        public BKWriteOnReadOnlyBookieException() {
+            super(Code.WriteOnReadOnlyBookieException);
+        }
+    }
 }

Modified: zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
URL: http://svn.apache.org/viewvc/zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java?rev=1416590&r1=1416589&r2=1416590&view=diff
==============================================================================
--- zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java (original)
+++ zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java Mon Dec  3 16:59:04 2012
@@ -373,6 +373,10 @@ public class BookKeeperAdmin {
                         return;
                     }
                     for (String bookieNode : children) {
+                        if (Bookie.READONLY.equals(bookieNode)) {
+                            // exclude the readonly node from available bookies.
+                            continue;
+                        }
                         String parts[] = bookieNode.split(COLON);
                         if (parts.length < 2) {
                             LOG.error("Bookie Node retrieved from ZK has invalid name format: " + bookieNode);

Modified: zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookieWatcher.java
URL: http://svn.apache.org/viewvc/zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookieWatcher.java?rev=1416590&r1=1416589&r2=1416590&view=diff
==============================================================================
--- zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookieWatcher.java (original)
+++ zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookieWatcher.java Mon Dec  3 16:59:04 2012
@@ -29,17 +29,22 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
+
+import org.apache.bookkeeper.bookie.Bookie;
 import org.apache.bookkeeper.client.BKException.BKNotEnoughBookiesException;
 import org.apache.bookkeeper.conf.ClientConfiguration;
 import org.apache.bookkeeper.util.SafeRunnable;
 import org.apache.bookkeeper.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.WatchedEvent;
 import org.apache.zookeeper.Watcher;
 import org.apache.zookeeper.AsyncCallback.ChildrenCallback;
 import org.apache.zookeeper.KeeperException.Code;
+import org.apache.zookeeper.KeeperException.NodeExistsException;
+import org.apache.zookeeper.ZooDefs.Ids;
 
 /**
  * This class is responsible for maintaining a consistent view of what bookies
@@ -67,12 +72,14 @@ class BookieWatcher implements Watcher, 
             readBookies();
         }
     };
+    private ReadOnlyBookieWatcher readOnlyBookieWatcher;
 
-    public BookieWatcher(ClientConfiguration conf, BookKeeper bk) {
+    public BookieWatcher(ClientConfiguration conf, BookKeeper bk) throws KeeperException, InterruptedException {
         this.bk = bk;
         // ZK bookie registration path
         this.bookieRegistrationPath = conf.getZkAvailableBookiesPath();
         this.scheduler = Executors.newSingleThreadScheduledExecutor();
+        readOnlyBookieWatcher = new ReadOnlyBookieWatcher(conf, bk);
     }
 
     public void halt() {
@@ -102,6 +109,27 @@ class BookieWatcher implements Watcher, 
             return;
         }
 
+        // Just exclude the 'readonly' znode to exclude r-o bookies from
+        // available nodes list.
+        children.remove(Bookie.READONLY);
+
+        HashSet<InetSocketAddress> newBookieAddrs = convertToBookieAddresses(children);
+
+        final HashSet<InetSocketAddress> deadBookies;
+        synchronized (this) {
+            deadBookies = (HashSet<InetSocketAddress>)knownBookies.clone();
+            deadBookies.removeAll(newBookieAddrs);
+            // No need to close readonly bookie clients.
+            deadBookies.removeAll(readOnlyBookieWatcher.getReadOnlyBookies());
+            knownBookies = newBookieAddrs;
+        }
+
+        if (bk.getBookieClient() != null) {
+            bk.getBookieClient().closeClients(deadBookies);
+        }
+    }
+
+    private static HashSet<InetSocketAddress> convertToBookieAddresses(List<String> children) {
         // Read the bookie addresses into a set for efficient lookup
         HashSet<InetSocketAddress> newBookieAddrs = new HashSet<InetSocketAddress>();
         for (String bookieAddrString : children) {
@@ -114,17 +142,7 @@ class BookieWatcher implements Watcher, 
             }
             newBookieAddrs.add(bookieAddr);
         }
-
-        final HashSet<InetSocketAddress> deadBookies;
-        synchronized (this) {
-            deadBookies = (HashSet<InetSocketAddress>)knownBookies.clone();
-            deadBookies.removeAll(newBookieAddrs);
-            knownBookies = newBookieAddrs;
-        }
-
-        if (bk.getBookieClient() != null) {
-            bk.getBookieClient().closeClients(deadBookies);
-        }
+        return newBookieAddrs;
     }
 
     /**
@@ -133,6 +151,9 @@ class BookieWatcher implements Watcher, 
      * @throws KeeperException
      */
     public void readBookiesBlocking() throws InterruptedException, KeeperException {
+        // Read readonly bookies first
+        readOnlyBookieWatcher.readROBookiesBlocking();
+
         final LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
         readBookies(new ChildrenCallback() {
             public void processResult(int rc, String path, Object ctx, List<String> children) {
@@ -213,4 +234,82 @@ class BookieWatcher implements Watcher, 
         throw new BKNotEnoughBookiesException();
     }
 
+    /**
+     * Watcher implementation to watch the readonly bookies under
+     * &lt;available&gt;/readonly
+     */
+    private static class ReadOnlyBookieWatcher implements Watcher, ChildrenCallback {
+
+        private final static Logger LOG = LoggerFactory.getLogger(ReadOnlyBookieWatcher.class);
+        private HashSet<InetSocketAddress> readOnlyBookies = new HashSet<InetSocketAddress>();
+        private BookKeeper bk;
+        private String readOnlyBookieRegPath;
+
+        public ReadOnlyBookieWatcher(ClientConfiguration conf, BookKeeper bk) throws KeeperException,
+                InterruptedException {
+            this.bk = bk;
+            readOnlyBookieRegPath = conf.getZkAvailableBookiesPath() + "/" + Bookie.READONLY;
+            if (null == bk.getZkHandle().exists(readOnlyBookieRegPath, false)) {
+                try {
+                    bk.getZkHandle().create(readOnlyBookieRegPath, new byte[0], Ids.OPEN_ACL_UNSAFE,
+                            CreateMode.PERSISTENT);
+                } catch (NodeExistsException e) {
+                    // this node is just now created by someone.
+                }
+            }
+        }
+
+        @Override
+        public void process(WatchedEvent event) {
+            readROBookies();
+        }
+
+        // read the readonly bookies in blocking fashion. Used only for first
+        // time.
+        void readROBookiesBlocking() throws InterruptedException, KeeperException {
+
+            final LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
+            readROBookies(new ChildrenCallback() {
+                public void processResult(int rc, String path, Object ctx, List<String> children) {
+                    try {
+                        ReadOnlyBookieWatcher.this.processResult(rc, path, ctx, children);
+                        queue.put(rc);
+                    } catch (InterruptedException e) {
+                        logger.error("Interruped when trying to read readonly bookies in a blocking fashion");
+                        throw new RuntimeException(e);
+                    }
+                }
+            });
+            int rc = queue.take();
+
+            if (rc != KeeperException.Code.OK.intValue()) {
+                throw KeeperException.create(Code.get(rc));
+            }
+        }
+
+        // Read children and register watcher for readonly bookies path
+        void readROBookies(ChildrenCallback callback) {
+            bk.getZkHandle().getChildren(this.readOnlyBookieRegPath, this, callback, null);
+        }
+
+        void readROBookies() {
+            readROBookies(this);
+        }
+
+        @Override
+        public void processResult(int rc, String path, Object ctx, List<String> children) {
+            if (rc != Code.OK.intValue()) {
+                LOG.error("Not able to read readonly bookies : ", KeeperException.create(Code.get(rc)));
+                return;
+            }
+
+            HashSet<InetSocketAddress> newReadOnlyBookies = convertToBookieAddresses(children);
+            readOnlyBookies = newReadOnlyBookies;
+        }
+
+        // returns the readonly bookies
+        public HashSet<InetSocketAddress> getReadOnlyBookies() {
+            return readOnlyBookies;
+        }
+    }
 }

Modified: zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/PerChannelBookieClient.java
URL: http://svn.apache.org/viewvc/zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/PerChannelBookieClient.java?rev=1416590&r1=1416589&r2=1416590&view=diff
==============================================================================
--- zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/PerChannelBookieClient.java (original)
+++ zookeeper/bookkeeper/trunk/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/PerChannelBookieClient.java Mon Dec  3 16:59:04 2012
@@ -558,6 +558,9 @@ public class PerChannelBookieClient exte
         case BookieProtocol.EUA:
             rc = BKException.Code.UnauthorizedAccessException;
             break;
+        case BookieProtocol.EREADONLY:
+            rc = BKException.Code.WriteOnReadOnlyBookieException;
+            break;
         default:
             LOG.error("Add for ledger: " + ledgerId + ", entry: " + entryId + " failed on bookie: " + addr
                       + " with code: " + rc);

Modified: zookeeper/bookkeeper/trunk/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/ReadOnlyBookieTest.java
URL: http://svn.apache.org/viewvc/zookeeper/bookkeeper/trunk/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/ReadOnlyBookieTest.java?rev=1416590&r1=1416589&r2=1416590&view=diff
==============================================================================
--- zookeeper/bookkeeper/trunk/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/ReadOnlyBookieTest.java (original)
+++ zookeeper/bookkeeper/trunk/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/ReadOnlyBookieTest.java Mon Dec  3 16:59:04 2012
@@ -167,4 +167,21 @@ public class ReadOnlyBookieTest extends 
         bsConfs.add(newConf);
         bs.add(startBookie(newConf));
     }
+
+    /**
+     * Test ledger creation with readonly bookies
+     */
+    public void testLedgerCreationShouldFailWithReadonlyBookie() throws Exception {
+        killBookie(1);
+        baseConf.setReadOnlyModeEnabled(true);
+        startNewBookie();
+        bs.get(1).getBookie().transitionToReadOnlyMode();
+        try {
+            bkc.readBookiesBlocking();
+            bkc.createLedger(2, 2, DigestType.CRC32, "".getBytes());
+            fail("Must throw exception, as there is one readonly bookie");
+        } catch (BKException e) {
+            // Expected
+        }
+    }
 }