This is an automated email from the ASF dual-hosted git repository.
kturner pushed a commit to branch elasticity
in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/elasticity by this push:
new 351f4a4ae3 adds tests for tablet adding new file to metadata (#4617)
351f4a4ae3 is described below
commit 351f4a4ae3bbb781d80297047daf8623abc44454
Author: Keith Turner <[email protected]>
AuthorDate: Thu May 30 17:12:05 2024 -0400
adds tests for tablet adding new file to metadata (#4617)
---
.../org/apache/accumulo/tserver/tablet/Tablet.java | 24 +-
.../test/ample/usage/TabletFileUpdateIT.java | 268 +++++++++++++++++++++
2 files changed, 284 insertions(+), 8 deletions(-)
diff --git
a/server/tserver/src/main/java/org/apache/accumulo/tserver/tablet/Tablet.java
b/server/tserver/src/main/java/org/apache/accumulo/tserver/tablet/Tablet.java
index 0747a10867..14d9aabe79 100644
---
a/server/tserver/src/main/java/org/apache/accumulo/tserver/tablet/Tablet.java
+++
b/server/tserver/src/main/java/org/apache/accumulo/tserver/tablet/Tablet.java
@@ -67,6 +67,7 @@ import
org.apache.accumulo.core.manager.state.tables.TableState;
import org.apache.accumulo.core.metadata.AccumuloTable;
import org.apache.accumulo.core.metadata.ReferencedTabletFile;
import org.apache.accumulo.core.metadata.StoredTabletFile;
+import org.apache.accumulo.core.metadata.TServerInstance;
import org.apache.accumulo.core.metadata.schema.Ample;
import org.apache.accumulo.core.metadata.schema.Ample.ConditionalResult;
import org.apache.accumulo.core.metadata.schema.Ample.ConditionalResult.Status;
@@ -111,6 +112,7 @@ import org.apache.zookeeper.KeeperException.NoNodeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
@@ -1343,7 +1345,7 @@ public class Tablet extends TabletBase {
/**
* Update tablet file data from flush. Returns a StoredTabletFile if there
are data entries.
*/
- public Optional<StoredTabletFile> updateTabletDataFile(long maxCommittedTime,
+ private Optional<StoredTabletFile> updateTabletDataFile(long
maxCommittedTime,
ReferencedTabletFile newDatafile, DataFileValue dfv, Set<LogEntry>
unusedWalLogs,
long flushId, MinorCompactionReason mincReason) {
@@ -1353,12 +1355,21 @@ public class Tablet extends TabletBase {
// code is locking properly these should not change during this method.
var lastTabletMetadata = getMetadata();
+ return updateTabletDataFile(getContext().getAmple(), maxCommittedTime,
newDatafile, dfv,
+ unusedWalLogs, flushId, mincReason, tabletServer.getTabletSession(),
extent,
+ lastTabletMetadata, tabletTime, RANDOM.get().nextLong());
+ }
+
+ @VisibleForTesting
+ public static Optional<StoredTabletFile> updateTabletDataFile(Ample ample,
long maxCommittedTime,
+ ReferencedTabletFile newDatafile, DataFileValue dfv, Set<LogEntry>
unusedWalLogs,
+ long flushId, MinorCompactionReason mincReason, TServerInstance
tserverInstance,
+ KeyExtent extent, TabletMetadata lastTabletMetadata, TabletTime
tabletTime, long flushNonce) {
while (true) {
- try (var tabletsMutator =
getContext().getAmple().conditionallyMutateTablets()) {
+ try (var tabletsMutator = ample.conditionallyMutateTablets()) {
var expectedLocation = mincReason == MinorCompactionReason.RECOVERY
- ? Location.future(tabletServer.getTabletSession())
- : Location.current(tabletServer.getTabletSession());
+ ? Location.future(tserverInstance) :
Location.current(tserverInstance);
var tablet =
tabletsMutator.mutateTablet(extent).requireLocation(expectedLocation);
@@ -1382,7 +1393,6 @@ public class Tablet extends TabletBase {
tablet.putFlushId(flushId);
- long flushNonce = RANDOM.get().nextLong();
tablet.putFlushNonce(flushNonce);
unusedWalLogs.forEach(tablet::deleteWal);
@@ -1391,7 +1401,6 @@ public class Tablet extends TabletBase {
// Can not check if the new file exists because of two reasons. First,
it could be compacted
// away between the write and check. Second, some flushes do not
produce a file.
tablet.submit(tabletMetadata -> {
- // ELASTICITY_TODO need to test this, need a general way of testing
these failure checks
var persistedNonce = tabletMetadata.getFlushNonce();
if (persistedNonce.isPresent()) {
return persistedNonce.getAsLong() == flushNonce;
@@ -1400,13 +1409,12 @@ public class Tablet extends TabletBase {
});
var result = tabletsMutator.process().get(extent);
- if (result.getStatus() == Ample.ConditionalResult.Status.ACCEPTED) {
+ if (result.getStatus() == Status.ACCEPTED) {
return newFile;
} else {
var updatedTableMetadata = result.readMetadata();
if (setTime &&
expectedLocation.equals(updatedTableMetadata.getLocation())
&&
!lastTabletMetadata.getTime().equals(updatedTableMetadata.getTime())) {
- // ELASTICITY_TODO need to test this
// The update failed because the time changed, so lets try again.
log.debug("Failed to add {} to {} because time changed {}!={},
will retry", newFile,
extent, lastTabletMetadata.getTime(),
updatedTableMetadata.getTime());
diff --git
a/test/src/main/java/org/apache/accumulo/test/ample/usage/TabletFileUpdateIT.java
b/test/src/main/java/org/apache/accumulo/test/ample/usage/TabletFileUpdateIT.java
new file mode 100644
index 0000000000..abc9242b58
--- /dev/null
+++
b/test/src/main/java/org/apache/accumulo/test/ample/usage/TabletFileUpdateIT.java
@@ -0,0 +1,268 @@
+/*
+ * 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
+ *
+ * https://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.accumulo.test.ample.usage;
+
+import static
org.apache.accumulo.core.client.ConditionalWriter.Status.ACCEPTED;
+import static org.apache.accumulo.core.client.ConditionalWriter.Status.UNKNOWN;
+import static
org.apache.accumulo.test.ample.metadata.ConditionalWriterInterceptor.withStatus;
+import static org.apache.accumulo.tserver.tablet.Tablet.updateTabletDataFile;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.accumulo.core.client.Accumulo;
+import org.apache.accumulo.core.client.admin.TimeType;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.ReferencedTabletFile;
+import org.apache.accumulo.core.metadata.TServerInstance;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.metadata.schema.DataFileValue;
+import org.apache.accumulo.core.metadata.schema.MetadataTime;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
+import org.apache.accumulo.harness.SharedMiniClusterBase;
+import org.apache.accumulo.server.tablets.TabletTime;
+import org.apache.accumulo.test.ample.metadata.TestAmple;
+import org.apache.accumulo.tserver.MinorCompactionReason;
+import org.apache.hadoop.fs.Path;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests tablet usage of ample to add a new minor compacted file to tablet.
This tests edge cases,
+ * the normal cases are well tested by many other ITs from simply running
Accumulo.
+ */
+public class TabletFileUpdateIT extends SharedMiniClusterBase {
+
+ @BeforeAll
+ public static void setup() throws Exception {
+ SharedMiniClusterBase.startMiniCluster();
+ }
+
+ @AfterAll
+ public static void teardown() {
+ SharedMiniClusterBase.stopMiniCluster();
+ }
+
+ private static final ReferencedTabletFile newFile =
+ ReferencedTabletFile.of(new
Path("file:///accumulo/tables/t-0/b-0/F1.rf"));
+ private static final DataFileValue dfv1 = new DataFileValue(1000, 100);
+ private static final TServerInstance tserverInstance =
+ new TServerInstance("localhost:9997", 0xabcdef123L);
+ private static final TableId tableId = TableId.of("99");
+ private static final KeyExtent extent = new KeyExtent(tableId, null, null);
+
+ /**
+ * This test ensures that a tablet handles tablet time changing externally
(like bulk import could
+ * change tablet time).
+ */
+ @Test
+ public void testTimeChanged() throws Exception {
+ String[] tableNames = getUniqueNames(1);
+ String metadataTable = tableNames[0];
+
+ var tabletTime = TabletTime.getInstance(new MetadataTime(1,
TimeType.LOGICAL));
+ long flushNonce = 42L;
+
+ try (var client = Accumulo.newClient().from(getClientProps()).build()) {
+ client.tableOperations().create(metadataTable);
+ TestAmple.create(getCluster().getServerContext(),
+ Map.of(Ample.DataLevel.USER, metadataTable));
+
+ Ample testAmple = TestAmple.create(getCluster().getServerContext(),
+ Map.of(Ample.DataLevel.USER, metadataTable));
+
+ assertNull(testAmple.readTablet(extent));
+
+ // create tablet in the fake metadata table
+
testAmple.mutateTablet(extent).putDirName("dir1").putTime(tabletTime.getMetadataTime())
+
.putLocation(Location.current(tserverInstance)).putPrevEndRow(null).mutate();
+
+ // get the tablets metadata
+ var lastMetadata = testAmple.readTablet(extent);
+
+ // update the tablets time, so it will differ
+
testAmple.mutateTablet(extent).putTime(tabletTime.getMetadataTime(tabletTime.getTime()
+ 2))
+ .mutate();
+
+ tabletTime.updateTimeIfGreater(tabletTime.getTime() + 6);
+
+ updateTabletDataFile(testAmple, tabletTime.getTime(), newFile, dfv1,
Set.of(), 7L,
+ MinorCompactionReason.SYSTEM, tserverInstance, extent, lastMetadata,
tabletTime,
+ flushNonce);
+
+ var updatedMetadata = testAmple.readTablet(extent);
+ assertEquals(Set.of(newFile.insert()), updatedMetadata.getFiles());
+ assertEquals(tabletTime.getMetadataTime(), updatedMetadata.getTime());
+ assertEquals(flushNonce, updatedMetadata.getFlushNonce().orElse(-1));
+
+ client.tableOperations().delete(metadataTable);
+ }
+ }
+
+ @Test
+ public void testNoTimeUpdate() throws Exception {
+ String[] tableNames = getUniqueNames(1);
+ String metadataTable = tableNames[0];
+
+ var tabletTime = TabletTime.getInstance(new MetadataTime(10,
TimeType.LOGICAL));
+ long flushNonce = 42L;
+
+ try (var client = Accumulo.newClient().from(getClientProps()).build()) {
+ client.tableOperations().create(metadataTable);
+ TestAmple.create(getCluster().getServerContext(),
+ Map.of(Ample.DataLevel.USER, metadataTable));
+
+ Ample testAmple = TestAmple.create(getCluster().getServerContext(),
+ Map.of(Ample.DataLevel.USER, metadataTable));
+
+ assertNull(testAmple.readTablet(extent));
+
+ // create tablet in the fake metadata table
+
testAmple.mutateTablet(extent).putDirName("dir1").putTime(tabletTime.getMetadataTime())
+
.putLocation(Location.current(tserverInstance)).putPrevEndRow(null).mutate();
+
+ // get the tablets metadata
+ var lastMetadata = testAmple.readTablet(extent);
+
+ var maxCommittedTime = tabletTime.getTime() + 2;
+
+ // Update the tablets time, simulating concurrent bulk imports updating
the time field
+ // externally
+
testAmple.mutateTablet(extent).putTime(tabletTime.getMetadataTime(tabletTime.getTime()
+ 4))
+ .mutate();
+
+ // Test the case where external bulk imports have pushed tablet time in
the metadata table
+ // higher than what was passed the function. In this case the time
should not be updated.
+ updateTabletDataFile(testAmple, maxCommittedTime, newFile, dfv1,
Set.of(), 7L,
+ MinorCompactionReason.SYSTEM, tserverInstance, extent, lastMetadata,
tabletTime,
+ flushNonce);
+
+ var updatedMetadata = testAmple.readTablet(extent);
+ assertEquals(Set.of(newFile.insert()), updatedMetadata.getFiles());
+ // the time passed to updateTabletDataFile should not have been set
because it was lower than
+ // what is in the metadata table
+ assertEquals(tabletTime.getMetadataTime(tabletTime.getTime() + 4),
updatedMetadata.getTime());
+ assertEquals(flushNonce, updatedMetadata.getFlushNonce().orElse(-1));
+
+ client.tableOperations().delete(metadataTable);
+ }
+ }
+
+ /**
+ * This test ensures that a tablet will not add a new minor compacted file
when the tablets
+ * location is not as expected.
+ */
+ @Test
+ public void testLocation() throws Exception {
+ String[] tableNames = getUniqueNames(1);
+ String metadataTable = tableNames[0];
+
+ var tabletTime = TabletTime.getInstance(new MetadataTime(1,
TimeType.LOGICAL));
+ long flushNonce = 42L;
+
+ try (var client = Accumulo.newClient().from(getClientProps()).build()) {
+ client.tableOperations().create(metadataTable);
+ TestAmple.create(getCluster().getServerContext(),
+ Map.of(Ample.DataLevel.USER, metadataTable));
+
+ Ample testAmple = TestAmple.create(getCluster().getServerContext(),
+ Map.of(Ample.DataLevel.USER, metadataTable));
+
+ assertNull(testAmple.readTablet(extent));
+
+ // create tablet in the fake metadata table
+
testAmple.mutateTablet(extent).putDirName("dir1").putTime(tabletTime.getMetadataTime())
+
.putLocation(Location.current(tserverInstance)).putPrevEndRow(null).mutate();
+
+ // get the tablets metadata
+ var lastMetadata = testAmple.readTablet(extent);
+
+
testAmple.mutateTablet(extent).deleteLocation(Location.current(tserverInstance)).mutate();
+
+ // try locations that differ in type, port, and session
+ for (var location : List.of(Location.future(tserverInstance),
+ Location.current(new TServerInstance("localhost:9998",
0xabcdef123L)),
+ Location.current(new TServerInstance("localhost:9997",
0xabcdef124L)))) {
+ // set a location on the tablet that will not match
+ testAmple.mutateTablet(extent).putLocation(location).mutate();
+ // should fail to add file to tablet because tablet location is not as
expected
+ assertThrows(IllegalStateException.class,
+ () -> updateTabletDataFile(testAmple, tabletTime.getTime(),
newFile, dfv1, Set.of(), 7L,
+ MinorCompactionReason.SYSTEM, tserverInstance, extent,
lastMetadata, tabletTime,
+ flushNonce));
+ // verify that files was not added
+ var updatedMetadata = testAmple.readTablet(extent);
+ assertEquals(Set.of(), updatedMetadata.getFiles());
+ testAmple.mutateTablet(extent).deleteLocation(location).mutate();
+ }
+
+ client.tableOperations().delete(metadataTable);
+ }
+ }
+
+ /**
+ * This test exercises conditional mutations returning unknown and this
causing reading of the
+ * flush nonce.
+ */
+ @Test
+ public void testFlushNonce() throws Exception {
+ String[] tableNames = getUniqueNames(1);
+ String metadataTable = tableNames[0];
+
+ var tabletTime = TabletTime.getInstance(new MetadataTime(1,
TimeType.LOGICAL));
+ long flushNonce = 42L;
+
+ try (var client = Accumulo.newClient().from(getClientProps()).build()) {
+ client.tableOperations().create(metadataTable);
+ TestAmple.create(getCluster().getServerContext(),
+ Map.of(Ample.DataLevel.USER, metadataTable));
+
+ // create test ample that will return unknown status for conditional
mutations.
+ Ample testAmple = TestAmple.create(getCluster().getServerContext(),
+ Map.of(Ample.DataLevel.USER, metadataTable), () ->
withStatus(ACCEPTED, UNKNOWN, 1));
+
+ assertNull(testAmple.readTablet(extent));
+
+ // create tablet in the fake metadata table
+
testAmple.mutateTablet(extent).putDirName("dir1").putTime(tabletTime.getMetadataTime())
+
.putLocation(Location.current(tserverInstance)).putPrevEndRow(null).mutate();
+
+ // get the tablets metadata
+ var lastMetadata = testAmple.readTablet(extent);
+
+ // This should get an unknown status and then check the flush nonce
+ updateTabletDataFile(testAmple, tabletTime.getTime() + 7, newFile, dfv1,
Set.of(), 7L,
+ MinorCompactionReason.SYSTEM, tserverInstance, extent, lastMetadata,
tabletTime,
+ flushNonce);
+
+ var updatedMetadata = testAmple.readTablet(extent);
+ assertEquals(Set.of(newFile.insert()), updatedMetadata.getFiles());
+ assertEquals(tabletTime.getMetadataTime(8), updatedMetadata.getTime());
+ assertEquals(flushNonce, updatedMetadata.getFlushNonce().orElse(-1));
+
+ client.tableOperations().delete(metadataTable);
+ }
+ }
+}