blob: c4e903df7390f1f44320e3586d5aff05323d0d7c [file] [log] [blame]
/*
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.hadoop.fs.s3a.s3guard;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.s3a.AbstractS3ATestBase;
import org.apache.hadoop.fs.s3a.S3AFileStatus;
import org.apache.hadoop.fs.s3a.S3AFileSystem;
import static org.apache.hadoop.fs.s3a.Constants.AUTHORITATIVE_PATH;
import static org.junit.Assume.assumeTrue;
import static org.apache.hadoop.fs.contract.ContractTestUtils.touch;
import static org.apache.hadoop.fs.s3a.Constants.METADATASTORE_AUTHORITATIVE;
import static org.apache.hadoop.fs.s3a.Constants.S3_METADATA_STORE_IMPL;
import static org.apache.hadoop.fs.s3a.S3ATestUtils.awaitFileStatus;
import static org.apache.hadoop.fs.s3a.S3ATestUtils.metadataStorePersistsAuthoritativeBit;
import static org.apache.hadoop.fs.s3a.S3ATestUtils.removeBaseAndBucketOverrides;
/**
* Integration tests for the S3Guard Fsck against a dyamodb backed metadata
* store.
*/
public class ITestS3GuardFsck extends AbstractS3ATestBase {
private S3AFileSystem guardedFs;
private S3AFileSystem rawFs;
private MetadataStore metadataStore;
@Before
public void setup() throws Exception {
super.setup();
S3AFileSystem fs = getFileSystem();
// These test will fail if no ms
assumeTrue("FS needs to have a DynamoDB metadatastore.",
fs.getMetadataStore() instanceof DynamoDBMetadataStore);
assumeTrue("Metadatastore should persist authoritative bit",
metadataStorePersistsAuthoritativeBit(fs.getMetadataStore()));
guardedFs = fs;
metadataStore = fs.getMetadataStore();
// create raw fs without s3guard
rawFs = createUnguardedFS();
assertFalse("Raw FS still has S3Guard " + rawFs,
rawFs.hasMetadataStore());
}
@Override
public void teardown() throws Exception {
if (guardedFs != null) {
IOUtils.cleanupWithLogger(LOG, guardedFs);
}
IOUtils.cleanupWithLogger(LOG, rawFs);
super.teardown();
}
/**
* Create a test filesystem which is always unguarded.
* This filesystem MUST be closed in test teardown.
* @return the new FS
*/
private S3AFileSystem createUnguardedFS() throws Exception {
S3AFileSystem testFS = getFileSystem();
Configuration config = new Configuration(testFS.getConf());
URI uri = testFS.getUri();
removeBaseAndBucketOverrides(uri.getHost(), config,
S3_METADATA_STORE_IMPL, METADATASTORE_AUTHORITATIVE,
AUTHORITATIVE_PATH);
S3AFileSystem fs2 = new S3AFileSystem();
fs2.initialize(uri, config);
return fs2;
}
@Test
public void testIDetectNoMetadataEntry() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file = new Path(cwd, "file");
try {
touchRawAndWaitRaw(file);
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
assertComparePairsSize(comparePairs, 2);
final S3GuardFsck.ComparePair pair = comparePairs.get(0);
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.NO_METADATA_ENTRY);
} finally {
// delete the working directory with all of its contents
cleanup(file, cwd);
}
}
@Test
public void testIDetectNoParentEntry() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file = new Path(cwd, "file");
try {
touchGuardedAndWaitRaw(file);
// delete the parent from the MS
metadataStore.forgetMetadata(cwd);
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
assertComparePairsSize(comparePairs, 2);
// check the parent that it does not exist
checkForViolationInPairs(cwd, comparePairs,
S3GuardFsck.Violation.NO_METADATA_ENTRY);
// check the child that there's no parent entry.
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.NO_PARENT_ENTRY);
} finally {
cleanup(file, cwd);
}
}
@Test
public void testIDetectParentIsAFile() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file = new Path(cwd, "file");
try {
touchGuardedAndWaitRaw(file);
// modify the cwd metadata and set that it's not a directory
final S3AFileStatus newParentFile = MetadataStoreTestBase
.basicFileStatus(cwd, 1, false, 1);
metadataStore.put(new PathMetadata(newParentFile));
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
assertComparePairsSize(comparePairs, 2);
// check the parent that it does not exist
checkForViolationInPairs(cwd, comparePairs,
S3GuardFsck.Violation.DIR_IN_S3_FILE_IN_MS);
// check the child that the parent is a file.
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.PARENT_IS_A_FILE);
} finally {
cleanup(file, cwd);
}
}
@Test
public void testIDetectParentTombstoned() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file = new Path(cwd, "file");
try {
touchGuardedAndWaitRaw(file);
// modify the parent metadata and set that it's not a directory
final PathMetadata cwdPmd = metadataStore.get(cwd);
cwdPmd.setIsDeleted(true);
metadataStore.put(cwdPmd);
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
// check the child that the parent is tombstoned
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.PARENT_TOMBSTONED);
} finally {
cleanup(file, cwd);
}
}
@Test
public void testIDetectDirInS3FileInMs() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
try {
// create a file with guarded fs
mkdirs(cwd);
awaitFileStatus(guardedFs, cwd);
// modify the cwd metadata and set that it's not a directory
final S3AFileStatus newParentFile = MetadataStoreTestBase
.basicFileStatus(cwd, 1, false, 1);
metadataStore.put(new PathMetadata(newParentFile));
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
assertComparePairsSize(comparePairs, 1);
// check the child that the dir in s3 is a file in the ms
checkForViolationInPairs(cwd, comparePairs,
S3GuardFsck.Violation.DIR_IN_S3_FILE_IN_MS);
} finally {
cleanup(cwd);
}
}
@Test
public void testIDetectFileInS3DirInMs() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file = new Path(cwd, "file");
try {
touchGuardedAndWaitRaw(file);
// modify the cwd metadata and set that it's not a directory
final S3AFileStatus newFile = MetadataStoreTestBase
.basicFileStatus(file, 1, true, 1);
metadataStore.put(new PathMetadata(newFile));
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
assertComparePairsSize(comparePairs, 1);
// check the child that the dir in s3 is a file in the ms
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.FILE_IN_S3_DIR_IN_MS);
} finally {
cleanup(file, cwd);
}
}
@Test
public void testIAuthoritativeDirectoryContentMismatch() throws Exception {
assumeTrue("Authoritative directory listings should be enabled for this "
+ "test", guardedFs.hasAuthoritativeMetadataStore());
// first dir listing will be correct
final Path cwdCorrect = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path fileC1 = new Path(cwdCorrect, "fileC1");
final Path fileC2 = new Path(cwdCorrect, "fileC2");
// second dir listing will be incorrect: missing entry from Dynamo
final Path cwdIncorrect = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path fileIc1 = new Path(cwdIncorrect, "fileC1");
final Path fileIc2 = new Path(cwdIncorrect, "fileC2");
try {
touchGuardedAndWaitRaw(fileC1);
touchGuardedAndWaitRaw(fileC2);
touchGuardedAndWaitRaw(fileIc1);
// get listing from ms and set it authoritative
final DirListingMetadata dlmC = metadataStore.listChildren(cwdCorrect);
final DirListingMetadata dlmIc = metadataStore.listChildren(cwdIncorrect);
dlmC.setAuthoritative(true);
dlmIc.setAuthoritative(true);
metadataStore.put(dlmC, Collections.emptyList(), null);
metadataStore.put(dlmIc, Collections.emptyList(), null);
// add a file raw so the listing will be different.
touchRawAndWaitRaw(fileIc2);
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> pairsCorrect =
s3GuardFsck.compareS3ToMs(cwdCorrect);
final List<S3GuardFsck.ComparePair> pairsIncorrect =
s3GuardFsck.compareS3ToMs(cwdIncorrect);
// Assert that the correct dir does not contain the violation.
assertTrue(pairsCorrect.stream()
.noneMatch(p -> p.getPath().equals(cwdCorrect)));
// Assert that the incorrect listing contains the violation.
checkForViolationInPairs(cwdIncorrect, pairsIncorrect,
S3GuardFsck.Violation.AUTHORITATIVE_DIRECTORY_CONTENT_MISMATCH);
} finally {
cleanup(fileC1, fileC2, fileIc1, fileIc2, cwdCorrect, cwdIncorrect);
}
}
@Test
public void testIDetectLengthMismatch() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file = new Path(cwd, "file");
try {
// create a file with guarded fs
touchGuardedAndWaitRaw(file);
// modify the file metadata so the length will not match
final S3AFileStatus newFile = MetadataStoreTestBase
.basicFileStatus(file, 9999, false, 1);
metadataStore.put(new PathMetadata(newFile));
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
assertComparePairsSize(comparePairs, 1);
// Assert that the correct dir does not contain the violation.
assertTrue(comparePairs.stream()
.noneMatch(p -> p.getPath().equals(cwd)));
// Assert that the incorrect file meta contains the violation.
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.LENGTH_MISMATCH);
} finally {
cleanup(file, cwd);
}
}
@Test
public void testIDetectModTimeMismatch() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file = new Path(cwd, "file");
try {
// create a file with guarded fs
touchGuardedAndWaitRaw(file);
// modify the parent meta entry so the MOD_TIME will surely be up to date
final FileStatus oldCwdFileStatus = rawFs.getFileStatus(cwd);
final S3AFileStatus newCwdFileStatus = MetadataStoreTestBase
.basicFileStatus(cwd, 0, true,
oldCwdFileStatus.getModificationTime());
metadataStore.put(new PathMetadata(newCwdFileStatus));
// modify the file metadata so the length will not match
final S3AFileStatus newFileStatus = MetadataStoreTestBase
.basicFileStatus(file, 0, false, 1);
metadataStore.put(new PathMetadata(newFileStatus));
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
assertComparePairsSize(comparePairs, 1);
// Assert that the correct dir does not contain the violation.
assertTrue(comparePairs.stream()
.noneMatch(p -> p.getPath().equals(cwd)));
// check the file meta that there's a violation.
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.MOD_TIME_MISMATCH);
} finally {
cleanup(file, cwd);
}
}
@Test
public void testIEtagMismatch() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file = new Path(cwd, "file");
try {
touchGuardedAndWaitRaw(file);
// modify the file metadata so the etag will not match
final S3AFileStatus newFileStatus = new S3AFileStatus(1, 1, file, 1, "",
"etag", "versionId");
metadataStore.put(new PathMetadata(newFileStatus));
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
assertComparePairsSize(comparePairs, 1);
// check the child that there's a BLOCKSIZE_MISMATCH
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.ETAG_MISMATCH);
} finally {
cleanup(file, cwd);
}
}
@Test
public void testINoEtag() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file1 = new Path(cwd, "file1");
final Path file2 = new Path(cwd, "file2");
try {
// create a file1 with guarded fs
touchGuardedAndWaitRaw(file1);
touchGuardedAndWaitRaw(file2);
// modify the file1 metadata so there's no etag
final S3AFileStatus newFile1Status =
new S3AFileStatus(1, 1, file1, 1, "", null, "versionId");
final S3AFileStatus newFile2Status =
new S3AFileStatus(1, 1, file2, 1, "", "etag", "versionId");
metadataStore.put(new PathMetadata(newFile1Status));
metadataStore.put(new PathMetadata(newFile2Status));
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
assertComparePairsSize(comparePairs, 2);
// check file 1 that there's NO_ETAG
checkForViolationInPairs(file1, comparePairs,
S3GuardFsck.Violation.NO_ETAG);
// check the child that there's no NO_ETAG violation
checkNoViolationInPairs(file2, comparePairs,
S3GuardFsck.Violation.NO_ETAG);
} finally {
cleanup(file1, file2, cwd);
}
}
@Test
public void testTombstonedInMsNotDeletedInS3() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file = new Path(cwd, "file");
try {
// create a file with guarded fs
touchGuardedAndWaitRaw(file);
// set isDeleted flag in ms to true (tombstone item)
final PathMetadata fileMeta = metadataStore.get(file);
fileMeta.setIsDeleted(true);
metadataStore.put(fileMeta);
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.compareS3ToMs(cwd);
assertComparePairsSize(comparePairs, 1);
// check if the violation is there
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.TOMBSTONED_IN_MS_NOT_DELETED_IN_S3);
} finally {
cleanup(file, cwd);
}
}
@Test
public void checkDdbInternalConsistency() throws Exception {
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final DynamoDBMetadataStore ms =
(DynamoDBMetadataStore) guardedFs.getMetadataStore();
s3GuardFsck.checkDdbInternalConsistency(
new Path("s3a://" + guardedFs.getBucket() + "/"));
}
@Test
public void testDdbInternalNoLastUpdatedField() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path file = new Path(cwd, "file");
try {
final S3AFileStatus s3AFileStatus = new S3AFileStatus(100, 100, file, 100,
"test", "etag", "version");
final PathMetadata pathMetadata = new PathMetadata(s3AFileStatus);
pathMetadata.setLastUpdated(0);
metadataStore.put(pathMetadata);
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.checkDdbInternalConsistency(cwd);
assertComparePairsSize(comparePairs, 1);
// check if the violation is there
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.NO_LASTUPDATED_FIELD);
} finally {
cleanup(file, cwd);
}
}
@Test
public void testDdbInternalOrphanEntry() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path parentDir = new Path(cwd, "directory");
final Path file = new Path(parentDir, "file");
try {
final S3AFileStatus s3AFileStatus = new S3AFileStatus(100, 100, file, 100,
"test", "etag", "version");
final PathMetadata pathMetadata = new PathMetadata(s3AFileStatus);
pathMetadata.setLastUpdated(1000);
metadataStore.put(pathMetadata);
metadataStore.forgetMetadata(parentDir);
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.checkDdbInternalConsistency(cwd);
// check if the violation is there
assertComparePairsSize(comparePairs, 1);
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.ORPHAN_DDB_ENTRY);
// fix the violation
s3GuardFsck.fixViolations(
comparePairs.stream().filter(cP -> cP.getViolations()
.contains(S3GuardFsck.Violation.ORPHAN_DDB_ENTRY))
.collect(Collectors.toList())
);
// assert that the violation is fixed
final List<S3GuardFsck.ComparePair> fixedComparePairs =
s3GuardFsck.checkDdbInternalConsistency(cwd);
checkNoViolationInPairs(file, fixedComparePairs,
S3GuardFsck.Violation.ORPHAN_DDB_ENTRY);
} finally {
cleanup(file, cwd);
}
}
@Test
public void testDdbInternalParentIsAFile() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path parentDir = new Path(cwd, "directory");
final Path file = new Path(parentDir, "file");
try {
final S3AFileStatus s3AFileStatus = new S3AFileStatus(100, 100, file, 100,
"test", "etag", "version");
final PathMetadata pathMetadata = new PathMetadata(s3AFileStatus);
pathMetadata.setLastUpdated(1000);
metadataStore.put(pathMetadata);
final S3AFileStatus dirAsFile = MetadataStoreTestBase
.basicFileStatus(parentDir, 1, false, 1);
final PathMetadata dirAsFilePm = new PathMetadata(dirAsFile);
dirAsFilePm.setLastUpdated(100);
metadataStore.put(dirAsFilePm);
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.checkDdbInternalConsistency(cwd);
// check if the violation is there
assertComparePairsSize(comparePairs, 1);
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.PARENT_IS_A_FILE);
} finally {
cleanup(file, cwd);
}
}
@Test
public void testDdbInternalParentTombstoned() throws Exception {
final Path cwd = path("/" + getMethodName() + "-" + UUID.randomUUID());
final Path parentDir = new Path(cwd, "directory");
final Path file = new Path(parentDir, "file");
try {
final S3AFileStatus s3AFileStatus = new S3AFileStatus(100, 100, file, 100,
"test", "etag", "version");
final PathMetadata pathMetadata = new PathMetadata(s3AFileStatus);
pathMetadata.setLastUpdated(1000);
metadataStore.put(pathMetadata);
metadataStore.delete(parentDir, null);
final S3GuardFsck s3GuardFsck = new S3GuardFsck(rawFs, metadataStore);
final List<S3GuardFsck.ComparePair> comparePairs =
s3GuardFsck.checkDdbInternalConsistency(cwd);
// check if the violation is there
assertComparePairsSize(comparePairs, 1);
checkForViolationInPairs(file, comparePairs,
S3GuardFsck.Violation.PARENT_TOMBSTONED);
} finally {
cleanup(file, cwd);
}
}
protected void assertComparePairsSize(
List<S3GuardFsck.ComparePair> comparePairs, int num) {
Assertions.assertThat(comparePairs)
.describedAs("Number of compare pairs")
.hasSize(num);
}
private void touchGuardedAndWaitRaw(Path file) throws Exception {
touchAndWait(guardedFs, rawFs, file);
}
private void touchRawAndWaitRaw(Path file) throws Exception {
touchAndWait(rawFs, rawFs, file);
}
private void touchAndWait(FileSystem forTouch, FileSystem forWait, Path file)
throws IOException {
touch(forTouch, file);
touch(forWait, file);
}
private void checkForViolationInPairs(Path file,
List<S3GuardFsck.ComparePair> comparePairs,
S3GuardFsck.Violation violation) {
final S3GuardFsck.ComparePair childPair = comparePairs.stream()
.filter(p -> p.getPath().equals(file))
.findFirst().get();
assertNotNull("The pair should not be null.", childPair);
assertTrue("The pair must contain a violation.",
childPair.containsViolation());
Assertions.assertThat(childPair.getViolations())
.describedAs("Violations in the pair")
.contains(violation);
}
/**
* Check that there is no violation in the pair provided.
*
* @param file the path to filter to in the comparePairs list.
* @param comparePairs the list to validate.
* @param violation the violation that should not be in the list.
*/
private void checkNoViolationInPairs(Path file,
List<S3GuardFsck.ComparePair> comparePairs,
S3GuardFsck.Violation violation) {
if (comparePairs.size() == 0) {
LOG.info("Compare pairs is empty, so there's no violation. (As expected.)");
return;
}
final S3GuardFsck.ComparePair comparePair = comparePairs.stream()
.filter(p -> p.getPath().equals(file))
.findFirst().get();
assertNotNull("The pair should not be null.", comparePair);
Assertions.assertThat(comparePair.getViolations())
.describedAs("Violations in the pair")
.doesNotContain(violation);
}
private void cleanup(Path... paths) {
for (Path path : paths) {
try {
metadataStore.forgetMetadata(path);
rawFs.delete(path, true);
} catch (IOException e) {
LOG.error("Error during cleanup.", e);
}
}
}
}