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

siyao pushed a commit to branch HDDS-4944
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/HDDS-4944 by this push:
     new 9fc34dc  HDDS-5754. [Multi-Tenant] Implement GetUserInfo (#2692)
9fc34dc is described below

commit 9fc34dc7ff0631038516db69a4e2bdeca91f2a15
Author: Siyao Meng <[email protected]>
AuthorDate: Mon Oct 4 11:28:39 2021 -0700

    HDDS-5754. [Multi-Tenant] Implement GetUserInfo (#2692)
---
 .../apache/hadoop/ozone/client/ObjectStore.java    |  12 +-
 .../ozone/client/protocol/ClientProtocol.java      |  10 +-
 .../apache/hadoop/ozone/client/rpc/RpcClient.java  |  15 +-
 .../main/java/org/apache/hadoop/ozone/OmUtils.java |   4 +-
 .../org/apache/hadoop/ozone/audit/OMAction.java    |   4 +-
 .../om/helpers/OmDBKerberosPrincipalInfo.java      |   5 +
 .../ozone/om/helpers/TenantUserInfoValue.java      |  87 +++++
 .../MultiTenantAccessAuthorizerDummyPlugin.java    | 131 +++++++
 .../ozone/om/protocol/OzoneManagerProtocol.java    |  10 +-
 ...OzoneManagerProtocolClientSideTranslatorPB.java |  22 +-
 .../smoketest/security/ozone-secure-tenant.robot   |  51 +++
 hadoop-ozone/dist/src/shell/ozone/ozone            |   5 +
 .../hadoop/ozone/shell/TestOzoneShellHA.java       |  17 -
 .../hadoop/ozone/shell/TestOzoneTenantShell.java   | 383 +++++++++++++++++++++
 .../src/main/proto/OmClientProtocol.proto          |  55 +--
 .../hadoop/ozone/om/OMMultiTenantManagerImpl.java  |  18 +-
 .../hadoop/ozone/om/OmMetadataManagerImpl.java     |  12 +-
 .../org/apache/hadoop/ozone/om/OzoneManager.java   |  54 ++-
 .../om/ratis/utils/OzoneManagerRatisUtils.java     |   9 +-
 .../s3/tenant/OMAssignUserToTenantRequest.java     |  16 +-
 ...java => OMRevokeUserAccessToTenantRequest.java} |   4 +-
 .../request/s3/tenant/OMTenantCreateRequest.java   |  85 +++--
 .../s3/tenant/OMTenantUserModifyRequest.java       |  50 ---
 .../protocolPB/OzoneManagerRequestHandler.java     |  25 ++
 .../s3/security/TestS3GetSecretRequest.java        |   4 +
 .../org/apache/hadoop/ozone/shell/s3/S3Shell.java  |   4 +-
 .../hadoop/ozone/shell/s3/TenantCommands.java      |  71 ----
 .../{s3 => tenant}/AssignUserToTenantHandler.java  |  13 +-
 .../ozone/shell/tenant/GetUserInfoHandler.java     |  83 +++++
 .../RevokeUserAccessToTenantHandler.java}          |  10 +-
 .../shell/{s3 => tenant}/TenantCreateHandler.java  |  10 +-
 .../shell/{s3 => tenant}/TenantDeleteHandler.java  |   8 +-
 .../ozone/shell/{s3 => tenant}/TenantHandler.java  |  29 +-
 .../shell/{s3 => tenant}/TenantModifyHandler.java  |   4 +-
 .../{s3/S3Shell.java => tenant/TenantShell.java}   |  29 +-
 .../shell/{s3 => tenant}/TenantUserCommands.java   |  10 +-
 .../package-info.java}                             |  18 +-
 37 files changed, 1083 insertions(+), 294 deletions(-)

diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
index 72b82d6..81b5101 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
@@ -34,6 +34,7 @@ import org.apache.hadoop.ozone.OzoneAcl;
 import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
+import org.apache.hadoop.ozone.om.helpers.TenantUserInfoValue;
 import org.apache.hadoop.ozone.security.OzoneTokenIdentifier;
 import org.apache.hadoop.ozone.security.acl.OzoneObj;
 import org.apache.hadoop.security.UserGroupInformation;
@@ -199,7 +200,16 @@ public class ObjectStore {
     return proxy.assignUserToTenant(username, tenantName, accessId);
   }
 
-  // TODO: modify, delete
+  /**
+   * Get tenant info for a user.
+   * @param userPrincipal Kerberos principal of a user.
+   * @return TenantUserInfo
+   * @throws IOException
+   */
+  public TenantUserInfoValue tenantGetUserInfo(String userPrincipal)
+      throws IOException {
+    return proxy.tenantGetUserInfo(userPrincipal);
+  }
 
   /**
    * Returns Iterator to iterate over all the volumes in object store.
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
index 2676710..33dfa3b 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
@@ -47,6 +47,7 @@ import 
org.apache.hadoop.ozone.om.helpers.OmMultipartUploadCompleteInfo;
 import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
 import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
+import org.apache.hadoop.ozone.om.helpers.TenantUserInfoValue;
 import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRoleInfo;
 import org.apache.hadoop.ozone.security.OzoneTokenIdentifier;
@@ -582,7 +583,14 @@ public interface ClientProtocol {
   S3SecretValue assignUserToTenant(String username, String tenantName,
       String accessId) throws IOException;
 
-  // TODO: modify, delete
+  /**
+   * Get tenant info for a user.
+   * @param userPrincipal Kerberos principal of a user.
+   * @return TenantUserInfo
+   * @throws IOException
+   */
+  TenantUserInfoValue tenantGetUserInfo(String userPrincipal)
+      throws IOException;
 
   /**
    * Get KMS client provider.
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 32aa18e..3b2e572 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
@@ -104,6 +104,7 @@ import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfo;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfoEx;
+import org.apache.hadoop.ozone.om.helpers.TenantUserInfoValue;
 import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
 import org.apache.hadoop.ozone.om.protocolPB.OmTransport;
 import org.apache.hadoop.ozone.om.protocolPB.OmTransportFactory;
@@ -655,7 +656,19 @@ public class RpcClient implements ClientProtocol {
         username, tenantName, accessId);
   }
 
-  // TODO: modify, delete
+  /**
+   * Get tenant info for a user.
+   * @param userPrincipal Kerberos principal of a user.
+   * @return TenantUserInfo
+   * @throws IOException
+   */
+  @Override
+  public TenantUserInfoValue tenantGetUserInfo(String userPrincipal)
+      throws IOException {
+    Preconditions.checkArgument(Strings.isNotBlank(userPrincipal),
+        "userPrincipal can't be null or empty.");
+    return ozoneManagerClient.tenantGetUserInfo(userPrincipal);
+  }
 
   @Override
   public void setBucketVersioning(
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 8dc4bfc..8502744 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
@@ -265,6 +265,7 @@ public final class OmUtils {
     case ListMultipartUploads:
     case FinalizeUpgradeProgress:
     case PrepareStatus:
+    case TenantGetUserInfo:
       return true;
     case CreateVolume:
     case SetVolumeProperty:
@@ -304,8 +305,7 @@ public final class OmUtils {
     case ModifyTenant:
     case DeleteTenant:
     case AssignUserToTenant:
-    case ModifyTenantUser:
-    case DeleteTenantUser:
+    case RevokeUserAccessToTenant:
       return false;
     default:
       LOG.error("CmdType {} is not categorized as readOnly or not.", cmdType);
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
index 390bbb5..961aacb 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
@@ -77,9 +77,9 @@ public enum OMAction implements AuditAction {
   MODIFY_TENANT,
   DELETE_TENANT,
 
+  TENANT_GET_USER_INFO,
   ASSIGN_USER_TO_TENANT,
-  MODIFY_TENANT_USER,
-  DELETE_TENANT_USER;
+  REVOKE_USER_ACCESS_TO_TENANT;
 
   @Override
   public String getAction() {
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmDBKerberosPrincipalInfo.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmDBKerberosPrincipalInfo.java
index e9beec2..a231ad4 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmDBKerberosPrincipalInfo.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmDBKerberosPrincipalInfo.java
@@ -30,6 +30,7 @@ import java.util.Set;
  * principal, but can be extended to store more fields later.
  */
 public final class OmDBKerberosPrincipalInfo {
+
   /**
    * A set of accessIds.
    */
@@ -48,6 +49,10 @@ public final class OmDBKerberosPrincipalInfo {
         Arrays.asList(serialized.split(SERIALIZATION_SPLIT_KEY)));
   }
 
+  public Set<String> getAccessIds() {
+    return accessIds;
+  }
+
   public boolean addAccessId(String accessId) {
     return accessIds.add(accessId);
   }
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/TenantUserInfoValue.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/TenantUserInfoValue.java
new file mode 100644
index 0000000..a0ac396
--- /dev/null
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/TenantUserInfoValue.java
@@ -0,0 +1,87 @@
+/*
+ * 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.helpers;
+
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.TenantAccessIdInfo;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.TenantUserInfo;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Utility class to handle protobuf message TenantUserInfo conversion.
+ */
+public class TenantUserInfoValue {
+
+  // Usually this is the Kerberos principal of a user.
+  private final String userPrincipal;
+
+  // A list of TenantAccessIdInfo from protobuf.
+  private final List<TenantAccessIdInfo> accessIdInfoList;
+
+  public String getUserPrincipal() {
+    return userPrincipal;
+  }
+
+  public List<TenantAccessIdInfo> getAccessIdInfoList() {
+    return accessIdInfoList;
+  }
+
+  public TenantUserInfoValue(String kerberosID,
+      List<TenantAccessIdInfo> accessIdInfoList) {
+    this.userPrincipal = kerberosID;
+    this.accessIdInfoList = accessIdInfoList;
+  }
+
+  public static TenantUserInfoValue fromProtobuf(
+      TenantUserInfo tenantUserInfo) {
+    return new TenantUserInfoValue(tenantUserInfo.getUserPrincipal(),
+        tenantUserInfo.getAccessIdInfoList());
+  }
+
+  public TenantUserInfo getProtobuf() {
+    final TenantUserInfo.Builder builder = TenantUserInfo.newBuilder();
+    builder.setUserPrincipal(this.userPrincipal);
+    accessIdInfoList.forEach(builder::addAccessIdInfo);
+    return builder.build();
+  }
+
+  @Override
+  public String toString() {
+    return "userPrincipal=" + userPrincipal +
+        "\naccessIdInfoList=" + accessIdInfoList;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    TenantUserInfoValue that = (TenantUserInfoValue) o;
+    return userPrincipal.equals(that.userPrincipal) &&
+        accessIdInfoList.equals(that.accessIdInfoList);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(userPrincipal, accessIdInfoList);
+  }
+}
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessAuthorizerDummyPlugin.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessAuthorizerDummyPlugin.java
new file mode 100644
index 0000000..8ab532d
--- /dev/null
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessAuthorizerDummyPlugin.java
@@ -0,0 +1,131 @@
+/*
+ * 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.multitenant;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.security.acl.IOzoneObj;
+import org.apache.hadoop.ozone.security.acl.RequestContext;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Dummy implementation of MultiTenantAccessAuthorizer when some parts of
+ * testing don't need to deal with Ranger.
+ */
+public class MultiTenantAccessAuthorizerDummyPlugin implements
+    MultiTenantAccessAuthorizer {
+
+  @Override
+  public void init(Configuration configuration) throws IOException {
+  }
+
+  @Override
+  public void shutdown() throws Exception {
+  }
+
+  @Override
+  public void grantAccess(BucketNameSpace bucketNameSpace,
+                          OzoneMultiTenantPrincipal user, ACLType aclType) {
+  }
+
+  @Override
+  public void revokeAccess(BucketNameSpace bucketNameSpace,
+                           OzoneMultiTenantPrincipal user, ACLType aclType) {
+  }
+
+  @Override
+  public void grantAccess(AccountNameSpace accountNameSpace,
+                          OzoneMultiTenantPrincipal user, ACLType aclType) {
+  }
+
+  @Override
+  public void revokeAccess(AccountNameSpace accountNameSpace,
+                           OzoneMultiTenantPrincipal user, ACLType aclType) {
+  }
+
+  public List<Pair<BucketNameSpace, ACLType>>
+      getAllBucketNameSpaceAccesses(OzoneMultiTenantPrincipal user) {
+    return null;
+  }
+
+  @Override
+  public boolean checkAccess(BucketNameSpace bucketNameSpace,
+                             OzoneMultiTenantPrincipal user) {
+    return true;
+  }
+
+  @Override
+  public boolean checkAccess(AccountNameSpace accountNameSpace,
+                             OzoneMultiTenantPrincipal user) {
+    return true;
+  }
+
+  @Override
+  public boolean checkAccess(IOzoneObj ozoneObject, RequestContext context)
+      throws OMException {
+    return true;
+  }
+
+  @Override
+  public String getGroupId(OzoneMultiTenantPrincipal principal)
+      throws Exception {
+    return "dummyGroupId";
+  }
+
+  @Override
+  public String getUserId(OzoneMultiTenantPrincipal principal)
+      throws Exception {
+    return "dummyUserId";
+  }
+
+  public String createUser(OzoneMultiTenantPrincipal principal,
+                           List<String> groupIDs)
+      throws Exception {
+    return "dummyCreateUser";
+  }
+
+  public String createGroup(OzoneMultiTenantPrincipal group) throws Exception {
+    return "dummyCreateGroup";
+  }
+
+  public String createAccessPolicy(AccessPolicy policy) throws Exception {
+    return "dummyCreateAccessPolicy";
+  }
+
+  public AccessPolicy getAccessPolicyByName(String policyName)
+      throws Exception {
+    return new RangerAccessPolicy(policyName);
+  }
+
+  public void deleteUser(String userId) throws IOException {
+  }
+
+  public void deleteGroup(String groupId) throws IOException {
+  }
+
+  @Override
+  public void deletePolicybyName(String policyName) throws Exception {
+  }
+
+  public void deletePolicybyId(String policyId) throws IOException {
+  }
+
+}
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
index db1e373..ceae46b 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
@@ -47,6 +47,7 @@ import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfo;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfoEx;
+import org.apache.hadoop.ozone.om.helpers.TenantUserInfoValue;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OzoneAclInfo;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.PrepareStatusResponse;
@@ -474,7 +475,14 @@ public interface OzoneManagerProtocol
   S3SecretValue assignUserToTenant(String username, String tenantName,
       String accessId) throws IOException;
 
-  // TODO: modify, delete
+  /**
+   * Get tenant info for a user.
+   * @param userPrincipal Kerberos principal of a user.
+   * @return TenantUserInfo
+   * @throws IOException
+   */
+  TenantUserInfoValue tenantGetUserInfo(String userPrincipal)
+      throws IOException;
 
   /**
    * OzoneFS api to get file status for an entry.
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
index d21e532..2b82c4b 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
@@ -56,6 +56,7 @@ import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfo;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfoEx;
+import org.apache.hadoop.ozone.om.helpers.TenantUserInfoValue;
 import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AddAclRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AddAclResponse;
@@ -920,8 +921,25 @@ public final class 
OzoneManagerProtocolClientSideTranslatorPB
 
     return S3SecretValue.fromProtobuf(resp.getS3Secret());
   }
-  // TODO: Add a variant that uses OmTenantUserArgs
-  // TODO: modify, delete
+  // TODO: Add a variant that uses OmTenantUserArgs?
+
+  @Override
+  public TenantUserInfoValue tenantGetUserInfo(String userPrincipal)
+      throws IOException {
+
+    final TenantGetUserInfoRequest request =
+        TenantGetUserInfoRequest.newBuilder()
+            .setUserPrincipal(userPrincipal)
+            .build();
+    final OMRequest omRequest = createOMRequest(Type.TenantGetUserInfo)
+        .setTenantGetUserInfoRequest(request)
+        .build();
+    final OMResponse omResponse = submitRequest(omRequest);
+    final TenantGetUserInfoResponse resp = handleError(omResponse)
+        .getTenantGetUserInfoResponse();
+
+    return TenantUserInfoValue.fromProtobuf(resp.getTenantUserInfo());
+  }
 
   /**
    * Return the proxy object underlying this protocol translator.
diff --git 
a/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-tenant.robot 
b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-tenant.robot
new file mode 100644
index 0000000..93ab1f9
--- /dev/null
+++ b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-tenant.robot
@@ -0,0 +1,51 @@
+# 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.
+
+*** Settings ***
+Documentation       Smoke test for ozone secure tenant commands.
+Library             OperatingSystem
+Library             String
+Library             BuiltIn
+Resource            ../commonlib.robot
+Test Timeout        5 minutes
+
+*** Variables ***
+${RANGER_ENDPOINT_URL}  https://ranger:6182
+
+*** Keywords ***
+Init Ranger MockServer
+    ${output} =         Execute          curl -k ${RANGER_ENDPOINT_URL}
+                        Should contain   ${output}         {}
+
+*** Test Cases ***
+Secure Tenant Create Tenant Success
+#    Run Keyword   Kinit test user     testuser     testuser.keytab
+    Run Keyword         Init Ranger MockServer
+    ${output} =         Execute          ozone tenant create finance
+                        Should contain   ${output}         Created tenant 
'finance'
+
+Secure Tenant Assign User Success
+    ${output} =         Execute          ozone tenant user assign 
[email protected] --tenant=finance
+                        Should contain   ${output}         Assigned 
'[email protected]' to 'finance'
+
+Secure Tenant GetUserInfo Success
+    ${output} =         Execute          ozone tenant user info 
[email protected]
+                        Should contain   ${output}         Tenant 'finance' 
with accessId '[email protected]'
+
+Secure Tenant Assign User Failure
+    ${rc}  ${result} =  Run And Return Rc And Output  ozone tenant user assign 
[email protected] --tenant=nonexistenttenant
+#    Should Be True    ${rc} > 0
+                        Should contain   ${result}         tenant 
'nonexistenttenant' doesn't exist
+
diff --git a/hadoop-ozone/dist/src/shell/ozone/ozone 
b/hadoop-ozone/dist/src/shell/ozone/ozone
index 777de10..a487726 100755
--- a/hadoop-ozone/dist/src/shell/ozone/ozone
+++ b/hadoop-ozone/dist/src/shell/ozone/ozone
@@ -53,6 +53,7 @@ function ozone_usage
   ozone_add_subcommand "recon" daemon "run the Recon service"
   ozone_add_subcommand "sh" client "command line interface for object store 
operations"
   ozone_add_subcommand "s3" client "command line interface for s3 related 
operations"
+  ozone_add_subcommand "tenant" client "command line interface for 
multi-tenant related operations"
   ozone_add_subcommand "insight" client "tool to get runtime operation 
information"
   ozone_add_subcommand "version" client "print the version"
   ozone_add_subcommand "dtutil" client "operations related to delegation 
tokens"
@@ -186,6 +187,10 @@ function ozonecmd_case
       OZONE_CLASSNAME='org.apache.hadoop.ozone.s3.Gateway'
       OZONE_RUN_ARTIFACT_NAME="ozone-s3gateway"
     ;;
+    tenant)
+      OZONE_CLASSNAME=org.apache.hadoop.ozone.shell.tenant.TenantShell
+      OZONE_RUN_ARTIFACT_NAME="ozone-tools"
+    ;;
     csi)
       OZONE_SUBCMD_SUPPORTDAEMONIZATION="true"
       OZONE_CLASSNAME='org.apache.hadoop.ozone.csi.CsiServer'
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
index 213b6c7..a78bd38 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
@@ -859,21 +859,4 @@ public class TestOzoneShellHA {
     objectStore.deleteVolume("vol4");
   }
 
-  /**
-   * Test ozone tenant commands.
-   */
-  @Test
-  public void testOzoneTenant() {
-    // TODO: tenant subcommand will be moved from s3 to admin later.
-
-    // Test create tenant
-    execute(s3Shell, new String[] {
-        "tenant", "create", "finance",
-        "--om-service-id=" + omServiceId});
-
-    // Test assign user
-    execute(s3Shell, new String[] {
-        "user", "assign", "[email protected]", "--tenant=finance",
-        "--om-service-id=" + omServiceId});
-  }
 }
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneTenantShell.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneTenantShell.java
new file mode 100644
index 0000000..8fd32ff
--- /dev/null
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneTenantShell.java
@@ -0,0 +1,383 @@
+/**
+ * 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.shell;
+
+import com.google.common.base.Strings;
+import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.hdds.cli.GenericCli;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.io.retry.RetryInvocationHandler;
+import org.apache.hadoop.ozone.MiniOzoneCluster;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.ha.ConfUtils;
+import org.apache.hadoop.ozone.om.OMConfigKeys;
+import org.apache.hadoop.ozone.om.OMMultiTenantManagerImpl;
+import 
org.apache.hadoop.ozone.om.request.s3.tenant.OMAssignUserToTenantRequest;
+import org.apache.hadoop.ozone.om.request.s3.tenant.OMTenantCreateRequest;
+import org.apache.hadoop.ozone.shell.tenant.TenantShell;
+import org.apache.ozone.test.GenericTestUtils;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.event.Level;
+import picocli.CommandLine;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
+
+/**
+ * Integration test for Ozone tenant shell command. HA enabled.
+ */
+public class TestOzoneTenantShell {
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(TestOzoneTenantShell.class);
+
+  static {
+    System.setProperty("log4j.configurationFile", "auditlog.properties");
+    System.setProperty("log4j2.contextSelector",
+        "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
+  }
+
+  private static final String DEFAULT_ENCODING = UTF_8.name();
+
+  /**
+   * Set the timeout for every test.
+   */
+  @Rule
+  public Timeout testTimeout = Timeout.seconds(300);
+
+  private static File baseDir;
+  private static File testFile;
+  private static final File AUDIT_LOG_FILE = new File("audit.log");
+
+  private static OzoneConfiguration conf = null;
+  private static MiniOzoneCluster cluster = null;
+  private static TenantShell tenantShell = null;
+
+  private final ByteArrayOutputStream out = new ByteArrayOutputStream();
+  private final ByteArrayOutputStream err = new ByteArrayOutputStream();
+  private static final PrintStream OLD_OUT = System.out;
+  private static final PrintStream OLD_ERR = System.err;
+
+  private static String omServiceId;
+  private static String clusterId;
+  private static String scmId;
+  private static int numOfOMs;
+
+  /**
+   * Create a MiniOzoneCluster for testing with using distributed Ozone
+   * handler type.
+   *
+   * @throws Exception
+   */
+  @BeforeClass
+  public static void init() throws Exception {
+    // Remove audit log output if it exists
+    if (AUDIT_LOG_FILE.exists()) {
+      AUDIT_LOG_FILE.delete();
+    }
+
+    conf = new OzoneConfiguration();
+
+    conf.setBoolean(
+        OMMultiTenantManagerImpl.OZONE_OM_TENANT_DEV_SKIP_RANGER, true);
+
+    String path = GenericTestUtils.getTempPath(
+        TestOzoneTenantShell.class.getSimpleName());
+    baseDir = new File(path);
+    baseDir.mkdirs();
+
+    testFile = new File(path + OzoneConsts.OZONE_URI_DELIMITER + "testFile");
+    testFile.getParentFile().mkdirs();
+    testFile.createNewFile();
+
+    tenantShell = new TenantShell();
+
+    // Init cluster
+    omServiceId = "om-service-test1";
+    numOfOMs = 3;
+    clusterId = UUID.randomUUID().toString();
+    scmId = UUID.randomUUID().toString();
+    cluster = MiniOzoneCluster.newOMHABuilder(conf)
+        .setClusterId(clusterId)
+        .setScmId(scmId)
+        .setOMServiceId(omServiceId)
+        .setNumOfOzoneManagers(numOfOMs)
+        .build();
+    cluster.waitForClusterToBeReady();
+//    MiniOzoneHAClusterImpl impl = (MiniOzoneOMHAClusterImpl) cluster;
+  }
+
+  /**
+   * shutdown MiniOzoneCluster.
+   */
+  @AfterClass
+  public static void shutdown() {
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+
+    if (baseDir != null) {
+      FileUtil.fullyDelete(baseDir, true);
+    }
+  }
+
+  @Before
+  public void setup() throws UnsupportedEncodingException {
+    System.setOut(new PrintStream(out, false, UTF_8.name()));
+    System.setErr(new PrintStream(err, false, UTF_8.name()));
+  }
+
+  @After
+  public void reset() {
+    // reset stream after each unit test
+    out.reset();
+    err.reset();
+
+    // restore system streams
+    System.setOut(OLD_OUT);
+    System.setErr(OLD_ERR);
+  }
+
+  private void execute(GenericCli shell, String[] args) {
+    LOG.info("Executing shell command with args {}", Arrays.asList(args));
+    CommandLine cmd = shell.getCmd();
+
+    CommandLine.IExecutionExceptionHandler exceptionHandler =
+        (ex, commandLine, parseResult) -> {
+          commandLine.getErr().println(ex.getMessage());
+          return commandLine.getCommandSpec().exitCodeOnExecutionException();
+        };
+
+    // Since there is no elegant way to pass Ozone config to the shell,
+    // the idea is to use 'set' to place those OM HA configs.
+    String[] argsWithHAConf = getHASetConfStrings(args);
+
+    cmd.setExecutionExceptionHandler(exceptionHandler);
+    cmd.execute(argsWithHAConf);
+  }
+
+  /**
+   * Helper that appends HA service id to args.
+   */
+  private void executeHA(GenericCli shell, String[] args) {
+    final String[] newArgs = new String[args.length + 1];
+    System.arraycopy(args, 0, newArgs, 0, args.length);
+    newArgs[args.length] = "--om-service-id=" + omServiceId;
+    execute(shell, newArgs);
+  }
+
+  /**
+   * Execute command, assert exception message and returns true if error
+   * was thrown.
+   */
+  private void executeWithError(OzoneShell shell, String[] args,
+      String expectedError) {
+    if (Strings.isNullOrEmpty(expectedError)) {
+      execute(shell, args);
+    } else {
+      try {
+        execute(shell, args);
+        fail("Exception is expected from command execution " + Arrays
+            .asList(args));
+      } catch (Exception ex) {
+        if (!Strings.isNullOrEmpty(expectedError)) {
+          Throwable exceptionToCheck = ex;
+          if (exceptionToCheck.getCause() != null) {
+            exceptionToCheck = exceptionToCheck.getCause();
+          }
+          Assert.assertTrue(
+              String.format(
+                  "Error of OzoneShell code doesn't contain the " +
+                      "exception [%s] in [%s]",
+                  expectedError, exceptionToCheck.getMessage()),
+              exceptionToCheck.getMessage().contains(expectedError));
+        }
+      }
+    }
+  }
+
+  private String getSetConfStringFromConf(String key) {
+    return String.format("--set=%s=%s", key, conf.get(key));
+  }
+
+  private String generateSetConfString(String key, String value) {
+    return String.format("--set=%s=%s", key, value);
+  }
+
+  /**
+   * Helper function to get a String array to be fed into OzoneShell.
+   * @param numOfArgs Additional number of arguments after the HA conf string,
+   *                  this translates into the number of empty array elements
+   *                  after the HA conf string.
+   * @return String array.
+   */
+  private String[] getHASetConfStrings(int numOfArgs) {
+    assert(numOfArgs >= 0);
+    String[] res = new String[1 + 1 + numOfOMs + numOfArgs];
+    final int indexOmServiceIds = 0;
+    final int indexOmNodes = 1;
+    final int indexOmAddressStart = 2;
+
+    res[indexOmServiceIds] = getSetConfStringFromConf(
+        OMConfigKeys.OZONE_OM_SERVICE_IDS_KEY);
+
+    String omNodesKey = ConfUtils.addKeySuffixes(
+        OMConfigKeys.OZONE_OM_NODES_KEY, omServiceId);
+    String omNodesVal = conf.get(omNodesKey);
+    res[indexOmNodes] = generateSetConfString(omNodesKey, omNodesVal);
+
+    String[] omNodesArr = omNodesVal.split(",");
+    // Sanity check
+    assert(omNodesArr.length == numOfOMs);
+    for (int i = 0; i < numOfOMs; i++) {
+      res[indexOmAddressStart + i] =
+          getSetConfStringFromConf(ConfUtils.addKeySuffixes(
+              OMConfigKeys.OZONE_OM_ADDRESS_KEY, omServiceId, omNodesArr[i]));
+    }
+
+    return res;
+  }
+
+  /**
+   * Helper function to create a new set of arguments that contains HA configs.
+   * @param existingArgs Existing arguments to be fed into OzoneShell command.
+   * @return String array.
+   */
+  private String[] getHASetConfStrings(String[] existingArgs) {
+    // Get a String array populated with HA configs first
+    String[] res = getHASetConfStrings(existingArgs.length);
+
+    int indexCopyStart = res.length - existingArgs.length;
+    // Then copy the existing args to the returned String array
+    System.arraycopy(existingArgs, 0, res, indexCopyStart,
+        existingArgs.length);
+    return res;
+  }
+
+  /**
+   * Helper function that checks command output AND clears it.
+   */
+  private void checkOutput(ByteArrayOutputStream stream, String stringToMatch,
+      boolean exactMatch) throws IOException {
+    stream.flush();
+    final String str = stream.toString(DEFAULT_ENCODING);
+    checkOutput(str, stringToMatch, exactMatch);
+    stream.reset();
+  }
+
+  private void checkOutput(String str, String stringToMatch,
+      boolean exactMatch) {
+    if (exactMatch) {
+      Assert.assertEquals(stringToMatch, str);
+    } else {
+      Assert.assertTrue(str.contains(stringToMatch));
+    }
+  }
+
+  /**
+   * Test tenant create, assign user and get user info.
+   */
+  @Test
+  public void testOzoneTenantCreateAssignInfo() throws IOException {
+
+    // Suppress OMNotLeaderException in the log
+    GenericTestUtils.setLogLevel(RetryInvocationHandler.LOG, Level.WARN);
+
+    GenericTestUtils.setLogLevel(OMTenantCreateRequest.LOG, Level.DEBUG);
+    GenericTestUtils.setLogLevel(OMAssignUserToTenantRequest.LOG, Level.DEBUG);
+
+    List<String> lines = FileUtils.readLines(AUDIT_LOG_FILE, (String)null);
+    Assert.assertEquals(0, lines.size());
+
+    // Create tenants
+    // Equivalent to `ozone tenant create finance`
+    executeHA(tenantShell, new String[] {"create", "finance"});
+    checkOutput(out, "Created tenant 'finance'.\n", true);
+    checkOutput(err, "", true);
+
+    lines = FileUtils.readLines(AUDIT_LOG_FILE, (String)null);
+    checkOutput(lines.get(lines.size() - 1), "ret=SUCCESS", false);
+
+    // Creating the tenant with the same name again should result in failure
+    executeHA(tenantShell, new String[] {"create", "finance"});
+    checkOutput(out, "", true);
+    checkOutput(err, "Failed to create tenant 'finance':"
+        + " Tenant already exists\n", true);
+
+    executeHA(tenantShell, new String[] {"create", "research"});
+    checkOutput(out, "Created tenant 'research'.\n", true);
+    checkOutput(err, "", true);
+
+    executeHA(tenantShell, new String[] {"create", "dev"});
+    checkOutput(out, "Created tenant 'dev'.\n", true);
+    checkOutput(err, "", true);
+
+    // Assign user
+    // Equivalent to `ozone tenant user assign [email protected] 
--tenant=finance`
+    executeHA(tenantShell, new String[] {
+        "user", "assign", "[email protected]", "--tenant=finance"});
+    checkOutput(out, "export AWS_ACCESS_KEY_ID='[email protected]'\n"
+        + "export AWS_SECRET_ACCESS_KEY='", false);
+    checkOutput(err, "Assigned '[email protected]' to 'finance' with accessId"
+        + " '[email protected]'.\n", true);
+
+    executeHA(tenantShell, new String[] {
+        "user", "assign", "[email protected]", "--tenant=research"});
+    checkOutput(out, "export AWS_ACCESS_KEY_ID='[email protected]'\n"
+        + "export AWS_SECRET_ACCESS_KEY='", false);
+    checkOutput(err, "Assigned '[email protected]' to 'research' with accessId"
+        + " '[email protected]'.\n", true);
+
+    executeHA(tenantShell, new String[] {
+        "user", "assign", "[email protected]", "--tenant=dev"});
+    checkOutput(out, "export AWS_ACCESS_KEY_ID='[email protected]'\n"
+        + "export AWS_SECRET_ACCESS_KEY='", false);
+    checkOutput(err, "Assigned '[email protected]' to 'dev' with accessId"
+        + " '[email protected]'.\n", true);
+
+    // Get user info
+    // Equivalent to `ozone tenant user info [email protected]`
+    executeHA(tenantShell, new String[] {
+        "user", "info", "[email protected]"});
+    checkOutput(out, "User '[email protected]' is assigned to:\n"
+        + "- Tenant 'finance' with accessId '[email protected]'\n"
+        + "- Tenant 'research' with accessId '[email protected]'\n"
+        + "- Tenant 'dev' with accessId '[email protected]'\n\n", true);
+    checkOutput(err, "", true);
+  }
+
+}
diff --git 
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto 
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 62fec08..916427a 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -107,9 +107,9 @@ enum Type {
   ModifyTenant = 96;
   DeleteTenant = 97;
 
-  AssignUserToTenant = 98;
-  ModifyTenantUser = 99;
-  DeleteTenantUser = 100;
+  TenantGetUserInfo = 98;
+  AssignUserToTenant = 99;
+  RevokeUserAccessToTenant = 100;
 }
 
 message OMRequest {
@@ -197,9 +197,9 @@ message OMRequest {
   optional ModifyTenantRequest              ModifyTenantRequest            = 
96;
   optional DeleteTenantRequest              DeleteTenantRequest            = 
97;
 
-  optional AssignUserToTenantRequest        AssignUserToTenantRequest      = 
98;
-  optional ModifyTenantUserRequest          ModifyTenantUserRequest        = 
99;
-  optional DeleteTenantUserRequest          DeleteTenantUserRequest        = 
100;
+  optional TenantGetUserInfoRequest         TenantGetUserInfoRequest       = 
98;
+  optional AssignUserToTenantRequest        AssignUserToTenantRequest      = 
99;
+  optional RevokeUserAccessToTenantRequest  RevokeUserAccessToTenantRequest = 
100;
 }
 
 message OMResponse {
@@ -282,11 +282,9 @@ message OMResponse {
   optional ModifyTenantResponse              ModifyTenantResponse          = 
96;
   optional DeleteTenantResponse              DeleteTenantResponse          = 
97;
 
-  optional AssignUserToTenantResponse        AssignUserToTenantResponse    = 
98;
-  // TODO: Remove ModifyTenantUserResponse since it won't be useful anymore?
-  optional ModifyTenantUserResponse          ModifyTenantUserResponse      = 
99;
-  // TODO: Rename this to UnassignUserFromTenant Response ?
-  optional DeleteTenantUserResponse          DeleteTenantUserResponse      = 
100;
+  optional TenantGetUserInfoResponse         TenantGetUserInfoResponse     = 
98;
+  optional AssignUserToTenantResponse        AssignUserToTenantResponse    = 
99;
+  optional RevokeUserAccessToTenantResponse  RevokeUserAccessToTenantResponse 
= 100;
 }
 
 enum Status {
@@ -1361,6 +1359,25 @@ message GetS3SecretResponse {
     required S3Secret s3Secret = 2;
 }
 
+message TenantUserInfo {
+  optional string userPrincipal = 1;
+  repeated TenantAccessIdInfo accessIdInfo = 2;
+}
+
+message TenantAccessIdInfo {
+  optional string accessId = 1;
+  optional string tenantName = 2;
+}
+
+message TenantGetUserInfoRequest {
+  optional string userPrincipal = 1;
+}
+
+message TenantGetUserInfoResponse {
+  optional bool success = 1;
+  optional TenantUserInfo tenantUserInfo = 2;
+}
+
 message LayoutVersion {
   required uint64 version = 1;
 }
@@ -1371,6 +1388,7 @@ message RevokeS3SecretRequest {
 
 message CreateTenantRequest {
     optional string tenantName = 1;
+    optional string tenantDefaultPolicyName = 2;
 }
 
 message ModifyTenantRequest {
@@ -1387,12 +1405,9 @@ message AssignUserToTenantRequest {
     optional string accessId = 3;
 }
 
-message ModifyTenantUserRequest {
-    optional string tenantUsername = 1;
-}
-
-message DeleteTenantUserRequest {
-    optional string tenantUsername = 1;
+message RevokeUserAccessToTenantRequest {
+    optional string accessId = 1;
+    optional string tenantName = 2;
 }
 
 message CreateTenantResponse {
@@ -1412,11 +1427,7 @@ message AssignUserToTenantResponse {
     optional S3Secret s3Secret = 2;
 }
 
-message ModifyTenantUserResponse {
-    optional bool success = 1;
-}
-
-message DeleteTenantUserResponse {
+message RevokeUserAccessToTenantResponse {
     optional bool success = 1;
 }
 
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMultiTenantManagerImpl.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMultiTenantManagerImpl.java
index 8237214..a444bb8 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMultiTenantManagerImpl.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMultiTenantManagerImpl.java
@@ -48,6 +48,7 @@ import 
org.apache.hadoop.ozone.om.multitenant.AccountNameSpace;
 import org.apache.hadoop.ozone.om.multitenant.BucketNameSpace;
 import org.apache.hadoop.ozone.om.multitenant.CephCompatibleTenantImpl;
 import org.apache.hadoop.ozone.om.multitenant.MultiTenantAccessAuthorizer;
+import 
org.apache.hadoop.ozone.om.multitenant.MultiTenantAccessAuthorizerDummyPlugin;
 import 
org.apache.hadoop.ozone.om.multitenant.MultiTenantAccessAuthorizerRangerPlugin;
 import org.apache.hadoop.ozone.om.multitenant.OzoneMultiTenantPrincipal;
 import org.apache.hadoop.ozone.om.multitenant.RangerAccessPolicy;
@@ -70,6 +71,12 @@ public class OMMultiTenantManagerImpl implements 
OMMultiTenantManager {
   private static final Logger LOG =
       LoggerFactory.getLogger(OMMultiTenantManagerImpl.class);
 
+  // TODO: Remove when proper testing infra is deployed.
+  // Internal dev flag to skip Ranger communication.
+  public static final String OZONE_OM_TENANT_DEV_SKIP_RANGER =
+      "ozone.om.tenant.dev.skip.ranger";
+  private final boolean devSkipRanger;
+
   private MultiTenantAccessAuthorizer authorizer;
   private OMMetadataManager omMetadataManager;
   private OzoneConfiguration conf;
@@ -129,12 +136,18 @@ public class OMMultiTenantManagerImpl implements 
OMMultiTenantManager {
 
     controlPathLock = new ReentrantReadWriteLock();
     omMetadataManager = mgr;
+
+    devSkipRanger = conf.getBoolean(OZONE_OM_TENANT_DEV_SKIP_RANGER, false);
     start(conf);
   }
 
   @Override
   public void start(OzoneConfiguration configuration) throws IOException {
-    authorizer = new MultiTenantAccessAuthorizerRangerPlugin();
+    if (devSkipRanger) {
+      authorizer = new MultiTenantAccessAuthorizerDummyPlugin();
+    } else {
+      authorizer = new MultiTenantAccessAuthorizerRangerPlugin();
+    }
     authorizer.init(configuration);
   }
 
@@ -247,6 +260,7 @@ public class OMMultiTenantManagerImpl implements 
OMMultiTenantManager {
 
   @Override
   public Tenant getTenantInfo(String tenantID) throws IOException {
+    // TODO:Should read from DB. Ditch the in-memory maps.
     if (!inMemoryTenantNameToTenantInfoMap.containsKey(tenantID)) {
       return null;
     }
@@ -266,6 +280,8 @@ public class OMMultiTenantManagerImpl implements 
OMMultiTenantManager {
 
   @Override
   public void destroyTenant(Tenant tenant) throws Exception {
+    // TODO: Make sure this is idempotent. This can be called by ALL 3 OMs
+    //  in the case of a createTenant checkAcl failure for instance.
     try {
       controlPathLock.writeLock().lock();
       for (AccessPolicy policy : tenant.getTenantAccessPolicies()) {
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
index 69ef912..3e0fe6c 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
@@ -141,7 +141,7 @@ public class OmMetadataManagerImpl implements 
OMMetadataManager {
    * |----------------------------------------------------------------------|
    * | principalToAccessIdsTable | Principal -> OmDBKerberosPrincipalInfo   |
    * |----------------------------------------------------------------------|
-   * | tenantStateTable          | accessId -> OmDBTenantInfo               |
+   * | tenantStateTable          | tenant name -> OmDBTenantInfo            |
    * |----------------------------------------------------------------------|
    * | tenantGroupTable          | accessId -> [tenant group A, B, ...]     |
    * |----------------------------------------------------------------------|
@@ -563,16 +563,17 @@ public class OmMetadataManagerImpl implements 
OMMetadataManager {
     checkTableStatus(metaTable, META_TABLE);
 
     // tenant user name -> tenant name string
+    // TODO: Unused. Remove.
     tenantUserTable = this.store.getTable(TENANT_USER_TABLE,
         String.class, String.class);
     checkTableStatus(tenantUserTable, TENANT_USER_TABLE);
 
-    // tenantId -> OmDBAccessIdInfo (tenantId, secret, Kerberos principal)
+    // accessId -> OmDBAccessIdInfo (tenantId, secret, Kerberos principal)
     tenantAccessIdTable = this.store.getTable(TENANT_ACCESS_ID_TABLE,
         String.class, OmDBAccessIdInfo.class);
     checkTableStatus(tenantAccessIdTable, TENANT_ACCESS_ID_TABLE);
 
-    // Kerberos principal -> OmDBKerberosPrincipalInfo (A list of accessIds)
+    // User principal -> OmDBKerberosPrincipalInfo (A list of accessIds)
     principalToAccessIdsTable = this.store.getTable(
         PRINCIPAL_TO_ACCESS_IDS_TABLE,
         String.class, OmDBKerberosPrincipalInfo.class);
@@ -583,13 +584,12 @@ public class OmMetadataManagerImpl implements 
OMMetadataManager {
         String.class, OmDBTenantInfo.class);
     checkTableStatus(tenantStateTable, TENANT_STATE_TABLE);
 
-    // tenant user name -> list of tenant groups the user belongs to
+    // accessId -> list of tenant groups the user belongs to
     tenantGroupTable = this.store.getTable(TENANT_GROUP_TABLE,
         String.class, String.class /* TODO: Use custom list */);
     checkTableStatus(tenantGroupTable, TENANT_GROUP_TABLE);
 
-    // tenant user name -> list of roles in a tenant. e.g. admin for "finance"
-    // TODO: Placeholder. Unused in the prototype.
+    // accessId -> list of roles in a tenant. e.g. admin for "finance"
     tenantRoleTable = this.store.getTable(TENANT_ROLE_TABLE,
         String.class, String.class /* TODO: Use custom list */);
     checkTableStatus(tenantRoleTable, TENANT_ROLE_TABLE);
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index e7d8931..0d1bb0d 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -117,6 +117,8 @@ import org.apache.hadoop.ozone.om.helpers.OMNodeDetails;
 import org.apache.hadoop.ozone.om.helpers.DBUpdates;
 import org.apache.hadoop.ozone.om.helpers.OmBucketArgs;
 import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
+import org.apache.hadoop.ozone.om.helpers.OmDBAccessIdInfo;
+import org.apache.hadoop.ozone.om.helpers.OmDBKerberosPrincipalInfo;
 import org.apache.hadoop.ozone.om.helpers.OmDeleteKeys;
 import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
 import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
@@ -135,6 +137,7 @@ import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfo;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfoEx;
+import org.apache.hadoop.ozone.om.helpers.TenantUserInfoValue;
 import org.apache.hadoop.ozone.om.protocol.OMInterServiceProtocol;
 import 
org.apache.hadoop.ozone.om.protocolPB.OMInterServiceProtocolClientSideImpl;
 import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
@@ -155,6 +158,7 @@ import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.KeyArgs
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRoleInfo;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OzoneAclInfo;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ServicePort;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.TenantAccessIdInfo;
 import org.apache.hadoop.ozone.protocolPB.OMInterServiceProtocolServerSideImpl;
 import 
org.apache.hadoop.ozone.storage.proto.OzoneManagerStorageProtos.PersistedUserVolumeInfo;
 import 
org.apache.hadoop.ozone.protocolPB.OzoneManagerProtocolServerSideTranslatorPB;
@@ -3084,7 +3088,55 @@ public final class OzoneManager extends 
ServiceRuntimeInfoImpl
         "non-Ratis assignUserToTenant() is not implemented");
   }
 
-  // TODO: modify, delete
+  /**
+   * Tenant get user info.
+   */
+  public TenantUserInfoValue tenantGetUserInfo(String userPrincipal)
+      throws IOException {
+
+    if (StringUtils.isEmpty(userPrincipal)) {
+      return null;
+    }
+
+    final List<TenantAccessIdInfo> accessIdInfoList = new ArrayList<>();
+
+    // Retrieve a list of accessIds associates to this user principal
+    final OmDBKerberosPrincipalInfo kerberosPrincipalInfo =
+        metadataManager.getPrincipalToAccessIdsTable().get(userPrincipal);
+    if (kerberosPrincipalInfo == null) {
+      return null;
+    }
+    final Set<String> accessIds = kerberosPrincipalInfo.getAccessIds();
+
+    final Map<String, String> auditMap = new LinkedHashMap<>();
+    auditMap.put(OzoneConsts.TENANT, userPrincipal);
+
+    accessIds.forEach(accessId -> {
+      try {
+        // Use get() intentionally, which throws if entry doesn't exist in 
table
+        final OmDBAccessIdInfo accessIdInfo =
+            metadataManager.getTenantAccessIdTable().get(accessId);
+        // Sanity check
+        assert(accessIdInfo.getKerberosPrincipal().equals(userPrincipal));
+        // Build TenantAccessIdInfo instances from accessId and tenantName
+        final String tenantName = accessIdInfo.getTenantId();
+        accessIdInfoList.add(TenantAccessIdInfo.newBuilder()
+            .setAccessId(accessId)
+            .setTenantName(tenantName)
+            .build());
+      } catch (IOException e) {
+        LOG.error("Found potential DB consistency issue! "
+            + "accessId '" + "' is supposed to exist in TenantAccessIdTable.");
+        AUDIT.logWriteFailure(buildAuditMessageForFailure(
+            OMAction.TENANT_GET_USER_INFO, auditMap, e));
+      }
+    });
+
+    AUDIT.logReadSuccess(buildAuditMessageForSuccess(
+        OMAction.TENANT_GET_USER_INFO, auditMap));
+
+    return new TenantUserInfoValue(userPrincipal, accessIdInfoList);
+  }
 
   @Override
   /**
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
index 0a8c3dd..ec33456 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
@@ -83,8 +83,7 @@ import 
org.apache.hadoop.ozone.om.request.s3.tenant.OMTenantCreateRequest;
 import org.apache.hadoop.ozone.om.request.s3.tenant.OMTenantDeleteRequest;
 import org.apache.hadoop.ozone.om.request.s3.tenant.OMTenantModifyRequest;
 import 
org.apache.hadoop.ozone.om.request.s3.tenant.OMAssignUserToTenantRequest;
-import org.apache.hadoop.ozone.om.request.s3.tenant.OMTenantUserDeleteRequest;
-import org.apache.hadoop.ozone.om.request.s3.tenant.OMTenantUserModifyRequest;
+import 
org.apache.hadoop.ozone.om.request.s3.tenant.OMRevokeUserAccessToTenantRequest;
 import 
org.apache.hadoop.ozone.om.request.security.OMCancelDelegationTokenRequest;
 import org.apache.hadoop.ozone.om.request.security.OMGetDelegationTokenRequest;
 import 
org.apache.hadoop.ozone.om.request.security.OMRenewDelegationTokenRequest;
@@ -266,10 +265,8 @@ public final class OzoneManagerRatisUtils {
       return new OMTenantDeleteRequest(omRequest);
     case AssignUserToTenant:
       return new OMAssignUserToTenantRequest(omRequest);
-    case ModifyTenantUser:
-      return new OMTenantUserModifyRequest(omRequest);
-    case DeleteTenantUser:
-      return new OMTenantUserDeleteRequest(omRequest);
+    case RevokeUserAccessToTenant:
+      return new OMRevokeUserAccessToTenantRequest(omRequest);
     default:
       throw new IllegalStateException("Unrecognized write command " +
           "type request" + cmdType);
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMAssignUserToTenantRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMAssignUserToTenantRequest.java
index 31a9b8f..5f2f489 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMAssignUserToTenantRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMAssignUserToTenantRequest.java
@@ -33,10 +33,9 @@ import 
org.apache.hadoop.ozone.om.helpers.OmDBKerberosPrincipalInfo;
 import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
 import org.apache.hadoop.ozone.om.multitenant.OzoneMultiTenantPrincipal;
 import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper;
+import org.apache.hadoop.ozone.om.request.OMClientRequest;
 import org.apache.hadoop.ozone.om.request.util.OmResponseUtil;
-import org.apache.hadoop.ozone.om.request.volume.OMVolumeRequest;
 import org.apache.hadoop.ozone.om.response.OMClientResponse;
-import org.apache.hadoop.ozone.om.response.s3.tenant.OMTenantCreateResponse;
 import 
org.apache.hadoop.ozone.om.response.s3.tenant.OMAssignUserToTenantResponse;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AssignUserToTenantRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AssignUserToTenantResponse;
@@ -96,8 +95,8 @@ import static 
org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.VOLUME_L
 /**
  * Handles OMAssignUserToTenantRequest.
  */
-public class OMAssignUserToTenantRequest extends OMVolumeRequest {
-  private static final Logger LOG =
+public class OMAssignUserToTenantRequest extends OMClientRequest {
+  public static final Logger LOG =
       LoggerFactory.getLogger(OMAssignUserToTenantRequest.class);
 
   public OMAssignUserToTenantRequest(OMRequest omRequest) {
@@ -233,9 +232,10 @@ public class OMAssignUserToTenantRequest extends 
OMVolumeRequest {
       // Inform MultiTenantManager of user assignment so it could
       //  initialize some policies in Ranger.
       // TODO: Is userId from MultiTenantManager still useful?
+      // TODO: Move this to preExecute as well.
       userId = ozoneManager.getMultiTenantManager()
           .assignUserToTenant(tenantName, accessId);
-      LOG.info("userId = {}", userId);
+      LOG.debug("userId = {}", userId);
 
       // Add to tenantAccessIdTable
       final OmDBAccessIdInfo omDBAccessIdInfo = new OmDBAccessIdInfo.Builder()
@@ -291,7 +291,7 @@ public class OMAssignUserToTenantRequest extends 
OMVolumeRequest {
       // Set response success flag to false
       omResponse.setAssignUserToTenantResponse(
           AssignUserToTenantResponse.newBuilder().setSuccess(false).build());
-      omClientResponse = new OMTenantCreateResponse(
+      omClientResponse = new OMAssignUserToTenantResponse(
           createErrorOMResponse(omResponse, ex));
     } finally {
       if (omClientResponse != null) {
@@ -313,11 +313,11 @@ public class OMAssignUserToTenantRequest extends 
OMVolumeRequest {
             getOmRequest().getUserInfo()));
 
     if (exception == null) {
-      LOG.info("Assigned user '{}' to tenant '{}' under accessId '{}'",
+      LOG.info("Assigned user '{}' to tenant '{}' with accessId '{}'",
           principal, tenantName, accessId);
       // TODO: omMetrics.incNumTenantUsers()
     } else {
-      LOG.error("Failed to assign '{}' to tenant '{}' under accessId '{}': {}",
+      LOG.error("Failed to assign '{}' to tenant '{}' with accessId '{}': {}",
           principal, tenantName, accessId, exception.getMessage());
       // TODO: Check if the exception message is sufficient.
       // TODO: omMetrics.incNumTenantUserCreateFails()
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantUserDeleteRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMRevokeUserAccessToTenantRequest.java
similarity index 92%
rename from 
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantUserDeleteRequest.java
rename to 
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMRevokeUserAccessToTenantRequest.java
index f2ea962..c542304 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantUserDeleteRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMRevokeUserAccessToTenantRequest.java
@@ -29,9 +29,9 @@ import java.io.IOException;
 /**
  * Handles OMTenantUserModify request.
  */
-public class OMTenantUserDeleteRequest extends OMVolumeRequest {
+public class OMRevokeUserAccessToTenantRequest extends OMVolumeRequest {
 
-  public OMTenantUserDeleteRequest(OMRequest omRequest) {
+  public OMRevokeUserAccessToTenantRequest(OMRequest omRequest) {
     super(omRequest);
   }
 
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 86ef85f..4cd62ee 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
@@ -21,6 +21,7 @@ package org.apache.hadoop.ozone.om.request.s3.tenant;
 import com.google.common.base.Optional;
 import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
+import org.apache.hadoop.ipc.ProtobufRpcEngine;
 import org.apache.hadoop.ozone.OmUtils;
 import org.apache.hadoop.ozone.OzoneConsts;
 import org.apache.hadoop.ozone.audit.OMAction;
@@ -29,6 +30,7 @@ import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.OmDBTenantInfo;
 import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
+import org.apache.hadoop.ozone.om.multitenant.AccessPolicy;
 import org.apache.hadoop.ozone.om.multitenant.Tenant;
 import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper;
 import org.apache.hadoop.ozone.om.request.util.OmResponseUtil;
@@ -44,6 +46,7 @@ import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.VolumeI
 import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
 import org.apache.hadoop.ozone.security.acl.OzoneObj;
 import 
org.apache.hadoop.ozone.storage.proto.OzoneManagerStorageProtos.PersistedUserVolumeInfo;
+import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.util.Time;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -53,6 +56,8 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.TENANT_ALREADY_EXISTS;
+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;
 
@@ -89,9 +94,12 @@ import static 
org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.VOLUME_L
 
 /**
  * Handles OMTenantCreate request.
+ *
+ * Extends OMVolumeRequest but not OMClientRequest since tenant creation
+ *  involves volume creation.
  */
 public class OMTenantCreateRequest extends OMVolumeRequest {
-  private static final Logger LOG =
+  public static final Logger LOG =
       LoggerFactory.getLogger(OMTenantCreateRequest.class);
 
   public OMTenantCreateRequest(OMRequest omRequest) {
@@ -115,7 +123,7 @@ public class OMTenantCreateRequest extends OMVolumeRequest {
         .isExist(tenantName)) {
       LOG.debug("tenant: {} already exists", tenantName);
       throw new OMException("Tenant already exists",
-          OMException.ResultCodes.TENANT_ALREADY_EXISTS);
+          TENANT_ALREADY_EXISTS);
     }
 
     // getUserName returns:
@@ -123,7 +131,11 @@ public class OMTenantCreateRequest extends OMVolumeRequest 
{
     // - 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 String owner = getOmRequest().getUserInfo().getUserName();
+    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 volumeName = tenantName;  // TODO: Configurable
     final VolumeInfo volumeInfo = VolumeInfo.newBuilder()
         .setVolume(volumeName)
@@ -133,6 +145,8 @@ public class OMTenantCreateRequest extends OMVolumeRequest {
     // Verify volume name
     OmUtils.validateVolumeName(volumeInfo.getVolume());
 
+    // TODO: Shall we check volume existence here as well?
+
     // Generate volume modification time
     long initialTime = Time.now();
     final VolumeInfo updatedVolumeInfo = volumeInfo.toBuilder()
@@ -142,11 +156,19 @@ public class OMTenantCreateRequest extends 
OMVolumeRequest {
 
     // If we fail after pre-execute. handleRequestFailure() callback
     // would clean up any state maintained by the getMultiTenantManager.
-    ozoneManager.getMultiTenantManager().createTenant(tenantName);
+    final Tenant tenant =
+        ozoneManager.getMultiTenantManager().createTenant(tenantName);
+
+    // Get the tenant default policy, pass this along
+    final String tenantDefaultPolicies = tenant.getTenantAccessPolicies()
+        .stream().map(AccessPolicy::getPolicyID)
+        .collect(Collectors.joining(","));
 
     final OMRequest.Builder omRequestBuilder = getOmRequest().toBuilder()
         .setCreateTenantRequest(
-            CreateTenantRequest.newBuilder().setTenantName(tenantName))
+            CreateTenantRequest.newBuilder()
+                .setTenantDefaultPolicyName(tenantDefaultPolicies)
+                .setTenantName(tenantName))
         .setCreateVolumeRequest(
             CreateVolumeRequest.newBuilder().setVolumeInfo(updatedVolumeInfo))
         .setUserInfo(getUserInfo())
@@ -165,7 +187,7 @@ public class OMTenantCreateRequest extends OMVolumeRequest {
     CreateTenantRequest request = getOmRequest().getCreateTenantRequest();
 
     try {
-      Tenant tenant = ozoneManager.getMultiTenantManager()
+      final Tenant tenant = ozoneManager.getMultiTenantManager()
           .getTenantInfo(request.getTenantName());
       // Cleanup any state maintained by OMMultiTenantManager
       if (tenant != null) {
@@ -179,6 +201,7 @@ public class OMTenantCreateRequest extends OMVolumeRequest {
   }
 
   @Override
+  @SuppressWarnings("methodlength")
   public OMClientResponse validateAndUpdateCache(
       OzoneManager ozoneManager, long transactionLogIndex,
       OzoneManagerDoubleBufferHelper ozoneManagerDoubleBufferHelper) {
@@ -193,13 +216,18 @@ public class OMTenantCreateRequest extends 
OMVolumeRequest {
     OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager();
     CreateTenantRequest request = getOmRequest().getCreateTenantRequest();
     final String tenantName = request.getTenantName();
-    Tenant tenant = null;
     final VolumeInfo volumeInfo =
         getOmRequest().getCreateVolumeRequest().getVolumeInfo();
     final String volumeName = volumeInfo.getVolume();
     final String dbVolumeKey = omMetadataManager.getVolumeKey(volumeName);
     IOException exception = null;
-    LOG.info("tenant: {} create Request", tenantName);
+
+    final String tenantDefaultPolicies = request.getTenantDefaultPolicyName();
+    assert(tenantDefaultPolicies != null);
+
+    LOG.debug("Processing tenant '{}' create request", tenantName);
+    LOG.debug("tenantDefaultPolicies = {}", tenantDefaultPolicies);
+
     try {
       // Check ACL: requires volume create permission. TODO: tenant create 
perm?
       if (ozoneManager.getAclsEnabled()) {
@@ -213,14 +241,12 @@ public class OMTenantCreateRequest extends 
OMVolumeRequest {
       // Check volume existence
       if (omMetadataManager.getVolumeTable().isExist(volumeName)) {
         LOG.debug("volume: {} already exists", volumeName);
-        throw new OMException("Volume already exists",
-            OMException.ResultCodes.VOLUME_ALREADY_EXISTS);
+        throw new OMException("Volume already exists", VOLUME_ALREADY_EXISTS);
       }
       // Check tenant existence in tenantStateTable
       if (omMetadataManager.getTenantStateTable().isExist(tenantName)) {
         LOG.debug("tenant: {} already exists", tenantName);
-        throw new OMException("Tenant already exists",
-            OMException.ResultCodes.TENANT_ALREADY_EXISTS);
+        throw new OMException("Tenant already exists", TENANT_ALREADY_EXISTS);
       }
 
       // Add to tenantStateTable. Redundant assignment for clarity
@@ -237,13 +263,6 @@ public class OMTenantCreateRequest extends OMVolumeRequest 
{
           new CacheKey<>(tenantName),
           new CacheValue<>(Optional.of(omDBTenantInfo), transactionLogIndex));
 
-
-      tenant = ozoneManager.getMultiTenantManager()
-          .getTenantInfo(tenantName);
-      final String tenantDefaultPolicies = tenant.getTenantAccessPolicies()
-          .stream().map(e->e.getPolicyID())
-          .collect(Collectors.joining(","));
-
       // Add to tenantPolicyTable
       omMetadataManager.getTenantPolicyTable().addCacheEntry(
           new CacheKey<>(userPolicyGroupName),
@@ -284,21 +303,29 @@ public class OMTenantCreateRequest extends 
OMVolumeRequest {
           omResponse.build(),
           omVolumeArgs, volumeList,
           omDBTenantInfo, tenantDefaultPolicies, bucketPolicyId);
+
     } catch (IOException ex) {
-      exception = ex;
-      if (tenant != null) {
-        try {
-          ozoneManager.getMultiTenantManager().destroyTenant(tenant);
-        } catch (Exception e) {
-          // TODO: Ignore for now. Multi-Tenant Manager is responsible for
-          //  cleaning up stale state eventually.
+      // Error handling. Clean up Ranger policies when necessary.
+      if (ex instanceof OMException) {
+        final OMException omEx = (OMException) ex;
+        if (!omEx.getResult().equals(VOLUME_ALREADY_EXISTS) &&
+            !omEx.getResult().equals(TENANT_ALREADY_EXISTS)) {
+          handleRequestFailure(ozoneManager);
         }
+        // Do NOT perform any clean-up if the exception is a result of
+        //  volume name or tenant name already existing.
+        //  Otherwise in a race condition a late-comer could wipe the
+        //  policies of an existing tenant from Ranger.
+      } else {
+        // ALL OMs should proactively call the clean-up handler in other cases
+        handleRequestFailure(ozoneManager);
       }
-      // Set response success flag to false
+      // Prepare omClientResponse
       omResponse.setCreateTenantResponse(
           CreateTenantResponse.newBuilder().setSuccess(false).build());
       omClientResponse = new OMTenantCreateResponse(
           createErrorOMResponse(omResponse, ex));
+      exception = ex;
     } finally {
       if (omClientResponse != null) {
         omClientResponse.setFlushFuture(ozoneManagerDoubleBufferHelper
@@ -320,10 +347,10 @@ public class OMTenantCreateRequest extends 
OMVolumeRequest {
             getOmRequest().getUserInfo()));
 
     if (exception == null) {
-      LOG.info("Created tenant: {}, and volume: {}", tenantName, volumeName);
+      LOG.info("Created tenant '{}' and volume '{}'", tenantName, volumeName);
       // TODO: omMetrics.incNumTenants()
     } else {
-      LOG.error("Failed to create tenant: {}", tenantName, exception);
+      LOG.error("Failed to create tenant '{}'", tenantName, exception);
       // TODO: omMetrics.incNumTenantCreateFails()
     }
     return omClientResponse;
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantUserModifyRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantUserModifyRequest.java
deleted file mode 100644
index 85957ff..0000000
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantUserModifyRequest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.request.s3.tenant;
-
-import org.apache.hadoop.ozone.om.OzoneManager;
-import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper;
-import org.apache.hadoop.ozone.om.request.volume.OMVolumeRequest;
-import org.apache.hadoop.ozone.om.response.OMClientResponse;
-import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
-
-import java.io.IOException;
-
-/**
- * Handles OMTenantUserModify request.
- */
-public class OMTenantUserModifyRequest extends OMVolumeRequest {
-
-  public OMTenantUserModifyRequest(OMRequest omRequest) {
-    super(omRequest);
-  }
-
-  @Override
-  public OMRequest preExecute(OzoneManager ozoneManager) throws IOException {
-    return getOmRequest();
-  }
-
-  @Override
-  public OMClientResponse validateAndUpdateCache(
-      OzoneManager ozoneManager, long transactionLogIndex,
-      OzoneManagerDoubleBufferHelper ozoneManagerDoubleBufferHelper) {
-
-    return null;
-  }
-}
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
index 0295a5b..b3ead46 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
@@ -43,6 +43,7 @@ import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
 import org.apache.hadoop.ozone.om.helpers.RepeatedOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfo;
 import org.apache.hadoop.ozone.om.helpers.ServiceInfoEx;
+import org.apache.hadoop.ozone.om.helpers.TenantUserInfoValue;
 import org.apache.hadoop.ozone.om.ratis.OzoneManagerDoubleBuffer;
 import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerRatisUtils;
 import org.apache.hadoop.ozone.om.request.OMClientRequest;
@@ -80,6 +81,8 @@ import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Prepare
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ServiceListRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.ServiceListResponse;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.TenantGetUserInfoRequest;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.TenantGetUserInfoResponse;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type;
 import org.apache.hadoop.ozone.security.acl.OzoneObjInfo;
 
@@ -221,6 +224,11 @@ public class OzoneManagerRequestHandler implements 
RequestHandler {
         PrepareStatusResponse prepareStatusResponse = getPrepareStatus();
         responseBuilder.setPrepareStatusResponse(prepareStatusResponse);
         break;
+      case TenantGetUserInfo:
+        TenantGetUserInfoResponse getUserInfoResponse = tenantGetUserInfo(
+            request.getTenantGetUserInfoRequest());
+        responseBuilder.setTenantGetUserInfoResponse(getUserInfoResponse);
+        break;
       default:
         responseBuilder.setSuccess(false);
         responseBuilder.setMessage("Unrecognized Command Type: " + cmdType);
@@ -345,6 +353,23 @@ public class OzoneManagerRequestHandler implements 
RequestHandler {
     return resp.build();
   }
 
+  private TenantGetUserInfoResponse tenantGetUserInfo(
+      TenantGetUserInfoRequest request) throws IOException {
+    final TenantGetUserInfoResponse.Builder resp =
+        TenantGetUserInfoResponse.newBuilder();
+    final String userPrincipal = request.getUserPrincipal();
+
+    TenantUserInfoValue ret = impl.tenantGetUserInfo(userPrincipal);
+    if (ret != null) {
+      resp.setSuccess(true);
+      resp.setTenantUserInfo(ret.getProtobuf());
+    } else {
+      resp.setSuccess(false);
+    }
+
+    return resp.build();
+  }
+
   private ListVolumeResponse listVolumes(ListVolumeRequest request)
       throws IOException {
     ListVolumeResponse.Builder resp = ListVolumeResponse.newBuilder();
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/security/TestS3GetSecretRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/security/TestS3GetSecretRequest.java
index d8f67e9..0fe87d8 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/security/TestS3GetSecretRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/security/TestS3GetSecretRequest.java
@@ -58,6 +58,7 @@ import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.UUID;
 
 import static 
org.apache.hadoop.ozone.OzoneConsts.TENANT_NAME_USER_NAME_DELIMITER;
@@ -134,6 +135,9 @@ public class TestS3GetSecretRequest {
     tenant = mock(Tenant.class);
     when(omMultiTenantManager.getTenantInfo(TENANT_NAME)).thenReturn(tenant);
     
when(ozoneManager.getMultiTenantManager()).thenReturn(omMultiTenantManager);
+
+    when(tenant.getTenantAccessPolicies()).thenReturn(new ArrayList<>());
+    when(omMultiTenantManager.createTenant(TENANT_NAME)).thenReturn(tenant);
   }
 
   @After
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java
index 29fc988..d85b0e9 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java
@@ -31,9 +31,7 @@ import picocli.CommandLine.Command;
     description = "Shell for S3 specific operations",
     subcommands = {
         GetS3SecretHandler.class,
-        RevokeS3SecretHandler.class,
-        TenantCommands.class,
-        TenantUserCommands.class
+        RevokeS3SecretHandler.class
     })
 public class S3Shell extends Shell {
 
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantCommands.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantCommands.java
deleted file mode 100644
index 790ffc8..0000000
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantCommands.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.shell.s3;
-
-import org.apache.hadoop.hdds.cli.GenericParentCommand;
-import org.apache.hadoop.hdds.cli.HddsVersionProvider;
-import org.apache.hadoop.hdds.cli.MissingSubcommandException;
-import org.apache.hadoop.hdds.cli.SubcommandWithParent;
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
-import org.apache.hadoop.ozone.shell.OzoneShell;
-import org.apache.hadoop.ozone.shell.Shell;
-import org.kohsuke.MetaInfServices;
-import picocli.CommandLine;
-
-import java.util.concurrent.Callable;
-
-/**
- * Subcommand to group tenant related operations. TODO: see VolumeCommands
- */
[email protected](name = "tenant",
-    description = "Tenant management",
-    subcommands = {
-        TenantCreateHandler.class,
-        TenantModifyHandler.class,
-        TenantDeleteHandler.class
-    },
-    mixinStandardHelpOptions = true,
-    versionProvider = HddsVersionProvider.class)
-@MetaInfServices(SubcommandWithParent.class)
-public class TenantCommands implements GenericParentCommand, Callable<Void>,
-    SubcommandWithParent {
-
-  @CommandLine.ParentCommand
-  private Shell shell;
-
-  @Override
-  public Void call() throws Exception {
-    throw new MissingSubcommandException(
-        this.shell.getCmd().getSubcommands().get("tenant"));
-  }
-
-  @Override
-  public boolean isVerbose() {
-    return shell.isVerbose();
-  }
-
-  @Override
-  public OzoneConfiguration createOzoneConfiguration() {
-    return shell.createOzoneConfiguration();
-  }
-
-  @Override
-  public Class<?> getParentType() {
-    return OzoneShell.class;
-  }
-}
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/AssignUserToTenantHandler.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/AssignUserToTenantHandler.java
similarity index 93%
rename from 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/AssignUserToTenantHandler.java
rename to 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/AssignUserToTenantHandler.java
index 1c465d8..c5feee1 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/AssignUserToTenantHandler.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/AssignUserToTenantHandler.java
@@ -15,7 +15,7 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.hadoop.ozone.shell.s3;
+package org.apache.hadoop.ozone.shell.tenant;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.hdds.cli.GenericCli;
@@ -33,11 +33,11 @@ import java.util.List;
 import static 
org.apache.hadoop.ozone.OzoneConsts.TENANT_NAME_USER_NAME_DELIMITER;
 
 /**
- * ozone s3 user assign.
+ * ozone tenant user assign.
  */
 @CommandLine.Command(name = "assign",
     description = "Assign user to tenant")
-public class AssignUserToTenantHandler extends S3Handler {
+public class AssignUserToTenantHandler extends TenantHandler {
 
   @CommandLine.Spec
   private CommandLine.Model.CommandSpec spec;
@@ -59,11 +59,6 @@ public class AssignUserToTenantHandler extends S3Handler {
   //  `s3 getsecret` and leak the secret if an admin isn't careful.
   private String accessId;
 
-  // TODO: support dry-run?
-//  @CommandLine.Option(names = {"--dry-run"},
-//      description = "Dry-run")
-//  private boolean dryRun;
-
   private boolean isEmptyList(List<String> list) {
     return list == null || list.size() == 0;
   }
@@ -104,7 +99,7 @@ public class AssignUserToTenantHandler extends S3Handler {
         final S3SecretValue resp =
             objStore.assignUserToTenant(principal, tenantName, accessId);
         err().println("Assigned '" + principal + "' to '" + tenantName +
-            "' under accessId '" + accessId + "'.");
+            "' with accessId '" + accessId + "'.");
         out().println("export AWS_ACCESS_KEY_ID='" +
             resp.getAwsAccessKey() + "'");
         out().println("export AWS_SECRET_ACCESS_KEY='" +
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/GetUserInfoHandler.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/GetUserInfoHandler.java
new file mode 100644
index 0000000..44f0bda
--- /dev/null
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/GetUserInfoHandler.java
@@ -0,0 +1,83 @@
+/*
+ * 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.shell.tenant;
+
+import org.apache.hadoop.hdds.cli.GenericCli;
+import org.apache.hadoop.ozone.client.ObjectStore;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.om.helpers.TenantUserInfoValue;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.TenantAccessIdInfo;
+import org.apache.hadoop.ozone.shell.OzoneAddress;
+import picocli.CommandLine;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ozone tenant user info.
+ */
[email protected](name = "info",
+    description = "Get tenant related information of a user")
+public class GetUserInfoHandler extends TenantHandler {
+
+  @CommandLine.Spec
+  private CommandLine.Model.CommandSpec spec;
+
+  @CommandLine.Parameters(description = "List of user principal(s)")
+  private List<String> userPrincipals = new ArrayList<>();
+
+  private boolean isEmptyList(List<String> list) {
+    return list == null || list.size() == 0;
+  }
+
+  @Override
+  protected void execute(OzoneClient client, OzoneAddress address) {
+    final ObjectStore objStore = client.getObjectStore();
+
+    if (isEmptyList(userPrincipals)) {
+      GenericCli.missingSubcommand(spec);
+      return;
+    }
+
+    for (final String userPrincipal : userPrincipals) {
+      try {
+        final TenantUserInfoValue tenantUserInfo =
+            objStore.tenantGetUserInfo(userPrincipal);
+        List<TenantAccessIdInfo> accessIdInfoList =
+            tenantUserInfo.getAccessIdInfoList();
+        if (accessIdInfoList.size() == 0) {
+          err().println("User '" + userPrincipal +
+              "' is not assigned to any tenant.");
+          continue;
+        }
+        out().println("User '" + userPrincipal + "' is assigned to:");
+
+        for (TenantAccessIdInfo accessIdInfo : accessIdInfoList) {
+          out().println("- Tenant '" + accessIdInfo.getTenantName() +
+              "' with accessId '" + accessIdInfo.getAccessId() + "'");
+        }
+
+        out().println();
+      } catch (IOException e) {
+        err().println("Failed to GetUserInfo of user '" + userPrincipal
+            + "': " + e.getMessage());
+      }
+    }
+  }
+}
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserModifyHandler.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/RevokeUserAccessToTenantHandler.java
similarity index 82%
rename from 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserModifyHandler.java
rename to 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/RevokeUserAccessToTenantHandler.java
index 010cea0..0c4cdf7 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserModifyHandler.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/RevokeUserAccessToTenantHandler.java
@@ -15,18 +15,18 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.hadoop.ozone.shell.s3;
+package org.apache.hadoop.ozone.shell.tenant;
 
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
 import picocli.CommandLine;
 
 /**
- * ozone s3 user modify.
+ * ozone tenant user revoke.
  */
[email protected](name = "modify",
-    description = "Modify a tenant user")
-public class TenantUserModifyHandler extends S3Handler {
[email protected](name = "revoke",
+    description = "Revoke user access to tenant")
+public class RevokeUserAccessToTenantHandler extends TenantHandler {
 
   @Override
   protected void execute(OzoneClient client, OzoneAddress address) {
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantCreateHandler.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantCreateHandler.java
similarity index 86%
rename from 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantCreateHandler.java
rename to 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantCreateHandler.java
index 672832e..86d9fbe 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantCreateHandler.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantCreateHandler.java
@@ -15,7 +15,7 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.hadoop.ozone.shell.s3;
+package org.apache.hadoop.ozone.shell.tenant;
 
 import org.apache.hadoop.hdds.cli.GenericCli;
 import org.apache.hadoop.ozone.client.OzoneClient;
@@ -27,11 +27,11 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
- * ozone s3 tenant create.
+ * ozone tenant create.
  */
 @CommandLine.Command(name = "create",
     description = "Create one or more tenants")
-public class TenantCreateHandler extends S3Handler {
+public class TenantCreateHandler extends TenantHandler {
 
   @CommandLine.Spec
   private CommandLine.Model.CommandSpec spec;
@@ -45,9 +45,9 @@ public class TenantCreateHandler extends S3Handler {
       for (String tenantName : tenants) {
         try {
           client.getObjectStore().createTenant(tenantName);
-          out().println("Created tenant " + tenantName);
+          out().println("Created tenant '" + tenantName + "'.");
         } catch (IOException e) {
-          out().println("Failed to create tenant " + tenantName + ": " +
+          err().println("Failed to create tenant '" + tenantName + "': " +
               e.getMessage());
         }
       }
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantDeleteHandler.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantDeleteHandler.java
similarity index 87%
rename from 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantDeleteHandler.java
rename to 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantDeleteHandler.java
index afbcd43..a1f2503 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantDeleteHandler.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantDeleteHandler.java
@@ -15,21 +15,21 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.hadoop.ozone.shell.s3;
+package org.apache.hadoop.ozone.shell.tenant;
 
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
 import picocli.CommandLine;
 
 /**
- * ozone s3 tenant delete.
+ * ozone tenant delete.
  */
 @CommandLine.Command(name = "delete",
     description = "Delete a tenant")
-public class TenantDeleteHandler extends S3Handler {
+public class TenantDeleteHandler extends TenantHandler {
 
   @Override
   protected void execute(OzoneClient client, OzoneAddress address) {
-    out().println("Not Implemented.");
+    err().println("Not Implemented.");
   }
 }
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantHandler.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantHandler.java
similarity index 58%
rename from 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantHandler.java
rename to 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantHandler.java
index 345409d..a76ab74 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantHandler.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantHandler.java
@@ -15,25 +15,40 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.hadoop.ozone.shell.s3;
+package org.apache.hadoop.ozone.shell.tenant;
 
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.client.OzoneClientException;
 import org.apache.hadoop.ozone.shell.Handler;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
-import org.apache.hadoop.ozone.shell.volume.VolumeUri;
 import picocli.CommandLine;
 
+import java.io.IOException;
+
 /**
  * Base class for tenant command handlers.
  */
-// TODO: VolumeHandler
 public abstract class TenantHandler extends Handler {
 
-  @CommandLine.Mixin
-  private VolumeUri address;
+  @CommandLine.Option(names = {"--om-service-id"},
+      required = false,
+      description = "Service ID is required when OM is running in HA" +
+          " cluster")
+  private String omServiceID;
+
+  public String getOmServiceID() {
+    return omServiceID;
+  }
+
+  @Override
+  protected OzoneAddress getAddress() throws OzoneClientException {
+    return new OzoneAddress();
+  }
 
   @Override
-  protected OzoneAddress getAddress() {
-    return address.getValue();
+  protected OzoneClient createClient(OzoneAddress address)
+      throws IOException, OzoneClientException {
+    return address.createClientForS3Commands(getConf(), omServiceID);
   }
 
 }
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantModifyHandler.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantModifyHandler.java
similarity index 91%
rename from 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantModifyHandler.java
rename to 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantModifyHandler.java
index 3c19099..a3f1877 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantModifyHandler.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantModifyHandler.java
@@ -15,7 +15,7 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.hadoop.ozone.shell.s3;
+package org.apache.hadoop.ozone.shell.tenant;
 
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
@@ -26,7 +26,7 @@ import picocli.CommandLine;
  */
 @CommandLine.Command(name = "modify",
     description = "Modify a tenant")
-public class TenantModifyHandler extends S3Handler {
+public class TenantModifyHandler extends TenantHandler {
 
   @Override
   protected void execute(OzoneClient client, OzoneAddress address) {
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantShell.java
similarity index 70%
copy from 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java
copy to 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantShell.java
index 29fc988..f26e874 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/S3Shell.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantShell.java
@@ -15,32 +15,31 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.hadoop.ozone.shell.s3;
-
-import java.util.function.Supplier;
+package org.apache.hadoop.ozone.shell.tenant;
 
 import org.apache.hadoop.hdds.tracing.TracingUtil;
 import org.apache.hadoop.ozone.shell.Shell;
-
 import picocli.CommandLine.Command;
 
+import java.util.function.Supplier;
+
 /**
- * Shell for s3 related operations.
+ * Shell for multi-tenant related operations.
  */
-@Command(name = "ozone s3",
-    description = "Shell for S3 specific operations",
+@Command(name = "ozone tenant",
+    description = "Shell for multi-tenant specific operations",
     subcommands = {
-        GetS3SecretHandler.class,
-        RevokeS3SecretHandler.class,
-        TenantCommands.class,
+        TenantCreateHandler.class,
+        TenantModifyHandler.class,
+        TenantDeleteHandler.class,
         TenantUserCommands.class
     })
-public class S3Shell extends Shell {
+public class TenantShell extends Shell {
 
   @Override
   public void execute(String[] argv) {
-    TracingUtil.initTracing("s3shell", createOzoneConfiguration());
-    TracingUtil.executeInNewSpan("s3shell",
+    TracingUtil.initTracing("tenant-shell", createOzoneConfiguration());
+    TracingUtil.executeInNewSpan("tenant-shell",
         (Supplier<Void>) () -> {
           super.execute(argv);
           return null;
@@ -48,11 +47,11 @@ public class S3Shell extends Shell {
   }
 
   /**
-   * Main for the S3Shell Command handling.
+   * Main for the TenantShell Command handling.
    *
    * @param argv - System Args Strings[]
    */
   public static void main(String[] argv) {
-    new S3Shell().run(argv);
+    new TenantShell().run(argv);
   }
 }
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserCommands.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantUserCommands.java
similarity index 90%
rename from 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserCommands.java
rename to 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantUserCommands.java
index 8db2484..49c58b5 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserCommands.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantUserCommands.java
@@ -15,7 +15,7 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.hadoop.ozone.shell.s3;
+package org.apache.hadoop.ozone.shell.tenant;
 
 import org.apache.hadoop.hdds.cli.GenericParentCommand;
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
@@ -35,15 +35,15 @@ import java.util.concurrent.Callable;
 @CommandLine.Command(name = "user",
     description = "Tenant user management",
     subcommands = {
+        GetUserInfoHandler.class,
         AssignUserToTenantHandler.class,
-        TenantUserModifyHandler.class,
-        TenantUserDeleteHandler.class
+        RevokeUserAccessToTenantHandler.class
     },
     mixinStandardHelpOptions = true,
     versionProvider = HddsVersionProvider.class)
 @MetaInfServices(SubcommandWithParent.class)
-public class TenantUserCommands implements GenericParentCommand, 
Callable<Void>,
-    SubcommandWithParent{
+public class TenantUserCommands implements
+    GenericParentCommand, Callable<Void>, SubcommandWithParent {
 
   @CommandLine.ParentCommand
   private Shell shell;
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserDeleteHandler.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/package-info.java
similarity index 63%
rename from 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserDeleteHandler.java
rename to 
hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/package-info.java
index 3dff2d4..dabb6bd 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserDeleteHandler.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/package-info.java
@@ -15,21 +15,7 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.hadoop.ozone.shell.s3;
-
-import org.apache.hadoop.ozone.client.OzoneClient;
-import org.apache.hadoop.ozone.shell.OzoneAddress;
-import picocli.CommandLine;
-
 /**
- * ozone s3 user delete.
+ * Tenant commands for Ozone.
  */
[email protected](name = "delete",
-    description = "Delete a tenant user")
-public class TenantUserDeleteHandler extends S3Handler {
-
-  @Override
-  protected void execute(OzoneClient client, OzoneAddress address) {
-    out().println("Not Implemented.");
-  }
-}
+package org.apache.hadoop.ozone.shell.tenant;

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

Reply via email to