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
commit 219d4f224e1d97d2bd7cd5dec5ec5968e89facef Merge: f9dabfc250 ef213b520b Author: Keith Turner <ktur...@apache.org> AuthorDate: Fri May 24 12:45:55 2024 -0400 Merge branch 'main' into elasticity .../apache/accumulo/core/metadata/schema/Ample.java | 4 ++-- .../core/metadata/schema/TabletMetadata.java | 20 +++++++++----------- .../core/metadata/schema/TabletMetadataBuilder.java | 5 +++-- .../core/metadata/schema/TabletMutatorBase.java | 11 +++++++---- .../core/metadata/schema/TabletMetadataTest.java | 11 +++++++---- .../accumulo/server/util/MetadataTableUtil.java | 8 ++++---- .../accumulo/server/SetEncodingIteratorTest.java | 13 +++++++++---- .../server/manager/state/TabletManagementTest.java | 15 ++++++++++++--- .../manager/tableOps/merge/DeleteTablets.java | 6 +++--- .../org/apache/accumulo/test/functional/SplitIT.java | 7 +++++-- .../org/apache/accumulo/test/metrics/MetricsIT.java | 4 +++- 11 files changed, 64 insertions(+), 40 deletions(-) diff --cc core/src/main/java/org/apache/accumulo/core/metadata/schema/Ample.java index 3c00d70f33,931e415774..d3875ca826 --- a/core/src/main/java/org/apache/accumulo/core/metadata/schema/Ample.java +++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/Ample.java @@@ -20,22 -20,15 +20,22 @@@ package org.apache.accumulo.core.metada import java.util.Collection; import java.util.Iterator; +import java.util.Map; - import java.util.Set; import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.stream.Stream; +import org.apache.accumulo.core.client.ConditionalWriter; +import org.apache.accumulo.core.client.admin.TabletAvailability; +import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.Mutation; import org.apache.accumulo.core.data.TableId; ++import org.apache.accumulo.core.data.Value; import org.apache.accumulo.core.dataImpl.KeyExtent; +import org.apache.accumulo.core.fate.FateId; import org.apache.accumulo.core.gc.GcCandidate; import org.apache.accumulo.core.gc.ReferenceFile; -import org.apache.accumulo.core.lock.ServiceLock; import org.apache.accumulo.core.metadata.AccumuloTable; import org.apache.accumulo.core.metadata.ReferencedTabletFile; import org.apache.accumulo.core.metadata.ScanServerRefTabletFile; @@@ -343,93 -265,50 +343,93 @@@ public interface Ample /** * Interface for changing a tablets persistent data. */ - interface TabletMutator { - TabletMutator putPrevEndRow(Text per); + interface TabletUpdates<T> { + T putPrevEndRow(Text per); + + T putFile(ReferencedTabletFile path, DataFileValue dfv); + + T putFile(StoredTabletFile path, DataFileValue dfv); + + T deleteFile(StoredTabletFile path); + + T putScan(StoredTabletFile path); - TabletMutator putFile(ReferencedTabletFile path, DataFileValue dfv); + T deleteScan(StoredTabletFile path); - TabletMutator putFile(StoredTabletFile path, DataFileValue dfv); + T putFlushId(long flushId); - TabletMutator deleteFile(StoredTabletFile path); + T putFlushNonce(long flushNonce); - TabletMutator putScan(StoredTabletFile path); + T putLocation(Location location); - TabletMutator deleteScan(StoredTabletFile path); + T deleteLocation(Location location); - TabletMutator putCompactionId(long compactionId); + T putDirName(String dirName); - TabletMutator putFlushId(long flushId); + T putWal(LogEntry logEntry); - TabletMutator putLocation(Location location); + T deleteWal(LogEntry logEntry); - TabletMutator deleteLocation(Location location); + T putTime(MetadataTime time); - TabletMutator putZooLock(ServiceLock zooLock); + T putBulkFile(ReferencedTabletFile bulkref, FateId fateId); - TabletMutator putDirName(String dirName); + T deleteBulkFile(StoredTabletFile bulkref); - TabletMutator putWal(LogEntry logEntry); + T putSuspension(TServerInstance tserver, SteadyTime suspensionTime); - TabletMutator deleteWal(LogEntry wal); + T deleteSuspension(); - TabletMutator putTime(MetadataTime time); + T putExternalCompaction(ExternalCompactionId ecid, CompactionMetadata ecMeta); - TabletMutator putBulkFile(ReferencedTabletFile bulkref, long tid); + T deleteExternalCompaction(ExternalCompactionId ecid); - TabletMutator deleteBulkFile(StoredTabletFile bulkref); + T putCompacted(FateId fateId); - TabletMutator putSuspension(TServerInstance tserver, SteadyTime suspensionTime); + T deleteCompacted(FateId fateId); - TabletMutator deleteSuspension(); + T putTabletAvailability(TabletAvailability tabletAvailability); - TabletMutator putExternalCompaction(ExternalCompactionId ecid, - ExternalCompactionMetadata ecMeta); + T setHostingRequested(); - TabletMutator deleteExternalCompaction(ExternalCompactionId ecid); + T deleteHostingRequested(); + + T putOperation(TabletOperationId opId); + + T deleteOperation(); + + T putSelectedFiles(SelectedFiles selectedFiles); + + T deleteSelectedFiles(); + /** + * Deletes all the columns in the keys. + * + * @throws IllegalArgumentException if rows in keys do not match tablet row or column visibility + * is not empty + */ - T deleteAll(Set<Key> keys); ++ T deleteAll(Collection<Map.Entry<Key,Value>> entries); + + T setMerged(); + + T deleteMerged(); + + T putUserCompactionRequested(FateId fateId); + + T deleteUserCompactionRequested(FateId fateId); + + T setUnSplittable(UnSplittableMetadata unSplittableMeta); + + T deleteUnSplittable(); + + /** + * By default the server lock is automatically added to mutations unless this method is set to + * false. + */ + T automaticallyPutServerLock(boolean b); + } + + interface TabletMutator extends TabletUpdates<TabletMutator> { /** * This method persist (or queues for persisting) previous put and deletes against this object. * Unless this method is called, previous calls will never be persisted. The purpose of this diff --cc core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java index ef1fd5ea9c,33d307ce59..59a2c1d760 --- a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java +++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java @@@ -39,12 -37,9 +39,11 @@@ import java.util.Objects import java.util.Optional; import java.util.OptionalLong; import java.util.Set; - import java.util.SortedMap; import org.apache.accumulo.core.Constants; +import org.apache.accumulo.core.client.admin.TabletAvailability; import org.apache.accumulo.core.clientImpl.ClientContext; +import org.apache.accumulo.core.clientImpl.TabletAvailabilityUtil; import org.apache.accumulo.core.data.ByteSequence; import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.TableId; @@@ -87,8 -77,7 +86,7 @@@ import com.google.common.base.Supplier import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSortedMap.Builder; +import com.google.common.collect.ImmutableSet; - import com.google.common.collect.ImmutableSortedMap; import com.google.common.net.HostAndPort; public class TabletMetadata { @@@ -111,20 -100,13 +109,20 @@@ private final String dirName; private final MetadataTime time; private final String cloned; - private final SortedMap<Key,Value> keyValues; + private final List<Entry<Key,Value>> keyValues; private final OptionalLong flush; + private final OptionalLong flushNonce; private final List<LogEntry> logs; - private final OptionalLong compact; - private final Double splitRatio; - private final Map<ExternalCompactionId,ExternalCompactionMetadata> extCompactions; + private final Map<ExternalCompactionId,CompactionMetadata> extCompactions; private final boolean merged; + private final TabletAvailability availability; + private final boolean onDemandHostingRequested; + private final TabletOperationId operationId; + private final boolean futureAndCurrentLocationSet; + private final Set<FateId> compacted; + private final Set<FateId> userCompactionsRequested; + private final UnSplittableMetadata unSplittableMetadata; + private final Supplier<Long> fileSize; private TabletMetadata(Builder tmBuilder) { this.tableId = tmBuilder.tableId; @@@ -142,29 -125,14 +140,29 @@@ this.dirName = tmBuilder.dirName; this.time = tmBuilder.time; this.cloned = tmBuilder.cloned; - this.keyValues = Optional.ofNullable(tmBuilder.keyValues).map(ImmutableSortedMap.Builder::build) - .orElse(null); + this.keyValues = + Optional.ofNullable(tmBuilder.keyValues).map(ImmutableList.Builder::build).orElse(null); this.flush = tmBuilder.flush; + this.flushNonce = tmBuilder.flushNonce; this.logs = Objects.requireNonNull(tmBuilder.logs.build()); - this.compact = Objects.requireNonNull(tmBuilder.compact); - this.splitRatio = tmBuilder.splitRatio; this.extCompactions = Objects.requireNonNull(tmBuilder.extCompactions.build()); this.merged = tmBuilder.merged; + this.availability = Objects.requireNonNull(tmBuilder.availability); + this.onDemandHostingRequested = tmBuilder.onDemandHostingRequested; + this.operationId = tmBuilder.operationId; + this.futureAndCurrentLocationSet = tmBuilder.futureAndCurrentLocationSet; + this.compacted = tmBuilder.compacted.build(); + this.userCompactionsRequested = tmBuilder.userCompactionsRequested.build(); + this.unSplittableMetadata = tmBuilder.unSplittableMetadata; + this.fileSize = Suppliers.memoize(() -> { + // This code was using a java stream. While profiling SplitMillionIT, the stream was showing + // up as hot when scanning 1 million tablets. Converted to a for loop to improve performance. + long sum = 0; + for (var dfv : files.values()) { + sum += dfv.getSize(); + } + return sum; + }); this.extent = Suppliers.memoize(() -> new KeyExtent(getTableId(), getEndRow(), getPrevEndRow())); } @@@ -415,54 -380,34 +413,54 @@@ return merged; } - public List<Entry<Key,Value>> getKeyValues() { - Preconditions.checkState(keyValues != null, "Requested key values when it was not saved"); - return keyValues; + public Set<FateId> getUserCompactionsRequested() { + ensureFetched(ColumnType.USER_COMPACTION_REQUESTED); + return userCompactionsRequested; } - public TabletState getTabletState(Set<TServerInstance> liveTServers) { - ensureFetched(ColumnType.LOCATION); - ensureFetched(ColumnType.LAST); - ensureFetched(ColumnType.SUSPEND); - try { - Location current = null; - Location future = null; - if (hasCurrent()) { - current = location; - } else { - future = location; - } - // only care about the state so don't need walogs and chopped params - // Use getExtent() when passing the extent as the private reference may not have been - // initialized yet. This will also ensure PREV_ROW was fetched - var tls = new TabletLocationState(getExtent(), future, current, last, suspend, null); - return tls.getState(liveTServers); - } catch (TabletLocationState.BadLocationStateException blse) { - throw new IllegalArgumentException("Error creating TabletLocationState", blse); + public TabletAvailability getTabletAvailability() { + if (AccumuloTable.ROOT.tableId().equals(getTableId()) + || AccumuloTable.METADATA.tableId().equals(getTableId())) { + // Override the availability for the system tables + return TabletAvailability.HOSTED; } + ensureFetched(ColumnType.AVAILABILITY); + return availability; + } + + public boolean getHostingRequested() { + ensureFetched(ColumnType.HOSTING_REQUESTED); + return onDemandHostingRequested; + } + + public UnSplittableMetadata getUnSplittable() { + ensureFetched(ColumnType.UNSPLITTABLE); + return unSplittableMetadata; } - public Map<ExternalCompactionId,ExternalCompactionMetadata> getExternalCompactions() { + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("tableId", tableId) + .append("prevEndRow", prevEndRow).append("sawPrevEndRow", sawPrevEndRow) + .append("endRow", endRow).append("location", location).append("files", files) + .append("scans", scans).append("loadedFiles", loadedFiles) + .append("fetchedCols", fetchedCols).append("extent", extent).append("last", last) + .append("suspend", suspend).append("dirName", dirName).append("time", time) + .append("cloned", cloned).append("flush", flush).append("logs", logs) + .append("extCompactions", extCompactions).append("availability", availability) + .append("onDemandHostingRequested", onDemandHostingRequested) + .append("operationId", operationId).append("selectedFiles", selectedFiles) + .append("futureAndCurrentLocationSet", futureAndCurrentLocationSet) + .append("userCompactionsRequested", userCompactionsRequested) + .append("unSplittableMetadata", unSplittableMetadata).toString(); + } + - public SortedMap<Key,Value> getKeyValues() { ++ public List<Entry<Key,Value>> getKeyValues() { + Preconditions.checkState(keyValues != null, "Requested key values when it was not saved"); + return keyValues; + } + + public Map<ExternalCompactionId,CompactionMetadata> getExternalCompactions() { ensureFetched(ColumnType.ECOMP); return extCompactions; } @@@ -677,21 -577,14 +675,21 @@@ private String dirName; private MetadataTime time; private String cloned; - private ImmutableSortedMap.Builder<Key,Value> keyValues; + private ImmutableList.Builder<Entry<Key,Value>> keyValues; private OptionalLong flush = OptionalLong.empty(); + private OptionalLong flushNonce = OptionalLong.empty(); private final ImmutableList.Builder<LogEntry> logs = ImmutableList.builder(); - private OptionalLong compact = OptionalLong.empty(); - private Double splitRatio = null; - private final ImmutableMap.Builder<ExternalCompactionId, - ExternalCompactionMetadata> extCompactions = ImmutableMap.builder(); + private final ImmutableMap.Builder<ExternalCompactionId,CompactionMetadata> extCompactions = + ImmutableMap.builder(); private boolean merged; + private TabletAvailability availability = TabletAvailability.ONDEMAND; + private boolean onDemandHostingRequested; + private TabletOperationId operationId; + private boolean futureAndCurrentLocationSet; + private final ImmutableSet.Builder<FateId> compacted = ImmutableSet.builder(); + private final ImmutableSet.Builder<FateId> userCompactionsRequested = ImmutableSet.builder(); + private UnSplittableMetadata unSplittableMetadata; + // private Supplier<Long> fileSize; void table(TableId tableId) { this.tableId = tableId; @@@ -776,36 -674,11 +774,36 @@@ this.merged = merged; } + void availability(TabletAvailability availability) { + this.availability = availability; + } + + void onDemandHostingRequested(boolean onDemandHostingRequested) { + this.onDemandHostingRequested = onDemandHostingRequested; + } + + void operationId(String val) { + Preconditions.checkState(operationId == null); + operationId = TabletOperationId.from(val); + } + + void compacted(FateId compacted) { + this.compacted.add(compacted); + } + + void userCompactionsRequested(FateId userCompactionRequested) { + this.userCompactionsRequested.add(userCompactionRequested); + } + + void unSplittableMetadata(UnSplittableMetadata unSplittableMetadata) { + this.unSplittableMetadata = unSplittableMetadata; + } + - void keyValue(Key key, Value value) { + void keyValue(Entry<Key,Value> kv) { if (this.keyValues == null) { - this.keyValues = ImmutableSortedMap.naturalOrder(); + this.keyValues = ImmutableList.builder(); } - this.keyValues.put(key, value); + this.keyValues.add(kv); } TabletMetadata build(EnumSet<ColumnType> fetchedCols) { diff --cc core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadataBuilder.java index eb924e7833,0000000000..9bfd2831bf mode 100644,000000..100644 --- a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadataBuilder.java +++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadataBuilder.java @@@ -1,333 -1,0 +1,334 @@@ +/* + * 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.core.metadata.schema; + +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.AVAILABILITY; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.COMPACTED; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.DIR; +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.FLUSH_NONCE; +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; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.MERGED; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.OPID; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.SCANS; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.SELECTED; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.SUSPEND; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.TIME; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.UNSPLITTABLE; +import static org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.USER_COMPACTION_REQUESTED; + +import java.util.Arrays; ++import java.util.Collection; +import java.util.EnumSet; - import java.util.Set; ++import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.accumulo.core.client.admin.TabletAvailability; +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Mutation; +import org.apache.accumulo.core.data.Value; +import org.apache.accumulo.core.dataImpl.KeyExtent; +import org.apache.accumulo.core.fate.FateId; +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.tabletserver.log.LogEntry; +import org.apache.accumulo.core.util.time.SteadyTime; +import org.apache.hadoop.io.Text; + +public class TabletMetadataBuilder implements Ample.TabletUpdates<TabletMetadataBuilder> { + + public static class InternalBuilder extends TabletMutatorBase<InternalBuilder> { + protected InternalBuilder(KeyExtent extent) { + super(extent); + } + + @Override + public Mutation getMutation() { + return super.getMutation(); + } + } + + private final InternalBuilder internalBuilder; + EnumSet<TabletMetadata.ColumnType> fetched; + + protected TabletMetadataBuilder(KeyExtent extent) { + internalBuilder = new InternalBuilder(extent); + fetched = EnumSet.noneOf(TabletMetadata.ColumnType.class); + putPrevEndRow(extent.prevEndRow()); + } + + @Override + public TabletMetadataBuilder putPrevEndRow(Text per) { + fetched.add(PREV_ROW); + internalBuilder.putPrevEndRow(per); + return this; + } + + @Override + public TabletMetadataBuilder putFile(ReferencedTabletFile path, DataFileValue dfv) { + fetched.add(FILES); + internalBuilder.putFile(path, dfv); + return this; + } + + @Override + public TabletMetadataBuilder putFile(StoredTabletFile path, DataFileValue dfv) { + fetched.add(FILES); + internalBuilder.putFile(path, dfv); + return this; + } + + @Override + public TabletMetadataBuilder deleteFile(StoredTabletFile path) { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putScan(StoredTabletFile path) { + fetched.add(SCANS); + internalBuilder.putScan(path); + return this; + } + + @Override + public TabletMetadataBuilder deleteScan(StoredTabletFile path) { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putFlushId(long flushId) { + fetched.add(FLUSH_ID); + internalBuilder.putFlushId(flushId); + return this; + } + + @Override + public TabletMetadataBuilder putFlushNonce(long flushNonce) { + fetched.add(FLUSH_NONCE); + internalBuilder.putFlushId(flushNonce); + return this; + } + + @Override + public TabletMetadataBuilder putLocation(TabletMetadata.Location location) { + fetched.add(LOCATION); + internalBuilder.putLocation(location); + return this; + } + + @Override + public TabletMetadataBuilder deleteLocation(TabletMetadata.Location location) { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putDirName(String dirName) { + fetched.add(DIR); + internalBuilder.putDirName(dirName); + return this; + } + + @Override + public TabletMetadataBuilder putWal(LogEntry logEntry) { + fetched.add(LOGS); + internalBuilder.putWal(logEntry); + return this; + } + + @Override + public TabletMetadataBuilder deleteWal(LogEntry logEntry) { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putTime(MetadataTime time) { + fetched.add(TIME); + internalBuilder.putTime(time); + return this; + } + + @Override + public TabletMetadataBuilder putBulkFile(ReferencedTabletFile bulkref, FateId fateId) { + fetched.add(LOADED); + internalBuilder.putBulkFile(bulkref, fateId); + return this; + } + + @Override + public TabletMetadataBuilder deleteBulkFile(StoredTabletFile bulkref) { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putSuspension(TServerInstance tserver, SteadyTime suspensionTime) { + fetched.add(SUSPEND); + internalBuilder.putSuspension(tserver, suspensionTime); + return this; + } + + @Override + public TabletMetadataBuilder deleteSuspension() { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putExternalCompaction(ExternalCompactionId ecid, + CompactionMetadata ecMeta) { + fetched.add(ECOMP); + internalBuilder.putExternalCompaction(ecid, ecMeta); + return this; + } + + @Override + public TabletMetadataBuilder deleteExternalCompaction(ExternalCompactionId ecid) { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putCompacted(FateId fateId) { + fetched.add(COMPACTED); + internalBuilder.putCompacted(fateId); + return this; + } + + @Override + public TabletMetadataBuilder deleteCompacted(FateId fateId) { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putTabletAvailability(TabletAvailability tabletAvailability) { + fetched.add(AVAILABILITY); + internalBuilder.putTabletAvailability(tabletAvailability); + return this; + } + + @Override + public TabletMetadataBuilder setHostingRequested() { + fetched.add(HOSTING_REQUESTED); + internalBuilder.setHostingRequested(); + return this; + } + + @Override + public TabletMetadataBuilder deleteHostingRequested() { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putOperation(TabletOperationId opId) { + fetched.add(OPID); + internalBuilder.putOperation(opId); + return this; + } + + @Override + public TabletMetadataBuilder deleteOperation() { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putSelectedFiles(SelectedFiles selectedFiles) { + fetched.add(SELECTED); + internalBuilder.putSelectedFiles(selectedFiles); + return this; + } + + @Override + public TabletMetadataBuilder deleteSelectedFiles() { + throw new UnsupportedOperationException(); + } + + @Override - public TabletMetadataBuilder deleteAll(Set<Key> keys) { ++ public TabletMetadataBuilder deleteAll(Collection<Map.Entry<Key,Value>> entries) { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder setMerged() { + fetched.add(MERGED); + internalBuilder.setMerged(); + return this; + } + + @Override + public TabletMetadataBuilder deleteMerged() { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder putUserCompactionRequested(FateId fateId) { + fetched.add(USER_COMPACTION_REQUESTED); + internalBuilder.putUserCompactionRequested(fateId); + return this; + } + + @Override + public TabletMetadataBuilder deleteUserCompactionRequested(FateId fateId) { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder setUnSplittable(UnSplittableMetadata unSplittableMeta) { + fetched.add(UNSPLITTABLE); + internalBuilder.setUnSplittable(unSplittableMeta); + return this; + } + + @Override + public TabletMetadataBuilder deleteUnSplittable() { + throw new UnsupportedOperationException(); + } + + @Override + public TabletMetadataBuilder automaticallyPutServerLock(boolean b) { + throw new UnsupportedOperationException(); + } + + /** + * @param extraFetched Anything that was put on the builder will automatically be added to the + * fetched set. However, for the case where something was not put and it needs to be + * fetched it can be passed here. For example to simulate a tablet w/o a location it, no + * location will be put and LOCATION would be passed in via this argument. + */ + public TabletMetadata build(TabletMetadata.ColumnType... extraFetched) { + var mutation = internalBuilder.getMutation(); + + SortedMap<Key,Value> rowMap = new TreeMap<>(); + mutation.getUpdates().forEach(cu -> { + Key k = new Key(mutation.getRow(), cu.getColumnFamily(), cu.getColumnQualifier(), + cu.getTimestamp()); + Value v = new Value(cu.getValue()); + rowMap.put(k, v); + }); + + fetched.addAll(Arrays.asList(extraFetched)); + + return TabletMetadata.convertRow(rowMap.entrySet().iterator(), fetched, true, false); + } + +} diff --cc core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMutatorBase.java index cf0208d497,a8b567775a..ab0d6490a3 --- a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMutatorBase.java +++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMutatorBase.java @@@ -16,15 -16,8 +16,16 @@@ * specific language governing permissions and limitations * under the License. */ -package org.apache.accumulo.server.metadata; +package org.apache.accumulo.core.metadata.schema; - import java.util.Set; ++import java.util.Collection; ++import java.util.Map; + +import org.apache.accumulo.core.client.admin.TabletAvailability; +import org.apache.accumulo.core.clientImpl.TabletAvailabilityUtil; +import org.apache.accumulo.core.data.ArrayByteSequence; +import org.apache.accumulo.core.data.ByteSequence; +import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.Mutation; import org.apache.accumulo.core.data.Value; import org.apache.accumulo.core.dataImpl.KeyExtent; @@@ -290,83 -236,6 +291,85 @@@ public abstract class TabletMutatorBase return mutation; } + @Override + public T putTabletAvailability(TabletAvailability tabletAvailability) { + TabletColumnFamily.AVAILABILITY_COLUMN.put(mutation, + TabletAvailabilityUtil.toValue(tabletAvailability)); + return getThis(); + } + + @Override + public T setHostingRequested() { + TabletColumnFamily.REQUESTED_COLUMN.put(mutation, EMPTY_VALUE); + return getThis(); + } + + @Override + public T deleteHostingRequested() { + TabletColumnFamily.REQUESTED_COLUMN.putDelete(mutation); + return getThis(); + } + + @Override - public T deleteAll(Set<Key> keys) { ++ public T deleteAll(Collection<Map.Entry<Key,Value>> entries) { + ByteSequence row = new ArrayByteSequence(mutation.getRow()); - keys.forEach(key -> { ++ entries.forEach(e -> { ++ var key = e.getKey(); + Preconditions.checkArgument(key.getRowData().equals(row), "Unexpected row %s %s", row, key); + Preconditions.checkArgument(key.getColumnVisibilityData().length() == 0, + "Non empty column visibility %s", key); + }); + - keys.forEach(key -> { ++ entries.forEach(e -> { ++ var key = e.getKey(); + mutation.putDelete(key.getColumnFamily(), key.getColumnQualifier()); + }); + + return getThis(); + } + + @Override + public T setMerged() { + MergedColumnFamily.MERGED_COLUMN.put(mutation, MergedColumnFamily.MERGED_VALUE); + return getThis(); + } + + @Override + public T deleteMerged() { + MergedColumnFamily.MERGED_COLUMN.putDelete(mutation); + return getThis(); + } + + @Override + public T putUserCompactionRequested(FateId fateId) { + mutation.put(UserCompactionRequestedColumnFamily.STR_NAME, fateId.canonical(), ""); + return getThis(); + } + + @Override + public T deleteUserCompactionRequested(FateId fateId) { + mutation.putDelete(UserCompactionRequestedColumnFamily.STR_NAME, fateId.canonical()); + return getThis(); + } + + @Override + public T setUnSplittable(UnSplittableMetadata unSplittableMeta) { + SplitColumnFamily.UNSPLITTABLE_COLUMN.put(mutation, new Value(unSplittableMeta.toBase64())); + return getThis(); + } + + @Override + public T deleteUnSplittable() { + SplitColumnFamily.UNSPLITTABLE_COLUMN.putDelete(mutation); + return getThis(); + } + + @Override + public T automaticallyPutServerLock(boolean b) { + putServerLock = b; + return getThis(); + } + public void setCloseAfterMutate(AutoCloseable closeable) { this.closeAfterMutate = closeable; } diff --cc core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java index 0f51d61283,8c3d84de2c..84bb870f99 --- a/core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java +++ b/core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java @@@ -53,13 -51,10 +54,14 @@@ import java.util.TreeMap import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.accumulo.core.client.admin.TabletAvailability; +import org.apache.accumulo.core.client.admin.TimeType; import org.apache.accumulo.core.data.Key; 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.data.Value; import org.apache.accumulo.core.dataImpl.KeyExtent; @@@ -173,11 -160,11 +175,13 @@@ public class TabletMetadataTest assertEquals(extent, tm.getExtent()); assertEquals(Set.of(tf1, tf2), Set.copyOf(tm.getFiles())); assertEquals(Map.of(tf1, dfv1, tf2, dfv2), tm.getFilesMap()); + assertEquals(tm.getFilesMap().values().stream().mapToLong(DataFileValue::getSize).sum(), + tm.getFileSize()); assertEquals(6L, tm.getFlushId().getAsLong()); - assertEquals(rowMap, tm.getKeyValues()); - TreeMap<Key,Value> actualRowMap = new TreeMap<>(); - tm.getKeyValues().forEach(entry -> actualRowMap.put(entry.getKey(), entry.getValue())); ++ SortedMap<Key,Value> actualRowMap = tm.getKeyValues().stream().collect( ++ Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b, TreeMap::new)); + assertEquals(rowMap, actualRowMap); - assertEquals(Map.of(new StoredTabletFile(bf1), 56L, new StoredTabletFile(bf2), 59L), + assertEquals(Map.of(new StoredTabletFile(bf1), fateId1, new StoredTabletFile(bf2), fateId2), tm.getLoaded()); assertEquals(HostAndPort.fromParts("server1", 8555), tm.getLocation().getHostAndPort()); assertEquals("s001", tm.getLocation().getSession()); @@@ -407,10 -390,8 +411,10 @@@ b.file(stf, new DataFileValue(0, 0, 0)); b.log(LogEntry.fromPath("localhost+8020/" + UUID.randomUUID())); b.scan(stf); - b.loadedFile(stf, 0L); + b.loadedFile(stf, FateId.from(FateInstanceType.USER, UUID.randomUUID())); + b.compacted(FateId.from(FateInstanceType.USER, UUID.randomUUID())); + b.userCompactionsRequested(FateId.from(FateInstanceType.USER, UUID.randomUUID())); - b.keyValue(new Key(), new Value()); + b.keyValue(new AbstractMap.SimpleImmutableEntry<>(new Key(), new Value())); var tm2 = b.build(EnumSet.allOf(ColumnType.class)); assertEquals(1, tm2.getExternalCompactions().size()); @@@ -427,143 -408,10 +431,142 @@@ assertEquals(1, tm2.getScans().size()); assertThrows(UnsupportedOperationException.class, () -> tm2.getScans().add(stf)); assertEquals(1, tm2.getLoaded().size()); - assertThrows(UnsupportedOperationException.class, () -> tm2.getLoaded().put(stf, 0L)); + assertThrows(UnsupportedOperationException.class, + () -> tm2.getLoaded().put(stf, FateId.from(FateInstanceType.USER, UUID.randomUUID()))); assertEquals(1, tm2.getKeyValues().size()); - assertThrows(UnsupportedOperationException.class, - () -> tm2.getKeyValues().put(new Key(), new Value())); + assertThrows(UnsupportedOperationException.class, () -> tm2.getKeyValues().remove(null)); + assertEquals(1, tm2.getCompacted().size()); + assertThrows(UnsupportedOperationException.class, + () -> tm2.getCompacted().add(FateId.from(FateInstanceType.USER, UUID.randomUUID()))); + assertEquals(1, tm2.getUserCompactionsRequested().size()); + assertThrows(UnsupportedOperationException.class, () -> tm2.getUserCompactionsRequested() + .add(FateId.from(FateInstanceType.USER, UUID.randomUUID()))); + } + + @Test + public void testCompactionRequestedColumn() { + KeyExtent extent = new KeyExtent(TableId.of("5"), new Text("df"), new Text("da")); + FateInstanceType type = FateInstanceType.fromTableId(extent.tableId()); + FateId userCompactFateId1 = FateId.from(type, UUID.randomUUID()); + FateId userCompactFateId2 = FateId.from(type, UUID.randomUUID()); + + // Test column set + Mutation mutation = TabletColumnFamily.createPrevRowMutation(extent); + mutation.put(UserCompactionRequestedColumnFamily.STR_NAME, userCompactFateId1.canonical(), ""); + mutation.put(UserCompactionRequestedColumnFamily.STR_NAME, userCompactFateId2.canonical(), ""); + + TabletMetadata tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(), + EnumSet.of(USER_COMPACTION_REQUESTED), true, false); + assertEquals(2, tm.getUserCompactionsRequested().size()); + assertTrue(tm.getUserCompactionsRequested().contains(userCompactFateId1)); + assertTrue(tm.getUserCompactionsRequested().contains(userCompactFateId2)); + + // Column not set + mutation = TabletColumnFamily.createPrevRowMutation(extent); + tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(), + EnumSet.of(USER_COMPACTION_REQUESTED), true, false); + assertTrue(tm.getUserCompactionsRequested().isEmpty()); + // Column not fetched + mutation = TabletColumnFamily.createPrevRowMutation(extent); + tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(), + EnumSet.of(ColumnType.PREV_ROW), true, false); + assertThrows(IllegalStateException.class, tm::getUserCompactionsRequested); + } + + @Test + public void testUnsplittableColumn() { + KeyExtent extent = new KeyExtent(TableId.of("5"), new Text("df"), new Text("da")); + + StoredTabletFile sf1 = StoredTabletFile.of(new Path("hdfs://nn1/acc/tables/1/t-0001/sf1.rf")); + StoredTabletFile sf2 = StoredTabletFile.of(new Path("hdfs://nn1/acc/tables/1/t-0001/sf2.rf")); + StoredTabletFile sf3 = StoredTabletFile.of(new Path("hdfs://nn1/acc/tables/1/t-0001/sf3.rf")); + // Same path as sf4 but with a range + StoredTabletFile sf4 = + StoredTabletFile.of(new Path("hdfs://nn1/acc/tables/1/t-0001/sf3.rf"), new Range("a", "b")); + + // Test with files + var unsplittableMeta1 = + UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120, Set.of(sf1, sf2, sf3)); + Mutation mutation = TabletColumnFamily.createPrevRowMutation(extent); + SplitColumnFamily.UNSPLITTABLE_COLUMN.put(mutation, new Value(unsplittableMeta1.toBase64())); + TabletMetadata tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(), + EnumSet.of(UNSPLITTABLE), true, false); + assertUnsplittable(unsplittableMeta1, tm.getUnSplittable(), true); + + // Test empty file set + var unsplittableMeta2 = UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120, Set.of()); + mutation = TabletColumnFamily.createPrevRowMutation(extent); + SplitColumnFamily.UNSPLITTABLE_COLUMN.put(mutation, new Value(unsplittableMeta2.toBase64())); + tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(), + EnumSet.of(UNSPLITTABLE), true, false); + assertUnsplittable(unsplittableMeta2, tm.getUnSplittable(), true); + + // Make sure not equals works as well + assertUnsplittable(unsplittableMeta1, unsplittableMeta2, false); + + // Test with ranges + // use sf4 which includes sf4 instead of sf3 which has a range + var unsplittableMeta3 = + UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120, Set.of(sf1, sf2, sf4)); + mutation = TabletColumnFamily.createPrevRowMutation(extent); + SplitColumnFamily.UNSPLITTABLE_COLUMN.put(mutation, new Value(unsplittableMeta3.toBase64())); + tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(), + EnumSet.of(UNSPLITTABLE), true, false); + assertUnsplittable(unsplittableMeta3, tm.getUnSplittable(), true); + + // make sure not equals when all the file paths are equal but one has a range + assertUnsplittable(unsplittableMeta1, unsplittableMeta3, false); + + // Column not set + mutation = TabletColumnFamily.createPrevRowMutation(extent); + tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(), + EnumSet.of(UNSPLITTABLE), true, false); + assertNull(tm.getUnSplittable()); + + // Column not fetched + mutation = TabletColumnFamily.createPrevRowMutation(extent); + tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(), + EnumSet.of(ColumnType.PREV_ROW), true, false); + assertThrows(IllegalStateException.class, tm::getUnSplittable); + } + + @Test + public void testUnsplittableWithRange() { + KeyExtent extent = new KeyExtent(TableId.of("5"), new Text("df"), new Text("da")); + + // Files with same path and different ranges + StoredTabletFile sf1 = StoredTabletFile.of(new Path("hdfs://nn1/acc/tables/1/t-0001/sf1.rf")); + StoredTabletFile sf2 = + StoredTabletFile.of(new Path("hdfs://nn1/acc/tables/1/t-0001/sf1.rf"), new Range("a", "b")); + StoredTabletFile sf3 = + StoredTabletFile.of(new Path("hdfs://nn1/acc/tables/1/t-0001/sf1.rf"), new Range("a", "d")); + + var meta1 = UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120, Set.of(sf1)); + var meta2 = UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120, Set.of(sf2)); + var meta3 = UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120, Set.of(sf3)); + + // compare each against the others to make sure not equal + assertUnsplittable(meta1, meta2, false); + assertUnsplittable(meta1, meta3, false); + assertUnsplittable(meta2, meta3, false); + } + + private void assertUnsplittable(UnSplittableMetadata meta1, UnSplittableMetadata meta2, + boolean equal) { + assertEquals(equal, meta1.equals(meta2)); + assertEquals(equal, meta1.hashCode() == meta2.hashCode()); + assertEquals(equal, meta1.toBase64().equals(meta2.toBase64())); + } + + @Test + public void testUnknownColFamily() { + KeyExtent extent = new KeyExtent(TableId.of("5"), new Text("df"), new Text("da")); + Mutation mutation = TabletColumnFamily.createPrevRowMutation(extent); + + mutation.put("1234567890abcdefg", "xyz", "v1"); + assertThrows(IllegalStateException.class, () -> TabletMetadata + .convertRow(toRowMap(mutation).entrySet().iterator(), EnumSet.of(MERGED), true, false)); } private SortedMap<Key,Value> toRowMap(Mutation mutation) { diff --cc server/base/src/main/java/org/apache/accumulo/server/util/MetadataTableUtil.java index ed8a835e34,e53c908907..e767bca510 --- a/server/base/src/main/java/org/apache/accumulo/server/util/MetadataTableUtil.java +++ b/server/base/src/main/java/org/apache/accumulo/server/util/MetadataTableUtil.java @@@ -233,13 -389,21 +233,13 @@@ public class MetadataTableUtil return new Pair<>(result, sizes); } - public static void removeUnusedWALEntries(ServerContext context, KeyExtent extent, - final List<LogEntry> entries, ServiceLock zooLock) { - TabletMutator tablet = context.getAmple().mutateTablet(extent); - entries.forEach(tablet::deleteWal); - tablet.putZooLock(zooLock); - tablet.mutate(); - } - private static Mutation createCloneMutation(TableId srcTableId, TableId tableId, - Map<Key,Value> tablet) { + Iterable<Entry<Key,Value>> tablet) { - KeyExtent ke = KeyExtent.fromMetaRow(tablet.keySet().iterator().next().getRow()); + KeyExtent ke = KeyExtent.fromMetaRow(tablet.iterator().next().getKey().getRow()); Mutation m = new Mutation(TabletsSection.encodeRow(tableId, ke.endRow())); - for (Entry<Key,Value> entry : tablet.entrySet()) { + for (Entry<Key,Value> entry : tablet) { if (entry.getKey().getColumnFamily().equals(DataFileColumnFamily.NAME)) { String cf = entry.getKey().getColumnQualifier().toString(); if (!cf.startsWith("../") && !cf.contains(":")) { @@@ -346,48 -523,33 +346,48 @@@ if (!cloneSuccessful) { srcFiles.addAll(srcTablet.getFiles()); } - } - if (cloneSuccessful) { - continue; - } + while (cmp > 0) { + srcTablet = srcIter.next(); + srcTablets.add(srcTablet); + srcEndRow = srcTablet.getEndRow(); + cmp = compareEndRows(cloneEndRow, srcEndRow); + if (cmp < 0) { + throw new TabletDeletedException( + "Tablets deleted from src during clone : " + cloneEndRow + " " + srcEndRow); + } - if (srcFiles.containsAll(cloneFiles)) { - // write out marker that this tablet was successfully cloned - Mutation m = new Mutation(cloneTablet.getExtent().toMetaRow()); - m.put(ClonedColumnFamily.NAME, new Text(""), new Value("OK")); - bw.addMutation(m); - } else { - // delete existing cloned tablet entry - Mutation m = new Mutation(cloneTablet.getExtent().toMetaRow()); + if (!cloneSuccessful) { + srcFiles.addAll(srcTablet.getFiles()); + } + } - for (Entry<Key,Value> entry : cloneTablet.getKeyValues()) { - Key k = entry.getKey(); - m.putDelete(k.getColumnFamily(), k.getColumnQualifier(), k.getTimestamp()); + if (cloneSuccessful) { + continue; } - bw.addMutation(m); + if (srcFiles.containsAll(cloneFiles)) { + // write out marker that this tablet was successfully cloned + Mutation m = new Mutation(cloneTablet.getExtent().toMetaRow()); + m.put(ClonedColumnFamily.NAME, new Text(""), new Value("OK")); + bw.addMutation(m); + } else { + // delete existing cloned tablet entry + Mutation m = new Mutation(cloneTablet.getExtent().toMetaRow()); - for (Entry<Key,Value> entry : cloneTablet.getKeyValues().entrySet()) { - for (TabletMetadata st : srcTablets) { - bw.addMutation(createCloneMutation(srcTableId, tableId, st.getKeyValues())); - } ++ for (Entry<Key,Value> entry : cloneTablet.getKeyValues()) { + Key k = entry.getKey(); + m.putDelete(k.getColumnFamily(), k.getColumnQualifier(), k.getTimestamp()); + } + + bw.addMutation(m); - rewrites++; + for (TabletMetadata st : srcTablets) { + bw.addMutation(createCloneMutation(srcTableId, tableId, st.getKeyValues())); + } + + rewrites++; + } } } diff --cc server/base/src/test/java/org/apache/accumulo/server/SetEncodingIteratorTest.java index 90195493cd,0000000000..fcf4f14ab4 mode 100644,000000..100644 --- a/server/base/src/test/java/org/apache/accumulo/server/SetEncodingIteratorTest.java +++ b/server/base/src/test/java/org/apache/accumulo/server/SetEncodingIteratorTest.java @@@ -1,204 -1,0 +1,209 @@@ +/* + * 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.server; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; ++import java.util.stream.Collectors; + +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Range; +import org.apache.accumulo.core.data.TableId; +import org.apache.accumulo.core.data.Value; +import org.apache.accumulo.core.dataImpl.KeyExtent; +import org.apache.accumulo.core.iteratorsImpl.system.SortedMapIterator; +import org.apache.accumulo.core.metadata.ReferencedTabletFile; +import org.apache.accumulo.core.metadata.StoredTabletFile; +import org.apache.accumulo.core.metadata.schema.DataFileValue; +import org.apache.accumulo.core.metadata.schema.MetadataSchema; +import org.apache.accumulo.core.metadata.schema.TabletMetadata; +import org.apache.accumulo.core.util.Pair; +import org.apache.accumulo.server.metadata.iterators.SetEncodingIterator; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.io.Text; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SetEncodingIteratorTest { + + private TabletMetadata tmOneFile; + private TabletMetadata tmMultipleFiles; + private SetEncodingIterator setEqualityIterator; + private SetEncodingIterator setEqualityIteratorNoFiles; + private SetEncodingIterator setEqualityIteratorOneFile; + private SortedMapIterator sortedMapIterator; + private SortedMapIterator sortedMapIteratorNoFiles; + private SortedMapIterator sortedMapIteratorOneFile; + + private KeyExtent extent = new KeyExtent(TableId.of("5"), new Text("df"), new Text("da")); + + private StoredTabletFile file1 = + new ReferencedTabletFile(new Path("dfs://nn1/acc/tables/1/t-0001/sf1.rf")).insert(); + private StoredTabletFile file2 = + new ReferencedTabletFile(new Path("dfs://nn1/acc/tables/1/t-0001/sf2.rf")).insert(); + private StoredTabletFile file3 = + new ReferencedTabletFile(new Path("dfs://nn1/acc/tables/1/t-0001/sf3.rf")).insert(); + + @BeforeEach + public void setUp() throws IOException { + + // Create tablet metadata with no files + TabletMetadata tmNoFiles = TabletMetadata.builder(extent).putFlushId(7).build(); + + // Create tablet metadata with one file + StoredTabletFile singleFile = + new ReferencedTabletFile(new Path("dfs://nn1/acc/tables/1/t-0001/sf1.rf")).insert(); + tmOneFile = TabletMetadata.builder(extent).putFile(singleFile, new DataFileValue(100, 50)) + .putFlushId(8).build(); + + // Create tablet metadata with multiple files + tmMultipleFiles = TabletMetadata.builder(extent).putFile(file1, new DataFileValue(0, 0)) + .putFile(file2, new DataFileValue(555, 23)).putFile(file3, new DataFileValue(234, 13)) + .putFlushId(6).build(); + + var extent2 = new KeyExtent(extent.tableId(), null, extent.endRow()); + // create another tablet metadata using extent2 w/ diff files and add it to sortedMap. This + // will add another row to the test data which ensures that iterator does not go to another row. + StoredTabletFile file4 = + new ReferencedTabletFile(new Path("dfs://nn1/acc/tables/1/t-0002/sf4.rf")).insert(); + StoredTabletFile file5 = + new ReferencedTabletFile(new Path("dfs://nn1/acc/tables/1/t-0002/sf5.rf")).insert(); + StoredTabletFile file6 = + new ReferencedTabletFile(new Path("dfs://nn1/acc/tables/1/t-0002/sf6.rf")).insert(); + TabletMetadata tmMultipleFiles2 = TabletMetadata.builder(extent2) + .putFile(file4, new DataFileValue(100, 50)).putFile(file5, new DataFileValue(200, 75)) + .putFile(file6, new DataFileValue(300, 100)).putFlushId(7).build(); + + // Convert TabletMetadata to a SortedMap - SortedMap<Key,Value> sortedMapNoFiles = new TreeMap<>(tmNoFiles.getKeyValues()); - SortedMap<Key,Value> sortedMapOneFile = new TreeMap<>(tmOneFile.getKeyValues()); - SortedMap<Key,Value> sortedMap = new TreeMap<>(tmMultipleFiles.getKeyValues()); - SortedMap<Key,Value> sortedMap2 = new TreeMap<>(tmMultipleFiles2.getKeyValues()); ++ SortedMap<Key,Value> sortedMapNoFiles = tmNoFiles.getKeyValues().stream().collect( ++ Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a3, b3) -> b3, TreeMap::new)); ++ SortedMap<Key,Value> sortedMapOneFile = tmOneFile.getKeyValues().stream().collect( ++ Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a2, b2) -> b2, TreeMap::new)); ++ SortedMap<Key,Value> sortedMap = tmMultipleFiles.getKeyValues().stream().collect( ++ Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, b1) -> b1, TreeMap::new)); ++ SortedMap<Key,Value> sortedMap2 = tmMultipleFiles2.getKeyValues().stream().collect( ++ Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b, TreeMap::new)); + // Add the second tablet metadata to the sortedMap + sortedMap.putAll(sortedMap2); + + // Create a SortedMapIterator using the SortedMap + sortedMapIterator = new SortedMapIterator(sortedMap); + sortedMapIteratorNoFiles = new SortedMapIterator(sortedMapNoFiles); + sortedMapIteratorOneFile = new SortedMapIterator(sortedMapOneFile); + + // Set the SortedMapIterator as the source for SetEqualityIterator + setEqualityIterator = new SetEncodingIterator(); + setEqualityIterator.init(sortedMapIterator, Map.of(SetEncodingIterator.CONCAT_VALUE, "true"), + null); + setEqualityIteratorNoFiles = new SetEncodingIterator(); + setEqualityIteratorNoFiles.init(sortedMapIteratorNoFiles, + Map.of(SetEncodingIterator.CONCAT_VALUE, "false"), null); + setEqualityIteratorOneFile = new SetEncodingIterator(); + setEqualityIteratorOneFile.init(sortedMapIteratorOneFile, + Map.of(SetEncodingIterator.CONCAT_VALUE, "true"), null); + } + + @Test + public void testTabletWithNoFiles() throws IOException { + // Creating a test range + Text tabletRow = new Text(extent.toMetaRow()); + Text family = MetadataSchema.TabletsSection.DataFileColumnFamily.NAME; + + Range range = Range.exact(tabletRow, family); + + // Invoking the seek method + setEqualityIteratorNoFiles.seek(range, Collections.emptyList(), false); + + // Asserting the result + assertEquals(new Key(tabletRow, family), setEqualityIteratorNoFiles.getTopKey()); + // The iterator should produce a value that is equal to the expected value on the condition + var condition = SetEncodingIterator.createCondition(Collections.emptySet(), + storedTabletFile -> ((StoredTabletFile) storedTabletFile).getMetadata().getBytes(UTF_8), + family); + assertArrayEquals(condition.getValue().toArray(), + setEqualityIteratorNoFiles.getTopValue().get()); + } + + @Test + public void testTabletWithOneFile() throws IOException { + // Creating a test range + Text tabletRow = new Text(extent.toMetaRow()); + Text family = MetadataSchema.TabletsSection.DataFileColumnFamily.NAME; + + Range range = Range.exact(tabletRow, family); + + // Invoking the seek method + setEqualityIteratorOneFile.seek(range, Collections.emptyList(), false); + + // Asserting the result + assertEquals(new Key(tabletRow, family), setEqualityIteratorOneFile.getTopKey()); + // The iterator should produce a value that is equal to the expected value on the condition + var condition = SetEncodingIterator.createConditionWithVal(tmOneFile.getFilesMap().entrySet(), + entry -> new Pair<>(entry.getKey().getMetadata().getBytes(UTF_8), + entry.getValue().encode()), + family); + assertArrayEquals(condition.getValue().toArray(), + setEqualityIteratorOneFile.getTopValue().get()); + } + + @Test + public void testTabletWithMultipleFiles() throws IOException { + // Creating a test range + Text tabletRow = new Text(extent.toMetaRow()); + Text family = MetadataSchema.TabletsSection.DataFileColumnFamily.NAME; + + Range range = Range.exact(tabletRow, family); + + // Invoking the seek method + setEqualityIterator.seek(range, Collections.emptyList(), false); + + // Asserting the result + assertEquals(new Key(tabletRow, family), setEqualityIterator.getTopKey()); + // The iterator should produce a value that is equal to the expected value on the condition + var condition = + SetEncodingIterator.createConditionWithVal(tmMultipleFiles.getFilesMap().entrySet(), + entry -> new Pair<>(entry.getKey().getMetadata().getBytes(UTF_8), + entry.getValue().encode()), + family); + assertArrayEquals(condition.getValue().toArray(), setEqualityIterator.getTopValue().get()); + + } + + @Test + public void testInvalidConcatValueOption() throws IOException { + SetEncodingIterator iter = new SetEncodingIterator(); + iter.init(null, Map.of(SetEncodingIterator.CONCAT_VALUE, "true"), null); + iter.init(null, Map.of(SetEncodingIterator.CONCAT_VALUE, "false"), null); + assertThrows(IllegalArgumentException.class, () -> iter.init(null, Map.of(), null)); + assertThrows(IllegalArgumentException.class, + () -> iter.init(null, Map.of(SetEncodingIterator.CONCAT_VALUE, "yes"), null)); + assertThrows(IllegalArgumentException.class, + () -> iter.init(null, Map.of(SetEncodingIterator.CONCAT_VALUE, ""), null)); + + } + +} diff --cc server/base/src/test/java/org/apache/accumulo/server/manager/state/TabletManagementTest.java index 207c1d0a01,0000000000..8347353f60 mode 100644,000000..100644 --- a/server/base/src/test/java/org/apache/accumulo/server/manager/state/TabletManagementTest.java +++ b/server/base/src/test/java/org/apache/accumulo/server/manager/state/TabletManagementTest.java @@@ -1,194 -1,0 +1,203 @@@ +/* + * 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.server.manager.state; + +import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily.DIRECTORY_COLUMN; +import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily.FLUSH_COLUMN; +import static org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily.TIME_COLUMN; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; ++import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.UUID; ++import java.util.stream.Collectors; + +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Mutation; +import org.apache.accumulo.core.data.TableId; +import org.apache.accumulo.core.data.Value; +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.iterators.user.WholeRowIterator; +import org.apache.accumulo.core.manager.state.TabletManagement; +import org.apache.accumulo.core.manager.state.TabletManagement.ManagementAction; +import org.apache.accumulo.core.metadata.ReferencedTabletFile; +import org.apache.accumulo.core.metadata.StoredTabletFile; +import org.apache.accumulo.core.metadata.schema.DataFileValue; +import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.BulkFileColumnFamily; +import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ClonedColumnFamily; +import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.CurrentLocationColumnFamily; +import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily; +import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LastLocationColumnFamily; +import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily; +import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily; ++import org.apache.accumulo.core.metadata.schema.TabletMetadata; +import org.apache.accumulo.core.tabletserver.log.LogEntry; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.io.Text; +import org.junit.jupiter.api.Test; + +public class TabletManagementTest { + + private SortedMap<Key,Value> toRowMap(Mutation mutation) { + SortedMap<Key,Value> rowMap = new TreeMap<>(); + mutation.getUpdates().forEach(cu -> { + Key k = new Key(mutation.getRow(), cu.getColumnFamily(), cu.getColumnQualifier(), + cu.getTimestamp()); + Value v = new Value(cu.getValue()); + rowMap.put(k, v); + }); + return rowMap; + } + + private SortedMap<Key,Value> createMetadataEntryKV(KeyExtent extent) { + + Mutation mutation = TabletColumnFamily.createPrevRowMutation(extent); + + FateInstanceType type = FateInstanceType.fromTableId(extent.tableId()); + FateId fateId1 = FateId.from(type, UUID.randomUUID()); + FateId fateId2 = FateId.from(type, UUID.randomUUID()); + + DIRECTORY_COLUMN.put(mutation, new Value("t-0001757")); + FLUSH_COLUMN.put(mutation, new Value("6")); + TIME_COLUMN.put(mutation, new Value("M123456789")); + + StoredTabletFile bf1 = + new ReferencedTabletFile(new Path("hdfs://nn1/acc/tables/1/t-0001/bf1")).insert(); + StoredTabletFile bf2 = + new ReferencedTabletFile(new Path("hdfs://nn1/acc/tables/1/t-0001/bf2")).insert(); + mutation.at().family(BulkFileColumnFamily.NAME).qualifier(bf1.getMetadata()) + .put(fateId1.canonical()); + mutation.at().family(BulkFileColumnFamily.NAME).qualifier(bf2.getMetadata()) + .put(fateId2.canonical()); + + mutation.at().family(ClonedColumnFamily.NAME).qualifier("").put("OK"); + + DataFileValue dfv1 = new DataFileValue(555, 23); + StoredTabletFile tf1 = + new ReferencedTabletFile(new Path("hdfs://nn1/acc/tables/1/t-0001/df1.rf")).insert(); + StoredTabletFile tf2 = + new ReferencedTabletFile(new Path("hdfs://nn1/acc/tables/1/t-0001/df2.rf")).insert(); + mutation.at().family(DataFileColumnFamily.NAME).qualifier(tf1.getMetadata()).put(dfv1.encode()); + DataFileValue dfv2 = new DataFileValue(234, 13); + mutation.at().family(DataFileColumnFamily.NAME).qualifier(tf2.getMetadata()).put(dfv2.encode()); + + mutation.at().family(CurrentLocationColumnFamily.NAME).qualifier("s001").put("server1:8555"); + + mutation.at().family(LastLocationColumnFamily.NAME).qualifier("s000").put("server2:8555"); + + LogEntry le1 = LogEntry.fromPath("localhost+8020/" + UUID.randomUUID()); + le1.addToMutation(mutation); + LogEntry le2 = LogEntry.fromPath("localhost+8020/" + UUID.randomUUID()); + le2.addToMutation(mutation); + + StoredTabletFile sf1 = + new ReferencedTabletFile(new Path("hdfs://nn1/acc/tables/1/t-0001/sf1.rf")).insert(); + StoredTabletFile sf2 = + new ReferencedTabletFile(new Path("hdfs://nn1/acc/tables/1/t-0001/sf2.rf")).insert(); + mutation.at().family(ScanFileColumnFamily.NAME).qualifier(sf1.getMetadata()).put(""); + mutation.at().family(ScanFileColumnFamily.NAME).qualifier(sf2.getMetadata()).put(""); + + return toRowMap(mutation); + + } + + @Test + public void testEncodeDecodeWithReasons() throws Exception { + KeyExtent extent = new KeyExtent(TableId.of("5"), new Text("df"), new Text("da")); + + final Set<ManagementAction> actions = + Set.of(ManagementAction.NEEDS_LOCATION_UPDATE, ManagementAction.NEEDS_SPLITTING); + + final SortedMap<Key,Value> entries = createMetadataEntryKV(extent); + + TabletManagement.addActions(entries::put, entries.firstKey().getRow(), actions); + Key key = entries.firstKey(); + Value val = WholeRowIterator.encodeRow(new ArrayList<>(entries.keySet()), + new ArrayList<>(entries.values())); + + // Remove the REASONS column from the entries map for the comparison check + // below + entries.remove(new Key(key.getRow().toString(), "REASONS", "")); + + TabletManagement tmi = new TabletManagement(key, val, true); - assertEquals(entries, tmi.getTabletMetadata().getKeyValues()); ++ TabletMetadata tabletMetadata = tmi.getTabletMetadata(); ++ assertEquals(entries, tabletMetadata.getKeyValues().stream().collect( ++ Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b, TreeMap::new))); + assertEquals(actions, tmi.getActions()); + } + + @Test + public void testEncodeDecodeWithErrors() throws Exception { + KeyExtent extent = new KeyExtent(TableId.of("5"), new Text("df"), new Text("da")); + + final SortedMap<Key,Value> entries = createMetadataEntryKV(extent); + + TabletManagement.addError(entries::put, entries.firstKey().getRow(), + new UnsupportedOperationException("Not supported.")); + Key key = entries.firstKey(); + Value val = WholeRowIterator.encodeRow(new ArrayList<>(entries.keySet()), + new ArrayList<>(entries.values())); + + // Remove the ERROR column from the entries map for the comparison check + // below + entries.remove(new Key(key.getRow().toString(), "ERROR", "")); + + TabletManagement tmi = new TabletManagement(key, val, true); - assertEquals(entries, tmi.getTabletMetadata().getKeyValues()); ++ TabletMetadata tabletMetadata = tmi.getTabletMetadata(); ++ assertEquals(entries, tabletMetadata.getKeyValues().stream().collect( ++ Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b, TreeMap::new))); + assertEquals("Not supported.", tmi.getErrorMessage()); + } + + @Test + public void testBinary() throws Exception { + // test end row with non ascii data + Text endRow = new Text(new byte[] {'m', (byte) 0xff}); + KeyExtent extent = new KeyExtent(TableId.of("5"), endRow, new Text("da")); + + final Set<ManagementAction> actions = + Set.of(ManagementAction.NEEDS_LOCATION_UPDATE, ManagementAction.NEEDS_SPLITTING); + + final SortedMap<Key,Value> entries = createMetadataEntryKV(extent); + + TabletManagement.addActions(entries::put, entries.firstKey().getRow(), actions); + Key key = entries.firstKey(); + Value val = WholeRowIterator.encodeRow(new ArrayList<>(entries.keySet()), + new ArrayList<>(entries.values())); + + assertTrue(entries.keySet().stream().allMatch(k -> k.getRow().equals(extent.toMetaRow()))); + + // Remove the REASONS column from the entries map for the comparison check + // below + entries.remove(new Key(key.getRow(), new Text("REASONS"), new Text(""))); + + TabletManagement tmi = new TabletManagement(key, val, true); - assertEquals(entries, tmi.getTabletMetadata().getKeyValues()); ++ TabletMetadata tabletMetadata = tmi.getTabletMetadata(); ++ assertEquals(entries, tabletMetadata.getKeyValues().stream().collect( ++ Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b, TreeMap::new))); + assertEquals(actions, tmi.getActions()); + + } +} diff --cc server/manager/src/main/java/org/apache/accumulo/manager/tableOps/merge/DeleteTablets.java index 3cb48a68b0,0000000000..3f98f812fb mode 100644,000000..100644 --- a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/merge/DeleteTablets.java +++ b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/merge/DeleteTablets.java @@@ -1,128 -1,0 +1,128 @@@ +/* + * 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.manager.tableOps.merge; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +import org.apache.accumulo.core.dataImpl.KeyExtent; +import org.apache.accumulo.core.fate.FateId; +import org.apache.accumulo.core.fate.Repo; +import org.apache.accumulo.core.metadata.schema.Ample; +import org.apache.accumulo.core.metadata.schema.TabletOperationId; +import org.apache.accumulo.core.metadata.schema.TabletOperationType; +import org.apache.accumulo.core.util.TextUtil; +import org.apache.accumulo.manager.Manager; +import org.apache.accumulo.manager.tableOps.ManagerRepo; +import org.apache.hadoop.io.Text; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +/** + * Delete tablets that were merged into another tablet. + */ +public class DeleteTablets extends ManagerRepo { + + private static final long serialVersionUID = 1L; + + private final MergeInfo data; + + private final byte[] lastTabletEndRow; + + private static final Logger log = LoggerFactory.getLogger(DeleteTablets.class); + + DeleteTablets(MergeInfo mergeInfo, Text lastTabletEndRow) { + this.data = mergeInfo; + this.lastTabletEndRow = lastTabletEndRow == null ? null : TextUtil.getBytes(lastTabletEndRow); + } + + @Override + public Repo<Manager> call(FateId fateId, Manager manager) throws Exception { + + KeyExtent range = data.getMergeExtent(); + log.debug("{} Deleting tablets for {}", fateId, range); + var opid = TabletOperationId.from(TabletOperationType.MERGING, fateId); + + AtomicLong acceptedCount = new AtomicLong(); + AtomicLong rejectedCount = new AtomicLong(); + // delete tablets + Consumer<Ample.ConditionalResult> resultConsumer = result -> { + if (result.getStatus() == Ample.ConditionalResult.Status.ACCEPTED) { + acceptedCount.incrementAndGet(); + } else { + log.error("{} failed to update {}", fateId, result.getExtent()); + rejectedCount.incrementAndGet(); + } + }; + + long submitted = 0; + + try ( + var tabletsMetadata = + manager.getContext().getAmple().readTablets().forTable(range.tableId()) + .overlapping(range.prevEndRow(), range.endRow()).saveKeyValues().build(); + var tabletsMutator = + manager.getContext().getAmple().conditionallyMutateTablets(resultConsumer)) { + + var lastEndRow = lastTabletEndRow == null ? null : new Text(lastTabletEndRow); + + for (var tabletMeta : tabletsMetadata) { + MergeTablets.validateTablet(tabletMeta, fateId, opid, data.tableId); + + var tabletMutator = tabletsMutator.mutateTablet(tabletMeta.getExtent()) + .requireOperation(opid).requireAbsentLocation().requireAbsentLogs(); + + // do not delete the last tablet + if (Objects.equals(tabletMeta.getExtent().endRow(), lastEndRow)) { + // Clear the merged marker after we are finished on the last tablet + tabletMutator.deleteMerged(); + tabletMutator.submit((tm) -> !tm.hasMerged()); + submitted++; + break; + } + + if (log.isTraceEnabled()) { - tabletMeta.getKeyValues().keySet().forEach(key -> { - log.trace("{} deleting {}", fateId, key); ++ tabletMeta.getKeyValues().forEach(entry -> { ++ log.trace("{} deleting {}", fateId, entry.getKey()); + }); + } + - tabletMutator.deleteAll(tabletMeta.getKeyValues().keySet()); ++ tabletMutator.deleteAll(tabletMeta.getKeyValues()); + + // the entire tablet is being deleted, so do not want to add lock entry to the tablet + tabletMutator.automaticallyPutServerLock(false); + + // if the tablet no longer exists, then it was successful + tabletMutator.submit(Ample.RejectionHandler.acceptAbsentTablet()); + submitted++; + } + } + + Preconditions.checkState(acceptedCount.get() == submitted && rejectedCount.get() == 0, + "Failed to delete tablets accepted:%s != %s rejected:%s", acceptedCount.get(), submitted, + rejectedCount.get()); + + log.debug("{} deleted {} tablets", fateId, submitted); + + return new FinishTableRangeOp(data); + } +} diff --cc test/src/main/java/org/apache/accumulo/test/functional/SplitIT.java index 1a711ba8d9,8169037e99..a25b6c749e --- a/test/src/main/java/org/apache/accumulo/test/functional/SplitIT.java +++ b/test/src/main/java/org/apache/accumulo/test/functional/SplitIT.java @@@ -37,14 -30,6 +37,15 @@@ import java.util.List import java.util.Map; import java.util.Map.Entry; import java.util.Random; +import java.util.Set; ++import java.util.SortedMap; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; import org.apache.accumulo.core.client.Accumulo; import org.apache.accumulo.core.client.AccumuloClient; @@@ -397,59 -280,4 +398,61 @@@ public class SplitIT extends AccumuloCl assertEquals(1000, c.createScanner(tableName).stream().count()); } } + + /** + * Test attempting to split a tablet that has an unexpected column + */ + @Test + public void testUnexpectedColumn() throws Exception { + try (AccumuloClient c = Accumulo.newClient().from(getClientProps()).build()) { + String tableName = getUniqueNames(1)[0]; + c.tableOperations().create(tableName); + + var ctx = getServerContext(); + var tableId = ctx.getTableId(tableName); + var extent = new KeyExtent(tableId, null, null); + + // This column is not expected to be present + var tabletMutator = ctx.getAmple().mutateTablet(extent); + tabletMutator.setMerged(); + tabletMutator.mutate(); + + var tabletMetadata = ctx.getAmple().readTablets().forTable(tableId).saveKeyValues().build() + .stream().collect(MoreCollectors.onlyElement()); + assertEquals(extent, tabletMetadata.getExtent()); + + // remove the srv:lock column for tests as this will change + // because we are changing the metadata from the IT. - var original = new TreeMap<>(tabletMetadata.getKeyValues()); ++ var original = (SortedMap<Key,Value>) tabletMetadata.getKeyValues().stream() ++ .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a1, b1) -> b1, TreeMap::new)); + assertTrue(original.keySet().removeIf(LOCK_COLUMN::hasColumns)); + + // Split operation should fail because of the unexpected column. + var splits = new TreeSet<>(List.of(new Text("m"))); + assertThrows(AccumuloException.class, () -> c.tableOperations().addSplits(tableName, splits)); + assertEquals(Set.of(), Set.copyOf(c.tableOperations().listSplits(tableName))); + + // The tablet should be left in a bad state, so simulate a manual cleanup + var tabletMetadata2 = ctx.getAmple().readTablets().forTable(tableId).saveKeyValues().build() + .stream().collect(MoreCollectors.onlyElement()); + assertEquals(extent, tabletMetadata.getExtent()); + + // tablet should have an operation id set, but nothing else changed - var kvCopy = new TreeMap<>(tabletMetadata2.getKeyValues()); ++ var kvCopy = (SortedMap<Key,Value>) tabletMetadata2.getKeyValues().stream() ++ .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, TreeMap::new)); + assertTrue(kvCopy.keySet().removeIf(LOCK_COLUMN::hasColumns)); + assertTrue(kvCopy.keySet().removeIf(OPID_COLUMN::hasColumns)); + assertEquals(original, kvCopy); + + // remove the offending columns + tabletMutator = ctx.getAmple().mutateTablet(extent); + tabletMutator.deleteMerged(); + tabletMutator.deleteOperation(); + tabletMutator.mutate(); + + // after cleaning up the tablet metadata, should be able to split + c.tableOperations().addSplits(tableName, splits); + assertEquals(Set.of(new Text("m")), Set.copyOf(c.tableOperations().listSplits(tableName))); + } + } } diff --cc test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java index 402ed1ff50,7667f84207..a82f4eddc5 --- a/test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java +++ b/test/src/main/java/org/apache/accumulo/test/metrics/MetricsIT.java @@@ -103,14 -96,15 +103,16 @@@ public class MetricsIT extends Configur @Test public void confirmMetricsPublished() throws Exception { - doWorkToGenerateMetrics(); - cluster.stop(); - Set<String> unexpectedMetrics = Set.of(METRICS_COMPACTOR_MAJC_STUCK, METRICS_SCAN_YIELDS); + // add sserver as flaky until scan server included in mini tests. - Set<String> flakyMetrics = Set.of(METRICS_FATE_TYPE_IN_PROGRESS, METRICS_GC_WAL_ERRORS, - METRICS_SCAN_BUSY_TIMEOUT_COUNTER, METRICS_SCAN_RESERVATION_CONFLICT_COUNTER, + Set<String> flakyMetrics = Set.of(METRICS_GC_WAL_ERRORS, METRICS_FATE_TYPE_IN_PROGRESS, + METRICS_TSERVER_TABLETS_ONLINE_ONDEMAND, METRICS_TSERVER_TABLETS_ONDEMAND_UNLOADED_FOR_MEM, + METRICS_COMPACTOR_MAJC_STUCK, METRICS_MANAGER_ROOT_TGW_ERRORS, + METRICS_MANAGER_META_TGW_ERRORS, METRICS_MANAGER_USER_TGW_ERRORS, + METRICS_SCAN_TABLET_METADATA_CACHE, METRICS_SCAN_BUSY_TIMEOUT_COUNTER, METRICS_SCAN_RESERVATION_TOTAL_TIMER, METRICS_SCAN_RESERVATION_WRITEOUT_TIMER, - METRICS_SERVER_IDLE); ++ METRICS_SERVER_IDLE, METRICS_SCAN_RESERVATION_CONFLICT_COUNTER, METRICS_GC_WAL_ERRORS, + METRICS_SCAN_TABLET_METADATA_CACHE); Map<String,String> expectedMetricNames = this.getMetricFields(); flakyMetrics.forEach(expectedMetricNames::remove); // might not see these