Skip to content
10 changes: 10 additions & 0 deletions hadoop-hdds/common/src/main/resources/ozone-default.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3466,6 +3466,16 @@
</description>
</property>

<property>
<name>ozone.om.enable.ofs.shared.tmp.dir</name>
<value>false</value>
<tag>OZONE, OM</tag>
<description>
Enable shared ofs tmp directory ofs://tmp. Allows a root tmp
directory with sticky-bit behaviour.
</description>
</property>

<property>
<name>ozone.fs.listing.page.size</name>
<value>1024</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.common.base.Preconditions;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.http.ParseException;
import org.apache.hadoop.hdds.annotation.InterfaceAudience;
Expand All @@ -35,6 +36,8 @@
import java.util.StringTokenizer;

import static org.apache.hadoop.fs.FileSystem.TRASH_PREFIX;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ENABLE_OFS_SHARED_TMP_DIR;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ENABLE_OFS_SHARED_TMP_DIR_DEFAULT;
import static org.apache.hadoop.ozone.OzoneConsts.OZONE_OFS_URI_SCHEME;
import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_DELIMITER;

Expand Down Expand Up @@ -63,16 +66,22 @@ public class OFSPath {
private String bucketName = "";
private String mountName = "";
private String keyName = "";
private OzoneConfiguration conf;
private static final String OFS_MOUNT_NAME_TMP = "tmp";
// Hard-code the volume name to tmp for the first implementation
@VisibleForTesting
public static final String OFS_MOUNT_TMP_VOLUMENAME = "tmp";
private static final String OFS_SHARED_TMP_BUCKETNAME = "tmp";
// Hard-coded bucket name to use when OZONE_OM_ENABLE_OFS_SHARED_TMP_DIR
// enabled; HDDS-7746 to make this name configurable.

public OFSPath(Path path) {
public OFSPath(Path path, OzoneConfiguration conf) {
this.conf = conf;
initOFSPath(path.toUri(), false);
}

public OFSPath(String pathStr) {
public OFSPath(String pathStr, OzoneConfiguration conf) {
this.conf = conf;
if (StringUtils.isEmpty(pathStr)) {
return;
}
Expand Down Expand Up @@ -102,7 +111,12 @@ private void initOFSPath(URI uri, boolean endsWithSlash) {
// TODO: Make this configurable in the future.
volumeName = OFS_MOUNT_TMP_VOLUMENAME;
try {
bucketName = getTempMountBucketNameOfCurrentUser();
if (conf.getBoolean(OZONE_OM_ENABLE_OFS_SHARED_TMP_DIR,
OZONE_OM_ENABLE_OFS_SHARED_TMP_DIR_DEFAULT)) {
bucketName = OFS_SHARED_TMP_BUCKETNAME;
} else {
bucketName = getTempMountBucketNameOfCurrentUser();
}
} catch (IOException ex) {
throw new ParseException(
"Failed to get temp bucket name for current user.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,4 +405,9 @@ private OMConfigKeys() {

public static final TimeDuration OZONE_OM_CONTAINER_LOCATION_CACHE_TTL_DEFAULT
= TimeDuration.valueOf(360, TimeUnit.MINUTES);

public static final String OZONE_OM_ENABLE_OFS_SHARED_TMP_DIR
= "ozone.om.enable.ofs.shared.tmp.dir";
public static final boolean OZONE_OM_ENABLE_OFS_SHARED_TMP_DIR_DEFAULT
= false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ Library OperatingSystem
Library String
Library BuiltIn
Resource ../commonlib.robot
Resource ../lib/fs.robot
Test Timeout 5 minutes

*** Variables ***
${ENDPOINT_URL} http://s3g:9878
${SCM} scm
${TMP_MOUNT} tmp
${TMP_DIR} tmp
${SCHEME} ofs

*** Keywords ***
Setup volume names
Expand All @@ -33,6 +37,17 @@ Setup volume names
Set Suite Variable ${volume3} fstest3${random}
Set Suite Variable ${volume4} fstest4${random}

Format ofs TMPMOUNT
[arguments] ${volume} ${path}=${EMPTY} ${om}=${OM_SERVICE_ID}

${om_with_trailing} = Run Keyword If '${om}' != '${EMPTY}' Ensure Trailing / ${om}
... ELSE Set Variable ${EMPTY}

${path_with_leading} = Run Keyword If '${path}' != '${EMPTY}' Ensure Leading / ${path}
... ELSE Set Variable ${EMPTY}

[return] ofs://${om_with_trailing}${volume}/${path_with_leading}

*** Test Cases ***
Create volume bucket with wrong credentials
Execute kdestroy
Expand Down Expand Up @@ -153,3 +168,39 @@ Test native authorizer
Execute ozone sh key list /${volume3}/bk1
Execute kdestroy
Run Keyword Kinit test user testuser testuser.keytab

Test tmp mount for shared ofs tmp dir
${result} = Execute And Ignore Error ozone getconf confKey ozone.om.enable.ofs.shared.tmp.dir
${contains} = Evaluate "true" in """${result}"""
IF ${contains} == ${True}
Run Keyword Kinit test user testuser testuser.keytab
Execute ozone sh volume create /${TMP_MOUNT} -u testuser
Execute ozone sh bucket create /${TMP_MOUNT}/${TMP_DIR} -u testuser
Execute ozone sh volume addacl /${TMP_MOUNT} -a user:testuser/scm@EXAMPLE.COM:a,user:testuser2/scm@EXAMPLE.COM:rw
Execute ozone sh bucket addacl /${TMP_MOUNT}/${TMP_DIR} -a user:testuser/scm@EXAMPLE.COM:a,user:testuser2/scm@EXAMPLE.COM:rwlc

${tmpdirmount} = Format ofs TMPMOUNT ${TMP_MOUNT}
${result} = Execute ozone fs -put ./NOTICE.txt ${tmpdirmount}
Should Be Empty ${result}
Run Keyword Kinit test user testuser2 testuser2.keytab
${result} = Execute ozone fs -put ./LICENSE.txt ${tmpdirmount}
Should Be Empty ${result}

${result} = Execute ozone fs -ls ${tmpdirmount}
Should contain ${result} NOTICE.txt
Should contain ${result} LICENSE.txt


${result} = Execute And Ignore Error ozone fs -rm -skipTrash ${tmpdirmount}/NOTICE.txt
Should contain ${result} error
${result} = Execute ozone fs -rm -skipTrash ${tmpdirmount}/LICENSE.txt
Should contain ${result} Deleted

Run Keyword Kinit test user testuser testuser.keytab
${result} = Execute ozone fs -rm -skipTrash ${tmpdirmount}/NOTICE.txt
Should contain ${result} Deleted

Execute ozone fs -rm -r -skipTrash ${tmpdirmount}
Execute ozone sh volume delete /${TMP_MOUNT}
END

Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -110,8 +111,14 @@
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_FS_ITERATE_BATCH_SIZE;
import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_DELIMITER;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ENABLE_OFS_SHARED_TMP_DIR;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.BUCKET_NOT_FOUND;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_NOT_FOUND;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.PERMISSION_DENIED;
import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.READ;
import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.WRITE;
import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.DELETE;
import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.LIST;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
Expand Down Expand Up @@ -296,7 +303,7 @@ public void testCreateDoesNotAddParentDirKeys() throws Exception {
ContractTestUtils.touch(fs, child);

OzoneKeyDetails key = getKey(child, false);
OFSPath childOFSPath = new OFSPath(child);
OFSPath childOFSPath = new OFSPath(child, conf);
Assert.assertEquals(key.getName(), childOFSPath.getKeyName());

// Creating a child should not add parent keys to the bucket
Expand Down Expand Up @@ -382,7 +389,7 @@ public void testDeleteCreatesFakeParentDir() throws Exception {

// Deleting the only child should create the parent dir key if it does
// not exist
OFSPath parentOFSPath = new OFSPath(parent);
OFSPath parentOFSPath = new OFSPath(parent, conf);
String parentKey = parentOFSPath.getKeyName() + "/";
OzoneKeyDetails parentKeyInfo = getKey(parent, true);
Assert.assertEquals(parentKey, parentKeyInfo.getName());
Expand Down Expand Up @@ -695,7 +702,7 @@ public void testMkdirOnNonExistentVolumeBucketDir() throws Exception {
// Check volume and bucket existence, they should both be created.
OzoneVolume ozoneVolume = objectStore.getVolume(volumeNameLocal);
OzoneBucket ozoneBucket = ozoneVolume.getBucket(bucketNameLocal);
OFSPath ofsPathDir1 = new OFSPath(dir12);
OFSPath ofsPathDir1 = new OFSPath(dir12, conf);
String key = ofsPathDir1.getKeyName() + "/";
OzoneKeyDetails ozoneKeyDetails = ozoneBucket.getKey(key);
Assert.assertEquals(key, ozoneKeyDetails.getName());
Expand Down Expand Up @@ -965,7 +972,7 @@ private OzoneKeyDetails getKey(Path keyPath, boolean isDirectory)
if (isDirectory) {
key = key + OZONE_URI_DELIMITER;
}
OFSPath ofsPath = new OFSPath(key);
OFSPath ofsPath = new OFSPath(key, conf);
String keyInBucket = ofsPath.getKeyName();
return cluster.getClient().getObjectStore().getVolume(volumeName)
.getBucket(bucketName).getKey(keyInBucket);
Expand Down Expand Up @@ -1006,7 +1013,7 @@ private void teardownVolumeBucketWithDir(Path bucketPath1)
throws IOException {
fs.delete(new Path(bucketPath1, "dir1"), true);
fs.delete(new Path(bucketPath1, "dir2"), true);
OFSPath ofsPath = new OFSPath(bucketPath1);
OFSPath ofsPath = new OFSPath(bucketPath1, conf);
OzoneVolume volume = objectStore.getVolume(ofsPath.getVolumeName());
volume.deleteBucket(ofsPath.getBucketName());
objectStore.deleteVolume(ofsPath.getVolumeName());
Expand All @@ -1029,7 +1036,7 @@ public void testListStatusRootAndVolumeNonRecursive() throws Exception {
Assert.assertEquals(2, fileStatusBucket.length);
// listStatus("/volume")
Path volume = new Path(
OZONE_URI_DELIMITER + new OFSPath(bucketPath1).getVolumeName());
OZONE_URI_DELIMITER + new OFSPath(bucketPath1, conf).getVolumeName());
FileStatus[] fileStatusVolume = ofs.listStatus(volume);
Assert.assertEquals(1, fileStatusVolume.length);
Assert.assertEquals(ownerShort, fileStatusVolume[0].getOwner());
Expand Down Expand Up @@ -1130,7 +1137,7 @@ public void testListStatusRootAndVolumeRecursive() throws IOException {
listStatusCheckHelper(bucketPath1);
// listStatus("/volume")
Path volume = new Path(
OZONE_URI_DELIMITER + new OFSPath(bucketPath1).getVolumeName());
OZONE_URI_DELIMITER + new OFSPath(bucketPath1, conf).getVolumeName());
listStatusCheckHelper(volume);
// listStatus("/")
Path root = new Path(OZONE_URI_DELIMITER);
Expand Down Expand Up @@ -1216,7 +1223,123 @@ public void testListStatusRootAndVolumeContinuation() throws IOException {
}
}

/*
@Test
public void testSharedTmpDir() throws IOException {
// Prep
conf.setBoolean(OZONE_OM_ENABLE_OFS_SHARED_TMP_DIR, true);
// Use ClientProtocol to pass in volume ACL, ObjectStore won't do it
ClientProtocol proxy = objectStore.getClientProxy();
// Get default acl rights for user
OzoneAclConfig aclConfig = conf.getObject(OzoneAclConfig.class);
ACLType userRights = aclConfig.getUserDefaultRights();
// Construct ACL for world access
// ACL admin owner, world read+write
BitSet aclRights = new BitSet();
aclRights.set(READ.ordinal());
aclRights.set(WRITE.ordinal());
List<OzoneAcl> objectAcls = new ArrayList<>();
objectAcls.add(new OzoneAcl(ACLIdentityType.WORLD, "",
aclRights, ACCESS));
objectAcls.add(new OzoneAcl(ACLIdentityType.USER, "admin", userRights,
ACCESS));
// volume acls have all access to admin and read+write access to world

// Construct VolumeArgs
VolumeArgs volumeArgs = new VolumeArgs.Builder()
.setAdmin("admin")
.setOwner("admin")
.setAcls(Collections.unmodifiableList(objectAcls))
.setQuotaInNamespace(1000)
.setQuotaInBytes(Long.MAX_VALUE).build();
// Sanity check
Assert.assertEquals("admin", volumeArgs.getOwner());
Assert.assertEquals("admin", volumeArgs.getAdmin());
Assert.assertEquals(Long.MAX_VALUE, volumeArgs.getQuotaInBytes());
Assert.assertEquals(1000, volumeArgs.getQuotaInNamespace());
Assert.assertEquals(0, volumeArgs.getMetadata().size());
Assert.assertEquals(2, volumeArgs.getAcls().size());
// Create volume "tmp" with world access read+write to access tmp mount
// admin has all access to tmp mount
proxy.createVolume(OFSPath.OFS_MOUNT_TMP_VOLUMENAME, volumeArgs);

OzoneVolume vol = objectStore.getVolume(OFSPath.OFS_MOUNT_TMP_VOLUMENAME);
Assert.assertNotNull(vol);

// Begin test
String hashedUsername = OFSPath.getTempMountBucketNameOfCurrentUser();

// Expect failure since temp bucket for current user is not created yet
try {
vol.getBucket(hashedUsername);
} catch (OMException ex) {
// Expect BUCKET_NOT_FOUND
if (!ex.getResult().equals(BUCKET_NOT_FOUND)) {
Assert.fail("Temp bucket for current user shouldn't have been created");
}
}

// set acls for shared tmp mount under the tmp volume
objectAcls.clear();
objectAcls.add(new OzoneAcl(ACLIdentityType.USER, "admin", userRights,
ACCESS));
aclRights.clear(DELETE.ordinal());
aclRights.set(LIST.ordinal());
objectAcls.add(new OzoneAcl(ACLIdentityType.WORLD, "",
aclRights, ACCESS));
objectAcls.add(new OzoneAcl(ACLIdentityType.USER, "admin", userRights,
ACCESS));
// bucket acls have all access to admin and read+write+list access to world

BucketArgs bucketArgs = new BucketArgs.Builder()
.setOwner("admin")
.setAcls(Collections.unmodifiableList(objectAcls))
.setQuotaInNamespace(1000)
.setQuotaInBytes(Long.MAX_VALUE).build();

// Create bucket "tmp" with world access read+write+list to tmp directory
// admin has all access to tmp mount
proxy.createBucket(OFSPath.OFS_MOUNT_TMP_VOLUMENAME,
OFSPath.OFS_MOUNT_TMP_VOLUMENAME, bucketArgs);

// Write under /tmp/
Path dir1 = new Path("/tmp/dir1");
userOfs.mkdirs(dir1);

try (FSDataOutputStream stream = userOfs.create(new Path(
"/tmp/dir1/file1"))) {
stream.write(1);
}

// Verify temp bucket creation
OzoneBucket bucket = vol.getBucket("tmp");
Assert.assertNotNull(bucket);
// Verify dir1 creation
FileStatus[] fileStatuses = fs.listStatus(new Path("/tmp/"));
Assert.assertEquals(1, fileStatuses.length);
Assert.assertEquals(
"/tmp/dir1", fileStatuses[0].getPath().toUri().getPath());
// Verify file1 creation
FileStatus[] fileStatusesInDir1 = fs.listStatus(dir1);
Assert.assertEquals(1, fileStatusesInDir1.length);
Assert.assertEquals("/tmp/dir1/file1",
fileStatusesInDir1[0].getPath().toUri().getPath());

// Cleanup
userOfs.delete(dir1, true);
try {
userOfs.delete(new Path("/tmp"), true);
} catch (OMException ex) {
// Expect PERMISSION_DENIED, User regularuser1 doesn't have DELETE
// permission for /tmp
if (!ex.getResult().equals(PERMISSION_DENIED)) {
Assert.fail("Temp bucket cannot be deleted by current user");
}
}
fs.delete(new Path("/tmp"), true);
proxy.deleteVolume(OFSPath.OFS_MOUNT_TMP_VOLUMENAME);
}

/*
* OFS: Test /tmp mount behavior.
*/
@Test
Expand Down Expand Up @@ -1917,7 +2040,7 @@ public void testBucketDefaultsShouldNotBeInheritedToFileForNonEC()
.createFile(vol + "/" + buck + "/test", (short) 3, true, false)) {
file.write(new byte[1024]);
}
OFSPath ofsPath = new OFSPath(vol + "/" + buck + "/test");
OFSPath ofsPath = new OFSPath(vol + "/" + buck + "/test", conf);
final OzoneBucket bucket = adapter.getBucket(ofsPath, false);
final OzoneKeyDetails key = bucket.getKey(ofsPath.getKeyName());
Assert.assertEquals(key.getReplicationConfig().getReplicationType().name(),
Expand Down Expand Up @@ -1947,7 +2070,7 @@ public void testBucketDefaultsShouldBeInheritedToFileForEC()
.createFile(vol + "/" + buck + "/test", (short) 3, true, false)) {
file.write(new byte[1024]);
}
OFSPath ofsPath = new OFSPath(vol + "/" + buck + "/test");
OFSPath ofsPath = new OFSPath(vol + "/" + buck + "/test", conf);
final OzoneBucket bucket = adapter.getBucket(ofsPath, false);
final OzoneKeyDetails key = bucket.getKey(ofsPath.getKeyName());
Assert.assertEquals(ReplicationType.EC.name(),
Expand Down
Loading