You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@bookkeeper.apache.org by GitBox <gi...@apache.org> on 2018/12/03 12:37:05 UTC

[GitHub] ivankelly closed pull request #1852: Users of LedgerMetadata should use the api interface

ivankelly closed pull request #1852: Users of LedgerMetadata should use the api interface
URL: https://github.com/apache/bookkeeper/pull/1852
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java
index c58fa83441..9108ef36d2 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java
@@ -90,8 +90,8 @@
 import org.apache.bookkeeper.client.BookieInfoReader.BookieInfo;
 import org.apache.bookkeeper.client.LedgerEntry;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerMetadata;
 import org.apache.bookkeeper.client.UpdateLedgerOp;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.annotation.InterfaceAudience.Private;
 import org.apache.bookkeeper.common.util.OrderedExecutor;
 import org.apache.bookkeeper.conf.ClientConfiguration;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java
index a3c4007972..b8f703dc23 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java
@@ -1041,8 +1041,7 @@ public LedgerHandle createLedgerAdv(final long ledgerId,
             throw BKException.create(BKException.Code.UnexpectedConditionException);
         }
 
-        LOG.info("Ensemble: {} for ledger: {}", lh.getLedgerMetadata().getEnsemble(0L),
-                lh.getId());
+        LOG.info("Ensemble: {} for ledger: {}", lh.getLedgerMetadata().getEnsembleAt(0L), lh.getId());
 
         return lh;
     }
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
index 3f7cee45e8..3f837dbaf7 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
@@ -60,6 +60,7 @@
 import org.apache.bookkeeper.client.LedgerFragmentReplicator.SingleFragmentCallback;
 import org.apache.bookkeeper.client.SyncCallbackUtils.SyncOpenCallback;
 import org.apache.bookkeeper.client.SyncCallbackUtils.SyncReadCallback;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.conf.ClientConfiguration;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.discover.RegistrationClient.RegistrationListener;
@@ -500,7 +501,8 @@ public void process(final Long lid, final AsyncCallback.VoidCallback cb) {
                                 cb.processResult(BKException.getExceptionCode(exception), null, null);
                                 return;
                             }
-                            Set<BookieSocketAddress> bookiesInLedger = metadata.getValue().getBookiesInThisLedger();
+                            Set<BookieSocketAddress> bookiesInLedger =
+                                LedgerMetadataUtils.getBookiesInThisLedger(metadata.getValue());
                             Sets.SetView<BookieSocketAddress> intersection =
                                 Sets.intersection(bookiesInLedger, bookies);
                             if (!intersection.isEmpty()) {
@@ -739,7 +741,7 @@ public void openComplete(int rc, final LedgerHandle lh, Object ctx) {
                 }
 
                 LedgerMetadata lm = lh.getLedgerMetadata();
-                if (skipOpenLedgers && !lm.isClosed() && !lm.isInRecovery()) {
+                if (skipOpenLedgers && lm.getState() == LedgerMetadata.State.OPEN) {
                     LOG.info("Skip recovering open ledger {}.", lId);
                     try {
                         lh.close();
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/EnsembleUtils.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/EnsembleUtils.java
index e4ab118f7d..6995a069e1 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/EnsembleUtils.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/EnsembleUtils.java
@@ -28,6 +28,7 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 
 import org.slf4j.Logger;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerCreateOp.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerCreateOp.java
index d95665c035..eaa316360c 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerCreateOp.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerCreateOp.java
@@ -38,6 +38,7 @@
 import org.apache.bookkeeper.client.SyncCallbackUtils.SyncCreateCallback;
 import org.apache.bookkeeper.client.api.CreateAdvBuilder;
 import org.apache.bookkeeper.client.api.CreateBuilder;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.WriteAdvHandle;
 import org.apache.bookkeeper.client.api.WriteFlag;
 import org.apache.bookkeeper.client.api.WriteHandle;
@@ -216,7 +217,7 @@ private void metadataCallback(Versioned<LedgerMetadata> writtenMetadata, Throwab
                 return;
             }
 
-            List<BookieSocketAddress> curEns = lh.getLedgerMetadata().getEnsemble(0L);
+            List<BookieSocketAddress> curEns = lh.getLedgerMetadata().getEnsembleAt(0L);
             LOG.info("Ensemble: {} for ledger: {}", curEns, lh.getId());
 
             for (BookieSocketAddress bsa : curEns) {
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerFragment.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerFragment.java
index e3224fdea9..382fe4e6b7 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerFragment.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerFragment.java
@@ -48,7 +48,7 @@
         this.firstEntryId = firstEntryId;
         this.lastKnownEntryId = lastKnownEntryId;
         this.bookieIndexes = bookieIndexes;
-        this.ensemble = lh.getLedgerMetadata().getEnsemble(firstEntryId);
+        this.ensemble = lh.getLedgerMetadata().getEnsembleAt(firstEntryId);
         this.schedule = lh.getDistributionSchedule();
         SortedMap<Long, ? extends List<BookieSocketAddress>> ensembles = lh
                 .getLedgerMetadata().getAllEnsembles();
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
index 9af3271308..075788bed3 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
@@ -67,6 +67,7 @@
 import org.apache.bookkeeper.client.api.BKException.Code;
 import org.apache.bookkeeper.client.api.LastConfirmedAndEntry;
 import org.apache.bookkeeper.client.api.LedgerEntries;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.WriteFlag;
 import org.apache.bookkeeper.client.api.WriteHandle;
 import org.apache.bookkeeper.client.impl.LedgerEntryImpl;
@@ -1688,7 +1689,7 @@ public long readExplicitLastConfirmed() throws InterruptedException, BKException
 
     // close the ledger and send fails to all the adds in the pipeline
     void handleUnrecoverableErrorDuringAdd(int rc) {
-        if (getLedgerMetadata().isInRecovery()) {
+        if (getLedgerMetadata().getState() == LedgerMetadata.State.IN_RECOVERY) {
             // we should not close ledger if ledger is recovery mode
             // otherwise we may lose entry.
             errorOutPendingAdds(rc);
@@ -1854,16 +1855,17 @@ void ensembleChangeLoop(List<BookieSocketAddress> origEnsemble, Map<Integer, Boo
         new MetadataUpdateLoop(
                 clientCtx.getLedgerManager(), getId(),
                 this::getVersionedLedgerMetadata,
-                (metadata) -> !metadata.isClosed() && !metadata.isInRecovery()
+                (metadata) -> metadata.getState() == LedgerMetadata.State.OPEN
                         && failedBookies.entrySet().stream().anyMatch(
-                                (e) -> metadata.getLastEnsembleValue().get(e.getKey()).equals(e.getValue())),
+                                e -> LedgerMetadataUtils.getLastEnsembleValue(metadata)
+                                             .get(e.getKey()).equals(e.getValue())),
                 (metadata) -> {
                     attempts.incrementAndGet();
 
                     List<BookieSocketAddress> currentEnsemble = getCurrentEnsemble();
                     List<BookieSocketAddress> newEnsemble = EnsembleUtils.replaceBookiesInEnsemble(
                             clientCtx.getBookieWatcher(), metadata, currentEnsemble, failedBookies, logContext);
-                    Long lastEnsembleKey = metadata.getLastEnsembleKey();
+                    Long lastEnsembleKey = LedgerMetadataUtils.getLastEnsembleKey(metadata);
                     LedgerMetadataBuilder builder = LedgerMetadataBuilder.from(metadata);
                     long newEnsembleStartEntry = getLastAddConfirmed() + 1;
                     checkState(lastEnsembleKey <= newEnsembleStartEntry,
@@ -1890,7 +1892,7 @@ void ensembleChangeLoop(List<BookieSocketAddress> origEnsemble, Map<Integer, Boo
                                       + " Another client must have recovered the ledger.", logContext, attempts.get());
                         }
                         handleUnrecoverableErrorDuringAdd(BKException.Code.LedgerClosedException);
-                    } else if (metadata.getValue().isInRecovery()) {
+                    } else if (metadata.getValue().getState() == LedgerMetadata.State.IN_RECOVERY) {
                         if (LOG.isDebugEnabled()) {
                             LOG.debug("{}[attempt:{}] Metadata marked as in-recovery during attempt to replace bookie."
                                       + " Another client must be recovering the ledger.", logContext, attempts.get());
@@ -1963,6 +1965,6 @@ public void closeComplete(int rc, LedgerHandle lh, Object ctx) {
         // Getting current ensemble from the metadata is only a temporary
         // thing until metadata is immutable. At that point, current ensemble
         // becomes a property of the LedgerHandle itself.
-        return versionedMetadata.getValue().getCurrentEnsemble();
+        return LedgerMetadataUtils.getCurrentEnsemble(versionedMetadata.getValue());
     }
 }
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandleAdv.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandleAdv.java
index 6f38b8e678..14317d0514 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandleAdv.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandleAdv.java
@@ -34,6 +34,7 @@
 import org.apache.bookkeeper.client.AsyncCallback.AddCallback;
 import org.apache.bookkeeper.client.AsyncCallback.AddCallbackWithLatency;
 import org.apache.bookkeeper.client.SyncCallbackUtils.SyncAddCallback;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.WriteAdvHandle;
 import org.apache.bookkeeper.client.api.WriteFlag;
 import org.apache.bookkeeper.util.SafeRunnable;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadataBuilder.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadataBuilder.java
index c5a329cc33..0c80315f4d 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadataBuilder.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadataBuilder.java
@@ -32,6 +32,7 @@
 import java.util.TreeMap;
 
 import org.apache.bookkeeper.client.api.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.LedgerMetadata.State;
 import org.apache.bookkeeper.common.annotation.InterfaceAudience.LimitedPrivate;
 import org.apache.bookkeeper.common.annotation.InterfaceStability.Unstable;
@@ -87,7 +88,9 @@ public static LedgerMetadataBuilder from(LedgerMetadata other) {
         }
 
         builder.ctime = other.getCtime();
-        builder.storeCtime = other.storeCtime;
+
+        /** Hack to get around fact that ctime was never versioned correctly */
+        builder.storeCtime = LedgerMetadataUtils.shouldStoreCtime(other);
 
         builder.customMetadata = ImmutableMap.copyOf(other.getCustomMetadata());
 
@@ -182,11 +185,11 @@ public LedgerMetadata build() {
         checkArgument(ensembleSize >= writeQuorumSize, "Write quorum must be less or equal to ensemble size");
         checkArgument(writeQuorumSize >= ackQuorumSize, "Write quorum must be greater or equal to ack quorum");
 
-        return new LedgerMetadata(metadataFormatVersion,
-                                  ensembleSize, writeQuorumSize, ackQuorumSize,
-                                  state, lastEntryId, length, ensembles,
-                                  digestType, password, ctime, storeCtime,
-                                  customMetadata);
+        return new LedgerMetadataImpl(metadataFormatVersion,
+                                      ensembleSize, writeQuorumSize, ackQuorumSize,
+                                      state, lastEntryId, length, ensembles,
+                                      digestType, password, ctime, storeCtime,
+                                      customMetadata);
     }
 
 }
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadata.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadataImpl.java
similarity index 76%
rename from bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadata.java
rename to bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadataImpl.java
index fa32f67b33..c0fcadd07e 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadata.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadataImpl.java
@@ -18,26 +18,22 @@
 package org.apache.bookkeeper.client;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
 
-import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableMap;
 import java.util.Optional;
-import java.util.Set;
-import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
 import lombok.EqualsAndHashCode;
 import org.apache.bookkeeper.client.api.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.LedgerMetadata.State;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.slf4j.Logger;
@@ -50,8 +46,8 @@
  * <p>It provides parsing and serialization methods of such metadata.
  */
 @EqualsAndHashCode
-public class LedgerMetadata implements org.apache.bookkeeper.client.api.LedgerMetadata {
-    static final Logger LOG = LoggerFactory.getLogger(LedgerMetadata.class);
+class LedgerMetadataImpl implements LedgerMetadata {
+    static final Logger LOG = LoggerFactory.getLogger(LedgerMetadataImpl.class);
 
     private final int metadataFormatVersion;
     private final int ensembleSize;
@@ -73,19 +69,19 @@
 
     private final Map<String, byte[]> customMetadata;
 
-    LedgerMetadata(int metadataFormatVersion,
-                   int ensembleSize,
-                   int writeQuorumSize,
-                   int ackQuorumSize,
-                   State state,
-                   Optional<Long> lastEntryId,
-                   Optional<Long> length,
-                   Map<Long, List<BookieSocketAddress>> ensembles,
-                   Optional<DigestType> digestType,
-                   Optional<byte[]> password,
-                   long ctime,
-                   boolean storeCtime,
-                   Map<String, byte[]> customMetadata) {
+    LedgerMetadataImpl(int metadataFormatVersion,
+                       int ensembleSize,
+                       int writeQuorumSize,
+                       int ackQuorumSize,
+                       State state,
+                       Optional<Long> lastEntryId,
+                       Optional<Long> length,
+                       Map<Long, List<BookieSocketAddress>> ensembles,
+                       Optional<DigestType> digestType,
+                       Optional<byte[]> password,
+                       long ctime,
+                       boolean storeCtime,
+                       Map<String, byte[]> customMetadata) {
         checkArgument(ensembles.size() > 0, "There must be at least one ensemble in the ledger");
         if (state == State.CLOSED) {
             checkArgument(length.isPresent(), "Closed ledger must have a length");
@@ -165,11 +161,12 @@ public long getCtime() {
      *
      * @return whether the password has been stored in the metadata
      */
+    @Override
     public boolean hasPassword() {
         return hasPassword;
     }
 
-    @VisibleForTesting
+    @Override
     public byte[] getPassword() {
         if (!hasPassword()) {
             return new byte[0];
@@ -202,47 +199,18 @@ public boolean isClosed() {
         return state == State.CLOSED;
     }
 
-    public boolean isInRecovery() {
-        return state == State.IN_RECOVERY;
-    }
-
     @Override
     public State getState() {
         return state;
     }
 
-    List<BookieSocketAddress> getCurrentEnsemble() {
-        return currentEnsemble;
-    }
-
-    List<BookieSocketAddress> getEnsemble(long entryId) {
+    @Override
+    public List<BookieSocketAddress> getEnsembleAt(long entryId) {
         // the head map cannot be empty, since we insert an ensemble for
         // entry-id 0, right when we start
         return ensembles.get(ensembles.headMap(entryId + 1).lastKey());
     }
 
-    @Override
-    public List<BookieSocketAddress> getEnsembleAt(long entryId) {
-        return getEnsemble(entryId);
-    }
-
-    /**
-     * the entry id greater than the given entry-id at which the next ensemble change takes
-     * place.
-     *
-     * @param entryId
-     * @return the entry id of the next ensemble change (-1 if no further ensemble changes)
-     */
-    long getNextEnsembleChange(long entryId) {
-        SortedMap<Long, ? extends List<BookieSocketAddress>> tailMap = ensembles.tailMap(entryId + 1);
-
-        if (tailMap.isEmpty()) {
-            return -1;
-        } else {
-            return tailMap.firstKey();
-        }
-    }
-
     @Override
     public Map<String, byte[]> getCustomMetadata() {
         return this.customMetadata;
@@ -260,6 +228,7 @@ public String toString() {
      * @return a string representation of the object without password field in
      *         it.
      */
+    @Override
     public String toSafeString() {
         return toStringRepresentation(false);
     }
@@ -291,30 +260,12 @@ private String toStringRepresentation(boolean withPassword) {
         return helper.toString();
     }
 
-    Set<BookieSocketAddress> getBookiesInThisLedger() {
-        Set<BookieSocketAddress> bookies = new HashSet<BookieSocketAddress>();
-        for (List<BookieSocketAddress> ensemble : ensembles.values()) {
-            bookies.addAll(ensemble);
-        }
-        return bookies;
-    }
-
-    List<BookieSocketAddress> getLastEnsembleValue() {
-        checkState(!ensembles.isEmpty(), "Metadata should never be created with no ensembles");
-        return ensembles.lastEntry().getValue();
-    }
-
-    Long getLastEnsembleKey() {
-        checkState(!ensembles.isEmpty(), "Metadata should never be created with no ensembles");
-        return ensembles.lastKey();
-    }
-
+    @Override
     public int getMetadataFormatVersion() {
         return metadataFormatVersion;
     }
 
-    // temporarily method, until storeCtime is removed from the metadata object itself
-    public boolean shouldStoreCtime() {
+    boolean shouldStoreCtime() {
         return storeCtime;
     }
 }
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadataUtils.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadataUtils.java
new file mode 100644
index 0000000000..07d7340961
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadataUtils.java
@@ -0,0 +1,79 @@
+/**
+ * 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.client;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedMap;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
+import org.apache.bookkeeper.net.BookieSocketAddress;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utilities for working with ledger metadata.
+ */
+public class LedgerMetadataUtils {
+    static final Logger LOG = LoggerFactory.getLogger(LedgerMetadataUtils.class);
+
+    static List<BookieSocketAddress> getCurrentEnsemble(LedgerMetadata metadata) {
+        return getLastEnsembleValue(metadata);
+    }
+
+    /**
+     * the entry id greater than the given entry-id at which the next ensemble change takes
+     * place.
+     *
+     * @param entryId
+     * @return the entry id of the next ensemble change (-1 if no further ensemble changes)
+     */
+    static long getNextEnsembleChange(LedgerMetadata metadata, long entryId) {
+        SortedMap<Long, ? extends List<BookieSocketAddress>> tailMap = metadata.getAllEnsembles().tailMap(entryId + 1);
+
+        if (tailMap.isEmpty()) {
+            return -1;
+        } else {
+            return tailMap.firstKey();
+        }
+    }
+
+    static Set<BookieSocketAddress> getBookiesInThisLedger(LedgerMetadata metadata) {
+        Set<BookieSocketAddress> bookies = new HashSet<BookieSocketAddress>();
+        for (List<BookieSocketAddress> ensemble : metadata.getAllEnsembles().values()) {
+            bookies.addAll(ensemble);
+        }
+        return bookies;
+    }
+
+    static List<BookieSocketAddress> getLastEnsembleValue(LedgerMetadata metadata) {
+        checkArgument(!metadata.getAllEnsembles().isEmpty(), "Metadata should never be created with no ensembles");
+        return metadata.getAllEnsembles().lastEntry().getValue();
+    }
+
+    static Long getLastEnsembleKey(LedgerMetadata metadata) {
+        checkArgument(!metadata.getAllEnsembles().isEmpty(), "Metadata should never be created with no ensembles");
+        return metadata.getAllEnsembles().lastKey();
+    }
+
+    public static boolean shouldStoreCtime(LedgerMetadata metadata) {
+        return metadata instanceof LedgerMetadataImpl && ((LedgerMetadataImpl) metadata).shouldStoreCtime();
+    }
+}
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerOpenOp.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerOpenOp.java
index c2dc33a6c6..2193b3ef9f 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerOpenOp.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerOpenOp.java
@@ -34,6 +34,7 @@
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
 import org.apache.bookkeeper.client.SyncCallbackUtils.SyncOpenCallback;
 import org.apache.bookkeeper.client.api.BKException.Code;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.ReadHandle;
 import org.apache.bookkeeper.client.impl.OpenBuilderBase;
 import org.apache.bookkeeper.stats.OpStatsLogger;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerRecoveryOp.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerRecoveryOp.java
index bc2985a957..2dd6ea2799 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerRecoveryOp.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerRecoveryOp.java
@@ -22,6 +22,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicLong;
 import org.apache.bookkeeper.client.AsyncCallback.AddCallback;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.ReadEntryListener;
 import org.apache.bookkeeper.proto.checksum.DigestManager.RecoveryData;
 import org.slf4j.Logger;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/MetadataUpdateLoop.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/MetadataUpdateLoop.java
index 435ff69f62..5c8cb14e65 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/MetadataUpdateLoop.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/MetadataUpdateLoop.java
@@ -23,6 +23,7 @@
 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
 import java.util.function.Supplier;
 
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.meta.LedgerManager;
 import org.apache.bookkeeper.versioning.Version;
 import org.apache.bookkeeper.versioning.Versioned;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/PendingReadOp.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/PendingReadOp.java
index a66d889a89..aa89eaa7f9 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/PendingReadOp.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/PendingReadOp.java
@@ -35,6 +35,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.apache.bookkeeper.client.BKException.BKDigestMatchException;
 import org.apache.bookkeeper.client.api.LedgerEntries;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.impl.LedgerEntriesImpl;
 import org.apache.bookkeeper.client.impl.LedgerEntryImpl;
 import org.apache.bookkeeper.common.util.SafeRunnable;
@@ -506,8 +507,8 @@ void initiate() {
         List<BookieSocketAddress> ensemble = null;
         do {
             if (i == nextEnsembleChange) {
-                ensemble = getLedgerMetadata().getEnsemble(i);
-                nextEnsembleChange = getLedgerMetadata().getNextEnsembleChange(i);
+                ensemble = getLedgerMetadata().getEnsembleAt(i);
+                nextEnsembleChange = LedgerMetadataUtils.getNextEnsembleChange(getLedgerMetadata(), i);
             }
             LedgerEntryRequest entry;
             if (parallelRead) {
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ReadLastConfirmedAndEntryOp.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ReadLastConfirmedAndEntryOp.java
index cb9de32111..ed1bece65f 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ReadLastConfirmedAndEntryOp.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ReadLastConfirmedAndEntryOp.java
@@ -28,6 +28,7 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.impl.LedgerEntryImpl;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.proto.BookieProtocol;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ReadOnlyLedgerHandle.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ReadOnlyLedgerHandle.java
index 59ddd5651b..72930f7ace 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ReadOnlyLedgerHandle.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ReadOnlyLedgerHandle.java
@@ -37,6 +37,7 @@
 import org.apache.bookkeeper.client.AsyncCallback.CloseCallback;
 import org.apache.bookkeeper.client.AsyncCallback.ReadCallback;
 import org.apache.bookkeeper.client.AsyncCallback.ReadLastConfirmedCallback;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.WriteFlag;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.GenericCallback;
@@ -266,11 +267,11 @@ void recover(GenericCallback<Void> finalCb,
             clientCtx.getClientStats().getRecoverOpLogger());
 
         MetadataUpdateLoop.NeedsUpdatePredicate needsUpdate =
-            (metadata) -> !(metadata.isClosed() || metadata.isInRecovery());
+            (metadata) -> metadata.getState() == LedgerMetadata.State.OPEN;
         if (forceRecovery) {
             // in the force recovery case, we want to update the metadata
             // to IN_RECOVERY, even if the ledger is already closed
-            needsUpdate = (metadata) -> !metadata.isInRecovery();
+            needsUpdate = (metadata) -> metadata.getState() != LedgerMetadata.State.IN_RECOVERY;
         }
         new MetadataUpdateLoop(
                 clientCtx.getLedgerManager(), getId(),
@@ -309,10 +310,10 @@ void recover(GenericCallback<Void> finalCb,
         CompletableFuture<Versioned<LedgerMetadata>> f = new MetadataUpdateLoop(
                 clientCtx.getLedgerManager(), getId(),
                 this::getVersionedLedgerMetadata,
-                (metadata) -> metadata.isInRecovery(),
+                (metadata) -> metadata.getState() == LedgerMetadata.State.IN_RECOVERY,
                 (metadata) -> {
                     LedgerMetadataBuilder builder = LedgerMetadataBuilder.from(metadata);
-                    Long lastEnsembleKey = metadata.getLastEnsembleKey();
+                    Long lastEnsembleKey = LedgerMetadataUtils.getLastEnsembleKey(metadata);
                     synchronized (metadataLock) {
                         newEnsemblesFromRecovery.entrySet().forEach(
                                 (e) -> {
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/UpdateLedgerOp.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/UpdateLedgerOp.java
index 98c544e9a2..6fce7e1fc3 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/UpdateLedgerOp.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/UpdateLedgerOp.java
@@ -35,6 +35,7 @@
 import java.util.stream.Collectors;
 
 import org.apache.bookkeeper.bookie.BookieShell.UpdateLedgerNotifier;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.meta.LedgerManager;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.versioning.Versioned;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/LedgerMetadata.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/LedgerMetadata.java
index de86832a88..2ce19404d0 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/LedgerMetadata.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/LedgerMetadata.java
@@ -74,8 +74,25 @@
      */
     long getLength();
 
+    /**
+     * Whether the metadata contains the password and digest type for the ledger.
+     * Ledgers created with version 4.1.0 clients or older do not have this information.
+     *
+     * @return true if the metadata contains the password and digest type, false otherwise.
+     */
+    boolean hasPassword();
+
+    /**
+     * Get the password for the ledger.
+     * For ledgers created with version 4.1.0 or older, an empty byte array is returned.
+     *
+     * @return the password for the ledger.
+     */
+    byte[] getPassword();
+
     /**
      * Returns the digest type used by this ledger.
+     * May return null if the ledger was created with version 4.1.0 or below.
      *
      * @return the digest type used by this ledger.
      */
@@ -143,4 +160,18 @@
         */
         CLOSED;
     }
+
+    /**
+     * Similar to #toString(), but omits the password of the ledger, so that it is safe to log the output.
+     *
+     * @return a string representation of the metadata, omitting the password.
+     */
+    String toSafeString();
+
+    /**
+     * Get the format version which should be used to serialize the metadata.
+     *
+     * @return the format version.
+     */
+    int getMetadataFormatVersion();
 }
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/AbstractConfiguration.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/AbstractConfiguration.java
index e395cb3825..7f00d09adb 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/AbstractConfiguration.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/AbstractConfiguration.java
@@ -84,6 +84,7 @@
     protected static final String REREPLICATION_ENTRY_BATCH_SIZE = "rereplicationEntryBatchSize";
     protected static final String STORE_SYSTEMTIME_AS_LEDGER_UNDERREPLICATED_MARK_TIME =
             "storeSystemTimeAsLedgerUnderreplicatedMarkTime";
+    protected static final String STORE_SYSTEMTIME_AS_LEDGER_CREATION_TIME = "storeSystemTimeAsLedgerCreationTime";
 
     // Metastore settings, only being used when LEDGER_MANAGER_FACTORY_CLASS is MSLedgerManagerFactory
     protected static final String METASTORE_IMPL_CLASS = "metastoreImplClass";
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ClientConfiguration.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ClientConfiguration.java
index 86ec44457b..7d0d319882 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ClientConfiguration.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ClientConfiguration.java
@@ -154,9 +154,6 @@
     protected static final String ENSEMBLE_PLACEMENT_POLICY_ORDER_SLOW_BOOKIES =
         "ensemblePlacementPolicyOrderSlowBookies";
 
-    // Ledger Metadata Parameters
-    protected static final String STORE_SYSTEMTIME_AS_LEDGER_CREATION_TIME = "storeSystemTimeAsLedgerCreationTime";
-
     // Stats
     protected static final String ENABLE_TASK_EXECUTION_STATS = "enableTaskExecutionStats";
     protected static final String TASK_EXECUTION_WARN_TIME_MICROS = "taskExecutionWarnTimeMicros";
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/AbstractZkLedgerManager.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/AbstractZkLedgerManager.java
index 2bd9b13900..5dbdd06791 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/AbstractZkLedgerManager.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/AbstractZkLedgerManager.java
@@ -36,7 +36,7 @@
 import java.util.concurrent.TimeUnit;
 
 import org.apache.bookkeeper.client.BKException;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
 import org.apache.bookkeeper.conf.AbstractConfiguration;
 import org.apache.bookkeeper.meta.zk.ZKMetadataDriverBase;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/CleanupLedgerManager.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/CleanupLedgerManager.java
index 311cb0aa3e..25c5aca1c6 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/CleanupLedgerManager.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/CleanupLedgerManager.java
@@ -28,7 +28,7 @@
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import org.apache.bookkeeper.client.BKException;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.GenericCallback;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.LedgerMetadataListener;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/LedgerManager.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/LedgerManager.java
index 8bcda063ad..cc7630b10b 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/LedgerManager.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/LedgerManager.java
@@ -24,7 +24,7 @@
 import java.util.TreeSet;
 import java.util.concurrent.CompletableFuture;
 
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.LedgerMetadataListener;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.Processor;
 import org.apache.bookkeeper.versioning.Version;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/LedgerMetadataSerDe.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/LedgerMetadataSerDe.java
index 26b616b6ec..6020a3bf4f 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/LedgerMetadataSerDe.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/LedgerMetadataSerDe.java
@@ -34,9 +34,10 @@
 import java.util.Optional;
 import java.util.stream.Collectors;
 
-import org.apache.bookkeeper.client.LedgerMetadata;
 import org.apache.bookkeeper.client.LedgerMetadataBuilder;
+import org.apache.bookkeeper.client.LedgerMetadataUtils;
 import org.apache.bookkeeper.client.api.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.LedgerMetadata.State;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.proto.DataFormats.LedgerMetadataFormat;
@@ -135,7 +136,8 @@ public LedgerMetadataFormat buildProtoFormat(LedgerMetadata metadata) {
             break;
         }
 
-        if (metadata.shouldStoreCtime()) {
+        /** Hack to get around fact that ctime was never versioned correctly */
+        if (LedgerMetadataUtils.shouldStoreCtime(metadata)) {
             builder.setCtime(metadata.getCtime());
         }
 
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/MSLedgerManagerFactory.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/MSLedgerManagerFactory.java
index 7e28217d02..fc87632445 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/MSLedgerManagerFactory.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/meta/MSLedgerManagerFactory.java
@@ -39,7 +39,7 @@
 import java.util.concurrent.TimeUnit;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.bookkeeper.client.BKException;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
 import org.apache.bookkeeper.conf.AbstractConfiguration;
 import org.apache.bookkeeper.meta.zk.ZKMetadataDriverBase;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookkeeperInternalCallbacks.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookkeeperInternalCallbacks.java
index 8412e06f42..2ade9e9c29 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookkeeperInternalCallbacks.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookkeeperInternalCallbacks.java
@@ -34,7 +34,7 @@
 import org.apache.bookkeeper.client.BookieInfoReader.BookieInfo;
 import org.apache.bookkeeper.client.LedgerEntry;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.stats.OpStatsLogger;
 import org.apache.bookkeeper.util.MathUtils;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/replication/ReplicationWorker.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/replication/ReplicationWorker.java
index 6339810b04..eeaa96bc9a 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/replication/ReplicationWorker.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/replication/ReplicationWorker.java
@@ -51,7 +51,7 @@
 import org.apache.bookkeeper.client.LedgerChecker;
 import org.apache.bookkeeper.client.LedgerFragment;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.meta.AbstractZkLedgerManagerFactory;
 import org.apache.bookkeeper.meta.LedgerManagerFactory;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/GetLedgerMetaService.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/GetLedgerMetaService.java
index 4225c08121..7fcaeda810 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/GetLedgerMetaService.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/GetLedgerMetaService.java
@@ -23,7 +23,7 @@
 
 import com.google.common.collect.Maps;
 import java.util.Map;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.util.JsonUtil;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.http.HttpServer;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/ListLedgerService.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/ListLedgerService.java
index 1df1b36ca0..f553b6431b 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/ListLedgerService.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/ListLedgerService.java
@@ -25,7 +25,7 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.util.JsonUtil;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.http.HttpServer;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieShellTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieShellTest.java
index 9f12e15e4c..386746651a 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieShellTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieShellTest.java
@@ -43,7 +43,7 @@
 import org.apache.bookkeeper.bookie.BookieShell.MyCommand;
 import org.apache.bookkeeper.bookie.BookieShell.RecoverCmd;
 import org.apache.bookkeeper.client.BookKeeperAdmin;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.conf.ClientConfiguration;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.discover.RegistrationManager;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/CompactionTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/CompactionTest.java
index e35c30973f..c110eeb99d 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/CompactionTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/CompactionTest.java
@@ -57,7 +57,7 @@
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
 import org.apache.bookkeeper.client.LedgerEntry;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.conf.TestBKConfiguration;
 import org.apache.bookkeeper.meta.LedgerManager;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/TestGcOverreplicatedLedger.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/TestGcOverreplicatedLedger.java
index 8020f1fa27..bb393294b0 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/TestGcOverreplicatedLedger.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/TestGcOverreplicatedLedger.java
@@ -33,7 +33,7 @@
 import org.apache.bookkeeper.bookie.GarbageCollector.GarbageCleaner;
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.meta.HierarchicalLedgerManagerFactory;
 import org.apache.bookkeeper.meta.LedgerManagerFactory;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookKeeperDiskSpaceWeightedLedgerPlacementTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookKeeperDiskSpaceWeightedLedgerPlacementTest.java
index fec5dd84b6..33512ae883 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookKeeperDiskSpaceWeightedLedgerPlacementTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookKeeperDiskSpaceWeightedLedgerPlacementTest.java
@@ -162,7 +162,7 @@ public void testDiskSpaceWeightedBookieSelection() throws Exception {
 
         for (int i = 0; i < 2000; i++) {
             LedgerHandle lh = client.createLedger(3, 3, DigestType.CRC32, "testPasswd".getBytes());
-            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsemble(0)) {
+            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsembleAt(0)) {
                 m.put(b, m.get(b) + 1);
             }
         }
@@ -212,7 +212,7 @@ public void testDiskSpaceWeightedBookieSelectionWithChangingWeights() throws Exc
 
         for (int i = 0; i < 2000; i++) {
             LedgerHandle lh = client.createLedger(3, 3, DigestType.CRC32, "testPasswd".getBytes());
-            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsemble(0)) {
+            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsembleAt(0)) {
                 m.put(b, m.get(b) + 1);
             }
         }
@@ -247,7 +247,7 @@ public void testDiskSpaceWeightedBookieSelectionWithChangingWeights() throws Exc
         }
         for (int i = 0; i < 2000; i++) {
             LedgerHandle lh = client.createLedger(3, 3, DigestType.CRC32, "testPasswd".getBytes());
-            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsemble(0)) {
+            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsembleAt(0)) {
                 m.put(b, m.get(b) + 1);
             }
         }
@@ -302,7 +302,7 @@ public void testDiskSpaceWeightedBookieSelectionWithBookiesDying() throws Except
 
         for (int i = 0; i < 2000; i++) {
             LedgerHandle lh = client.createLedger(3, 3, DigestType.CRC32, "testPasswd".getBytes());
-            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsemble(0)) {
+            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsembleAt(0)) {
                 m.put(b, m.get(b) + 1);
             }
         }
@@ -330,7 +330,7 @@ public void testDiskSpaceWeightedBookieSelectionWithBookiesDying() throws Except
 
         for (int i = 0; i < 2000; i++) {
             LedgerHandle lh = client.createLedger(3, 3, DigestType.CRC32, "testPasswd".getBytes());
-            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsemble(0)) {
+            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsembleAt(0)) {
                 m.put(b, m.get(b) + 1);
             }
         }
@@ -382,7 +382,7 @@ public void testDiskSpaceWeightedBookieSelectionWithBookiesBeingAdded() throws E
 
         for (int i = 0; i < 2000; i++) {
             LedgerHandle lh = client.createLedger(3, 3, DigestType.CRC32, "testPasswd".getBytes());
-            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsemble(0)) {
+            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsembleAt(0)) {
                 m.put(b, m.get(b) + 1);
             }
         }
@@ -406,7 +406,7 @@ public void testDiskSpaceWeightedBookieSelectionWithBookiesBeingAdded() throws E
         }
         for (int i = 0; i < 2000; i++) {
             LedgerHandle lh = client.createLedger(3, 3, DigestType.CRC32, "testPasswd".getBytes());
-            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsemble(0)) {
+            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsembleAt(0)) {
                 m.put(b, m.get(b) + 1);
             }
         }
@@ -462,7 +462,7 @@ public void testDiskSpaceWeightedBookieSelectionWithPeriodicBookieInfoUpdate() t
 
         for (int i = 0; i < 2000; i++) {
             LedgerHandle lh = client.createLedger(3, 3, DigestType.CRC32, "testPasswd".getBytes());
-            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsemble(0)) {
+            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsembleAt(0)) {
                 m.put(b, m.get(b) + 1);
             }
         }
@@ -491,7 +491,7 @@ public void testDiskSpaceWeightedBookieSelectionWithPeriodicBookieInfoUpdate() t
         }
         for (int i = 0; i < 2000; i++) {
             LedgerHandle lh = client.createLedger(3, 3, DigestType.CRC32, "testPasswd".getBytes());
-            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsemble(0)) {
+            for (BookieSocketAddress b : lh.getLedgerMetadata().getEnsembleAt(0)) {
                 m.put(b, m.get(b) + 1);
             }
         }
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookieRecoveryTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookieRecoveryTest.java
index 506f5122b8..2fc1d5fa3b 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookieRecoveryTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookieRecoveryTest.java
@@ -41,6 +41,7 @@
 
 import org.apache.bookkeeper.client.AsyncCallback.RecoverCallback;
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.conf.ClientConfiguration;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.net.BookieSocketAddress;
@@ -262,7 +263,7 @@ void metadataConflictWithRecovery(BookKeeper bkc) throws Exception {
         for (int i = 0; i < numEntries; i++) {
             lh.addEntry(data);
         }
-        BookieSocketAddress bookieToKill = lh.getLedgerMetadata().getEnsemble(numEntries - 1).get(1);
+        BookieSocketAddress bookieToKill = lh.getLedgerMetadata().getEnsembleAt(numEntries - 1).get(1);
         killBookie(bookieToKill);
         startNewBookie();
         for (int i = 0; i < numEntries; i++) {
@@ -270,7 +271,7 @@ void metadataConflictWithRecovery(BookKeeper bkc) throws Exception {
         }
         bkAdmin.recoverBookieData(bookieToKill);
         // fail another bookie to cause ensemble change again
-        bookieToKill = lh.getLedgerMetadata().getEnsemble(2 * numEntries - 1).get(1);
+        bookieToKill = lh.getLedgerMetadata().getEnsembleAt(2 * numEntries - 1).get(1);
         ServerConfiguration confOfKilledBookie = killBookie(bookieToKill);
         startNewBookie();
         for (int i = 0; i < numEntries; i++) {
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ClientUtil.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ClientUtil.java
index f56c350fb7..68a68463bb 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ClientUtil.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ClientUtil.java
@@ -25,6 +25,7 @@
 import java.security.GeneralSecurityException;
 import java.util.function.Function;
 
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.meta.LedgerManager;
 import org.apache.bookkeeper.proto.DataFormats.LedgerMetadataFormat.DigestType;
 import org.apache.bookkeeper.proto.checksum.DigestManager;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/GenericEnsemblePlacementPolicyTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/GenericEnsemblePlacementPolicyTest.java
index 8fbb009d24..bb55d0c6d0 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/GenericEnsemblePlacementPolicyTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/GenericEnsemblePlacementPolicyTest.java
@@ -144,7 +144,7 @@ public void testReplaceBookie() throws Exception {
                 try (LedgerHandle lh = bk.createLedger(2, 2, 2, digestType, PASSWORD.getBytes(), customMetadata)) {
                     lh.addEntry(value);
                     long lId = lh.getId();
-                    List<BookieSocketAddress> ensembleAtFirstEntry = lh.getLedgerMetadata().getEnsemble(lId);
+                    List<BookieSocketAddress> ensembleAtFirstEntry = lh.getLedgerMetadata().getEnsembleAt(lId);
                     assertEquals(2, ensembleAtFirstEntry.size());
                     killBookie(ensembleAtFirstEntry.get(0));
                     lh.addEntry(value);
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/HandleFailuresTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/HandleFailuresTest.java
index 2f75e12a83..9f95a94fbf 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/HandleFailuresTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/HandleFailuresTest.java
@@ -33,6 +33,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.WriteFlag;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
 import org.apache.bookkeeper.net.BookieSocketAddress;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerClose2Test.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerClose2Test.java
index 541af0a54d..45b2713593 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerClose2Test.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerClose2Test.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Lists;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.WriteFlag;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
 import org.apache.bookkeeper.net.BookieSocketAddress;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerHandleAdapter.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerHandleAdapter.java
index 7098f4ee47..9450e512f1 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerHandleAdapter.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerHandleAdapter.java
@@ -29,13 +29,6 @@
  */
 public class LedgerHandleAdapter {
 
-    /**
-     * Get the ledger handle.
-     */
-    public static LedgerMetadata getLedgerMetadata(LedgerHandle lh) {
-        return lh.getLedgerMetadata();
-    }
-
     public static ByteBufList toSend(LedgerHandle lh, long entryId, ByteBuf data) {
         return lh.getDigestManager().computeDigestAndPackageForSending(entryId, lh.getLastAddConfirmed(),
                 lh.addToLength(data.readableBytes()), data);
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerMetadataTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerMetadataTest.java
index 80e1c75980..a9ffc2d422 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerMetadataTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerMetadataTest.java
@@ -29,6 +29,7 @@
 import java.util.Collections;
 import java.util.List;
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.meta.LedgerMetadataSerDe;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.proto.DataFormats.LedgerMetadataFormat;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerRecovery2Test.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerRecovery2Test.java
index d9c5d35e2d..2e17836bd5 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerRecovery2Test.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/LedgerRecovery2Test.java
@@ -26,6 +26,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import org.apache.bookkeeper.client.api.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.GenericCallbackFuture;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MetadataUpdateLoopTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MetadataUpdateLoopTest.java
index 2544a20ef0..ffacb21f05 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MetadataUpdateLoopTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MetadataUpdateLoopTest.java
@@ -42,6 +42,7 @@
 import lombok.AllArgsConstructor;
 import lombok.Data;
 
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.meta.LedgerManager;
 import org.apache.bookkeeper.meta.MockLedgerManager;
 import org.apache.bookkeeper.net.BookieSocketAddress;
@@ -86,7 +87,7 @@ public void testBasicUpdate() throws Exception {
                     reference::get,
                     (currentMetadata) -> true,
                     (currentMetadata) -> {
-                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsemble(0L));
+                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsembleAt(0L));
                         ensemble.set(0, newAddress);
                         return LedgerMetadataBuilder.from(currentMetadata).replaceEnsembleEntry(0L, ensemble).build();
                     },
@@ -94,7 +95,7 @@ public void testBasicUpdate() throws Exception {
             loop.run().get();
 
             Assert.assertNotEquals(reference.get(), writtenMetadata);
-            Assert.assertEquals(reference.get().getValue().getEnsemble(0L).get(0), newAddress);
+            Assert.assertEquals(reference.get().getValue().getEnsembleAt(0L).get(0), newAddress);
         }
     }
 
@@ -123,9 +124,9 @@ public void testConflictOnWrite() throws Exception {
                     lm,
                     ledgerId,
                     reference1::get,
-                    (currentMetadata) -> currentMetadata.getEnsemble(0L).contains(b0),
+                    (currentMetadata) -> currentMetadata.getEnsembleAt(0L).contains(b0),
                     (currentMetadata) -> {
-                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsemble(0L));
+                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsembleAt(0L));
                         ensemble.set(0, b2);
                         return LedgerMetadataBuilder.from(currentMetadata).replaceEnsembleEntry(0L, ensemble).build();
                     },
@@ -136,9 +137,9 @@ public void testConflictOnWrite() throws Exception {
                     lm,
                     ledgerId,
                     reference2::get,
-                    (currentMetadata) -> currentMetadata.getEnsemble(0L).contains(b1),
+                    (currentMetadata) -> currentMetadata.getEnsembleAt(0L).contains(b1),
                     (currentMetadata) -> {
-                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsemble(0L));
+                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsembleAt(0L));
                         ensemble.set(1, b3);
                         return LedgerMetadataBuilder.from(currentMetadata).replaceEnsembleEntry(0L, ensemble).build();
                     },
@@ -154,11 +155,11 @@ public void testConflictOnWrite() throws Exception {
 
             Assert.assertEquals(l1meta.getVersion().compare(l2meta.getVersion()), Version.Occurred.BEFORE);
 
-            Assert.assertEquals(l1meta.getValue().getEnsemble(0L).get(0), b2);
-            Assert.assertEquals(l1meta.getValue().getEnsemble(0L).get(1), b1);
+            Assert.assertEquals(l1meta.getValue().getEnsembleAt(0L).get(0), b2);
+            Assert.assertEquals(l1meta.getValue().getEnsembleAt(0L).get(1), b1);
 
-            Assert.assertEquals(l2meta.getValue().getEnsemble(0L).get(0), b2);
-            Assert.assertEquals(l2meta.getValue().getEnsemble(0L).get(1), b3);
+            Assert.assertEquals(l2meta.getValue().getEnsembleAt(0L).get(0), b2);
+            Assert.assertEquals(l2meta.getValue().getEnsembleAt(0L).get(1), b3);
 
             verify(lm, times(3)).writeLedgerMetadata(anyLong(), any(), any());
         }
@@ -188,9 +189,9 @@ public void testConflictOnWriteBothWritingSame() throws Exception {
                     lm,
                     ledgerId,
                     reference::get,
-                    (currentMetadata) -> currentMetadata.getEnsemble(0L).contains(b0),
+                    (currentMetadata) -> currentMetadata.getEnsembleAt(0L).contains(b0),
                     (currentMetadata) -> {
-                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsemble(0L));
+                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsembleAt(0L));
                         ensemble.set(0, b2);
                         return LedgerMetadataBuilder.from(currentMetadata).replaceEnsembleEntry(0L, ensemble).build();
                     },
@@ -199,9 +200,9 @@ public void testConflictOnWriteBothWritingSame() throws Exception {
                     lm,
                     ledgerId,
                     reference::get,
-                    (currentMetadata) -> currentMetadata.getEnsemble(0L).contains(b0),
+                    (currentMetadata) -> currentMetadata.getEnsembleAt(0L).contains(b0),
                     (currentMetadata) -> {
-                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsemble(0L));
+                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsembleAt(0L));
                         ensemble.set(0, b2);
                         return LedgerMetadataBuilder.from(currentMetadata).replaceEnsembleEntry(0L, ensemble).build();
                     },
@@ -212,8 +213,8 @@ public void testConflictOnWriteBothWritingSame() throws Exception {
             Assert.assertEquals(loop1.get(), loop2.get());
             Assert.assertEquals(loop1.get(), reference.get());
 
-            Assert.assertEquals(reference.get().getValue().getEnsemble(0L).get(0), b2);
-            Assert.assertEquals(reference.get().getValue().getEnsemble(0L).get(1), b1);
+            Assert.assertEquals(reference.get().getValue().getEnsembleAt(0L).get(0), b2);
+            Assert.assertEquals(reference.get().getValue().getEnsembleAt(0L).get(1), b1);
 
             verify(lm, times(2)).writeLedgerMetadata(anyLong(), any(), any());
         }
@@ -241,9 +242,9 @@ public void testConflictOnLocalUpdate() throws Exception {
                     lm,
                     ledgerId,
                     reference::get,
-                    (currentMetadata) -> currentMetadata.getEnsemble(0L).contains(b0),
+                    (currentMetadata) -> currentMetadata.getEnsembleAt(0L).contains(b0),
                     (currentMetadata) -> {
-                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsemble(0L));
+                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsembleAt(0L));
                         ensemble.set(0, b2);
                         return LedgerMetadataBuilder.from(currentMetadata).replaceEnsembleEntry(0L, ensemble).build();
                     },
@@ -254,9 +255,9 @@ public void testConflictOnLocalUpdate() throws Exception {
                     lm,
                     ledgerId,
                     reference::get,
-                    (currentMetadata) -> currentMetadata.getEnsemble(0L).contains(b1),
+                    (currentMetadata) -> currentMetadata.getEnsembleAt(0L).contains(b1),
                     (currentMetadata) -> {
-                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsemble(0L));
+                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsembleAt(0L));
                         ensemble.set(1, b3);
                         return LedgerMetadataBuilder.from(currentMetadata).replaceEnsembleEntry(0L, ensemble).build();
                     },
@@ -267,8 +268,8 @@ public void testConflictOnLocalUpdate() throws Exception {
 
             Assert.assertEquals(loop1.get(), reference.get());
 
-            Assert.assertEquals(reference.get().getValue().getEnsemble(0L).get(0), b2);
-            Assert.assertEquals(reference.get().getValue().getEnsemble(0L).get(1), b3);
+            Assert.assertEquals(reference.get().getValue().getEnsembleAt(0L).get(0), b2);
+            Assert.assertEquals(reference.get().getValue().getEnsembleAt(0L).get(1), b3);
 
             verify(lm, times(3)).writeLedgerMetadata(anyLong(), any(), any());
         }
@@ -312,9 +313,9 @@ public void testHammer() throws Exception {
                     lm,
                     ledgerId,
                     reference::get,
-                    (currentMetadata) -> currentMetadata.getEnsemble(0L).contains(initialEnsemble.get(i)),
+                    (currentMetadata) -> currentMetadata.getEnsembleAt(0L).contains(initialEnsemble.get(i)),
                     (currentMetadata) -> {
-                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsemble(0L));
+                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsembleAt(0L));
                         ensemble.set(i, replacementBookies.get(i));
                         return LedgerMetadataBuilder.from(currentMetadata).replaceEnsembleEntry(0L, ensemble).build();
                     },
@@ -323,7 +324,7 @@ public void testHammer() throws Exception {
 
             loops.forEach((l) -> l.join());
 
-            Assert.assertEquals(reference.get().getValue().getEnsemble(0L), replacementBookies);
+            Assert.assertEquals(reference.get().getValue().getEnsembleAt(0L), replacementBookies);
         }
     }
 
@@ -364,11 +365,11 @@ public void testNewestValueCannotBeUsedAfterReadBack() throws Exception {
                         if (currentMetadata.isClosed()) {
                             throw new BKException.BKLedgerClosedException();
                         } else {
-                            return currentMetadata.getEnsemble(0L).contains(b0);
+                            return currentMetadata.getEnsembleAt(0L).contains(b0);
                         }
                     },
                     (currentMetadata) -> {
-                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsemble(0L));
+                        List<BookieSocketAddress> ensemble = Lists.newArrayList(currentMetadata.getEnsembleAt(0L));
                         ensemble.set(0, b1);
                         return LedgerMetadataBuilder.from(currentMetadata).replaceEnsembleEntry(0L, ensemble).build();
                     },
@@ -383,7 +384,7 @@ public void testNewestValueCannotBeUsedAfterReadBack() throws Exception {
                 Assert.assertEquals(ee.getCause().getClass(), BKException.BKLedgerClosedException.class);
             }
             Assert.assertEquals(l1meta, reference.get());
-            Assert.assertEquals(l1meta.getValue().getEnsemble(0L).get(0), b0);
+            Assert.assertEquals(l1meta.getValue().getEnsembleAt(0L).get(0), b0);
             Assert.assertTrue(l1meta.getValue().isClosed());
 
             verify(lm, times(2)).writeLedgerMetadata(anyLong(), any(), any());
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockBookKeeperTestCase.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockBookKeeperTestCase.java
index a88f356108..6d6e4d15a2 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockBookKeeperTestCase.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockBookKeeperTestCase.java
@@ -53,6 +53,7 @@
 import org.apache.bookkeeper.client.BKException.Code;
 import org.apache.bookkeeper.client.api.CreateBuilder;
 import org.apache.bookkeeper.client.api.DeleteBuilder;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.OpenBuilder;
 import org.apache.bookkeeper.common.util.OrderedExecutor;
 import org.apache.bookkeeper.common.util.OrderedScheduler;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockLedgerHandle.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockLedgerHandle.java
index b7cb9e14a3..3aaeedb56a 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockLedgerHandle.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockLedgerHandle.java
@@ -39,6 +39,7 @@
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
 import org.apache.bookkeeper.client.api.LastConfirmedAndEntry;
 import org.apache.bookkeeper.client.api.LedgerEntries;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.ReadHandle;
 import org.apache.bookkeeper.client.api.WriteFlag;
 import org.apache.bookkeeper.client.impl.LedgerEntryImpl;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ParallelLedgerRecoveryTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ParallelLedgerRecoveryTest.java
index 9571d1e7fb..ab966fb4ae 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ParallelLedgerRecoveryTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ParallelLedgerRecoveryTest.java
@@ -43,6 +43,7 @@
 import org.apache.bookkeeper.bookie.BookieException;
 import org.apache.bookkeeper.bookie.InterleavedLedgerStorage;
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.WriteFlag;
 import org.apache.bookkeeper.conf.ClientConfiguration;
 import org.apache.bookkeeper.conf.ServerConfiguration;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ReadLastConfirmedAndEntryOpTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ReadLastConfirmedAndEntryOpTest.java
index 527ce8d39c..a1032a16e0 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ReadLastConfirmedAndEntryOpTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/ReadLastConfirmedAndEntryOpTest.java
@@ -47,6 +47,7 @@
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
 import org.apache.bookkeeper.client.ReadLastConfirmedAndEntryOp.LastConfirmedAndEntryCallback;
 import org.apache.bookkeeper.client.api.LastConfirmedAndEntry;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.impl.LastConfirmedAndEntryImpl;
 import org.apache.bookkeeper.client.impl.LedgerEntryImpl;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestBookieHealthCheck.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestBookieHealthCheck.java
index ad35450b4a..cbee711624 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestBookieHealthCheck.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestBookieHealthCheck.java
@@ -55,7 +55,7 @@ public void testBkQuarantine() throws Exception {
             lh.addEntry(msg);
         }
 
-        BookieSocketAddress bookieToQuarantine = lh.getLedgerMetadata().getEnsemble(numEntries).get(0);
+        BookieSocketAddress bookieToQuarantine = lh.getLedgerMetadata().getEnsembleAt(numEntries).get(0);
         sleepBookie(bookieToQuarantine, baseClientConf.getAddEntryTimeout() * 2).await();
 
         byte[] tempMsg = "temp-msg".getBytes();
@@ -79,12 +79,12 @@ public void addComplete(int rc, LedgerHandle lh, long entryId, Object ctx) {
         // the bookie to be left out of the ensemble should always be the quarantined bookie
         LedgerHandle lh1 = bkc.createLedger(2, 2, 2, BookKeeper.DigestType.CRC32, new byte[] {});
         LedgerHandle lh2 = bkc.createLedger(3, 3, 3, BookKeeper.DigestType.CRC32, new byte[] {});
-        Assert.assertFalse(lh1.getLedgerMetadata().getEnsemble(0).contains(bookieToQuarantine));
-        Assert.assertFalse(lh2.getLedgerMetadata().getEnsemble(0).contains(bookieToQuarantine));
+        Assert.assertFalse(lh1.getLedgerMetadata().getEnsembleAt(0).contains(bookieToQuarantine));
+        Assert.assertFalse(lh2.getLedgerMetadata().getEnsembleAt(0).contains(bookieToQuarantine));
 
         // the quarantined bookie can still be in the ensemble if we do not have enough healthy bookies
         LedgerHandle lh3 = bkc.createLedger(4, 4, 4, BookKeeper.DigestType.CRC32, new byte[] {});
-        Assert.assertTrue(lh3.getLedgerMetadata().getEnsemble(0).contains(bookieToQuarantine));
+        Assert.assertTrue(lh3.getLedgerMetadata().getEnsembleAt(0).contains(bookieToQuarantine));
 
         // make sure faulty bookie is out of quarantine
         Thread.sleep(baseClientConf.getBookieQuarantineTimeSeconds() * 1000);
@@ -97,7 +97,7 @@ public void addComplete(int rc, LedgerHandle lh, long entryId, Object ctx) {
     public void testNoQuarantineOnBkRestart() throws Exception {
         final LedgerHandle lh = bkc.createLedger(2, 2, 2, BookKeeper.DigestType.CRC32, new byte[] {});
         final int numEntries = 20;
-        BookieSocketAddress bookieToRestart = lh.getLedgerMetadata().getEnsemble(0).get(0);
+        BookieSocketAddress bookieToRestart = lh.getLedgerMetadata().getEnsembleAt(0).get(0);
 
         // we add entries on a separate thread so that we can restart a bookie on the current thread
         Thread addEntryThread = new Thread() {
@@ -132,8 +132,8 @@ public void testNoQuarantineOnExpectedBkErrors() throws Exception {
             byte[] msg = ("msg-" + i).getBytes();
             lh.addEntry(msg);
         }
-        BookieSocketAddress bookie1 = lh.getLedgerMetadata().getEnsemble(0).get(0);
-        BookieSocketAddress bookie2 = lh.getLedgerMetadata().getEnsemble(0).get(1);
+        BookieSocketAddress bookie1 = lh.getLedgerMetadata().getEnsembleAt(0).get(0);
+        BookieSocketAddress bookie2 = lh.getLedgerMetadata().getEnsembleAt(0).get(1);
         try {
             // we read an entry that is not added
             lh.readEntries(10, 10);
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestDelayEnsembleChange.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestDelayEnsembleChange.java
index c76a75c3cf..f8c5be5876 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestDelayEnsembleChange.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestDelayEnsembleChange.java
@@ -37,6 +37,7 @@
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.ReadEntryCallback;
@@ -103,7 +104,7 @@ private void verifyEntries(LedgerHandle lh, long startEntry, long untilEntry,
         LedgerMetadata md = lh.getLedgerMetadata();
 
         for (long eid = startEntry; eid < untilEntry; eid++) {
-            List<BookieSocketAddress> addresses = md.getEnsemble(eid);
+            List<BookieSocketAddress> addresses = md.getEnsembleAt(eid);
             VerificationCallback callback = new VerificationCallback(addresses.size());
             for (BookieSocketAddress addr : addresses) {
                 bkc.getBookieClient().readEntry(addr, lh.getId(), eid,
@@ -121,7 +122,7 @@ private void verifyEntriesRange(LedgerHandle lh, long startEntry, long untilEntr
         LedgerMetadata md = lh.getLedgerMetadata();
 
         for (long eid = startEntry; eid < untilEntry; eid++) {
-            List<BookieSocketAddress> addresses = md.getEnsemble(eid);
+            List<BookieSocketAddress> addresses = md.getEnsembleAt(eid);
             VerificationCallback callback = new VerificationCallback(addresses.size());
             for (BookieSocketAddress addr : addresses) {
                 bkc.getBookieClient().readEntry(addr, lh.getId(), eid,
@@ -257,8 +258,8 @@ public void testChangeEnsembleIfBrokenAckQuorum() throws Exception {
                         CLIENT_SCOPE + "." + WATCHER_SCOPE + "." + REPLACE_BOOKIE_TIME)
                         .getSuccessCount() > 0);
 
-        List<BookieSocketAddress> firstFragment = lh.getLedgerMetadata().getEnsemble(0);
-        List<BookieSocketAddress> secondFragment = lh.getLedgerMetadata().getEnsemble(3 * numEntries);
+        List<BookieSocketAddress> firstFragment = lh.getLedgerMetadata().getEnsembleAt(0);
+        List<BookieSocketAddress> secondFragment = lh.getLedgerMetadata().getEnsembleAt(3 * numEntries);
         assertFalse(firstFragment.get(0).equals(secondFragment.get(0)));
         assertFalse(firstFragment.get(1).equals(secondFragment.get(1)));
         assertFalse(firstFragment.get(2).equals(secondFragment.get(2)));
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestFencing.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestFencing.java
index cfe9979bc9..aa2dd6b9b0 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestFencing.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestFencing.java
@@ -266,7 +266,7 @@ public void testFencingInteractionWithBookieRecovery() throws Exception {
             writelh.addEntry(tmp.getBytes());
         }
 
-        BookieSocketAddress bookieToKill = writelh.getLedgerMetadata().getEnsemble(numEntries).get(0);
+        BookieSocketAddress bookieToKill = writelh.getLedgerMetadata().getEnsembleAt(numEntries).get(0);
         killBookie(bookieToKill);
 
         // write entries to change ensemble
@@ -318,7 +318,7 @@ public void testFencingInteractionWithBookieRecovery2() throws Exception {
         LedgerHandle readlh = bkc.openLedger(writelh.getId(),
                                              digestType, "testPasswd".getBytes());
         // should be fenced by now
-        BookieSocketAddress bookieToKill = writelh.getLedgerMetadata().getEnsemble(numEntries).get(0);
+        BookieSocketAddress bookieToKill = writelh.getLedgerMetadata().getEnsembleAt(numEntries).get(0);
         killBookie(bookieToKill);
         admin.recoverBookieData(bookieToKill);
 
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestGetBookieInfoTimeout.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestGetBookieInfoTimeout.java
index 6291413614..4ec5992a39 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestGetBookieInfoTimeout.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestGetBookieInfoTimeout.java
@@ -100,7 +100,7 @@ public void testGetBookieInfoTimeout() throws Exception {
         ClientConfiguration cConf = new ClientConfiguration();
         cConf.setGetBookieInfoTimeout(2);
 
-        final BookieSocketAddress bookieToSleep = writelh.getLedgerMetadata().getEnsemble(0).get(0);
+        final BookieSocketAddress bookieToSleep = writelh.getLedgerMetadata().getEnsembleAt(0).get(0);
         int sleeptime = cConf.getBookieInfoTimeout() * 3;
         CountDownLatch latch = sleepBookie(bookieToSleep, sleeptime);
         latch.await();
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestLedgerFragmentReplication.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestLedgerFragmentReplication.java
index 4b8d06c312..68580edd9b 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestLedgerFragmentReplication.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestLedgerFragmentReplication.java
@@ -34,6 +34,7 @@
 import java.util.concurrent.CountDownLatch;
 
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.client.api.WriteFlag;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.GenericCallback;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestParallelRead.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestParallelRead.java
index 68fd29c942..84edc62eb9 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestParallelRead.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestParallelRead.java
@@ -156,7 +156,7 @@ public void testFailParallelRecoveryReadMissingEntryImmediately() throws Excepti
 
         LedgerHandle lh = bkc.openLedger(id, digestType, passwd);
 
-        List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsemble(10);
+        List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsembleAt(10);
         CountDownLatch latch1 = new CountDownLatch(1);
         CountDownLatch latch2 = new CountDownLatch(1);
         // sleep two bookie
@@ -187,7 +187,7 @@ public void testParallelReadWithFailedBookies() throws Exception {
 
         LedgerHandle lh = bkc.openLedger(id, digestType, passwd);
 
-        List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsemble(5);
+        List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsembleAt(5);
         // kill two bookies
         killBookie(ensemble.get(0));
         killBookie(ensemble.get(1));
@@ -223,7 +223,7 @@ public void testParallelReadFailureWithFailedBookies() throws Exception {
 
         LedgerHandle lh = bkc.openLedger(id, digestType, passwd);
 
-        List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsemble(5);
+        List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsembleAt(5);
         // kill two bookies
         killBookie(ensemble.get(0));
         killBookie(ensemble.get(1));
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadEntryListener.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadEntryListener.java
index 7f28cc3e07..2ef72ff7dd 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadEntryListener.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadEntryListener.java
@@ -231,7 +231,7 @@ private void readWithFailedBookiesTest(boolean parallelRead) throws Exception {
         LedgerHandle lh = bkc.openLedger(id, digestType, passwd);
 
         List<BookieSocketAddress> ensemble =
-                lh.getLedgerMetadata().getEnsemble(5);
+                lh.getLedgerMetadata().getEnsembleAt(5);
         // kill two bookies
         killBookie(ensemble.get(0));
         killBookie(ensemble.get(1));
@@ -270,7 +270,7 @@ private void readFailureWithFailedBookiesTest(boolean parallelRead) throws Excep
         LedgerHandle lh = bkc.openLedger(id, digestType, passwd);
 
         List<BookieSocketAddress> ensemble =
-            lh.getLedgerMetadata().getEnsemble(5);
+            lh.getLedgerMetadata().getEnsembleAt(5);
         // kill bookies
         killBookie(ensemble.get(0));
         killBookie(ensemble.get(1));
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadLastConfirmedLongPoll.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadLastConfirmedLongPoll.java
index 48f638c333..c4ec8f7a95 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadLastConfirmedLongPoll.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadLastConfirmedLongPoll.java
@@ -156,7 +156,7 @@ public void testReadLACLongPollWhenSomeBookiesDown() throws Exception {
             ServerConfiguration[] confs = new ServerConfiguration[numEntries - 1];
             for (int j = 0; j < numEntries - 1; j++) {
                 int idx = (i + 1 + j) % numEntries;
-                confs[j] = killBookie(lh.getLedgerMetadata().getLastEnsembleValue().get(idx));
+                confs[j] = killBookie(LedgerMetadataUtils.getLastEnsembleValue(lh.getLedgerMetadata()).get(idx));
             }
 
             final AtomicBoolean entryAsExpected = new AtomicBoolean(false);
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadTimeout.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadTimeout.java
index d244218faf..1e2f17e9a6 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadTimeout.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestReadTimeout.java
@@ -62,9 +62,9 @@ public void testReadTimeout() throws Exception {
         }
 
         Set<BookieSocketAddress> beforeSet = new HashSet<BookieSocketAddress>();
-        beforeSet.addAll(writelh.getLedgerMetadata().getEnsemble(numEntries));
+        beforeSet.addAll(writelh.getLedgerMetadata().getEnsembleAt(numEntries));
 
-        final BookieSocketAddress bookieToSleep = writelh.getLedgerMetadata().getEnsemble(numEntries).get(0);
+        final BookieSocketAddress bookieToSleep = writelh.getLedgerMetadata().getEnsembleAt(numEntries).get(0);
         int sleeptime = baseClientConf.getReadTimeout() * 3;
         CountDownLatch latch = sleepBookie(bookieToSleep, sleeptime);
         latch.await();
@@ -80,7 +80,7 @@ public void addComplete(int rc, LedgerHandle lh,
         Assert.assertTrue("Write request did not finish", completed.get());
 
         Set<BookieSocketAddress> afterSet = new HashSet<BookieSocketAddress>();
-        afterSet.addAll(writelh.getLedgerMetadata().getEnsemble(numEntries + 1));
+        afterSet.addAll(writelh.getLedgerMetadata().getEnsembleAt(numEntries + 1));
         beforeSet.removeAll(afterSet);
         Assert.assertTrue("Bookie set should not match", beforeSet.size() != 0);
 
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestWatchEnsembleChange.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestWatchEnsembleChange.java
index 25906662d7..5c6a8c722d 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestWatchEnsembleChange.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestWatchEnsembleChange.java
@@ -34,6 +34,7 @@
 import java.util.concurrent.TimeUnit;
 import lombok.Cleanup;
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.meta.HierarchicalLedgerManagerFactory;
 import org.apache.bookkeeper.meta.LedgerIdGenerator;
 import org.apache.bookkeeper.meta.LedgerManager;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/UpdateLedgerCmdTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/UpdateLedgerCmdTest.java
index fa184972da..4e8d892721 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/UpdateLedgerCmdTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/UpdateLedgerCmdTest.java
@@ -99,7 +99,7 @@ private int getUpdatedLedgersCount(BookKeeper bk, List<LedgerHandle> ledgers, Bo
         for (LedgerHandle lh : ledgers) {
             lh.close();
             LedgerHandle openLedger = bk.openLedger(lh.getId(), digestType, PASSWORD.getBytes());
-            ensemble = openLedger.getLedgerMetadata().getEnsemble(0);
+            ensemble = openLedger.getLedgerMetadata().getEnsembleAt(0);
             if (ensemble.contains(toBookieAddr)) {
                 updatedLedgersCount++;
             }
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/UpdateLedgerOpTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/UpdateLedgerOpTest.java
index ad5c450ceb..c2b0d75704 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/UpdateLedgerOpTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/UpdateLedgerOpTest.java
@@ -35,6 +35,7 @@
 import org.apache.bookkeeper.bookie.BookieShell.UpdateLedgerNotifier;
 import org.apache.bookkeeper.client.AsyncCallback.AddCallback;
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.proto.BookieServer;
@@ -98,7 +99,7 @@ public void testManyLedgers(boolean useShortHostName) throws Exception {
                 ledgers.add(createLedgerWithEntries(bk, 0));
             }
 
-            List<BookieSocketAddress> ensemble = lh1.getLedgerMetadata().getEnsemble(0);
+            List<BookieSocketAddress> ensemble = lh1.getLedgerMetadata().getEnsembleAt(0);
 
             BookieSocketAddress curBookieAddr = ensemble.get(0);
             baseConf.setUseHostNameAsBookieID(true);
@@ -112,7 +113,7 @@ public void testManyLedgers(boolean useShortHostName) throws Exception {
             for (LedgerHandle lh : ledgers) {
                 lh.close();
                 LedgerHandle openLedger = bk.openLedger(lh.getId(), digestType, PASSWORD.getBytes());
-                ensemble = openLedger.getLedgerMetadata().getEnsemble(0);
+                ensemble = openLedger.getLedgerMetadata().getEnsembleAt(0);
                 assertTrue("Failed to update the ledger metadata to use bookie host name",
                         ensemble.contains(toBookieAddr));
                 assertFalse("Failed to update the ledger metadata to use bookie host name",
@@ -137,7 +138,7 @@ public void testLimitLessThanTotalLedgers() throws Exception {
                 ledgers.add(createLedgerWithEntries(bk, 0));
             }
 
-            List<BookieSocketAddress> ensemble = lh1.getLedgerMetadata().getEnsemble(0);
+            List<BookieSocketAddress> ensemble = lh1.getLedgerMetadata().getEnsembleAt(0);
 
             BookieSocketAddress curBookieAddr = ensemble.get(0);
             baseConf.setUseHostNameAsBookieID(true);
@@ -193,7 +194,7 @@ public void testChangeEnsembleAfterRenaming(boolean useShortHostName) throws Exc
             LedgerHandle lh = createLedgerWithEntries(bk, 100);
 
             BookieServer bookieServer = bs.get(0);
-            List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsemble(0);
+            List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsembleAt(0);
             BookieSocketAddress curBookieAddr = null;
             for (BookieSocketAddress bookieSocketAddress : ensemble) {
                 if (bookieServer.getLocalAddress().equals(bookieSocketAddress)) {
@@ -234,7 +235,7 @@ public void addComplete(int rccb, LedgerHandle lh, long entryId, Object ctx) {
             LedgerHandle openLedger = bk.openLedger(lh.getId(), digestType, PASSWORD.getBytes());
             final LedgerMetadata ledgerMetadata = openLedger.getLedgerMetadata();
             assertEquals("Failed to reform ensemble!", 2, ledgerMetadata.getAllEnsembles().size());
-            ensemble = ledgerMetadata.getEnsemble(0);
+            ensemble = ledgerMetadata.getEnsembleAt(0);
             assertTrue("Failed to update the ledger metadata to use bookie host name",
                     ensemble.contains(toBookieAddr));
         }
@@ -273,7 +274,7 @@ public void addComplete(int rccb, LedgerHandle lh, long entryId, Object ctx) {
                 }
             };
             th.start();
-            List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsemble(0);
+            List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsembleAt(0);
             BookieSocketAddress curBookieAddr = ensemble.get(0);
             BookieSocketAddress toBookieAddr = new BookieSocketAddress("localhost:" + curBookieAddr.getPort());
             UpdateLedgerOp updateLedgerOp = new UpdateLedgerOp(bk, bkadmin);
@@ -287,7 +288,7 @@ public void addComplete(int rccb, LedgerHandle lh, long entryId, Object ctx) {
             }
             lh.close();
             LedgerHandle openLedger = bk.openLedger(lh.getId(), digestType, PASSWORD.getBytes());
-            ensemble = openLedger.getLedgerMetadata().getEnsemble(0);
+            ensemble = openLedger.getLedgerMetadata().getEnsembleAt(0);
             assertTrue("Failed to update the ledger metadata to use bookie host name",
                     ensemble.contains(toBookieAddr));
         }
@@ -300,7 +301,7 @@ private int getUpdatedLedgersCount(BookKeeper bk, List<LedgerHandle> ledgers, Bo
         for (LedgerHandle lh : ledgers) {
             lh.close();
             LedgerHandle openLedger = bk.openLedger(lh.getId(), digestType, PASSWORD.getBytes());
-            ensemble = openLedger.getLedgerMetadata().getEnsemble(0);
+            ensemble = openLedger.getLedgerMetadata().getEnsembleAt(0);
             if (ensemble.contains(toBookieAddr)) {
                 updatedLedgersCount++;
             }
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperBuildersTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperBuildersTest.java
index 0316b7f269..85cb727140 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperBuildersTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperBuildersTest.java
@@ -33,7 +33,6 @@
 import org.apache.bookkeeper.client.BKException.BKNoSuchLedgerExistsException;
 import org.apache.bookkeeper.client.BookKeeper;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerMetadata;
 import org.apache.bookkeeper.client.LedgerMetadataBuilder;
 import org.apache.bookkeeper.client.MockBookKeeperTestCase;
 import org.apache.bookkeeper.conf.ClientConfiguration;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/AbstractZkLedgerManagerTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/AbstractZkLedgerManagerTest.java
index 6e37fee51c..37f55b3e6e 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/AbstractZkLedgerManagerTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/AbstractZkLedgerManagerTest.java
@@ -50,8 +50,8 @@
 import java.util.concurrent.TimeUnit;
 import org.apache.bookkeeper.client.BKException;
 import org.apache.bookkeeper.client.BKException.Code;
-import org.apache.bookkeeper.client.LedgerMetadata;
 import org.apache.bookkeeper.client.LedgerMetadataBuilder;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.testing.executors.MockExecutorController;
 import org.apache.bookkeeper.conf.ClientConfiguration;
 import org.apache.bookkeeper.meta.zk.ZKMetadataDriverBase;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/GcLedgersTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/GcLedgersTest.java
index 182ffa117a..d2735657e1 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/GcLedgersTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/GcLedgersTest.java
@@ -63,8 +63,8 @@
 import org.apache.bookkeeper.bookie.ScanAndCompareGarbageCollector;
 import org.apache.bookkeeper.bookie.StateManager;
 import org.apache.bookkeeper.client.BKException;
-import org.apache.bookkeeper.client.LedgerMetadata;
 import org.apache.bookkeeper.client.LedgerMetadataBuilder;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.util.Watcher;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.meta.LedgerManager.LedgerRange;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/LedgerManagerIteratorTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/LedgerManagerIteratorTest.java
index 03e13585e8..bdb2902976 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/LedgerManagerIteratorTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/LedgerManagerIteratorTest.java
@@ -46,8 +46,8 @@
 
 import org.apache.bookkeeper.client.BKException;
 import org.apache.bookkeeper.client.BookKeeper;
-import org.apache.bookkeeper.client.LedgerMetadata;
 import org.apache.bookkeeper.client.LedgerMetadataBuilder;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.meta.LedgerManager.LedgerRangeIterator;
 import org.apache.bookkeeper.meta.zk.ZKMetadataDriverBase;
 import org.apache.bookkeeper.net.BookieSocketAddress;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/MockLedgerManager.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/MockLedgerManager.java
index ff0126a920..398bb07d71 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/MockLedgerManager.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/meta/MockLedgerManager.java
@@ -29,7 +29,7 @@
 import java.util.concurrent.Executors;
 
 import org.apache.bookkeeper.client.BKException;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.LedgerMetadataListener;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.Processor;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/AuditorLedgerCheckerTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/AuditorLedgerCheckerTest.java
index ef57d6bddb..f25aa88180 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/AuditorLedgerCheckerTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/AuditorLedgerCheckerTest.java
@@ -51,8 +51,8 @@
 import org.apache.bookkeeper.client.BKException;
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerMetadata;
 import org.apache.bookkeeper.client.LedgerMetadataBuilder;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.util.OrderedScheduler;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.meta.LedgerManager;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/AuditorPeriodicCheckTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/AuditorPeriodicCheckTest.java
index 23b33788f8..fd97da31fe 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/AuditorPeriodicCheckTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/AuditorPeriodicCheckTest.java
@@ -55,7 +55,6 @@
 import org.apache.bookkeeper.client.BookKeeper;
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerHandleAdapter;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.meta.LedgerManagerFactory;
 import org.apache.bookkeeper.meta.LedgerUnderreplicationManager;
@@ -480,9 +479,8 @@ void setLatch(CountDownLatch latch) {
 
     private BookieSocketAddress replaceBookieWithWriteFailingBookie(LedgerHandle lh) throws Exception {
         int bookieIdx = -1;
-        Long entryId = LedgerHandleAdapter.getLedgerMetadata(lh).getAllEnsembles().firstKey();
-        List<BookieSocketAddress> curEnsemble = LedgerHandleAdapter
-                .getLedgerMetadata(lh).getAllEnsembles().get(entryId);
+        Long entryId = lh.getLedgerMetadata().getAllEnsembles().firstKey();
+        List<BookieSocketAddress> curEnsemble = lh.getLedgerMetadata().getAllEnsembles().get(entryId);
 
         // Identify a bookie in the current ledger ensemble to be replaced
         BookieSocketAddress replacedBookie = null;
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/BookieAutoRecoveryTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/BookieAutoRecoveryTest.java
index 07b937550e..45cd2eb7e1 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/BookieAutoRecoveryTest.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/BookieAutoRecoveryTest.java
@@ -36,7 +36,6 @@
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
 import org.apache.bookkeeper.client.BookKeeperTestClient;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerHandleAdapter;
 import org.apache.bookkeeper.common.util.OrderedScheduler;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.meta.LedgerManager;
@@ -150,8 +149,7 @@ public void testOpenLedgers() throws Exception {
         List<LedgerHandle> listOfLedgerHandle = createLedgersAndAddEntries(1, 5);
         LedgerHandle lh = listOfLedgerHandle.get(0);
         int ledgerReplicaIndex = 0;
-        BookieSocketAddress replicaToKillAddr = LedgerHandleAdapter
-                .getLedgerMetadata(lh).getAllEnsembles().get(0L).get(0);
+        BookieSocketAddress replicaToKillAddr = lh.getLedgerMetadata().getAllEnsembles().get(0L).get(0);
 
         final String urLedgerZNode = getUrLedgerZNode(lh);
         ledgerReplicaIndex = getReplicaIndexInLedger(lh, replicaToKillAddr);
@@ -199,8 +197,7 @@ public void testClosedLedgers() throws Exception {
         closeLedgers(listOfLedgerHandle);
         LedgerHandle lhandle = listOfLedgerHandle.get(0);
         int ledgerReplicaIndex = 0;
-        BookieSocketAddress replicaToKillAddr = LedgerHandleAdapter
-                .getLedgerMetadata(lhandle).getAllEnsembles().get(0L).get(0);
+        BookieSocketAddress replicaToKillAddr = lhandle.getLedgerMetadata().getAllEnsembles().get(0L).get(0);
 
         CountDownLatch latch = new CountDownLatch(listOfLedgerHandle.size());
         for (LedgerHandle lh : listOfLedgerHandle) {
@@ -261,8 +258,7 @@ public void testStopWhileReplicationInProgress() throws Exception {
                 numberOfLedgers, 5);
         closeLedgers(listOfLedgerHandle);
         LedgerHandle handle = listOfLedgerHandle.get(0);
-        BookieSocketAddress replicaToKillAddr = LedgerHandleAdapter
-                .getLedgerMetadata(handle).getAllEnsembles().get(0L).get(0);
+        BookieSocketAddress replicaToKillAddr = handle.getLedgerMetadata().getAllEnsembles().get(0L).get(0);
         LOG.info("Killing Bookie:" + replicaToKillAddr);
 
         // Each ledger, there will be two events : create urLedger and after
@@ -340,13 +336,13 @@ public void testNoSuchLedgerExists() throws Exception {
             assertNull("UrLedger already exists!",
                     watchUrLedgerNode(getUrLedgerZNode(lh), latch));
         }
-        BookieSocketAddress replicaToKillAddr = LedgerHandleAdapter
-                .getLedgerMetadata(listOfLedgerHandle.get(0)).getAllEnsembles()
-                .get(0L).get(0);
+        BookieSocketAddress replicaToKillAddr = listOfLedgerHandle.get(0)
+            .getLedgerMetadata().getAllEnsembles()
+            .get(0L).get(0);
         killBookie(replicaToKillAddr);
-        replicaToKillAddr = LedgerHandleAdapter
-                .getLedgerMetadata(listOfLedgerHandle.get(0)).getAllEnsembles()
-                .get(0L).get(0);
+        replicaToKillAddr = listOfLedgerHandle.get(0)
+            .getLedgerMetadata().getAllEnsembles()
+            .get(0L).get(0);
         killBookie(replicaToKillAddr);
         // waiting to publish urLedger znode by Auditor
         latch.await();
@@ -383,10 +379,9 @@ public void testEmptyLedgerLosesQuorumEventually() throws Exception {
         String urZNode = getUrLedgerZNode(lh);
         watchUrLedgerNode(urZNode, latch);
 
-        BookieSocketAddress replicaToKill = LedgerHandleAdapter
-            .getLedgerMetadata(lh).getAllEnsembles().get(0L).get(2);
+        BookieSocketAddress replicaToKill = lh.getLedgerMetadata().getAllEnsembles().get(0L).get(2);
         LOG.info("Killing last bookie, {}, in ensemble {}", replicaToKill,
-                 LedgerHandleAdapter.getLedgerMetadata(lh).getAllEnsembles().get(0L));
+                 lh.getLedgerMetadata().getAllEnsembles().get(0L));
         killBookie(replicaToKill);
 
         getAuditor(10, TimeUnit.SECONDS).submitAuditTask().get(); // ensure auditor runs
@@ -398,10 +393,9 @@ public void testEmptyLedgerLosesQuorumEventually() throws Exception {
             assertTrue("Should be marked as replicated", latch.await(10, TimeUnit.SECONDS));
         }
 
-        replicaToKill = LedgerHandleAdapter
-            .getLedgerMetadata(lh).getAllEnsembles().get(0L).get(1);
+        replicaToKill = lh.getLedgerMetadata().getAllEnsembles().get(0L).get(1);
         LOG.info("Killing second bookie, {}, in ensemble {}", replicaToKill,
-                 LedgerHandleAdapter.getLedgerMetadata(lh).getAllEnsembles().get(0L));
+                 lh.getLedgerMetadata().getAllEnsembles().get(0L));
         killBookie(replicaToKill);
 
         getAuditor(10, TimeUnit.SECONDS).submitAuditTask().get(); // ensure auditor runs
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/TestAutoRecoveryAlongWithBookieServers.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/TestAutoRecoveryAlongWithBookieServers.java
index 8131999708..0e6f9276c1 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/TestAutoRecoveryAlongWithBookieServers.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/replication/TestAutoRecoveryAlongWithBookieServers.java
@@ -29,7 +29,6 @@
 import org.apache.bookkeeper.client.BookKeeper;
 import org.apache.bookkeeper.client.LedgerEntry;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerHandleAdapter;
 import org.apache.bookkeeper.meta.zk.ZKMetadataDriverBase;
 import org.apache.bookkeeper.net.BookieSocketAddress;
 import org.apache.bookkeeper.test.BookKeeperClusterTestCase;
@@ -74,8 +73,7 @@ public void testAutoRecoveryAlongWithBookieServers() throws Exception {
             lh.addEntry(testData);
         }
         lh.close();
-        BookieSocketAddress replicaToKill = LedgerHandleAdapter
-                .getLedgerMetadata(lh).getAllEnsembles().get(0L).get(0);
+        BookieSocketAddress replicaToKill = lh.getLedgerMetadata().getAllEnsembles().get(0L).get(0);
 
         killBookie(replicaToKill);
 
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/tls/TestTLS.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/tls/TestTLS.java
index 5d92979437..306a7f6a6b 100644
--- a/bookkeeper-server/src/test/java/org/apache/bookkeeper/tls/TestTLS.java
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/tls/TestTLS.java
@@ -48,7 +48,7 @@
 import org.apache.bookkeeper.client.BookKeeperTestClient;
 import org.apache.bookkeeper.client.LedgerEntry;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.conf.ClientConfiguration;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.net.BookieSocketAddress;
diff --git a/metadata-drivers/etcd/src/main/java/org/apache/bookkeeper/metadata/etcd/EtcdLedgerManager.java b/metadata-drivers/etcd/src/main/java/org/apache/bookkeeper/metadata/etcd/EtcdLedgerManager.java
index 5155fad8b1..d571bf8057 100644
--- a/metadata-drivers/etcd/src/main/java/org/apache/bookkeeper/metadata/etcd/EtcdLedgerManager.java
+++ b/metadata-drivers/etcd/src/main/java/org/apache/bookkeeper/metadata/etcd/EtcdLedgerManager.java
@@ -44,7 +44,7 @@
 import java.util.function.Function;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.bookkeeper.client.BKException;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.meta.LedgerManager;
 import org.apache.bookkeeper.meta.LedgerMetadataSerDe;
 import org.apache.bookkeeper.metadata.etcd.helpers.KeyIterator;
diff --git a/metadata-drivers/etcd/src/main/java/org/apache/bookkeeper/metadata/etcd/LedgerMetadataConsumer.java b/metadata-drivers/etcd/src/main/java/org/apache/bookkeeper/metadata/etcd/LedgerMetadataConsumer.java
index d466b340d7..292d973637 100644
--- a/metadata-drivers/etcd/src/main/java/org/apache/bookkeeper/metadata/etcd/LedgerMetadataConsumer.java
+++ b/metadata-drivers/etcd/src/main/java/org/apache/bookkeeper/metadata/etcd/LedgerMetadataConsumer.java
@@ -16,7 +16,7 @@
 
 import java.util.Objects;
 import java.util.function.Consumer;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.LedgerMetadataListener;
 import org.apache.bookkeeper.versioning.Versioned;
 
diff --git a/metadata-drivers/etcd/src/test/java/org/apache/bookkeeper/metadata/etcd/EtcdLedgerManagerTest.java b/metadata-drivers/etcd/src/test/java/org/apache/bookkeeper/metadata/etcd/EtcdLedgerManagerTest.java
index a7df94f14c..984224c6b8 100644
--- a/metadata-drivers/etcd/src/test/java/org/apache/bookkeeper/metadata/etcd/EtcdLedgerManagerTest.java
+++ b/metadata-drivers/etcd/src/test/java/org/apache/bookkeeper/metadata/etcd/EtcdLedgerManagerTest.java
@@ -50,8 +50,8 @@
 import org.apache.bookkeeper.client.BKException;
 import org.apache.bookkeeper.client.BKException.Code;
 import org.apache.bookkeeper.client.BookKeeper.DigestType;
-import org.apache.bookkeeper.client.LedgerMetadata;
 import org.apache.bookkeeper.client.LedgerMetadataBuilder;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
 import org.apache.bookkeeper.meta.LedgerManager.LedgerRange;
 import org.apache.bookkeeper.meta.LedgerManager.LedgerRangeIterator;
diff --git a/stream/distributedlog/core/src/main/java/org/apache/bookkeeper/client/BookKeeperAccessor.java b/stream/distributedlog/core/src/main/java/org/apache/bookkeeper/client/BookKeeperAccessor.java
index 3152a74c56..2ca0268c3a 100644
--- a/stream/distributedlog/core/src/main/java/org/apache/bookkeeper/client/BookKeeperAccessor.java
+++ b/stream/distributedlog/core/src/main/java/org/apache/bookkeeper/client/BookKeeperAccessor.java
@@ -37,8 +37,4 @@ public static void forceRecoverLedger(LedgerHandle lh,
                       "Recovery can only run on ReadOnlyLedgerHandle");
         ((ReadOnlyLedgerHandle) lh).recover(cb, null, true);
     }
-
-    public static LedgerMetadata getLedgerMetadata(LedgerHandle lh) {
-        return lh.getLedgerMetadata();
-    }
 }
diff --git a/stream/distributedlog/core/src/main/java/org/apache/bookkeeper/client/LedgerReader.java b/stream/distributedlog/core/src/main/java/org/apache/bookkeeper/client/LedgerReader.java
index c06bf848ac..b7a6ba9120 100644
--- a/stream/distributedlog/core/src/main/java/org/apache/bookkeeper/client/LedgerReader.java
+++ b/stream/distributedlog/core/src/main/java/org/apache/bookkeeper/client/LedgerReader.java
@@ -119,7 +119,7 @@ public void readEntryComplete(int rc, long lid, long eid, ByteBuf buffer, Object
             }
         };
 
-        List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsemble(eid);
+        List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsembleAt(eid);
         for (int i = 0; i < writeSet.size(); i++) {
             int idx = writeSet.get(i);
             clientCtx.getBookieClient().readEntry(ensemble.get(idx), lh.getId(), eid, readEntryCallback,
@@ -224,7 +224,7 @@ public void readLacs(final LedgerHandle lh, long eid,
             }
         };
 
-        List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsemble(eid);
+        List<BookieSocketAddress> ensemble = lh.getLedgerMetadata().getEnsembleAt(eid);
         for (int i = 0; i < writeSet.size(); i++) {
             int idx = writeSet.get(i);
             clientCtx.getBookieClient().readEntry(ensemble.get(idx), lh.getId(), eid, readEntryCallback,
diff --git a/stream/distributedlog/core/src/test/java/org/apache/distributedlog/TestAsyncReaderWriter.java b/stream/distributedlog/core/src/test/java/org/apache/distributedlog/TestAsyncReaderWriter.java
index 75e22acfd3..b04c27b9bb 100644
--- a/stream/distributedlog/core/src/test/java/org/apache/distributedlog/TestAsyncReaderWriter.java
+++ b/stream/distributedlog/core/src/test/java/org/apache/distributedlog/TestAsyncReaderWriter.java
@@ -42,9 +42,8 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.bookkeeper.client.BookKeeper;
-import org.apache.bookkeeper.client.BookKeeperAccessor;
 import org.apache.bookkeeper.client.LedgerHandle;
-import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
 import org.apache.bookkeeper.common.concurrent.FutureEventListener;
 import org.apache.bookkeeper.common.concurrent.FutureUtils;
 import org.apache.bookkeeper.feature.FixedValueFeature;
@@ -1992,7 +1991,7 @@ public void testCreateLogStreamWithDifferentReplicationFactor() throws Exception
         long ledgerId = segments.get(0).getLogSegmentId();
         LedgerHandle lh = ((BKNamespaceDriver) namespace.getNamespaceDriver()).getReaderBKC().get()
                 .openLedgerNoRecovery(ledgerId, BookKeeper.DigestType.CRC32, confLocal.getBKDigestPW().getBytes(UTF_8));
-        LedgerMetadata metadata = BookKeeperAccessor.getLedgerMetadata(lh);
+        LedgerMetadata metadata = lh.getLedgerMetadata();
         assertEquals(DistributedLogConfiguration.BKDL_BOOKKEEPER_ENSEMBLE_SIZE_DEFAULT, metadata.getEnsembleSize());
         lh.close();
         Utils.close(writer);
@@ -2011,7 +2010,7 @@ public void testCreateLogStreamWithDifferentReplicationFactor() throws Exception
         ledgerId = segments.get(0).getLogSegmentId();
         lh = ((BKNamespaceDriver) namespace.getNamespaceDriver()).getReaderBKC().get()
                 .openLedgerNoRecovery(ledgerId, BookKeeper.DigestType.CRC32, confLocal.getBKDigestPW().getBytes(UTF_8));
-        metadata = BookKeeperAccessor.getLedgerMetadata(lh);
+        metadata = lh.getLedgerMetadata();
         assertEquals(DistributedLogConfiguration.BKDL_BOOKKEEPER_ENSEMBLE_SIZE_DEFAULT - 1, metadata.getEnsembleSize());
         lh.close();
         Utils.close(writer);
diff --git a/tests/backward-compat/recovery-no-password/src/test/groovy/org/apache/bookkeeper/tests/backwardcompat/TestCompatRecoveryNoPassword.groovy b/tests/backward-compat/recovery-no-password/src/test/groovy/org/apache/bookkeeper/tests/backwardcompat/TestCompatRecoveryNoPassword.groovy
index 7f229e7907..b7987a8356 100644
--- a/tests/backward-compat/recovery-no-password/src/test/groovy/org/apache/bookkeeper/tests/backwardcompat/TestCompatRecoveryNoPassword.groovy
+++ b/tests/backward-compat/recovery-no-password/src/test/groovy/org/apache/bookkeeper/tests/backwardcompat/TestCompatRecoveryNoPassword.groovy
@@ -30,7 +30,7 @@ import org.apache.bookkeeper.client.BKException
 import org.apache.bookkeeper.client.BookKeeper
 import org.apache.bookkeeper.client.BookKeeperAdmin
 import org.apache.bookkeeper.client.LedgerHandle
-import org.apache.bookkeeper.client.LedgerMetadata
+import org.apache.bookkeeper.client.api.LedgerMetadata
 import org.apache.bookkeeper.conf.ClientConfiguration
 import org.apache.bookkeeper.net.BookieSocketAddress
 import org.apache.bookkeeper.proto.BookieProtocol


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services