/* * 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 * * http://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.hadoop.fs.s3a; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.contract.ContractTestUtils; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileNotFoundException; import java.net.URI; import static org.apache.hadoop.fs.contract.ContractTestUtils.*; import static org.apache.hadoop.fs.s3a.Statistic.*; import static org.apache.hadoop.fs.s3a.S3ATestUtils.*; import static org.apache.hadoop.fs.s3a.S3ATestUtils.MetricDiff; /** * Use metrics to assert about the cost of file status queries. * {@link S3AFileSystem#getFileStatus(Path)}. */ public class ITestS3AFileOperationCost extends AbstractS3ATestBase { private MetricDiff metadataRequests; private MetricDiff listRequests; private static final Logger LOG = LoggerFactory.getLogger(ITestS3AFileOperationCost.class); @Override public void setup() throws Exception { super.setup(); S3AFileSystem fs = getFileSystem(); metadataRequests = new MetricDiff(fs, OBJECT_METADATA_REQUESTS); listRequests = new MetricDiff(fs, OBJECT_LIST_REQUESTS); } @Test public void testCostOfGetFileStatusOnFile() throws Throwable { describe("performing getFileStatus on a file"); Path simpleFile = path("simple.txt"); S3AFileSystem fs = getFileSystem(); touch(fs, simpleFile); resetMetricDiffs(); S3AFileStatus status = fs.getFileStatus(simpleFile); assertTrue("not a file: " + status, status.isFile()); metadataRequests.assertDiffEquals(1); listRequests.assertDiffEquals(0); } private void resetMetricDiffs() { reset(metadataRequests, listRequests); } @Test public void testCostOfGetFileStatusOnEmptyDir() throws Throwable { describe("performing getFileStatus on an empty directory"); S3AFileSystem fs = getFileSystem(); Path dir = path("empty"); fs.mkdirs(dir); resetMetricDiffs(); S3AFileStatus status = fs.getFileStatus(dir); assertTrue("not empty: " + status, status.isEmptyDirectory()); metadataRequests.assertDiffEquals(2); listRequests.assertDiffEquals(0); } @Test public void testCostOfGetFileStatusOnMissingFile() throws Throwable { describe("performing getFileStatus on a missing file"); S3AFileSystem fs = getFileSystem(); Path path = path("missing"); resetMetricDiffs(); try { S3AFileStatus status = fs.getFileStatus(path); fail("Got a status back from a missing file path " + status); } catch (FileNotFoundException expected) { // expected } metadataRequests.assertDiffEquals(2); listRequests.assertDiffEquals(1); } @Test public void testCostOfGetFileStatusOnMissingSubPath() throws Throwable { describe("performing getFileStatus on a missing file"); S3AFileSystem fs = getFileSystem(); Path path = path("missingdir/missingpath"); resetMetricDiffs(); try { S3AFileStatus status = fs.getFileStatus(path); fail("Got a status back from a missing file path " + status); } catch (FileNotFoundException expected) { // expected } metadataRequests.assertDiffEquals(2); listRequests.assertDiffEquals(1); } @Test public void testCostOfGetFileStatusOnNonEmptyDir() throws Throwable { describe("performing getFileStatus on a non-empty directory"); S3AFileSystem fs = getFileSystem(); Path dir = path("empty"); fs.mkdirs(dir); Path simpleFile = new Path(dir, "simple.txt"); touch(fs, simpleFile); resetMetricDiffs(); S3AFileStatus status = fs.getFileStatus(dir); if (status.isEmptyDirectory()) { // erroneous state String fsState = fs.toString(); fail("FileStatus says directory isempty: " + status + "\n" + ContractTestUtils.ls(fs, dir) + "\n" + fsState); } metadataRequests.assertDiffEquals(2); listRequests.assertDiffEquals(1); } @Test public void testCostOfCopyFromLocalFile() throws Throwable { describe("testCostOfCopyFromLocalFile"); String testDirProp = System.getProperty("test.build.data", "target" + File.separator + "test" + File.separator + "data"); File localTestDir = new File(testDirProp, "tmp").getAbsoluteFile(); localTestDir.mkdirs(); File tmpFile = File.createTempFile("tests3acost", ".txt", localTestDir); tmpFile.delete(); try { URI localFileURI = tmpFile.toURI(); FileSystem localFS = FileSystem.get(localFileURI, getFileSystem().getConf()); Path localPath = new Path(localFileURI); int len = 10 * 1024; byte[] data = dataset(len, 'A', 'Z'); writeDataset(localFS, localPath, data, len, 1024, true); S3AFileSystem s3a = getFileSystem(); MetricDiff copyLocalOps = new MetricDiff(s3a, INVOCATION_COPY_FROM_LOCAL_FILE); MetricDiff putRequests = new MetricDiff(s3a, OBJECT_PUT_REQUESTS); MetricDiff putBytes = new MetricDiff(s3a, OBJECT_PUT_BYTES); Path remotePath = path("copied"); s3a.copyFromLocalFile(false, true, localPath, remotePath); verifyFileContents(s3a, remotePath, data); copyLocalOps.assertDiffEquals(1); putRequests.assertDiffEquals(1); putBytes.assertDiffEquals(len); // print final stats LOG.info("Filesystem {}", s3a); } finally { tmpFile.delete(); } } private void reset(MetricDiff... diffs) { for (MetricDiff diff : diffs) { diff.reset(); } } @Test public void testFakeDirectoryDeletion() throws Throwable { describe("Verify whether create file works after renaming a file. " + "In S3, rename deletes any fake directories as a part of " + "clean up activity"); S3AFileSystem fs = getFileSystem(); Path srcBaseDir = path("src"); mkdirs(srcBaseDir); MetricDiff deleteRequests = new MetricDiff(fs, Statistic.OBJECT_DELETE_REQUESTS); MetricDiff directoriesDeleted = new MetricDiff(fs, Statistic.DIRECTORIES_DELETED); MetricDiff fakeDirectoriesDeleted = new MetricDiff(fs, Statistic.FAKE_DIRECTORIES_DELETED); MetricDiff directoriesCreated = new MetricDiff(fs, Statistic.DIRECTORIES_CREATED); Path srcDir = new Path(srcBaseDir, "1/2/3/4/5/6"); Path srcFilePath = new Path(srcDir, "source.txt"); int srcDirDepth = directoriesInPath(srcDir); // one dir created, one removed mkdirs(srcDir); String state = "after mkdir(srcDir)"; directoriesCreated.assertDiffEquals(state, 1); /* TODO: uncomment once HADOOP-13222 is in deleteRequests.assertDiffEquals(state, 1); directoriesDeleted.assertDiffEquals(state, 0); fakeDirectoriesDeleted.assertDiffEquals(state, srcDirDepth); */ reset(deleteRequests, directoriesCreated, directoriesDeleted, fakeDirectoriesDeleted); // creating a file should trigger demise of the src dir touch(fs, srcFilePath); state = "after touch(fs, srcFilePath)"; deleteRequests.assertDiffEquals(state, 1); directoriesCreated.assertDiffEquals(state, 0); directoriesDeleted.assertDiffEquals(state, 0); fakeDirectoriesDeleted.assertDiffEquals(state, srcDirDepth); reset(deleteRequests, directoriesCreated, directoriesDeleted, fakeDirectoriesDeleted); Path destBaseDir = path("dest"); Path destDir = new Path(destBaseDir, "1/2/3/4/5/6"); Path destFilePath = new Path(destDir, "dest.txt"); mkdirs(destDir); state = "after mkdir(destDir)"; int destDirDepth = directoriesInPath(destDir); directoriesCreated.assertDiffEquals(state, 1); /* TODO: uncomment once HADOOP-13222 "s3a.mkdirs() to delete empty fake parent directories" is in deleteRequests.assertDiffEquals(state,1); directoriesDeleted.assertDiffEquals(state,0); fakeDirectoriesDeleted.assertDiffEquals(state,destDirDepth); */ reset(deleteRequests, directoriesCreated, directoriesDeleted, fakeDirectoriesDeleted); fs.rename(srcFilePath, destFilePath); state = "after rename(srcFilePath, destFilePath)"; directoriesCreated.assertDiffEquals(state, 1); // one for the renamed file, one for the parent deleteRequests.assertDiffEquals(state, 2); directoriesDeleted.assertDiffEquals(state, 0); fakeDirectoriesDeleted.assertDiffEquals(state, destDirDepth); reset(deleteRequests, directoriesCreated, directoriesDeleted, fakeDirectoriesDeleted); assertIsFile(destFilePath); assertIsDirectory(srcDir); } private int directoriesInPath(Path path) { return path.isRoot() ? 0 : 1 + directoriesInPath(path.getParent()); } }