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 <[email protected]> Authored: Mon Jan 12 15:18:55 2015 +0100 Committer: Ivan Kelly <[email protected]> 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"); + } +}
