This is an automated email from the ASF dual-hosted git repository.
ctubbsii pushed a commit to branch temp-3142-merge
in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/temp-3142-merge by this push:
new c8fa164a79 Add a last location mode property (#3142)
c8fa164a79 is described below
commit c8fa164a792490b48d93588411c61c449197a967
Author: Ivan Bella <[email protected]>
AuthorDate: Thu Jan 26 11:45:48 2023 -0500
Add a last location mode property (#3142)
This adds a property to allow users to change when
the last location is updated in a tablet's metadata.
The last location can be updated on compaction or
on assignment.
In combination with the `manager.startup.tserver.*`
properties, this will allow users to better manage
initial assignments of tablets according to their
preferences, depending on the configured balancer
that respects the last location field.
Co-authored-by: Christopher Tubbs <[email protected]>
---
.../org/apache/accumulo/core/conf/Property.java | 8 ++
.../apache/accumulo/core/conf/PropertyType.java | 3 +
.../accumulo/core/conf/PropertyTypeTest.java | 6 ++
.../server/manager/state/MetaDataStateStore.java | 14 ++-
.../server/manager/state/TabletStateStore.java | 2 +-
.../server/manager/state/ZooTabletStateStore.java | 12 ++-
.../accumulo/server/util/ManagerMetadataUtil.java | 79 +++++++++++---
.../manager/state/RootTabletStateStoreTest.java | 9 +-
.../accumulo/manager/TabletGroupWatcher.java | 9 ++
.../test/functional/AssignLocationModeIT.java | 111 +++++++++++++++++++
.../test/functional/CompactLocationModeIT.java | 118 +++++++++++++++++++++
11 files changed, 350 insertions(+), 21 deletions(-)
diff --git a/core/src/main/java/org/apache/accumulo/core/conf/Property.java
b/core/src/main/java/org/apache/accumulo/core/conf/Property.java
index 3acd28d476..832327bbd8 100644
--- a/core/src/main/java/org/apache/accumulo/core/conf/Property.java
+++ b/core/src/main/java/org/apache/accumulo/core/conf/Property.java
@@ -705,6 +705,14 @@ public enum Property {
"The number of threads on each tablet server available to retrieve"
+ " summary data, that is not currently in cache, from RFiles.",
"2.0.0"),
+ TSERV_LAST_LOCATION_MODE("tserver.last.location.mode", "compaction",
+ PropertyType.LAST_LOCATION_MODE,
+ "Describes how the system will record the 'last' location for tablets,
which can be used for assigning them when a cluster restarts."
+ + " If 'compaction' is the mode, then the system will record the
location where the tablet's most recent compaction occurred."
+ + " If 'assignment' is the mode, then the most recently assigned
location will be recorded."
+ + " The manager.startup.tserver properties might also need to be set
to ensure"
+ + " the tserver is available before tablets are initially assigned
if the 'last' location is to be used.",
+ "3.0.0"),
// accumulo garbage collector properties
GC_PREFIX("gc.", null, PropertyType.PREFIX,
diff --git a/core/src/main/java/org/apache/accumulo/core/conf/PropertyType.java
b/core/src/main/java/org/apache/accumulo/core/conf/PropertyType.java
index c590054485..b13a026c8c 100644
--- a/core/src/main/java/org/apache/accumulo/core/conf/PropertyType.java
+++ b/core/src/main/java/org/apache/accumulo/core/conf/PropertyType.java
@@ -128,6 +128,9 @@ public enum PropertyType {
GC_POST_ACTION("gc_post_action", in(true, null, "none", "flush", "compact"),
"One of 'none', 'flush', or 'compact'."),
+ LAST_LOCATION_MODE("last_location_mode", in(true, null, "assignment",
"compaction"),
+ "Defines how to update the last location. One of 'assignment', or
'compaction'."),
+
STRING("string", x -> true,
"An arbitrary string of characters whose format is unspecified and"
+ " interpreted based on the context of the property to which it
applies."),
diff --git
a/core/src/test/java/org/apache/accumulo/core/conf/PropertyTypeTest.java
b/core/src/test/java/org/apache/accumulo/core/conf/PropertyTypeTest.java
index 412a67180f..514fd1e95c 100644
--- a/core/src/test/java/org/apache/accumulo/core/conf/PropertyTypeTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/conf/PropertyTypeTest.java
@@ -144,6 +144,12 @@ public class PropertyTypeTest extends WithTestNames {
invalid("", "other");
}
+ @Test
+ public void testTypeLAST_LOCATION_MODE() {
+ valid(null, "compaction", "assignment");
+ invalid("", "other");
+ }
+
@Test
public void testTypeFRACTION() {
valid(null, "1", "0", "1.0", "25%", "2.5%", "10.2E-3", "10.2E-3%", ".3");
diff --git
a/server/base/src/main/java/org/apache/accumulo/server/manager/state/MetaDataStateStore.java
b/server/base/src/main/java/org/apache/accumulo/server/manager/state/MetaDataStateStore.java
index 9cf186d89a..18a3386360 100644
---
a/server/base/src/main/java/org/apache/accumulo/server/manager/state/MetaDataStateStore.java
+++
b/server/base/src/main/java/org/apache/accumulo/server/manager/state/MetaDataStateStore.java
@@ -31,6 +31,7 @@ import
org.apache.accumulo.core.metadata.schema.Ample.TabletMutator;
import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
import org.apache.accumulo.core.tabletserver.log.LogEntry;
+import org.apache.accumulo.server.util.ManagerMetadataUtil;
import org.apache.hadoop.fs.Path;
class MetaDataStateStore implements TabletStateStore {
@@ -60,9 +61,13 @@ class MetaDataStateStore implements TabletStateStore {
public void setLocations(Collection<Assignment> assignments) throws
DistributedStoreException {
try (var tabletsMutator = ample.mutateTablets()) {
for (Assignment assignment : assignments) {
- tabletsMutator.mutateTablet(assignment.tablet)
- .putLocation(assignment.server, LocationType.CURRENT)
- .deleteLocation(assignment.server,
LocationType.FUTURE).deleteSuspension().mutate();
+ TabletMutator tabletMutator =
tabletsMutator.mutateTablet(assignment.tablet);
+ tabletMutator.putLocation(assignment.server, LocationType.CURRENT);
+ ManagerMetadataUtil.updateLastForAssignmentMode(context, ample,
tabletMutator,
+ assignment.tablet, assignment.server);
+ tabletMutator.deleteLocation(assignment.server, LocationType.FUTURE);
+ tabletMutator.deleteSuspension();
+ tabletMutator.mutate();
}
} catch (RuntimeException ex) {
throw new DistributedStoreException(ex);
@@ -102,6 +107,8 @@ class MetaDataStateStore implements TabletStateStore {
for (TabletLocationState tls : tablets) {
TabletMutator tabletMutator = tabletsMutator.mutateTablet(tls.extent);
if (tls.current != null) {
+ ManagerMetadataUtil.updateLastForAssignmentMode(context, ample,
tabletMutator, tls.extent,
+ tls.current);
tabletMutator.deleteLocation(tls.current, LocationType.CURRENT);
if (logsForDeadServers != null) {
List<Path> logs = logsForDeadServers.get(tls.current);
@@ -147,4 +154,5 @@ class MetaDataStateStore implements TabletStateStore {
public String name() {
return "Normal Tablets";
}
+
}
diff --git
a/server/base/src/main/java/org/apache/accumulo/server/manager/state/TabletStateStore.java
b/server/base/src/main/java/org/apache/accumulo/server/manager/state/TabletStateStore.java
index 999667f9b8..c96912ee58 100644
---
a/server/base/src/main/java/org/apache/accumulo/server/manager/state/TabletStateStore.java
+++
b/server/base/src/main/java/org/apache/accumulo/server/manager/state/TabletStateStore.java
@@ -112,7 +112,7 @@ public interface TabletStateStore extends
Iterable<TabletLocationState> {
TabletStateStore tss;
switch (level) {
case ROOT:
- tss = new ZooTabletStateStore(context.getAmple());
+ tss = new ZooTabletStateStore(context);
break;
case METADATA:
tss = new RootTabletStateStore(context, state);
diff --git
a/server/base/src/main/java/org/apache/accumulo/server/manager/state/ZooTabletStateStore.java
b/server/base/src/main/java/org/apache/accumulo/server/manager/state/ZooTabletStateStore.java
index e412b09837..4cee8f8e23 100644
---
a/server/base/src/main/java/org/apache/accumulo/server/manager/state/ZooTabletStateStore.java
+++
b/server/base/src/main/java/org/apache/accumulo/server/manager/state/ZooTabletStateStore.java
@@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
+import org.apache.accumulo.core.clientImpl.ClientContext;
import org.apache.accumulo.core.metadata.RootTable;
import org.apache.accumulo.core.metadata.TServerInstance;
import org.apache.accumulo.core.metadata.TabletLocationState;
@@ -34,6 +35,7 @@ import
org.apache.accumulo.core.metadata.schema.TabletMetadata;
import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
import org.apache.accumulo.core.metadata.schema.TabletMetadata.LocationType;
import org.apache.accumulo.core.tabletserver.log.LogEntry;
+import org.apache.accumulo.server.util.ManagerMetadataUtil;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,9 +44,11 @@ class ZooTabletStateStore implements TabletStateStore {
private static final Logger log =
LoggerFactory.getLogger(ZooTabletStateStore.class);
private final Ample ample;
+ private final ClientContext context;
- ZooTabletStateStore(Ample ample) {
- this.ample = ample;
+ ZooTabletStateStore(ClientContext context) {
+ this.context = context;
+ this.ample = context.getAmple();
}
@Override
@@ -134,6 +138,8 @@ class ZooTabletStateStore implements TabletStateStore {
TabletMutator tabletMutator = ample.mutateTablet(assignment.tablet);
tabletMutator.putLocation(assignment.server, LocationType.CURRENT);
+ ManagerMetadataUtil.updateLastForAssignmentMode(context, ample,
tabletMutator,
+ assignment.tablet, assignment.server);
tabletMutator.deleteLocation(assignment.server, LocationType.FUTURE);
tabletMutator.mutate();
@@ -154,6 +160,8 @@ class ZooTabletStateStore implements TabletStateStore {
tabletMutator.deleteLocation(tls.futureOrCurrent(), LocationType.FUTURE);
tabletMutator.deleteLocation(tls.futureOrCurrent(), LocationType.CURRENT);
+ ManagerMetadataUtil.updateLastForAssignmentMode(context, ample,
tabletMutator, tls.extent,
+ tls.futureOrCurrent());
if (logsForDeadServers != null) {
List<Path> logs = logsForDeadServers.get(tls.futureOrCurrent());
if (logs != null) {
diff --git
a/server/base/src/main/java/org/apache/accumulo/server/util/ManagerMetadataUtil.java
b/server/base/src/main/java/org/apache/accumulo/server/util/ManagerMetadataUtil.java
index ea54864a7a..6652d59f07 100644
---
a/server/base/src/main/java/org/apache/accumulo/server/util/ManagerMetadataUtil.java
+++
b/server/base/src/main/java/org/apache/accumulo/server/util/ManagerMetadataUtil.java
@@ -34,7 +34,9 @@ import java.util.concurrent.TimeUnit;
import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.clientImpl.ClientContext;
import org.apache.accumulo.core.clientImpl.ScannerImpl;
+import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.PartialKey;
import org.apache.accumulo.core.data.Range;
@@ -46,6 +48,7 @@ import org.apache.accumulo.core.metadata.MetadataTable;
import org.apache.accumulo.core.metadata.StoredTabletFile;
import org.apache.accumulo.core.metadata.TServerInstance;
import org.apache.accumulo.core.metadata.TabletFile;
+import org.apache.accumulo.core.metadata.schema.Ample;
import org.apache.accumulo.core.metadata.schema.Ample.TabletMutator;
import org.apache.accumulo.core.metadata.schema.DataFileValue;
import org.apache.accumulo.core.metadata.schema.ExternalCompactionId;
@@ -204,13 +207,7 @@ public class ManagerMetadataUtil {
tablet.putCompactionId(compactionId);
}
- TServerInstance self = getTServerInstance(address, zooLock);
- tablet.putLocation(self, LocationType.LAST);
-
- // remove the old location
- if (lastLocation != null && !lastLocation.equals(self)) {
- tablet.deleteLocation(lastLocation, LocationType.LAST);
- }
+ updateLastForCompactionMode(context, tablet, lastLocation, address,
zooLock);
if (ecid.isPresent()) {
tablet.deleteExternalCompaction(ecid.get());
@@ -239,13 +236,7 @@ public class ManagerMetadataUtil {
tablet.putTime(time);
newFile = Optional.of(newDatafile.insert());
- TServerInstance self = getTServerInstance(address, zooLock);
- tablet.putLocation(self, LocationType.LAST);
-
- // remove the old location
- if (lastLocation != null && !lastLocation.equals(self)) {
- tablet.deleteLocation(lastLocation, LocationType.LAST);
- }
+ updateLastForCompactionMode(context, tablet, lastLocation, address,
zooLock);
}
tablet.putFlushId(flushId);
@@ -256,4 +247,64 @@ public class ManagerMetadataUtil {
tablet.mutate();
return newFile;
}
+
+ /**
+ * Update the last location if the location mode is "assignment". This will
delete the previous
+ * last location if needed and set the new last location
+ *
+ * @param context The server context
+ * @param ample The metadata persistence layer
+ * @param tabletMutator The mutator being built
+ * @param extent The tablet extent
+ * @param location The new location
+ */
+ public static void updateLastForAssignmentMode(ClientContext context, Ample
ample,
+ Ample.TabletMutator tabletMutator, KeyExtent extent, TServerInstance
location) {
+ // if the location mode is assignment, then preserve the current location
in the last
+ // location value
+ if
("assignment".equals(context.getConfiguration().get(Property.TSERV_LAST_LOCATION_MODE)))
{
+ TabletMetadata lastMetadata = ample.readTablet(extent,
TabletMetadata.ColumnType.LAST);
+ TServerInstance lastLocation = (lastMetadata == null ? null :
lastMetadata.getLast());
+ ManagerMetadataUtil.updateLast(tabletMutator, lastLocation, location);
+ }
+ }
+
+ /**
+ * Update the last location if the location mode is "compaction". This will
delete the previous
+ * last location if needed and set the new last location
+ *
+ * @param context The server context
+ * @param tabletMutator The mutator being built
+ * @param lastLocation The last location
+ * @param address The server address
+ * @param zooLock The zookeeper lock
+ */
+ public static void updateLastForCompactionMode(ClientContext context,
TabletMutator tabletMutator,
+ TServerInstance lastLocation, String address, ServiceLock zooLock) {
+ // if the location mode is 'compaction', then preserve the current
compaction location in the
+ // last location value
+ if
("compaction".equals(context.getConfiguration().get(Property.TSERV_LAST_LOCATION_MODE)))
{
+ TServerInstance newLocation = getTServerInstance(address, zooLock);
+ updateLast(tabletMutator, lastLocation, newLocation);
+ }
+ }
+
+ /**
+ * Update the last location, deleting the previous location if needed
+ *
+ * @param tabletMutator The mutator being built
+ * @param lastLocation The last location (may be null)
+ * @param newLocation The new location
+ */
+ public static void updateLast(TabletMutator tabletMutator, TServerInstance
lastLocation,
+ TServerInstance newLocation) {
+ if (lastLocation != null) {
+ if (!lastLocation.equals(newLocation)) {
+ tabletMutator.deleteLocation(lastLocation, LocationType.LAST);
+ tabletMutator.putLocation(newLocation, LocationType.LAST);
+ }
+ } else {
+ tabletMutator.putLocation(newLocation, LocationType.LAST);
+ }
+ }
}
diff --git
a/server/base/src/test/java/org/apache/accumulo/server/manager/state/RootTabletStateStoreTest.java
b/server/base/src/test/java/org/apache/accumulo/server/manager/state/RootTabletStateStoreTest.java
index 86e8eb96bc..90079a9bfc 100644
---
a/server/base/src/test/java/org/apache/accumulo/server/manager/state/RootTabletStateStoreTest.java
+++
b/server/base/src/test/java/org/apache/accumulo/server/manager/state/RootTabletStateStoreTest.java
@@ -20,6 +20,7 @@ package org.apache.accumulo.server.manager.state;
import static java.nio.charset.StandardCharsets.UTF_8;
import static
org.apache.accumulo.server.init.ZooKeeperInitializer.getInitialRootTabletJson;
+import static org.easymock.EasyMock.expect;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -40,7 +41,10 @@ import
org.apache.accumulo.core.metadata.schema.RootTabletMetadata;
import org.apache.accumulo.core.metadata.schema.TabletMetadata;
import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
import org.apache.accumulo.core.metadata.schema.TabletsMetadata;
+import org.apache.accumulo.server.MockServerContext;
+import org.apache.accumulo.server.ServerContext;
import org.apache.accumulo.server.metadata.TabletMutatorBase;
+import org.easymock.EasyMock;
import org.junit.jupiter.api.Test;
import com.google.common.base.Preconditions;
@@ -85,7 +89,10 @@ public class RootTabletStateStoreTest {
@Test
public void testRootTabletStateStore() throws DistributedStoreException {
- ZooTabletStateStore tstore = new ZooTabletStateStore(new TestAmple());
+ ServerContext context = MockServerContext.get();
+ expect(context.getAmple()).andReturn(new TestAmple()).anyTimes();
+ EasyMock.replay(context);
+ ZooTabletStateStore tstore = new ZooTabletStateStore(context);
KeyExtent root = RootTable.EXTENT;
String sessionId = "this is my unique session data";
TServerInstance server =
diff --git
a/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
b/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
index 30c4e4ee94..33c4346dff 100644
---
a/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
+++
b/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
@@ -915,6 +915,15 @@ abstract class TabletGroupWatcher extends
AccumuloDaemonThread {
continue;
}
+ TServerInstance lastLocation = unassigned.get(assignment.getKey());
+ if (lastLocation != null
+ &&
!assignment.getValue().getHostPort().equals(lastLocation.getHostPort())) {
+ Manager.log.warn(
+ "balancer assigned {} to {} which is not the suggested
location of {}",
+ assignment.getKey(), assignment.getValue().getHostPort(),
+ lastLocation.getHostPort());
+ }
+
tLists.assignments.add(new Assignment(assignment.getKey(),
assignment.getValue()));
}
} else {
diff --git
a/test/src/main/java/org/apache/accumulo/test/functional/AssignLocationModeIT.java
b/test/src/main/java/org/apache/accumulo/test/functional/AssignLocationModeIT.java
new file mode 100644
index 0000000000..dae97130f7
--- /dev/null
+++
b/test/src/main/java/org/apache/accumulo/test/functional/AssignLocationModeIT.java
@@ -0,0 +1,111 @@
+/*
+ * 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.functional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.time.Duration;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.BatchWriter;
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.metadata.MetadataTable;
+import org.apache.accumulo.core.metadata.TabletLocationState;
+import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
+import org.apache.accumulo.core.util.UtilWaitThread;
+import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl;
+import org.apache.accumulo.server.manager.state.MetaDataTableScanner;
+import org.apache.hadoop.conf.Configuration;
+import org.junit.jupiter.api.Test;
+
+public class AssignLocationModeIT extends ConfigurableMacBase {
+
+ @Override
+ protected Duration defaultTimeout() {
+ return Duration.ofMinutes(2);
+ }
+
+ @Override
+ public void configure(MiniAccumuloConfigImpl cfg, Configuration fsConf) {
+ cfg.setProperty(Property.TSERV_LAST_LOCATION_MODE, "assignment");
+ }
+
+ @Test
+ public void test() throws Exception {
+ try (AccumuloClient c =
+ getCluster().createAccumuloClient("root", new
PasswordToken(ROOT_PASSWORD))) {
+ String tableName = super.getUniqueNames(1)[0];
+ c.tableOperations().create(tableName);
+ String tableId = c.tableOperations().tableIdMap().get(tableName);
+ // wait for the table to be online
+ TabletLocationState newTablet;
+ do {
+ UtilWaitThread.sleep(250);
+ newTablet = getTabletLocationState(c, tableId);
+ } while (newTablet.current == null);
+ // this would be null if the mode was not "assign"
+ assertEquals(newTablet.current, newTablet.last);
+ assertNull(newTablet.future);
+
+ // put something in it
+ try (BatchWriter bw = c.createBatchWriter(tableName)) {
+ Mutation m = new Mutation("a");
+ m.put("b", "c", "d");
+ bw.addMutation(m);
+ }
+ // assert that the default mode is "assign"
+ assertEquals("assignment",
c.instanceOperations().getSystemConfiguration()
+ .get(Property.TSERV_LAST_LOCATION_MODE.getKey()));
+
+ // last location should not be set yet
+ TabletLocationState unflushed = getTabletLocationState(c, tableId);
+ assertEquals(newTablet.current, unflushed.current);
+ assertEquals(newTablet.current, unflushed.last);
+ assertNull(newTablet.future);
+
+ // take the tablet offline
+ c.tableOperations().offline(tableName, true);
+ TabletLocationState offline = getTabletLocationState(c, tableId);
+ assertNull(offline.future);
+ assertNull(offline.current);
+ assertEquals(newTablet.current, offline.last);
+
+ // put it back online, should have the same last location
+ c.tableOperations().online(tableName, true);
+ TabletLocationState online = getTabletLocationState(c, tableId);
+ assertNull(online.future);
+ assertNotNull(online.current);
+ assertEquals(newTablet.last, online.last);
+ }
+ }
+
+ private TabletLocationState getTabletLocationState(AccumuloClient c, String
tableId) {
+ try (MetaDataTableScanner s = new MetaDataTableScanner((ClientContext) c,
+ new Range(TabletsSection.encodeRow(TableId.of(tableId), null)),
MetadataTable.NAME)) {
+ return s.next();
+ }
+ }
+}
diff --git
a/test/src/main/java/org/apache/accumulo/test/functional/CompactLocationModeIT.java
b/test/src/main/java/org/apache/accumulo/test/functional/CompactLocationModeIT.java
new file mode 100644
index 0000000000..4f2dccaa97
--- /dev/null
+++
b/test/src/main/java/org/apache/accumulo/test/functional/CompactLocationModeIT.java
@@ -0,0 +1,118 @@
+/*
+ * 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.functional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.time.Duration;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.BatchWriter;
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.metadata.MetadataTable;
+import org.apache.accumulo.core.metadata.TabletLocationState;
+import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection;
+import org.apache.accumulo.core.util.UtilWaitThread;
+import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl;
+import org.apache.accumulo.server.manager.state.MetaDataTableScanner;
+import org.apache.hadoop.conf.Configuration;
+import org.junit.jupiter.api.Test;
+
+public class CompactLocationModeIT extends ConfigurableMacBase {
+
+ @Override
+ protected Duration defaultTimeout() {
+ return Duration.ofMinutes(2);
+ }
+
+ @Override
+ public void configure(MiniAccumuloConfigImpl cfg, Configuration fsConf) {
+ cfg.setProperty(Property.TSERV_LAST_LOCATION_MODE, "compaction");
+ }
+
+ @Test
+ public void test() throws Exception {
+ try (AccumuloClient c =
+ getCluster().createAccumuloClient("root", new
PasswordToken(ROOT_PASSWORD))) {
+ String tableName = super.getUniqueNames(1)[0];
+ c.tableOperations().create(tableName);
+ String tableId = c.tableOperations().tableIdMap().get(tableName);
+ // wait for the table to be online
+ TabletLocationState newTablet;
+ do {
+ UtilWaitThread.sleep(250);
+ newTablet = getTabletLocationState(c, tableId);
+ } while (newTablet.current == null);
+ assertNull(newTablet.last);
+ assertNull(newTablet.future);
+
+ // put something in it
+ try (BatchWriter bw = c.createBatchWriter(tableName)) {
+ Mutation m = new Mutation("a");
+ m.put("b", "c", "d");
+ bw.addMutation(m);
+ }
+ // assert that the default mode is "compact"
+ assertEquals("compaction",
c.instanceOperations().getSystemConfiguration()
+ .get(Property.TSERV_LAST_LOCATION_MODE.getKey()));
+
+ // no last location should be set yet
+ TabletLocationState unflushed = getTabletLocationState(c, tableId);
+ assertEquals(newTablet.current, unflushed.current);
+ assertNull(unflushed.last);
+ assertNull(newTablet.future);
+
+ // This should give it a last location if the mode is being used
correctly
+ c.tableOperations().flush(tableName, null, null, true);
+
+ TabletLocationState flushed = getTabletLocationState(c, tableId);
+ assertEquals(newTablet.current, flushed.current);
+ assertEquals(flushed.current, flushed.last);
+ assertNull(newTablet.future);
+
+ // take the tablet offline
+ c.tableOperations().offline(tableName, true);
+ TabletLocationState offline = getTabletLocationState(c, tableId);
+ assertNull(offline.future);
+ assertNull(offline.current);
+ assertEquals(flushed.current, offline.last);
+
+ // put it back online, should have the same last location
+ c.tableOperations().online(tableName, true);
+ TabletLocationState online = getTabletLocationState(c, tableId);
+ assertNull(online.future);
+ assertNotNull(online.current);
+ assertEquals(offline.last, online.last);
+ }
+ }
+
+ private TabletLocationState getTabletLocationState(AccumuloClient c, String
tableId) {
+ try (MetaDataTableScanner s = new MetaDataTableScanner((ClientContext) c,
+ new Range(TabletsSection.encodeRow(TableId.of(tableId), null)),
MetadataTable.NAME)) {
+ return s.next();
+ }
+ }
+}