This is an automated email from the ASF dual-hosted git repository.

adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 5d68ac93f9 HDDS-8484. Allow Ozone volume name to have special 
characters (#4630)
5d68ac93f9 is described below

commit 5d68ac93f9b27f2b1392f00d8ef05eecdf3a6469
Author: DaveTeng0 <[email protected]>
AuthorDate: Sun May 7 23:56:03 2023 -0700

    HDDS-8484. Allow Ozone volume name to have special characters (#4630)
---
 .../apache/hadoop/ozone/client/rpc/RpcClient.java  |   6 +-
 .../main/java/org/apache/hadoop/ozone/OmUtils.java |   5 +-
 .../om/multitenant/TestMultiTenantVolume.java      |  18 ++
 .../request/s3/tenant/OMTenantCreateRequest.java   |  31 ++--
 .../om/request/volume/OMVolumeCreateRequest.java   |   3 +-
 .../hadoop/ozone/om/TestOMTenantCreateRequest.java | 182 +++++++++++++++++++++
 .../request/volume/TestOMVolumeCreateRequest.java  |  72 ++++++++
 7 files changed, 300 insertions(+), 17 deletions(-)

diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
index d965fd7b90..268f333d8f 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
@@ -491,7 +491,7 @@ public class RpcClient implements ClientProtocol {
   @Override
   public void setVolumeQuota(String volumeName, long quotaInNamespace,
       long quotaInBytes) throws IOException {
-    HddsClientUtils.verifyResourceName(volumeName);
+    verifyVolumeName(volumeName);
     verifyCountsQuota(quotaInNamespace);
     verifySpaceQuota(quotaInBytes);
     // If the volume is old, we need to remind the user on the client side
@@ -707,7 +707,7 @@ public class RpcClient implements ClientProtocol {
 
   private static void verifyVolumeName(String volumeName) throws OMException {
     try {
-      HddsClientUtils.verifyResourceName(volumeName);
+      HddsClientUtils.verifyResourceName(volumeName, false);
     } catch (IllegalArgumentException e) {
       throw new OMException(e.getMessage(),
           OMException.ResultCodes.INVALID_VOLUME_NAME);
@@ -1095,7 +1095,7 @@ public class RpcClient implements ClientProtocol {
   @Override
   public void setBucketQuota(String volumeName, String bucketName,
       long quotaInNamespace, long quotaInBytes) throws IOException {
-    HddsClientUtils.verifyResourceName(volumeName);
+    verifyVolumeName(volumeName);
     verifyCountsQuota(quotaInNamespace);
     verifySpaceQuota(quotaInBytes);
     OmBucketArgs.Builder builder = OmBucketArgs.newBuilder();
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
index 358f8ff3f9..940fffb603 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
@@ -487,9 +487,10 @@ public final class OmUtils {
   /**
    * Verify volume name is a valid DNS name.
    */
-  public static void validateVolumeName(String volumeName) throws OMException {
+  public static void validateVolumeName(String volumeName, boolean isStrictS3)
+      throws OMException {
     try {
-      HddsClientUtils.verifyResourceName(volumeName);
+      HddsClientUtils.verifyResourceName(volumeName, isStrictS3);
     } catch (IllegalArgumentException e) {
       throw new OMException("Invalid volume name: " + volumeName,
           OMException.ResultCodes.INVALID_VOLUME_NAME);
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantVolume.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantVolume.java
index 01cebd3d4e..4a0dfcd186 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantVolume.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantVolume.java
@@ -51,6 +51,7 @@ import java.util.concurrent.TimeoutException;
 import static 
org.apache.hadoop.ozone.admin.scm.FinalizeUpgradeCommandUtil.isDone;
 import static 
org.apache.hadoop.ozone.admin.scm.FinalizeUpgradeCommandUtil.isStarting;
 import static 
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_MULTITENANCY_ENABLED;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /**
  * Tests that S3 requests for a tenant are directed to that tenant's volume,
@@ -295,4 +296,21 @@ public class TestMultiTenantVolume {
     store.deleteTenant(TENANT_ID);
     store.deleteVolume(TENANT_ID);
   }
+
+  @Test
+  public void testRejectNonS3CompliantTenantIdCreationWithDefaultStrictS3True()
+      throws Exception {
+    ObjectStore store = getStoreForAccessID(ACCESS_ID);
+    String[] nonS3CompliantTenantId =
+        {"tenantid_underscore", "_tenantid___multi_underscore_", "tenantid_"};
+
+    for (String tenantId : nonS3CompliantTenantId) {
+      OMException e = assertThrows(
+          OMException.class,
+          () -> store.createTenant(tenantId));
+
+      Assert.assertTrue(e.getMessage().contains("Invalid volume name: "
+          + tenantId));
+    }
+  }
 }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantCreateRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantCreateRequest.java
index 2ff87ab140..2c7158c4f4 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantCreateRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantCreateRequest.java
@@ -57,6 +57,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.TENANT_ALREADY_EXISTS;
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.USER_NOT_FOUND;
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_ALREADY_EXISTS;
 import static 
org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.USER_LOCK;
 import static 
org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.VOLUME_LOCK;
@@ -137,20 +138,11 @@ public class OMTenantCreateRequest extends 
OMVolumeRequest {
           TENANT_ALREADY_EXISTS);
     }
 
-    // getUserName returns:
-    // - Kerberos principal when Kerberos security is enabled
-    // - User's login name when security is not enabled
-    // - AWS_ACCESS_KEY_ID if the original request comes from S3 Gateway.
-    //    Not Applicable to TenantCreateRequest.
-    final UserGroupInformation ugi = ProtobufRpcEngine.Server.getRemoteUser();
-    // getShortUserName here follows RpcClient#createVolume
-    // A caveat is that this assumes OM's auth_to_local is the same as
-    //  the client's. Maybe move this logic to the client and pass VolumeArgs?
-    final String owner = ugi.getShortUserName();
+    final String owner = getUserName();
     // Volume name defaults to tenant name if unspecified in the request
     final String volumeName = request.getVolumeName();
     // Validate volume name
-    OmUtils.validateVolumeName(volumeName);
+    OmUtils.validateVolumeName(volumeName, ozoneManager.isStrictS3());
 
     final String dbVolumeKey = ozoneManager.getMetadataManager()
         .getVolumeKey(volumeName);
@@ -361,4 +353,21 @@ public class OMTenantCreateRequest extends OMVolumeRequest 
{
     }
     return omClientResponse;
   }
+
+  public String getUserName() throws IOException {
+    // getUserName returns:
+    // - Kerberos principal when Kerberos security is enabled
+    // - User's login name when security is not enabled
+    // - AWS_ACCESS_KEY_ID if the original request comes from S3 Gateway.
+    //    Not Applicable to TenantCreateRequest.
+    final UserGroupInformation ugi = ProtobufRpcEngine.Server.getRemoteUser();
+    // getShortUserName here follows RpcClient#createVolume
+    // A caveat is that this assumes OM's auth_to_local is the same as
+    //  the client's. Maybe move this logic to the client and pass VolumeArgs?
+    if (ugi != null) {
+      return ugi.getShortUserName();
+    } else {
+      throw new OMException("User name is null.", USER_NOT_FOUND);
+    }
+  }
 }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/volume/OMVolumeCreateRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/volume/OMVolumeCreateRequest.java
index ca2a8ff82a..aa6cec9600 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/volume/OMVolumeCreateRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/volume/OMVolumeCreateRequest.java
@@ -74,7 +74,8 @@ public class OMVolumeCreateRequest extends OMVolumeRequest {
     VolumeInfo volumeInfo  =
         getOmRequest().getCreateVolumeRequest().getVolumeInfo();
     // Verify resource name
-    OmUtils.validateVolumeName(volumeInfo.getVolume());
+    OmUtils.validateVolumeName(volumeInfo.getVolume(),
+        ozoneManager.isStrictS3());
 
     // Set creation time & set modification time
     long initialTime = Time.now();
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMTenantCreateRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMTenantCreateRequest.java
new file mode 100644
index 0000000000..fb95fba6ed
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMTenantCreateRequest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.ozone.om;
+
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.audit.AuditLogger;
+import org.apache.hadoop.ozone.audit.AuditMessage;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.multitenant.AuthorizerLock;
+import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper;
+import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
+import org.apache.hadoop.ozone.om.request.s3.tenant.OMTenantCreateRequest;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.upgrade.OMLayoutVersionManager;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import org.apache.ozone.test.LambdaTestUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.UUID;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests create tenant request.
+ */
+public class TestOMTenantCreateRequest {
+  @Rule
+  public TemporaryFolder folder = new TemporaryFolder();
+  private OzoneManager ozoneManager;
+  private OMMetrics omMetrics;
+  private OMMetadataManager omMetadataManager;
+  private AuditLogger auditLogger;
+  // Just setting ozoneManagerDoubleBuffer which does nothing.
+  private OzoneManagerDoubleBufferHelper ozoneManagerDoubleBufferHelper =
+      ((response, transactionIndex) -> {
+        return null;
+      });
+
+  @Before
+  public void setup() throws Exception {
+    ozoneManager = mock(OzoneManager.class);
+    omMetrics = OMMetrics.create();
+    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
+    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
+        folder.newFolder().getAbsolutePath());
+    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration);
+    when(ozoneManager.getMetrics()).thenReturn(omMetrics);
+    when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
+    when(ozoneManager.getMaxUserVolumeCount()).thenReturn(10L);
+    OMLayoutVersionManager lvm = mock(OMLayoutVersionManager.class);
+    when(lvm.getMetadataLayoutVersion()).thenReturn(0);
+    when(lvm.isAllowed(anyString())).thenReturn(true);
+    when(ozoneManager.getVersionManager()).thenReturn(lvm);
+    when(ozoneManager.isRatisEnabled()).thenReturn(true);
+    auditLogger = mock(AuditLogger.class);
+    when(ozoneManager.getAuditLogger()).thenReturn(auditLogger);
+    Mockito.doNothing().when(auditLogger).logWrite(any(AuditMessage.class));
+
+    OMMultiTenantManager multiTenantManager = mock(OMMultiTenantManager.class);
+    Mockito.doNothing().when(multiTenantManager).checkAdmin();
+    AuthorizerLock authorizerLock = mock(AuthorizerLock.class);
+    Mockito.doNothing().when(authorizerLock).tryWriteLockInOMRequest();
+    when(multiTenantManager.getAuthorizerLock()).thenReturn(authorizerLock);
+    TenantOp tenantOp = mock(TenantOp.class);
+    Mockito.doNothing().when(tenantOp).createTenant(any(), any(), any());
+    when(multiTenantManager.getAuthorizerOp()).thenReturn(tenantOp);
+    when(multiTenantManager.getCacheOp()).thenReturn(tenantOp);
+
+    when(ozoneManager.getMultiTenantManager()).thenReturn(multiTenantManager);
+  }
+
+  @After
+  public void stop() {
+    omMetrics.unRegister();
+    Mockito.framework().clearInlineMocks();
+  }
+
+
+  @Test
+  public void
+      testAcceptS3CompliantTenantIdCreationRegardlessOfStrictS3Setting()
+      throws Exception {
+    boolean[] omStrictS3Configs = {true, false};
+    for (boolean isStrictS3 : omStrictS3Configs) {
+      when(ozoneManager.isStrictS3()).thenReturn(isStrictS3);
+      String tenantId = UUID.randomUUID().toString();
+      acceptTenantIdCreationHelper(tenantId);
+    }
+  }
+
+  @Test
+  public void testRejectNonS3CompliantTenantIdCreationWithStrictS3True()
+      throws Exception {
+    String[] nonS3CompliantTenantId =
+        {"tenantid_underscore", "_tenantid___multi_underscore_", "tenantid_"};
+    when(ozoneManager.isStrictS3()).thenReturn(true);
+    for (String tenantId : nonS3CompliantTenantId) {
+      rejectTenantIdCreationHelper(tenantId);
+    }
+  }
+
+  @Test
+  public void testAcceptNonS3CompliantTenantIdCreationWithStrictS3False()
+      throws Exception {
+    String[] nonS3CompliantTenantId =
+        {"tenantid_underscore", "_tenantid___multi_underscore_", "tenantid_"};
+    when(ozoneManager.isStrictS3()).thenReturn(false);
+    for (String tenantId : nonS3CompliantTenantId) {
+      acceptTenantIdCreationHelper(tenantId);
+    }
+  }
+
+  private void acceptTenantIdCreationHelper(String tenantId)
+      throws Exception {
+    OMRequest originalRequest =
+        OMRequestTestUtils.createTenantRequest(tenantId);
+    OMTenantCreateRequest omTenantCreateRequest =
+        Mockito.spy(new OMTenantCreateRequest(originalRequest));
+    Mockito.doReturn("username").when(omTenantCreateRequest).getUserName();
+
+    OMRequest modifiedRequest = omTenantCreateRequest.preExecute(ozoneManager);
+    omTenantCreateRequest = new OMTenantCreateRequest(modifiedRequest);
+    long txLogIndex = 1;
+    OMClientResponse omClientResponse =
+        omTenantCreateRequest.validateAndUpdateCache(ozoneManager, txLogIndex,
+            ozoneManagerDoubleBufferHelper);
+    OzoneManagerProtocolProtos.OMResponse omResponse =
+        omClientResponse.getOMResponse();
+
+    Assert.assertNotNull(omResponse.getCreateTenantResponse());
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.OK,
+        omResponse.getStatus());
+    Assert.assertNotNull(omMetadataManager.getVolumeTable().get(
+        omMetadataManager.getVolumeKey(tenantId)));
+  }
+
+  private void rejectTenantIdCreationHelper(String tenantId)
+      throws Exception {
+    // Verify exception thrown on invalid volume name
+    LambdaTestUtils.intercept(OMException.class, "Invalid volume name: "
+            + tenantId,
+        () -> doPreExecute(tenantId));
+  }
+
+  private void doPreExecute(String tenantId) throws Exception {
+    OMRequest originalRequest =
+        OMRequestTestUtils.createTenantRequest(tenantId);
+
+    OMTenantCreateRequest omTenantCreateRequest =
+        Mockito.spy(new OMTenantCreateRequest(originalRequest));
+    Mockito.doReturn("username").when(omTenantCreateRequest).getUserName();
+
+    omTenantCreateRequest.preExecute(ozoneManager);
+  }
+
+}
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/volume/TestOMVolumeCreateRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/volume/TestOMVolumeCreateRequest.java
index a0b94d1626..164ea386b5 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/volume/TestOMVolumeCreateRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/volume/TestOMVolumeCreateRequest.java
@@ -210,6 +210,78 @@ public class TestOMVolumeCreateRequest extends 
TestOMVolumeRequest {
         omMetadataManager.getVolumeKey(volumeName)));
   }
 
+  @Test
+  public void 
+        testAcceptS3CompliantVolumeNameCreationRegardlessOfStrictS3Setting()
+        throws Exception {
+    String adminName = UUID.randomUUID().toString();
+    String ownerName = UUID.randomUUID().toString();
+    boolean[] omStrictS3Configs = {true, false};
+    for (boolean isStrictS3 : omStrictS3Configs) {
+      when(ozoneManager.isStrictS3()).thenReturn(isStrictS3);
+      String volumeName = UUID.randomUUID().toString();
+      acceptVolumeCreationHelper(volumeName, adminName, ownerName);
+    }
+  }
+
+  @Test
+  public void testRejectNonS3CompliantVolumeNameCreationWithStrictS3True()
+        throws Exception {
+    String adminName = UUID.randomUUID().toString();
+    String ownerName = UUID.randomUUID().toString();        
+    String[] nonS3CompliantVolumeName = 
+        {"volume_underscore", "_volume___multi_underscore_", "volume_"};
+    when(ozoneManager.isStrictS3()).thenReturn(true);
+    for (String volumeName : nonS3CompliantVolumeName) {
+      rejectVolumeCreationHelper(volumeName, adminName, ownerName);
+    }
+  }
+
+  @Test
+  public void testAcceptNonS3CompliantVolumeNameCreationWithStrictS3False()
+        throws Exception {
+    String adminName = UUID.randomUUID().toString();
+    String ownerName = UUID.randomUUID().toString();        
+    String[] nonS3CompliantVolumeName = 
+        {"volume_underscore", "_volume___multi_underscore_", "volume_"};
+    when(ozoneManager.isStrictS3()).thenReturn(false);
+    for (String volumeName : nonS3CompliantVolumeName) {
+      acceptVolumeCreationHelper(volumeName, adminName, ownerName);
+    }
+  }
+
+  private void acceptVolumeCreationHelper(String volumeName, String adminName,
+        String ownerName)
+        throws Exception {
+    OMRequest originalRequest = createVolumeRequest(volumeName, adminName,
+        ownerName);
+    OMVolumeCreateRequest omVolumeCreateRequest =
+            new OMVolumeCreateRequest(originalRequest);
+    OMRequest modifiedRequest = omVolumeCreateRequest.preExecute(ozoneManager);
+    omVolumeCreateRequest = new OMVolumeCreateRequest(modifiedRequest);
+    long txLogIndex = 1;
+    OMClientResponse omClientResponse =
+        omVolumeCreateRequest.validateAndUpdateCache(ozoneManager, txLogIndex,
+            ozoneManagerDoubleBufferHelper);
+    OzoneManagerProtocolProtos.OMResponse omResponse =
+        omClientResponse.getOMResponse();
+
+    Assert.assertNotNull(omResponse.getCreateVolumeResponse());
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.OK,
+        omResponse.getStatus());
+    Assert.assertNotNull(omMetadataManager.getVolumeTable().get(
+        omMetadataManager.getVolumeKey(volumeName)));
+  }
+
+  private void rejectVolumeCreationHelper(String volumeName, String adminName,
+        String ownerName)
+        throws Exception {
+    // Verify exception thrown on invalid volume name
+    LambdaTestUtils.intercept(OMException.class, "Invalid volume name: " 
+        + volumeName,
+        () -> doPreExecute(volumeName, adminName, ownerName));
+  }
+
   private void doPreExecute(String volumeName,
       String adminName, String ownerName) throws Exception {
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to