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 7139931de4 adds test to cover all conditional updates and fixes bug 
(#4624)
7139931de4 is described below

commit 7139931de44d848f66778b7e69b6eb0ef8489eb2
Author: Keith Turner <ktur...@apache.org>
AuthorDate: Sat Jun 1 13:41:20 2024 -0400

    adds test to cover all conditional updates and fixes bug (#4624)
    
    Ran AmpleConditionalWriterIT with code coverage and looked at
    what code was not covered in ConditionalTabletMutatorImpl and
    wrote test to cover that code.  Found a bug in
    requireAbsentTablet in the process, that code was not working
    at all.
---
 .../server/constraints/MetadataConstraints.java    |   3 +-
 .../metadata/iterators/TabletExistsIterator.java   |   2 +-
 .../test/functional/AmpleConditionalWriterIT.java  | 201 ++++++++++++++++++++-
 3 files changed, 197 insertions(+), 9 deletions(-)

diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/constraints/MetadataConstraints.java
 
b/server/base/src/main/java/org/apache/accumulo/server/constraints/MetadataConstraints.java
index ad8cd05c38..2500803fe7 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/constraints/MetadataConstraints.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/constraints/MetadataConstraints.java
@@ -416,7 +416,8 @@ public class MetadataConstraints implements Constraint {
     }
 
     if (violations != null) {
-      log.debug("violating metadata mutation : {}", new 
String(mutation.getRow(), UTF_8));
+      log.debug("violating metadata mutation : {} {}", new 
String(mutation.getRow(), UTF_8),
+          violations);
       for (ColumnUpdate update : mutation.getUpdates()) {
         log.debug(" update: {}:{} value {}", new 
String(update.getColumnFamily(), UTF_8),
             new String(update.getColumnQualifier(), UTF_8),
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/metadata/iterators/TabletExistsIterator.java
 
b/server/base/src/main/java/org/apache/accumulo/server/metadata/iterators/TabletExistsIterator.java
index a258f19e6d..b63971f98b 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/metadata/iterators/TabletExistsIterator.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/metadata/iterators/TabletExistsIterator.java
@@ -39,7 +39,7 @@ public class TabletExistsIterator extends WrappingIterator {
 
     Text tabletRow = SetEncodingIterator.getTabletRow(range);
 
-    Range r = new Range(tabletRow, true, tabletRow, false);
+    Range r = new Range(tabletRow);
 
     super.seek(r, Set.of(), false);
   }
diff --git 
a/test/src/main/java/org/apache/accumulo/test/functional/AmpleConditionalWriterIT.java
 
b/test/src/main/java/org/apache/accumulo/test/functional/AmpleConditionalWriterIT.java
index 6e7df27e2b..7353be3cd6 100644
--- 
a/test/src/main/java/org/apache/accumulo/test/functional/AmpleConditionalWriterIT.java
+++ 
b/test/src/main/java/org/apache/accumulo/test/functional/AmpleConditionalWriterIT.java
@@ -22,8 +22,10 @@ import static 
com.google.common.collect.MoreCollectors.onlyElement;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static 
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily.SELECTED_COLUMN;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.COMPACTED;
+import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.ECOMP;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FILES;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FLUSH_ID;
+import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.HOSTING_REQUESTED;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOADED;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOGS;
@@ -75,13 +77,16 @@ import org.apache.accumulo.core.dataImpl.KeyExtent;
 import org.apache.accumulo.core.fate.FateId;
 import org.apache.accumulo.core.fate.FateInstanceType;
 import org.apache.accumulo.core.metadata.AccumuloTable;
+import org.apache.accumulo.core.metadata.ReferencedTabletFile;
 import org.apache.accumulo.core.metadata.RootTable;
 import org.apache.accumulo.core.metadata.StoredTabletFile;
 import org.apache.accumulo.core.metadata.SuspendingTServer;
 import org.apache.accumulo.core.metadata.TServerInstance;
 import org.apache.accumulo.core.metadata.schema.Ample;
 import org.apache.accumulo.core.metadata.schema.Ample.ConditionalResult.Status;
+import org.apache.accumulo.core.metadata.schema.CompactionMetadata;
 import org.apache.accumulo.core.metadata.schema.DataFileValue;
+import org.apache.accumulo.core.metadata.schema.ExternalCompactionId;
 import org.apache.accumulo.core.metadata.schema.MetadataTime;
 import org.apache.accumulo.core.metadata.schema.SelectedFiles;
 import org.apache.accumulo.core.metadata.schema.TabletMetadata;
@@ -96,6 +101,8 @@ import 
org.apache.accumulo.core.metadata.schema.filters.HasCurrentFilter;
 import org.apache.accumulo.core.metadata.schema.filters.TabletMetadataFilter;
 import org.apache.accumulo.core.security.TablePermission;
 import org.apache.accumulo.core.spi.balancer.TableLoadBalancer;
+import org.apache.accumulo.core.spi.compaction.CompactionKind;
+import org.apache.accumulo.core.spi.compaction.CompactorGroupId;
 import org.apache.accumulo.core.tabletserver.log.LogEntry;
 import org.apache.accumulo.core.util.time.SteadyTime;
 import org.apache.accumulo.harness.AccumuloClusterHarness;
@@ -116,8 +123,6 @@ import com.google.common.collect.Sets;
 
 public class AmpleConditionalWriterIT extends AccumuloClusterHarness {
 
-  // ELASTICITY_TODO ensure that all conditional updates are tested
-
   private TableId tid;
   private KeyExtent e1;
   private KeyExtent e2;
@@ -485,11 +490,12 @@ public class AmpleConditionalWriterIT extends 
AccumuloClusterHarness {
     // Test adding a WAL to a tablet and verifying its presence
     String walFilePath =
         java.nio.file.Path.of("tserver+8080", 
UUID.randomUUID().toString()).toString();
-    LogEntry originalLogEntry = LogEntry.fromPath(walFilePath);
+    final LogEntry originalLogEntry = LogEntry.fromPath(walFilePath);
     ConditionalTabletsMutatorImpl ctmi = new 
ConditionalTabletsMutatorImpl(context);
     // create a tablet metadata with no write ahead logs
     var tmEmptySet = TabletMetadata.builder(e1).build(LOGS);
     // tablet should not have any logs to start with so requireSame with the 
empty logs should pass
+    assertTrue(context.getAmple().readTablet(e1).getLogs().isEmpty());
     ctmi.mutateTablet(e1).requireAbsentOperation().requireSame(tmEmptySet, 
LOGS)
         .putWal(originalLogEntry).submit(tm -> false);
     var results = ctmi.process();
@@ -559,6 +565,23 @@ public class AmpleConditionalWriterIT extends 
AccumuloClusterHarness {
     // Verify that the update went through as expected
     assertEquals(List.of(newLogEntry), 
context.getAmple().readTablet(e1).getLogs(),
         "Only the new LogEntry should remain after deleting the original.");
+
+    // test the requireAbsentLogs() function when logs are not present in the 
tablet metadata
+    assertTrue(context.getAmple().readTablet(e2).getLogs().isEmpty());
+    ctmi = new ConditionalTabletsMutatorImpl(context);
+    
ctmi.mutateTablet(e2).requireAbsentOperation().requireAbsentLogs().putWal(originalLogEntry)
+        .submit(tm -> false);
+    results = ctmi.process();
+    assertEquals(Status.ACCEPTED, results.get(e2).getStatus());
+
+    // test the requireAbsentLogs() function when logs are present in the 
tablet metadata
+    assertFalse(context.getAmple().readTablet(e2).getLogs().isEmpty());
+    ctmi = new ConditionalTabletsMutatorImpl(context);
+    
ctmi.mutateTablet(e2).requireAbsentOperation().requireAbsentLogs().deleteWal(originalLogEntry)
+        .submit(tm -> false);
+    results = ctmi.process();
+    assertEquals(Status.REJECTED, results.get(e2).getStatus());
+    assertFalse(context.getAmple().readTablet(e2).getLogs().isEmpty());
   }
 
   @Test
@@ -799,29 +822,38 @@ public class AmpleConditionalWriterIT extends 
AccumuloClusterHarness {
 
       assertEquals(Set.of(e1, e2), results.keySet());
 
+      var e5 = new KeyExtent(tid, new Text("yz"), new Text("ya"));
+      assertNull(context.getAmple().readTablet(e5));
+
+      // in addition to testing multiple tablets also testing 
requireAbsentTablet() which is
+      // currently not called elsewhere in this IT
       ctmi = new ConditionalTabletsMutatorImpl(context);
       ctmi.mutateTablet(e1).requireAbsentOperation().requireAbsentLocation()
           .putLocation(Location.future(ts2)).submit(tm -> false);
-      ctmi.mutateTablet(e2).requireAbsentOperation().requireAbsentLocation()
-          .putLocation(Location.future(ts1)).submit(tm -> false);
+      
ctmi.mutateTablet(e2).requireAbsentTablet().putLocation(Location.future(ts1))
+          .submit(tm -> false);
       ctmi.mutateTablet(e3).requireAbsentOperation().requireAbsentLocation()
           .putLocation(Location.future(ts1)).submit(tm -> false);
       ctmi.mutateTablet(e4).requireAbsentOperation().requireAbsentLocation()
           .putLocation(Location.future(ts2)).submit(tm -> false);
+      ctmi.mutateTablet(e5).requireAbsentTablet().putDirName("t-54321")
+          
.putPrevEndRow(e5.prevEndRow()).putTabletAvailability(TabletAvailability.ONDEMAND)
+          .submit(tm -> false);
       results = ctmi.process();
 
       assertEquals(Status.REJECTED, results.get(e1).getStatus());
       assertEquals(Status.REJECTED, results.get(e2).getStatus());
       assertEquals(Status.ACCEPTED, results.get(e3).getStatus());
       assertEquals(Status.ACCEPTED, results.get(e4).getStatus());
+      assertEquals(Status.ACCEPTED, results.get(e5).getStatus());
 
       assertEquals(Location.future(ts1), 
context.getAmple().readTablet(e1).getLocation());
       assertEquals(Location.future(ts2), 
context.getAmple().readTablet(e2).getLocation());
       assertEquals(Location.future(ts1), 
context.getAmple().readTablet(e3).getLocation());
       assertEquals(Location.future(ts2), 
context.getAmple().readTablet(e4).getLocation());
+      assertEquals("t-54321", context.getAmple().readTablet(e5).getDirName());
 
-      assertEquals(Set.of(e1, e2, e3, e4), results.keySet());
-
+      assertEquals(Set.of(e1, e2, e3, e4, e5), results.keySet());
     }
   }
 
@@ -877,6 +909,86 @@ public class AmpleConditionalWriterIT extends 
AccumuloClusterHarness {
     }
   }
 
+  @Test
+  public void testCompaction() {
+    try (AccumuloClient c = 
Accumulo.newClient().from(getClientProps()).build()) {
+      var context = cluster.getServerContext();
+
+      ExternalCompactionId ecid1 = 
ExternalCompactionId.generate(UUID.randomUUID());
+      ExternalCompactionId ecid2 = 
ExternalCompactionId.generate(UUID.randomUUID());
+
+      FateInstanceType type = FateInstanceType.fromTableId(tid);
+      FateId fateId = FateId.from(type, UUID.randomUUID());
+
+      ReferencedTabletFile tmpFile =
+          ReferencedTabletFile.of(new 
Path("file:///accumulo/tables/t-0/b-0/c1.rf"));
+      CompactorGroupId ceid = CompactorGroupId.of("G1");
+      Set<StoredTabletFile> jobFiles =
+          Set.of(StoredTabletFile.of(new 
Path("file:///accumulo/tables/t-0/b-0/b2.rf")));
+      CompactionMetadata ecMeta = new CompactionMetadata(jobFiles, tmpFile, 
"localhost:4444",
+          CompactionKind.SYSTEM, (short) 2, ceid, false, fateId);
+
+      // create a tablet metadata w/ en empty set of compactions
+      var tabletMetaNoCompactions = TabletMetadata.builder(e1).build(ECOMP);
+      var tabletMetaCompactions =
+          TabletMetadata.builder(e1).putExternalCompaction(ecid1, 
ecMeta).build();
+
+      // Test different compaction requirement scenarios for tablets w/o any 
compactions in the
+      // metadata table
+      var ctmi = new ConditionalTabletsMutatorImpl(context);
+      
ctmi.mutateTablet(e1).requireAbsentOperation().requireSame(tabletMetaNoCompactions,
 ECOMP)
+          .putExternalCompaction(ecid1, ecMeta).submit(tm -> false);
+      ctmi.mutateTablet(e2).requireAbsentOperation().requireCompaction(ecid2)
+          .putExternalCompaction(ecid1, ecMeta).submit(tm -> false);
+      
ctmi.mutateTablet(e3).requireAbsentOperation().requireSame(tabletMetaNoCompactions,
 ECOMP)
+          .putExternalCompaction(ecid1, ecMeta).putExternalCompaction(ecid2, 
ecMeta)
+          .submit(tm -> false);
+      
ctmi.mutateTablet(e4).requireAbsentOperation().requireSame(tabletMetaCompactions,
 ECOMP)
+          .putExternalCompaction(ecid1, ecMeta).submit(tm -> false);
+      var results = ctmi.process();
+      assertEquals(Status.ACCEPTED, results.get(e1).getStatus());
+      assertEquals(Status.REJECTED, results.get(e2).getStatus());
+      assertEquals(Status.ACCEPTED, results.get(e3).getStatus());
+      assertEquals(Status.REJECTED, results.get(e4).getStatus());
+      assertEquals(Set.of(ecid1),
+          context.getAmple().readTablet(e1).getExternalCompactions().keySet());
+      assertEquals(Set.of(), 
context.getAmple().readTablet(e2).getExternalCompactions().keySet());
+      assertEquals(Set.of(ecid1, ecid2),
+          context.getAmple().readTablet(e3).getExternalCompactions().keySet());
+      assertEquals(Set.of(), 
context.getAmple().readTablet(e4).getExternalCompactions().keySet());
+
+      // Test compaction requirements that do not match the compaction ids 
that exists in the
+      // metadata table.
+      ctmi = new ConditionalTabletsMutatorImpl(context);
+      ctmi.mutateTablet(e1).requireAbsentOperation().requireCompaction(ecid2)
+          .deleteExternalCompaction(ecid1).submit(tm -> false);
+      
ctmi.mutateTablet(e3).requireAbsentOperation().requireSame(tabletMetaCompactions,
 ECOMP)
+          .deleteExternalCompaction(ecid1).submit(tm -> false);
+      results = ctmi.process();
+      assertEquals(Status.REJECTED, results.get(e1).getStatus());
+      assertEquals(Status.REJECTED, results.get(e3).getStatus());
+      assertEquals(Set.of(ecid1),
+          context.getAmple().readTablet(e1).getExternalCompactions().keySet());
+      assertEquals(Set.of(ecid1, ecid2),
+          context.getAmple().readTablet(e3).getExternalCompactions().keySet());
+
+      // Test compaction requirements that match the compaction ids that 
exists in the metadata
+      // table.
+      ctmi = new ConditionalTabletsMutatorImpl(context);
+      
ctmi.mutateTablet(e1).requireAbsentOperation().requireSame(tabletMetaCompactions,
 ECOMP)
+          .deleteExternalCompaction(ecid1).submit(tm -> false);
+      ctmi.mutateTablet(e3).requireAbsentOperation().requireCompaction(ecid2)
+          .deleteExternalCompaction(ecid1).submit(tm -> false);
+      results = ctmi.process();
+      assertEquals(Status.ACCEPTED, results.get(e1).getStatus());
+      assertEquals(Status.ACCEPTED, results.get(e3).getStatus());
+      assertEquals(Set.of(), 
context.getAmple().readTablet(e1).getExternalCompactions().keySet());
+      assertEquals(Set.of(ecid2),
+          context.getAmple().readTablet(e3).getExternalCompactions().keySet());
+
+    }
+  }
+
   @Test
   public void testCompacted() {
     try (AccumuloClient c = 
Accumulo.newClient().from(getClientProps()).build()) {
@@ -1509,7 +1621,19 @@ public class AmpleConditionalWriterIT extends 
AccumuloClusterHarness {
         // and the current tablet metadata does.
         
assertTrue(tabletsMutator.process().get(originalTM.getExtent()).getStatus()
             .equals(Status.REJECTED));
+      }
+
+      // test require same when the tablet metadata passed in has a suspend 
column set
+      var suspendedTM = 
getServerContext().getAmple().readTablet(originalTM.getExtent());
+      assertNotNull(suspendedTM.getSuspend());
+      try (var tabletsMutator = 
getServerContext().getAmple().conditionallyMutateTablets()) {
+        
tabletsMutator.mutateTablet(originalTM.getExtent()).requireAbsentOperation()
+            .requireSame(suspendedTM, 
SUSPEND).putFlushNonce(6789).submit(tabletMetadata -> false);
 
+        // This should succeed because the tablet metadata does have a suspend 
column and the
+        // current tablet metadata does.
+        
assertTrue(tabletsMutator.process().get(originalTM.getExtent()).getStatus()
+            .equals(Status.ACCEPTED));
       }
 
       cluster.getClusterControl().start(ServerType.TABLET_SERVER);
@@ -1539,4 +1663,67 @@ public class AmpleConditionalWriterIT extends 
AccumuloClusterHarness {
     }
   }
 
+  @Test
+  public void testTabletAvailability() throws Exception {
+    try (AccumuloClient c = 
Accumulo.newClient().from(getClientProps()).build()) {
+      var context = cluster.getServerContext();
+
+      assertEquals(TabletAvailability.ONDEMAND,
+          context.getAmple().readTablet(e1).getTabletAvailability());
+      assertEquals(TabletAvailability.ONDEMAND,
+          context.getAmple().readTablet(e2).getTabletAvailability());
+
+      var ctmi = new ConditionalTabletsMutatorImpl(context);
+      ctmi.mutateTablet(e1).requireAbsentOperation()
+          .requireTabletAvailability(TabletAvailability.HOSTED)
+          .putTabletAvailability(TabletAvailability.UNHOSTED).submit(tm -> 
false);
+      ctmi.mutateTablet(e2).requireAbsentOperation()
+          .requireTabletAvailability(TabletAvailability.ONDEMAND)
+          .putTabletAvailability(TabletAvailability.UNHOSTED).submit(tm -> 
false);
+      var results = ctmi.process();
+      assertEquals(Status.REJECTED, results.get(e1).getStatus());
+      assertEquals(Status.ACCEPTED, results.get(e2).getStatus());
+
+      assertEquals(TabletAvailability.ONDEMAND,
+          context.getAmple().readTablet(e1).getTabletAvailability());
+      assertEquals(TabletAvailability.UNHOSTED,
+          context.getAmple().readTablet(e2).getTabletAvailability());
+    }
+  }
+
+  @Test
+  public void testErrors() {
+    try (AccumuloClient c = 
Accumulo.newClient().from(getClientProps()).build()) {
+      var context = cluster.getServerContext();
+
+      var ctmi1 = new ConditionalTabletsMutatorImpl(context);
+      ctmi1.mutateTablet(e1).requireAbsentTablet().putDirName("d1").submit(tm 
-> false);
+      // making multiple updates for the same tablet is not supported
+      assertThrows(IllegalStateException.class,
+          () -> 
ctmi1.mutateTablet(e1).requireAbsentTablet().putDirName("d2").submit(tm -> 
false));
+      // attempt to use a column that requireSame does not support
+      TabletMetadata tabletMetadata = TabletMetadata.builder(e2).build();
+      assertThrows(UnsupportedOperationException.class,
+          () -> ctmi1.mutateTablet(e2).requireAbsentOperation()
+              .requireSame(tabletMetadata, 
HOSTING_REQUESTED).putDirName("d2").submit(tm -> false));
+
+      var ctmi2 = new ConditionalTabletsMutatorImpl(context);
+      // the following end prev end row update should cause a constraint 
violation because a tablets
+      // prev end row must be less than its end row
+      
ctmi2.mutateTablet(e1).requireAbsentOperation().putPrevEndRow(e1.endRow())
+          .submit(tm -> false);
+      var results = ctmi2.process();
+      // getting status when a constraint violation happened should throw an 
exception
+      assertThrows(IllegalStateException.class, () -> 
results.get(e1).getStatus());
+
+      var ctmi3 = new ConditionalTabletsMutatorImpl(context);
+      
ctmi3.mutateTablet(e1).requireAbsentOperation().putFlushNonce(1234).submit(tm 
-> false);
+      var results2 = ctmi3.process();
+      assertEquals(Status.ACCEPTED, results2.get(e1).getStatus());
+
+      // attempting to use after calling proccess() should throw an exception
+      assertThrows(IllegalStateException.class, () -> ctmi3.mutateTablet(e1)
+          .requireAbsentOperation().putFlushNonce(1234).submit(tm -> false));
+    }
+  }
 }

Reply via email to