-
Notifications
You must be signed in to change notification settings - Fork 588
HDDS-11408. Snapshot rename table entries are propagated incorrectly on snapshot deletes #7200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0335ed2
53a5b5d
87ea520
6675538
96c3daa
535a6cd
7d49b64
f79c5c7
7124b1e
ae0085c
5fb5d8c
67b5a05
4b724fe
e2f0461
8600803
8b477b6
09b9523
7ee74dd
da7701f
b7380e1
e31c153
f6104fd
bc4de22
57d665a
075441b
c3a11a5
bd28a4c
1fd5f1d
d1c0a77
0d0e3e7
554e452
1aa55ff
364663c
9af1e99
0a8183d
73572ad
063f7b7
302491d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
|
|
||
| import org.apache.hadoop.hdds.conf.OzoneConfiguration; | ||
| import org.apache.hadoop.hdds.utils.db.Table; | ||
| import org.apache.hadoop.hdds.utils.db.TableIterator; | ||
| import org.apache.hadoop.ozone.common.BlockGroup; | ||
| import org.apache.hadoop.ozone.om.exceptions.OMException; | ||
| import org.apache.hadoop.ozone.om.helpers.BucketLayout; | ||
|
|
@@ -28,13 +29,15 @@ | |
| import org.apache.hadoop.ozone.om.helpers.OmMultipartUploadListParts; | ||
| import org.apache.hadoop.ozone.om.fs.OzoneManagerFS; | ||
| import org.apache.hadoop.hdds.utils.BackgroundService; | ||
| import org.apache.hadoop.ozone.om.service.DirectoryDeletingService; | ||
| import org.apache.hadoop.ozone.om.service.KeyDeletingService; | ||
| import org.apache.hadoop.ozone.om.service.SnapshotDeletingService; | ||
| import org.apache.hadoop.ozone.om.service.SnapshotDirectoryCleaningService; | ||
| import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ExpiredMultipartUploadsBucket; | ||
|
|
||
| import java.io.IOException; | ||
| import java.time.Duration; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| /** | ||
|
|
@@ -119,6 +122,29 @@ ListKeysResult listKeys(String volumeName, String bucketName, String startKey, | |
| */ | ||
| PendingKeysDeletion getPendingDeletionKeys(int count) throws IOException; | ||
|
|
||
| /** | ||
| * Returns a list rename entries from the snapshotRenamedTable. | ||
| * | ||
| * @param size max number of keys to return. | ||
| * @return a Pair of list of {@link org.apache.hadoop.hdds.utils.db.Table.KeyValue} representing the keys in the | ||
| * underlying metadataManager. | ||
| * @throws IOException | ||
| */ | ||
| List<Table.KeyValue<String, String>> getRenamesKeyEntries( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Is there a reason to return the List of Pairs and not the Map? If it is just to maintain the order, LinkedHashMap could be used. I feel that the map improves the readability.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no reason to creating another map since we are anyways going iterate over it.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not asking to create another map. I'm saying the return type could be a map rather than a list of pairs. |
||
| String volume, String bucket, String startKey, int size) throws IOException; | ||
|
|
||
|
|
||
| /** | ||
| * Returns a list deleted entries from the deletedTable. | ||
| * | ||
| * @param size max number of keys to return. | ||
| * @return a Pair of list of {@link org.apache.hadoop.hdds.utils.db.Table.KeyValue} representing the keys in the | ||
| * underlying metadataManager. | ||
| * @throws IOException | ||
| */ | ||
| List<Table.KeyValue<String, List<OmKeyInfo>>> getDeletedKeyEntries( | ||
| String volume, String bucket, String startKey, int size) throws IOException; | ||
|
|
||
| /** | ||
| * Returns the names of up to {@code count} open keys whose age is | ||
| * greater than or equal to {@code expireThreshold}. | ||
|
|
@@ -216,6 +242,26 @@ OmMultipartUploadListParts listParts(String volumeName, String bucketName, | |
| */ | ||
| Table.KeyValue<String, OmKeyInfo> getPendingDeletionDir() throws IOException; | ||
|
|
||
| /** | ||
| * Returns an iterator for pending deleted directories. | ||
| * @throws IOException | ||
| */ | ||
| TableIterator<String, ? extends Table.KeyValue<String, OmKeyInfo>> getDeletedDirEntries( | ||
| String volume, String bucket) throws IOException; | ||
|
|
||
| default List<Table.KeyValue<String, OmKeyInfo>> getDeletedDirEntries(String volume, String bucket, int size) | ||
| throws IOException { | ||
| List<Table.KeyValue<String, OmKeyInfo>> deletedDirEntries = new ArrayList<>(size); | ||
| try (TableIterator<String, ? extends Table.KeyValue<String, OmKeyInfo>> iterator = | ||
| getDeletedDirEntries(volume, bucket)) { | ||
| while (deletedDirEntries.size() < size && iterator.hasNext()) { | ||
| Table.KeyValue<String, OmKeyInfo> kv = iterator.next(); | ||
| deletedDirEntries.add(Table.newKeyValue(kv.getKey(), kv.getValue())); | ||
| } | ||
| return deletedDirEntries; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Returns all sub directories under the given parent directory. | ||
| * | ||
|
|
@@ -243,7 +289,7 @@ List<OmKeyInfo> getPendingDeletionSubFiles(long volumeId, | |
| * Returns the instance of Directory Deleting Service. | ||
| * @return Background service. | ||
| */ | ||
| BackgroundService getDirDeletingService(); | ||
| DirectoryDeletingService getDirDeletingService(); | ||
|
|
||
| /** | ||
| * Returns the instance of Open Key Cleanup Service. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ | |
| import java.util.Stack; | ||
| import java.util.TreeMap; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.Stream; | ||
|
|
||
|
|
@@ -86,6 +87,7 @@ | |
| import org.apache.hadoop.ozone.om.helpers.OzoneFSUtils; | ||
| import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus; | ||
| import org.apache.hadoop.ozone.om.helpers.BucketLayout; | ||
| import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo; | ||
| import org.apache.hadoop.ozone.om.request.OMClientRequest; | ||
| import org.apache.hadoop.ozone.om.request.file.OMFileRequest; | ||
| import org.apache.hadoop.ozone.om.request.util.OMMultipartUploadUtils; | ||
|
|
@@ -189,7 +191,7 @@ public class KeyManagerImpl implements KeyManager { | |
|
|
||
| private final KeyProviderCryptoExtension kmsProvider; | ||
| private final boolean enableFileSystemPaths; | ||
| private BackgroundService dirDeletingService; | ||
| private DirectoryDeletingService dirDeletingService; | ||
| private final OMPerformanceMetrics metrics; | ||
|
|
||
| private BackgroundService openKeyCleanupService; | ||
|
|
@@ -305,7 +307,7 @@ public void start(OzoneConfiguration configuration) { | |
| try { | ||
| snapshotDeletingService = new SnapshotDeletingService( | ||
| snapshotServiceInterval, snapshotServiceTimeout, | ||
| ozoneManager, scmClient.getBlockClient()); | ||
| ozoneManager); | ||
| snapshotDeletingService.start(); | ||
| } catch (IOException e) { | ||
| LOG.error("Error starting Snapshot Deleting Service", e); | ||
|
|
@@ -662,6 +664,60 @@ public PendingKeysDeletion getPendingDeletionKeys(final int count) | |
| .getPendingDeletionKeys(count, ozoneManager.getOmSnapshotManager()); | ||
| } | ||
|
|
||
| private <V, R> List<Table.KeyValue<String, R>> getTableEntries(String startKey, | ||
| TableIterator<String, ? extends Table.KeyValue<String, V>> tableIterator, | ||
| Function<V, R> valueFunction, int size) throws IOException { | ||
| List<Table.KeyValue<String, R>> entries = new ArrayList<>(); | ||
| /* Seek to the start key if it not null. The next key in queue is ensured to start with the bucket | ||
| prefix, {@link org.apache.hadoop.hdds.utils.db.Table#iterator(bucketPrefix)} would ensure this. | ||
| */ | ||
| if (startKey != null) { | ||
swamirishi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| tableIterator.seek(startKey); | ||
| tableIterator.seekToFirst(); | ||
| } | ||
| int currentCount = 0; | ||
| while (tableIterator.hasNext() && currentCount < size) { | ||
| Table.KeyValue<String, V> kv = tableIterator.next(); | ||
| if (kv != null) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this null check?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No harm in having one.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, there is no harm but it isn't how an iterator is supposed to be used unless null is allowed value in the collection. |
||
| entries.add(Table.newKeyValue(kv.getKey(), valueFunction.apply(kv.getValue()))); | ||
| currentCount++; | ||
| } | ||
| } | ||
| return entries; | ||
| } | ||
|
|
||
| private Optional<String> getBucketPrefix(String volumeName, String bucketName, boolean isFSO) throws IOException { | ||
| // Bucket prefix would be empty if both volume & bucket is empty i.e. either null or "". | ||
| if (StringUtils.isEmpty(volumeName) && StringUtils.isEmpty(bucketName)) { | ||
| return Optional.empty(); | ||
| } else if (StringUtils.isEmpty(bucketName) || StringUtils.isEmpty(volumeName)) { | ||
| throw new IOException("One of volume : " + volumeName + ", bucket: " + bucketName + " is empty." + | ||
| " Either both should be empty or none of the arguments should be empty"); | ||
| } | ||
| return isFSO ? Optional.of(metadataManager.getBucketKeyPrefixFSO(volumeName, bucketName)) : | ||
| Optional.of(metadataManager.getBucketKeyPrefix(volumeName, bucketName)); | ||
| } | ||
|
|
||
swamirishi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @Override | ||
| public List<Table.KeyValue<String, String>> getRenamesKeyEntries( | ||
| String volume, String bucket, String startKey, int size) throws IOException { | ||
| Optional<String> bucketPrefix = getBucketPrefix(volume, bucket, false); | ||
| try (TableIterator<String, ? extends Table.KeyValue<String, String>> | ||
| renamedKeyIter = metadataManager.getSnapshotRenamedTable().iterator(bucketPrefix.orElse(""))) { | ||
| return getTableEntries(startKey, renamedKeyIter, Function.identity(), size); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public List<Table.KeyValue<String, List<OmKeyInfo>>> getDeletedKeyEntries( | ||
| String volume, String bucket, String startKey, int size) throws IOException { | ||
| Optional<String> bucketPrefix = getBucketPrefix(volume, bucket, false); | ||
| try (TableIterator<String, ? extends Table.KeyValue<String, RepeatedOmKeyInfo>> | ||
| delKeyIter = metadataManager.getDeletedTable().iterator(bucketPrefix.orElse(""))) { | ||
| return getTableEntries(startKey, delKeyIter, RepeatedOmKeyInfo::cloneOmKeyInfoList, size); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public ExpiredOpenKeys getExpiredOpenKeys(Duration expireThreshold, | ||
| int count, BucketLayout bucketLayout, Duration leaseThreshold) throws IOException { | ||
|
|
@@ -688,7 +744,7 @@ public KeyDeletingService getDeletingService() { | |
| } | ||
|
|
||
| @Override | ||
| public BackgroundService getDirDeletingService() { | ||
| public DirectoryDeletingService getDirDeletingService() { | ||
| return dirDeletingService; | ||
| } | ||
|
|
||
|
|
@@ -723,8 +779,7 @@ public boolean isSstFilteringSvcEnabled() { | |
| TimeUnit.MILLISECONDS); | ||
| return serviceInterval != DISABLE_VALUE; | ||
| } | ||
|
|
||
|
|
||
|
|
||
| @Override | ||
| public OmMultipartUploadList listMultipartUploads(String volumeName, | ||
| String bucketName, String prefix) throws OMException { | ||
|
|
@@ -1325,7 +1380,6 @@ private OmKeyInfo createFakeDirIfShould(String volume, String bucket, | |
| return null; | ||
| } | ||
|
|
||
|
|
||
| private OzoneFileStatus getOzoneFileStatusFSO(OmKeyArgs args, | ||
| String clientAddress, boolean skipFileNotFoundError) throws IOException { | ||
| final String volumeName = args.getVolumeName(); | ||
|
|
@@ -1784,17 +1838,13 @@ private List<OzoneFileStatus> buildFinalStatusList( | |
| } | ||
| fileStatusFinalList.add(fileStatus); | ||
| } | ||
|
|
||
| return sortPipelineInfo(fileStatusFinalList, keyInfoList, | ||
| omKeyArgs, clientAddress); | ||
| } | ||
|
|
||
|
|
||
| private List<OzoneFileStatus> sortPipelineInfo( | ||
| List<OzoneFileStatus> fileStatusFinalList, List<OmKeyInfo> keyInfoList, | ||
| OmKeyArgs omKeyArgs, String clientAddress) throws IOException { | ||
|
|
||
|
|
||
| if (omKeyArgs.getLatestVersionLocation()) { | ||
| slimLocationVersion(keyInfoList.toArray(new OmKeyInfo[0])); | ||
| } | ||
|
|
@@ -1976,6 +2026,13 @@ public Table.KeyValue<String, OmKeyInfo> getPendingDeletionDir() | |
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public TableIterator<String, ? extends Table.KeyValue<String, OmKeyInfo>> getDeletedDirEntries( | ||
| String volume, String bucket) throws IOException { | ||
| Optional<String> bucketPrefix = getBucketPrefix(volume, bucket, true); | ||
| return metadataManager.getDeletedDirTable().iterator(bucketPrefix.orElse("")); | ||
| } | ||
|
|
||
| @Override | ||
| public List<OmKeyInfo> getPendingDeletionSubDirs(long volumeId, long bucketId, | ||
| OmKeyInfo parentInfo, long numEntries) throws IOException { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
qq: Is this for testing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@swamirishi , This seems incorrect. Filed HDDS-13235
BTW, testing code should reside in tests. Otherwise, it may introduce real bugs to production code.