You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bookkeeper.apache.org by iv...@apache.org on 2015/01/12 15:19:19 UTC

bookkeeper git commit: BOOKKEEPER-832: Allow starting bookie in ReadOnly mode (zhaijia via ivank)

Repository: bookkeeper
Updated Branches:
  refs/heads/master 264995cf2 -> 4050e7965


BOOKKEEPER-832: Allow starting bookie in ReadOnly mode (zhaijia via ivank)


Project: http://git-wip-us.apache.org/repos/asf/bookkeeper/repo
Commit: http://git-wip-us.apache.org/repos/asf/bookkeeper/commit/4050e796
Tree: http://git-wip-us.apache.org/repos/asf/bookkeeper/tree/4050e796
Diff: http://git-wip-us.apache.org/repos/asf/bookkeeper/diff/4050e796

Branch: refs/heads/master
Commit: 4050e79654704ebc0224cc024980e297fdc56473
Parents: 264995c
Author: Ivan Kelly <iv...@apache.org>
Authored: Mon Jan 12 15:18:55 2015 +0100
Committer: Ivan Kelly <iv...@apache.org>
Committed: Mon Jan 12 15:18:55 2015 +0100

----------------------------------------------------------------------
 CHANGES.txt                                     |   2 +
 .../org/apache/bookkeeper/bookie/Bookie.java    |  10 +-
 .../bookkeeper/bookie/ReadOnlyBookie.java       | 120 +++++++++++++++++++
 .../bookkeeper/conf/ServerConfiguration.java    |  25 ++++
 .../apache/bookkeeper/proto/BookieServer.java   |  11 +-
 .../test/BookKeeperClusterTestCase.java         |   8 +-
 .../test/ForceReadOnlyBookieTest.java           |  95 +++++++++++++++
 7 files changed, 263 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/bookkeeper/blob/4050e796/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index a3eca3e..ed31203 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -42,6 +42,8 @@ Trunk (unreleased changes)
 
       BOOKKEEPER-803: Guide for making a replicated log out of ledgers (ivank)
 
+      BOOKKEEPER-832: Allow starting bookie in ReadOnly mode (zhaijia via ivank)
+
       bookkeeper-client:
 
         BOOKKEEPER-810: Allow to configure TCP connect timeout (Charles Xie via sijie)

http://git-wip-us.apache.org/repos/asf/bookkeeper/blob/4050e796/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Bookie.java
----------------------------------------------------------------------
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Bookie.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Bookie.java
index 54a3c9f..24dd466 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Bookie.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Bookie.java
@@ -106,7 +106,7 @@ public class Bookie extends BookieCriticalThread {
     static final long METAENTRY_ID_FENCE_KEY  = -0x2000;
 
     // ZK registration path for this bookie
-    private final String bookieRegistrationPath;
+    protected final String bookieRegistrationPath;
 
     private final LedgerDirsManager ledgerDirsManager;
     private LedgerDirsManager indexDirsManager;
@@ -127,10 +127,10 @@ public class Bookie extends BookieCriticalThread {
 
     final ConcurrentMap<Long, byte[]> masterKeyCache = new ConcurrentHashMap<Long, byte[]>();
 
-    final private String zkBookieRegPath;
-    final private String zkBookieReadOnlyPath;
+    final protected String zkBookieRegPath;
+    final protected String zkBookieReadOnlyPath;
 
-    final private AtomicBoolean readOnly = new AtomicBoolean(false);
+    final protected AtomicBoolean readOnly = new AtomicBoolean(false);
 
     // Expose Stats
     private final Counter writeBytes;
@@ -795,7 +795,7 @@ public class Bookie extends BookieCriticalThread {
             // exit here as this is a fatal error.
             throw new IOException(ke);
         } catch (InterruptedException ie) {
-            LOG.error("ZK exception registering ephemeral Znode for Bookie!",
+            LOG.error("Interrupted exception registering ephemeral Znode for Bookie!",
                     ie);
             // Throw an IOException back up. This will cause the Bookie
             // constructor to error out. Alternatively, we could do a System

http://git-wip-us.apache.org/repos/asf/bookkeeper/blob/4050e796/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/ReadOnlyBookie.java
----------------------------------------------------------------------
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/ReadOnlyBookie.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/ReadOnlyBookie.java
new file mode 100644
index 0000000..d354fb3
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/ReadOnlyBookie.java
@@ -0,0 +1,120 @@
+/**
+ *
+ * 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.bookkeeper.bookie;
+
+import java.io.IOException;
+
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.stats.StatsLogger;
+import org.apache.bookkeeper.util.BookKeeperConstants;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.KeeperException.NodeExistsException;
+import org.apache.zookeeper.ZooDefs.Ids;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Implements a read only bookie.
+ * 
+ * ReadOnlyBookie is force started as readonly, and will not change to writable.
+ *
+ */
+public class ReadOnlyBookie extends Bookie {
+
+    private final static Logger LOG = LoggerFactory.getLogger(ReadOnlyBookie.class);
+
+    public ReadOnlyBookie(ServerConfiguration conf, StatsLogger statsLogger)
+            throws IOException, KeeperException, InterruptedException, BookieException {
+        super(conf, statsLogger);
+        if (conf.isReadOnlyModeEnabled()) {
+            readOnly.set(true);
+        } else {
+            String err = "Try to init ReadOnly Bookie, while ReadOnly mode is not enabled";
+            LOG.error(err);
+            throw new IOException(err);
+        }
+        LOG.info("successed call ReadOnlyBookie constructor");
+    }
+
+    /**
+     * Register as a read only bookie
+     */
+    @Override
+    protected void registerBookie(ServerConfiguration conf) throws IOException {
+        if (null == zk) {
+            // zookeeper instance is null, means not register itself to zk
+            return;
+        }
+
+        // ZK node for this ReadOnly Bookie.
+        try{
+            if (null == zk.exists(this.bookieRegistrationPath
+                        + BookKeeperConstants.READONLY, false)) {
+                try {
+                    zk.create(this.bookieRegistrationPath
+                            + BookKeeperConstants.READONLY + "/", new byte[0],
+                            Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+                    LOG.debug("successed create ReadOnlyBookie parent zk node");
+                } catch (NodeExistsException e) {
+                    // this node is just now created by someone.
+                }
+            }
+
+            if (!checkRegNodeAndWaitExpired(zkBookieReadOnlyPath)) {
+                // Create the ZK node for this RO Bookie.
+                zk.create(zkBookieReadOnlyPath, new byte[0], Ids.OPEN_ACL_UNSAFE,
+                        CreateMode.EPHEMERAL);
+                LOG.debug("successed create ReadOnlyBookie zk node");
+            }
+        } catch (KeeperException ke) {
+            LOG.error("ZK exception registering Znode for ReadOnly Bookie!", ke);
+            // Throw an IOException back up. This will cause the Bookie
+            // constructor to error out. Alternatively, we could do a System
+            // exit here as this is a fatal error.
+            throw new IOException(ke);
+        } catch (InterruptedException ie) {
+            LOG.error("Interruptted exception registering Znode for ReadOnly Bookie!",
+                    ie);
+            // Throw an IOException back up. This will cause the Bookie
+            // constructor to error out. Alternatively, we could do a System
+            // exit here as this is a fatal error.
+            throw new IOException(ie);
+        }
+    }
+
+    @VisibleForTesting
+    @Override
+    public void transitionToWritableMode() {
+        LOG.info("Skip transition to writable mode for readonly bookie");
+    }
+
+
+    @VisibleForTesting
+    @Override
+    public void transitionToReadOnlyMode() {
+        LOG.warn("Skip transition to readonly mode for readonly bookie");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/bookkeeper/blob/4050e796/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ServerConfiguration.java
----------------------------------------------------------------------
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ServerConfiguration.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ServerConfiguration.java
index 03b3be4..a06e770 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ServerConfiguration.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ServerConfiguration.java
@@ -82,6 +82,8 @@ public class ServerConfiguration extends AbstractConfiguration {
     protected final static String OPEN_LEDGER_REREPLICATION_GRACE_PERIOD = "openLedgerRereplicationGracePeriod";
     //ReadOnly mode support on all disk full
     protected final static String READ_ONLY_MODE_ENABLED = "readOnlyModeEnabled";
+    //Whether the bookie is force started in ReadOnly mode
+    protected final static String FORCE_READ_ONLY_BOOKIE = "forceReadOnlyBookie";
     //Disk utilization
     protected final static String DISK_USAGE_THRESHOLD = "diskUsageThreshold";
     protected final static String DISK_USAGE_WARN_THRESHOLD = "diskUsageWarnThreshold";
@@ -1218,6 +1220,29 @@ public class ServerConfiguration extends AbstractConfiguration {
     }
 
     /**
+     * Sets that whether force start a bookie in readonly mode
+     *
+     * @param enabled
+     *            - true if need to start a bookie in read only mode. Otherwise
+     *            false.
+     * @return ServerConfiguration
+     */
+    public ServerConfiguration setForceReadOnlyBookie(boolean enabled) {
+        setProperty(FORCE_READ_ONLY_BOOKIE, enabled);
+        return this;
+    }
+
+    /**
+     * Get whether the Bookie is force started in read only mode or not
+     *
+     * @return true - if need to start a bookie in read only mode. Otherwise
+     *         false.
+     */
+    public boolean isForceReadOnlyBookie() {
+        return getBoolean(FORCE_READ_ONLY_BOOKIE, false);
+    }
+
+    /**
      * Get the maximum number of entries which can be compacted without flushing.
      * Default is 100,000.
      *

http://git-wip-us.apache.org/repos/asf/bookkeeper/blob/4050e796/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java
----------------------------------------------------------------------
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java
index bf5f438..8e8349a 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java
@@ -26,6 +26,7 @@ import java.net.MalformedURLException;
 import java.net.UnknownHostException;
 
 import org.apache.bookkeeper.bookie.Bookie;
+import org.apache.bookkeeper.bookie.ReadOnlyBookie;
 import org.apache.bookkeeper.bookie.BookieCriticalThread;
 import org.apache.bookkeeper.bookie.BookieException;
 import org.apache.bookkeeper.bookie.ExitCode;
@@ -104,7 +105,9 @@ public class BookieServer {
 
     protected Bookie newBookie(ServerConfiguration conf)
         throws IOException, KeeperException, InterruptedException, BookieException {
-        return new Bookie(conf, statsLogger.scope(BOOKIE_SCOPE));
+        return conf.isForceReadOnlyBookie() ? 
+                new ReadOnlyBookie(conf, statsLogger.scope(BOOKIE_SCOPE)) :
+                new Bookie(conf, statsLogger.scope(BOOKIE_SCOPE));
     }
 
     public void start() throws IOException, UnavailableException {
@@ -265,6 +268,8 @@ public class BookieServer {
         bkOpts.addOption("c", "conf", true, "Configuration for Bookie Server");
         bkOpts.addOption("withAutoRecovery", false,
                 "Start Autorecovery service Bookie server");
+        bkOpts.addOption("readOnly", false,
+                "Force Start a ReadOnly Bookie server");
         bkOpts.addOption("h", "help", false, "Print help message");
     }
 
@@ -318,6 +323,10 @@ public class BookieServer {
                 conf.setAutoRecoveryDaemonEnabled(true);
             }
 
+            if (cmdLine.hasOption("readOnly")) {
+                conf.setForceReadOnlyBookie(true);
+            }
+
             if (leftArgs.length < 4) {
                 throw new IllegalArgumentException();
             }

http://git-wip-us.apache.org/repos/asf/bookkeeper/blob/4050e796/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/BookKeeperClusterTestCase.java
----------------------------------------------------------------------
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/BookKeeperClusterTestCase.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/BookKeeperClusterTestCase.java
index fbb79b0..0223a66 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/BookKeeperClusterTestCase.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/BookKeeperClusterTestCase.java
@@ -413,8 +413,12 @@ public abstract class BookKeeperClusterTestCase {
         if (conf.getUseHostNameAsBookieID()) {
             host = InetAddress.getLocalHost().getCanonicalHostName();
         }
-        while (bkc.getZkHandle().exists(
-                "/ledgers/available/" + host + ":" + port, false) == null) {
+        
+        while ( (!conf.isForceReadOnlyBookie() && (bkc.getZkHandle().exists(
+                    "/ledgers/available/" + host + ":" + port, false) == null)) ||
+                ( conf.isForceReadOnlyBookie() && ((bkc.getZkHandle().exists(
+                    "/ledgers/available/readonly/" + host + ":" + port, false) == null)))
+              ) {
             Thread.sleep(500);
         }
 

http://git-wip-us.apache.org/repos/asf/bookkeeper/blob/4050e796/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/ForceReadOnlyBookieTest.java
----------------------------------------------------------------------
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/ForceReadOnlyBookieTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/ForceReadOnlyBookieTest.java
new file mode 100644
index 0000000..914c7e2
--- /dev/null
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/test/ForceReadOnlyBookieTest.java
@@ -0,0 +1,95 @@
+/**
+ *
+ * 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.bookkeeper.test;
+
+import java.io.File;
+import java.util.Enumeration;
+
+import org.apache.bookkeeper.bookie.Bookie;
+import org.apache.bookkeeper.bookie.LedgerDirsManager;
+import org.apache.bookkeeper.client.BookKeeper.DigestType;
+import org.apache.bookkeeper.client.LedgerEntry;
+import org.apache.bookkeeper.client.LedgerHandle;
+import org.junit.Test;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import static org.junit.Assert.*;
+
+/**
+ * Test to verify force start readonly bookie
+ */
+public class ForceReadOnlyBookieTest extends BookKeeperClusterTestCase {
+
+    private final static Logger LOG = LoggerFactory.getLogger(ForceReadOnlyBookieTest.class);
+    public ForceReadOnlyBookieTest() {
+        super(2);
+        baseConf.setSortedLedgerStorageEnabled(false);
+        baseConf.setEntryLogFilePreAllocationEnabled(false);
+    }
+
+    /**
+     * Check force start readonly bookie
+     */
+    @Test(timeout = 60000)
+    public void testBookieForceStartAsReadOnly() throws Exception {
+        // create ledger, add entries
+        LedgerHandle ledger = bkc.createLedger(2, 2, DigestType.MAC,
+                "".getBytes());
+        for (int i = 0; i < 10; i++) {
+            ledger.addEntry("data".getBytes());
+        }
+        ledger.close();
+        LOG.info("successed prepare");
+
+        // start bookie 1 as readonly
+        bsConfs.get(1).setReadOnlyModeEnabled(true);
+        bsConfs.get(1).setForceReadOnlyBookie(true);
+        restartBookies();
+        Bookie bookie = bs.get(1).getBookie();
+        
+        assertTrue("Bookie should be running and in readonly mode",
+                bookie.isRunning() && bookie.isReadOnly());
+        LOG.info("successed force start ReadOnlyBookie");
+ 
+        // Check new bookie with readonly mode enabled.
+        File[] ledgerDirs = bsConfs.get(1).getLedgerDirs();
+        assertEquals("Only one ledger dir should be present", 1, ledgerDirs.length);
+
+        // kill the writable bookie
+        killBookie(0);
+        // read entry from read only bookie
+        Enumeration<LedgerEntry> readEntries = ledger.readEntries(0, 9);
+        while (readEntries.hasMoreElements()) {
+            LedgerEntry entry = readEntries.nextElement();
+            assertEquals("Entry should contain correct data", "data",
+                    new String(entry.getEntry()));
+        }
+        LOG.info("successed read entry from ReadOnlyBookie");
+
+        // test will not transfer to Writable mode.
+        LedgerDirsManager ledgerDirsManager = bookie.getLedgerDirsManager();
+        ledgerDirsManager.addToWritableDirs(new File(ledgerDirs[0], "current"), true);
+        assertTrue("Bookie should be running and in readonly mode",
+                bookie.isRunning() && bookie.isReadOnly());
+        LOG.info("successed: bookie still readonly");
+    }
+}