You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by si...@apache.org on 2022/05/20 17:14:38 UTC

[ozone] branch HDDS-4944 updated: HDDS-6340. [Multi-Tenant] Add tenant CLI option to print results in JSON (#3381)

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 f0b3013091 HDDS-6340. [Multi-Tenant] Add tenant CLI option to print results in JSON (#3381)
f0b3013091 is described below

commit f0b30130911f4d7bacbe711ce51be798f8d8ba80
Author: Siyao Meng <50...@users.noreply.github.com>
AuthorDate: Fri May 20 10:14:32 2022 -0700

    HDDS-6340. [Multi-Tenant] Add tenant CLI option to print results in JSON (#3381)
---
 .../docs/content/feature/S3-Tenant-Commands.md     | 137 +++++++++--
 .../smoketest/security/ozone-secure-tenant.robot   |  68 +++---
 .../hadoop/ozone/shell/TestOzoneTenantShell.java   | 270 ++++++++++++---------
 .../ozone/shell/tenant/GetUserInfoHandler.java     |  98 ++++----
 .../shell/tenant/TenantAssignAdminHandler.java     |  50 ++--
 .../tenant/TenantAssignUserAccessIdHandler.java    |  70 ++----
 .../shell/tenant/TenantBucketLinkHandler.java      |   2 -
 .../ozone/shell/tenant/TenantCreateHandler.java    |  22 +-
 .../ozone/shell/tenant/TenantDeleteHandler.java    |  51 ++--
 .../ozone/shell/tenant/TenantGetSecretHandler.java |  50 +---
 .../hadoop/ozone/shell/tenant/TenantHandler.java   |   2 -
 .../ozone/shell/tenant/TenantListHandler.java      |  74 +++---
 .../ozone/shell/tenant/TenantListUsersHandler.java |  49 ++--
 .../shell/tenant/TenantRevokeAdminHandler.java     |  50 ++--
 .../tenant/TenantRevokeUserAccessIdHandler.java    |  29 +--
 .../ozone/shell/tenant/TenantSetSecretHandler.java |  37 +--
 16 files changed, 550 insertions(+), 509 deletions(-)

diff --git a/hadoop-hdds/docs/content/feature/S3-Tenant-Commands.md b/hadoop-hdds/docs/content/feature/S3-Tenant-Commands.md
index 247d4888eb..9154fc80de 100644
--- a/hadoop-hdds/docs/content/feature/S3-Tenant-Commands.md
+++ b/hadoop-hdds/docs/content/feature/S3-Tenant-Commands.md
@@ -30,6 +30,10 @@ All Multi-Tenancy subcommands are located under CLI `ozone tenant`.
 
 The commands below assume a Kerberized Ozone cluster with Ranger install. Enabling HTTPS on S3 Gateway is optional but recommended.
 
+The exit code of a successful tenant command should be `0`.
+A non-zero exit code indicates failure, which should be accompanied an error message.
+
+
 ## Quick Start
 
 ### Setup
@@ -49,7 +53,7 @@ Apart from adding new OM DB entries, creating a tenant also does the following i
 3. Creates new Ranger policies that allows all tenant users to list and create buckets by default under the tenant volume, but only bucket owners and tenant admins are allowed to access the bucket contents.
 
 ```shell
-ozone tenant create <TENANT_NAME>
+ozone tenant [--verbose] create <TENANT_NAME>
 ```
 
 Example:
@@ -58,16 +62,26 @@ Example:
 bash-4.2$ kinit -kt /etc/security/keytabs/om.keytab om/om@EXAMPLE.COM
 bash-4.2$ ozone tenant create tenantone
 2022-02-16 00:00:00,000 [main] INFO rpc.RpcClient: Creating Tenant: 'tenantone', with new volume: 'tenantone'
-Created tenant 'tenantone'.
+```
+
+Verbose output example:
+
+```shell
+bash-4.2$ kinit -kt /etc/security/keytabs/om.keytab om/om@EXAMPLE.COM
+bash-4.2$ ozone tenant --verbose create tenantone
+2022-02-16 00:00:00,000 [main] INFO rpc.RpcClient: Creating Tenant: 'tenantone', with new volume: 'tenantone'
+{
+  "tenantId": "tenantone"
+}
 ```
 
 
 ### List tenants
 
-List all tenants in an Ozone cluster.
+List all tenants in an Ozone cluster. Optionally, use `--json` to print the detailed result in JSON.
 
 ```shell
-ozone tenant list
+ozone tenant list [--json]
 ```
 
 Example:
@@ -77,6 +91,20 @@ bash-4.2$ ozone tenant list
 tenantone
 ```
 
+```shell
+bash-4.2$ ozone tenant list --json
+[
+  {
+    "tenantId": "tenantone",
+    "bucketNamespaceName": "tenantone",
+    "userRoleName": "tenantone-UserRole",
+    "adminRoleName": "tenantone-AdminRole",
+    "bucketNamespacePolicyName": "tenantone-VolumeAccess",
+    "bucketPolicyName": "tenantone-BucketAccess"
+  }
+]
+```
+
 
 ### Assign a user to a tenant
 
@@ -84,12 +112,12 @@ The first user in a tenant must be assigned by an Ozone cluster administrator.
 
 By default when user `testuser` is assigned to tenant `tenantone`, the generated Access ID for the user in this tenant is `tenantone$testuser`.
 
-- Be sure to enclose the Access ID in single quotes in Bash when using it so it doesn't get auto-translated into environment variables.
+- Be sure to enclose the Access ID in single quotes in Bash when using it so it doesn't get expanded as environment variables.
 
 It is possible to assign a user to multiple tenants.
 
 ```shell
-ozone tenant user assign <USER_NAME> --tenant=<TENANT_NAME>
+ozone tenant [--verbose] user assign <USER_NAME> --tenant=<TENANT_NAME>
 ```
 
 `<USER_NAME>` should be a short user name for a Kerberos principal, e.g. `testuser` when the Kerberos principal is `testuser/scm@EXAMPLE.COM`
@@ -98,9 +126,18 @@ Example:
 
 ```shell
 bash-4.2$ ozone tenant user assign testuser --tenant=tenantone
-Assigned 'testuser' to 'tenantone' with accessId 'tenantone$testuser'.
 export AWS_ACCESS_KEY_ID='tenantone$testuser'
 export AWS_SECRET_ACCESS_KEY='<GENERATED_SECRET>'
+
+bash-4.2$ ozone tenant user assign testuser --tenant=tenantone
+TENANT_USER_ACCESS_ID_ALREADY_EXISTS accessId 'tenantone$testuser' already exists!
+```
+
+```shell
+bash-4.2$ ozone tenant --verbose user assign testuser2 --tenant=tenantone
+export AWS_ACCESS_KEY_ID='tenantone$testuser2'
+export AWS_SECRET_ACCESS_KEY='<GENERATED_SECRET>'
+Assigned 'testuser2' to 'tenantone' with accessId 'tenantone$testuser2'.
 ```
 
 
@@ -125,7 +162,18 @@ Example:
 
 ```shell
 bash-4.2$ ozone tenant user assignadmin 'tenantone$testuser' --tenant=tenantone
-Assigned admin to 'tenantone$testuser' in tenant 'tenantone'
+```
+
+By default, if the command succeeds, it exits with `0` and prints nothing. Use `--verbose` to print the result in JSON.
+
+```shell
+bash-4.2$ ozone tenant --verbose user assignadmin 'tenantone$testuser' --tenant=tenantone
+{
+  "accessId": "tenantone$testuser",
+  "tenantId": "tenantone",
+  "isAdmin": true,
+  "isDelegatedAdmin": true
+}
 ```
 
 Once `testuser` becomes a tenant admin of `tenantone`, one can kinit as `testuser` and assign new users to the tenant,
@@ -141,23 +189,37 @@ ozone tenant user assignadmin 'tenantone$testuser2' --tenant=tenantone
 ### List users in a tenant
 
 ```shell
-ozone tenant user list --tenant=<TENANT_NAME>
+ozone tenant user list [--json] <TENANT_NAME>
 ```
 
 Example:
 
 ```shell
-bash-4.2$ ozone tenant user list --tenant=tenantone
+bash-4.2$ ozone tenant user list tenantone
 - User 'testuser' with accessId 'tenantone$testuser'
+- User 'testuser2' with accessId 'tenantone$testuser2'
 ```
 
+```shell
+bash-4.2$ ozone tenant user list --json tenantone
+[
+  {
+    "user": "testuser",
+    "accessId": "tenantone$testuser"
+  },
+  {
+    "user": "testuser2",
+    "accessId": "tenantone$testuser2"
+  }
+]
+```
 
 ### Get tenant user info
 
 This command lists all tenants a user is assigned to.
 
 ```shell
-ozone tenant user info <USER_NAME>
+ozone tenant user info [--json] <USER_NAME>
 ```
 
 Example:
@@ -168,31 +230,59 @@ User 'testuser' is assigned to:
 - Tenant 'tenantone' delegated admin with accessId 'tenantone$testuser'
 ```
 
+```shell
+bash-4.2$ ozone tenant user info --json testuser
+{
+  "user": "testuser",
+  "tenants": [
+    {
+      "accessId": "tenantone$testuser",
+      "tenantId": "tenantone",
+      "isAdmin": true,
+      "isDelegatedAdmin": true
+    }
+  ]
+}
+```
 
 ### Revoke a tenant admin
 
 ```shell
-ozone tenant user revokeadmin <ACCESS_ID>
+ozone tenant [--verbose] user revokeadmin <ACCESS_ID>
 ```
 
 Example:
 
 ```shell
 bash-4.2$ ozone tenant user revokeadmin 'tenantone$testuser'
-Revoked admin role of 'tenantone$testuser'.
+```
+
+```shell
+bash-4.2$ ozone tenant --verbose user revokeadmin 'tenantone$testuser'
+{
+  "accessId": "tenantone$testuser",
+  "isAdmin": false,
+  "isDelegatedAdmin": false
+}
 ```
 
 
 ### Revoke user access from a tenant
 
 ```shell
-ozone tenant user revoke <ACCESS_ID>
+ozone tenant [--verbose] user revoke <ACCESS_ID>
 ```
 
 Example:
 
 ```shell
 bash-4.2$ ozone tenant user revoke 'tenantone$testuser'
+```
+
+With verbose output:
+
+```shell
+bash-4.2$ ozone tenant --verbose user revoke 'tenantone$testuser'
 Revoked accessId 'tenantone$testuser'.
 ```
 
@@ -205,8 +295,10 @@ Otherwise OM will throw `TENANT_NOT_EMPTY` exception and refuse to delete the te
 Note that it is intentional by design that the volume created and associated with the tenant during tenant creation is not removed.
 An admin has to remove the volume manually as prompt in the CLI, if deemed necessary.
 
+Verbose option, in addition, will print the Ozone Manager RAW response in JSON.
+
 ```shell
-ozone tenant delete <TENANT_NAME>
+ozone tenant [--verbose] delete <TENANT_NAME>
 ```
 
 Example:
@@ -218,6 +310,21 @@ But the associated volume 'tenantone' is not removed. To delete it, run
     ozone sh volume delete tenantone
 ```
 
+With verbose output:
+
+```shell
+bash-4.2$ ozone tenant --verbose delete tenantone
+Deleted tenant 'tenantone'.
+But the associated volume 'tenantone' is not removed. To delete it, run
+    ozone sh volume delete tenantone
+
+{
+  "tenantId": "tenantone",
+  "volumeName": "tenantone",
+  "volumeRefCount": 0
+}
+```
+
 If an Ozone cluster admin (or whoever has the permission to delete the volume in Ranger) tries delete a volume before the tenant is deleted using the command above,
 the `ozone sh volume delete` command would fail because the volume reference count is not zero:
 
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
index 2e59c8c260..fa9c2b1e95 100644
--- a/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-tenant.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-tenant.robot
@@ -32,14 +32,14 @@ Init Ranger MockServer
                         Should contain   ${output}         {}
 
 *** Test Cases ***
-Secure Tenant Create Tenant Success with Cluster Admin
+Create Tenant Success with Cluster Admin
     Run Keyword         Init Ranger MockServer
     Run Keyword         Kinit test user     testuser     testuser.keytab
-    ${output} =         Execute          ozone tenant create tenantone
-                        Should contain   ${output}         Created tenant 'tenantone'
+    ${output} =         Execute          ozone tenant --verbose create tenantone
+                        Should contain   ${output}         "tenantId": "tenantone"
 
-Secure Tenant Assign User Success with Cluster Admin
-    ${output} =         Execute          ozone tenant user assign testuser --tenant=tenantone
+Assign User Success with Cluster Admin
+    ${output} =         Execute          ozone tenant --verbose user assign testuser --tenant=tenantone
                         Should contain   ${output}         Assigned 'testuser' to 'tenantone'
     ${accessId} =       Get Regexp Matches   ${output}     (?<=export AWS_ACCESS_KEY_ID=).*
     ${secretKey} =      Get Regexp Matches   ${output}     (?<=export AWS_SECRET_ACCESS_KEY=).*
@@ -48,15 +48,19 @@ Secure Tenant Assign User Success with Cluster Admin
                         Set Global Variable  ${ACCESS_ID}   ${accessId}
                         Set Global Variable  ${SECRET_KEY}  ${secretKey}
 
-Secure Tenant Assign User Failure to Non-existent Tenant
+Assign User Failure to Non-existent Tenant
     ${rc}  ${output} =  Run And Return Rc And Output  ozone tenant user assign testuser --tenant=thistenantdoesnotexist
                         Should contain   ${output}         Tenant 'thistenantdoesnotexist' doesn't exist
 
-Secure Tenant GetUserInfo Success
+GetUserInfo Success
     ${output} =         Execute          ozone tenant user info testuser
                         Should contain   ${output}         Tenant 'tenantone' with accessId 'tenantone$testuser'
 
-Secure Tenant Create Bucket 1 Success via S3 API
+GetUserInfo as JSON Success
+    ${output} =         Execute          ozone tenant user info --json testuser | jq '.tenants | .[].accessId'
+                        Should contain   ${output}         "tenantone$testuser"
+
+Create Bucket 1 Success via S3 API
                         Execute          aws configure set aws_access_key_id ${ACCESS_ID}
                         Execute          aws configure set aws_secret_access_key ${SECRET_KEY}
     ${output} =         Execute          aws s3api --endpoint-url ${S3G_ENDPOINT_URL} create-bucket --bucket bucket-test1
@@ -64,77 +68,77 @@ Secure Tenant Create Bucket 1 Success via S3 API
     ${output} =         Execute          aws s3api --endpoint-url ${S3G_ENDPOINT_URL} list-buckets
                         Should contain   ${output}         bucket-test1
 
-Secure Tenant Verify Bucket 1 Owner
+Verify Bucket 1 Owner
     ${result} =         Execute          ozone sh bucket info /tenantone/bucket-test1 | jq -r '.owner'
                         Should Be Equal  ${result}       testuser
 
-Secure Tenant SetSecret Success with Cluster Admin
-    ${output} =         Execute          ozone tenant user setsecret 'tenantone$testuser' --secret=somesecret1 --export
+SetSecret Success with Cluster Admin
+    ${output} =         Execute          ozone tenant user setsecret 'tenantone$testuser' --secret=somesecret1
                         Should contain   ${output}         export AWS_SECRET_ACCESS_KEY='somesecret1'
 
-Secure Tenant SetSecret Failure For Invalid Secret 1
-    ${rc}  ${output} =  Run And Return Rc And Output  ozone tenant user setsecret 'tenantone$testuser' --secret='' --export
+SetSecret Failure For Invalid Secret 1
+    ${rc}  ${output} =  Run And Return Rc And Output  ozone tenant user setsecret 'tenantone$testuser' --secret=''
                         Should contain   ${output}         secretKey cannot be null or empty.
 
-Secure Tenant SetSecret Failure For Invalid Secret 2
-    ${rc}  ${output} =  Run And Return Rc And Output  ozone tenant user setsecret 'tenantone$testuser' --secret=short --export
+SetSecret Failure For Invalid Secret 2
+    ${rc}  ${output} =  Run And Return Rc And Output  ozone tenant user setsecret 'tenantone$testuser' --secret=short
                         Should contain   ${output}         Secret key length should be at least 8 characters
 
-Secure Tenant GetSecret Success
-    ${output} =         Execute          ozone tenant user getsecret 'tenantone$testuser' --export
+GetSecret Success
+    ${output} =         Execute          ozone tenant user getsecret 'tenantone$testuser'
                         Should contain   ${output}         export AWS_SECRET_ACCESS_KEY='somesecret1'
 
-Secure Tenant Delete Bucket 1 Failure With Old SecretKey via S3 API
+Delete Bucket 1 Failure With Old SecretKey via S3 API
     ${rc}  ${output} =  Run And Return Rc And Output  aws s3api --endpoint-url ${S3G_ENDPOINT_URL} delete-bucket --bucket bucket-test1
                         Should Be True	${rc} > 0
 
-Secure Tenant Delete Bucket 1 Success With Newly Set SecretKey via S3 API
+Delete Bucket 1 Success With Newly Set SecretKey via S3 API
                         Execute          aws configure set aws_secret_access_key 'somesecret1'
     ${output} =         Execute          aws s3api --endpoint-url ${S3G_ENDPOINT_URL} delete-bucket --bucket bucket-test1
 
-Secure Tenant Delete Tenant Failure Tenant Not Empty
+Delete Tenant Failure Tenant Not Empty
     ${rc}  ${output} =  Run And Return Rc And Output  ozone tenant delete tenantone
                         Should contain   ${output}         TENANT_NOT_EMPTY Tenant 'tenantone' is not empty. All accessIds associated to this tenant must be revoked before the tenant can be deleted. See `ozone tenant user revoke`
 
-Secure Tenant Create Tenant Failure with Regular User
+Create Tenant Failure with Regular User
     Run Keyword         Kinit test user     testuser2    testuser2.keytab
     ${rc}  ${output} =  Run And Return Rc And Output  ozone tenant create tenanttwo
                         Should contain   ${output}         PERMISSION_DENIED User 'testuser2/scm@EXAMPLE.COM' is not an Ozone admin.
 
-Secure Tenant SetSecret Failure with Regular User
-    ${rc}  ${output} =  Run And Return Rc And Output  ozone tenant user set-secret 'tenantone$testuser' --secret=somesecret2 --export
+SetSecret Failure with Regular User
+    ${rc}  ${output} =  Run And Return Rc And Output  ozone tenant user set-secret 'tenantone$testuser' --secret=somesecret2
                         Should contain   ${output}         Permission denied. Requested accessId
 
-Secure Tenant Create Bucket 2 Success with somesecret1 via S3 API
+Create Bucket 2 Success with somesecret1 via S3 API
     ${output} =         Execute          aws s3api --endpoint-url ${S3G_ENDPOINT_URL} create-bucket --bucket bucket-test2
                         Should contain   ${output}         bucket-test2
 
-Secure Tenant Delete Bucket 2 Failure with somesecret2 via S3 API
+Delete Bucket 2 Failure with somesecret2 via S3 API
                         Execute          aws configure set aws_secret_access_key 'somesecret2'
     ${rc}  ${output} =  Run And Return Rc And Output  aws s3api --endpoint-url ${S3G_ENDPOINT_URL} delete-bucket --bucket bucket-test2
                         Should Be True	${rc} > 0
 
-Secure Tenant Delete Bucket 2 Success with somesecret1 via S3 API
+Delete Bucket 2 Success with somesecret1 via S3 API
                         Execute          aws configure set aws_secret_access_key 'somesecret1'
     ${output} =         Execute          aws s3api --endpoint-url ${S3G_ENDPOINT_URL} delete-bucket --bucket bucket-test2
 
-Secure Tenant Revoke User AccessId Success with Cluster Admin
+Revoke User AccessId Success with Cluster Admin
     Run Keyword         Kinit test user     testuser     testuser.keytab
-    ${output} =         Execute          ozone tenant user revoke 'tenantone$testuser'
+    ${output} =         Execute          ozone tenant --verbose user revoke 'tenantone$testuser'
                         Should contain   ${output}         Revoked accessId 'tenantone$testuser'.
 
-Secure Tenant Create Bucket 3 Failure with Revoked AccessId via S3 API
+Create Bucket 3 Failure with Revoked AccessId via S3 API
     ${rc}  ${output} =  Run And Return Rc And Output  aws s3api --endpoint-url ${S3G_ENDPOINT_URL} create-bucket --bucket bucket-test3
                         Should Be True	${rc} > 0
 
-Secure Tenant Delete Tenant Success with Cluster Admin
+Delete Tenant Success with Cluster Admin
     ${output} =         Execute          ozone tenant delete tenantone
                         Should contain   ${output}         Deleted tenant 'tenantone'.
 
-Secure Tenant Delete Volume Success with Cluster Admin
+Delete Volume Success with Cluster Admin
     ${output} =         Execute          ozone sh volume delete tenantone
                         Should contain   ${output}         Volume tenantone is deleted
 
-Secure Tenant List Tenant Expect Empty Result
+List Tenant Expect Empty Result
     ${output} =         Execute          ozone tenant list
                         Should not contain   ${output}     tenantone
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
index 39bdbaae54..2b449906c9 100644
--- 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
@@ -17,6 +17,7 @@
  */
 package org.apache.hadoop.ozone.shell;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Strings;
 import org.apache.commons.io.FileUtils;
 import org.apache.hadoop.fs.FileUtil;
@@ -339,6 +340,19 @@ public class TestOzoneTenantShell {
     stream.reset();
   }
 
+  private void checkOutput(ByteArrayOutputStream stream, String stringToMatch,
+      boolean exactMatch, boolean expectValidJSON) throws IOException {
+    stream.flush();
+    final String str = stream.toString(DEFAULT_ENCODING);
+    if (expectValidJSON) {
+      // Verify if the String can be parsed as a valid JSON
+      final ObjectMapper objectMapper = new ObjectMapper();
+      objectMapper.readTree(str);
+    }
+    checkOutput(str, stringToMatch, exactMatch);
+    stream.reset();
+  }
+
   private void checkOutput(String str, String stringToMatch,
                            boolean exactMatch) {
     if (exactMatch) {
@@ -363,7 +377,7 @@ public class TestOzoneTenantShell {
     final String userName = "alice";
 
     executeHA(tenantShell, new String[] {"create", tenantName});
-    checkOutput(out, "Created tenant", false);
+    checkOutput(out, "", true);
     checkOutput(err, "", true);
 
     // Loop assign-revoke 4 times
@@ -371,31 +385,34 @@ public class TestOzoneTenantShell {
       executeHA(tenantShell, new String[] {
           "user", "assign", userName, "--tenant=" + tenantName});
       checkOutput(out, "export AWS_ACCESS_KEY_ID=", false);
-      checkOutput(err, "Assigned", false);
+      checkOutput(err, "", true);
 
-      executeHA(tenantShell, new String[] {"user", "assign-admin",
+      executeHA(tenantShell, new String[] {"--verbose", "user", "assign-admin",
           tenantName + "$" + userName, "--tenant=" + tenantName,
           "--delegated=true"});
-      checkOutput(out, "", true);
-      checkOutput(err, "Assigned admin", false);
+      checkOutput(out, "{\n" + "  \"accessId\": \"devaa$alice\",\n"
+          + "  \"tenantId\": \"devaa\",\n" + "  \"isAdmin\": true,\n"
+          + "  \"isDelegatedAdmin\": true\n" + "}\n", true, true);
+      checkOutput(err, "", true);
 
       // Clean up
-      executeHA(tenantShell, new String[] {
-          "user", "revoke-admin", tenantName + "$" + userName,
-          "--tenant=" + tenantName});
-      checkOutput(out, "", true);
-      checkOutput(err, "Revoked admin role of", false);
+      executeHA(tenantShell, new String[] {"--verbose", "user", "revoke-admin",
+          tenantName + "$" + userName, "--tenant=" + tenantName});
+      checkOutput(out, "{\n" + "  \"accessId\": \"devaa$alice\",\n"
+          + "  \"tenantId\": \"devaa\",\n" + "  \"isAdmin\": false,\n"
+          + "  \"isDelegatedAdmin\": false\n" + "}\n", true, true);
+      checkOutput(err, "", true);
 
       executeHA(tenantShell, new String[] {
           "user", "revoke", tenantName + "$" + userName});
       checkOutput(out, "", true);
-      checkOutput(err, "Revoked accessId", false);
+      checkOutput(err, "", true);
     }
 
     // Clean up
     executeHA(tenantShell, new String[] {"delete", tenantName});
-    checkOutput(out, "Deleted tenant '" + tenantName + "'.\n", false);
-    checkOutput(err, "", true);
+    checkOutput(out, "", true);
+    checkOutput(err, "Deleted tenant '" + tenantName + "'.\n", false);
     deleteVolume(tenantName);
 
     // Sanity check: tenant list should be empty
@@ -422,7 +439,7 @@ public class TestOzoneTenantShell {
     // Create tenants
     // Equivalent to `ozone tenant create finance`
     executeHA(tenantShell, new String[] {"create", "finance"});
-    checkOutput(out, "Created tenant 'finance'.\n", true);
+    checkOutput(out, "", true);
     checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {"list"});
@@ -440,36 +457,36 @@ public class TestOzoneTenantShell {
     // Creating the tenant with the same name again should fail
     executeHA(tenantShell, new String[] {"create", "finance"});
     checkOutput(out, "", true);
-    checkOutput(err, "Failed to create tenant 'finance'", false);
+    checkOutput(err, "Tenant 'finance' already exists\n", true);
 
     executeHA(tenantShell, new String[] {"create", "research"});
-    checkOutput(out, "Created tenant 'research'.\n", true);
+    checkOutput(out, "", true);
     checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {"create", "dev"});
-    checkOutput(out, "Created tenant 'dev'.\n", true);
+    checkOutput(out, "", true);
     checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {"ls"});
     checkOutput(out, "dev\nfinance\nresearch\n", true);
     checkOutput(err, "", true);
 
-    executeHA(tenantShell, new String[] {"list", "--long", "--header"});
-    // Not checking the entire output here yet
-    checkOutput(out, "Policy", false);
+    executeHA(tenantShell, new String[] {"list", "--json"});
+    // Not checking the full output here
+    checkOutput(out, "\"tenantId\": \"dev\",", false);
     checkOutput(err, "", true);
 
     // Attempt user getsecret before assignment, should fail
     executeHA(tenantShell, new String[] {
         "user", "getsecret", "finance$bob"});
     checkOutput(out, "", false);
-    checkOutput(err, "AccessId 'finance$bob' doesn't exist\n",
+    checkOutput(err, "accessId 'finance$bob' doesn't exist\n",
         true);
 
     // Assign user accessId
     // Equivalent to `ozone tenant user assign bob --tenant=finance`
     executeHA(tenantShell, new String[] {
-        "user", "assign", "bob", "--tenant=finance"});
+        "--verbose", "user", "assign", "bob", "--tenant=finance"});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='finance$bob'\n"
         + "export AWS_SECRET_ACCESS_KEY='", false);
     checkOutput(err, "Assigned 'bob' to 'finance' with accessId"
@@ -478,18 +495,12 @@ public class TestOzoneTenantShell {
     // Try user getsecret again after assignment, should succeed
     executeHA(tenantShell, new String[] {
         "user", "getsecret", "finance$bob"});
-    checkOutput(out, "awsAccessKey=finance$bob\n", false);
-    checkOutput(err, "", true);
-
-    // Try user getsecret again with -e option
-    executeHA(tenantShell, new String[] {
-        "user", "getsecret", "-e", "finance$bob"});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='finance$bob'\n",
             false);
     checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
-        "user", "assign", "bob", "--tenant=research"});
+        "--verbose", "user", "assign", "bob", "--tenant=research"});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='research$bob'\n"
         + "export AWS_SECRET_ACCESS_KEY='", false);
     checkOutput(err, "Assigned 'bob' to 'research' with accessId"
@@ -499,8 +510,7 @@ public class TestOzoneTenantShell {
         "user", "assign", "bob", "--tenant=dev"});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='dev$bob'\n"
         + "export AWS_SECRET_ACCESS_KEY='", false);
-    checkOutput(err, "Assigned 'bob' to 'dev' with accessId"
-        + " 'dev$bob'.\n", true);
+    checkOutput(err, "", true);
 
     // Get user info
     // Equivalent to `ozone tenant user info bob`
@@ -516,20 +526,33 @@ public class TestOzoneTenantShell {
     executeHA(tenantShell, new String[] {
         "user", "assign-admin", "dev$bob", "--tenant=dev"});
     checkOutput(out, "", true);
-    checkOutput(err,
-        "Assigned admin to 'dev$bob' in tenant 'dev'\n", true);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
         "user", "info", "bob"});
     checkOutput(out, "Tenant 'dev' delegated admin with accessId", false);
     checkOutput(err, "", true);
 
+    executeHA(tenantShell, new String[] {
+        "user", "info", "--json", "bob"});
+    checkOutput(out, "{\n" + "  \"user\": \"bob\",\n" + "  \"tenants\": [\n"
+        + "    {\n" + "      \"accessId\": \"research$bob\",\n"
+        + "      \"tenantId\": \"research\",\n" + "      \"isAdmin\": false,\n"
+        + "      \"isDelegatedAdmin\": false\n" + "    },\n" + "    {\n"
+        + "      \"accessId\": \"finance$bob\",\n"
+        + "      \"tenantId\": \"finance\",\n" + "      \"isAdmin\": false,\n"
+        + "      \"isDelegatedAdmin\": false\n" + "    },\n" + "    {\n"
+        + "      \"accessId\": \"dev$bob\",\n"
+        + "      \"tenantId\": \"dev\",\n" + "      \"isAdmin\": true,\n"
+        + "      \"isDelegatedAdmin\": true\n" + "    }\n" + "  ]\n" + "}\n",
+        true, true);
+    checkOutput(err, "", true);
+
     // Revoke admin
     executeHA(tenantShell, new String[] {
         "user", "revoke-admin", "dev$bob", "--tenant=dev"});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked admin role of 'dev$bob' "
-        + "from tenant 'dev'.\n", true);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
         "user", "info", "bob"});
@@ -543,7 +566,7 @@ public class TestOzoneTenantShell {
     executeHA(tenantShell, new String[] {
         "user", "revoke", "research$bob"});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked accessId 'research$bob'.\n", true);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
         "user", "info", "bob"});
@@ -557,16 +580,14 @@ public class TestOzoneTenantShell {
         "user", "assign", "bob", "--tenant=research"});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='research$bob'\n"
         + "export AWS_SECRET_ACCESS_KEY='", false);
-    checkOutput(err, "Assigned 'bob' to 'research' with accessId"
-        + " 'research$bob'.\n", true);
+    checkOutput(err, "", true);
 
     // Attempt to assign the user to the tenant again
     executeHA(tenantShell, new String[] {
         "user", "assign", "bob", "--tenant=research",
         "--accessId=research$bob"});
     checkOutput(out, "", false);
-    checkOutput(err, "Failed to assign 'bob' to 'research': "
-        + "accessId 'research$bob' already exists!\n", true);
+    checkOutput(err, "accessId 'research$bob' already exists!\n", true);
 
     // Attempt to assign the user to the tenant with a custom accessId
     executeHA(tenantShell, new String[] {
@@ -574,16 +595,9 @@ public class TestOzoneTenantShell {
         "--accessId=research$bob42"});
     checkOutput(out, "", false);
     // HDDS-6366: Disallow specifying custom accessId.
-    checkOutput(err, "Failed to assign 'bob' to 'research': "
-        + "Invalid accessId 'research$bob42'. "
+    checkOutput(err, "Invalid accessId 'research$bob42'. "
         + "Specifying a custom access ID disallowed. "
         + "Expected accessId to be assigned is 'research$bob'\n", true);
-    /*  Once HDDS-6366 is lifted, the following check should be used instead
-    checkOutput(err, "Failed to assign 'bob' to 'research': "
-        + "The same user is not allowed to be assigned to the same tenant "
-        + "more than once. User 'bob' is already assigned to tenant 'research' "
-        + "with accessId 'research$bob'.\n", true);
-    */
 
     executeHA(tenantShell, new String[] {"list"});
     checkOutput(out, "dev\nfinance\nresearch\n", true);
@@ -593,25 +607,25 @@ public class TestOzoneTenantShell {
     executeHA(tenantShell, new String[] {
         "user", "revoke", "research$bob"});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked accessId", false);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {"delete", "research"});
-    checkOutput(out, "Deleted tenant 'research'.\n", false);
-    checkOutput(err, "", true);
+    checkOutput(out, "", true);
+    checkOutput(err, "Deleted tenant 'research'.\n", false);
     deleteVolume("research");
 
     executeHA(tenantShell, new String[] {
         "user", "revoke", "finance$bob"});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked accessId", false);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {"list"});
     checkOutput(out, "dev\nfinance\n", true);
     checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {"delete", "finance"});
-    checkOutput(out, "Deleted tenant 'finance'.\n", false);
-    checkOutput(err, "", true);
+    checkOutput(out, "", true);
+    checkOutput(err, "Deleted tenant 'finance'.\n", false);
     deleteVolume("finance");
 
     executeHA(tenantShell, new String[] {"list"});
@@ -622,7 +636,9 @@ public class TestOzoneTenantShell {
     int exitCode = executeHA(tenantShell, new String[] {"delete", "dev"});
     Assert.assertTrue("Tenant delete should fail!", exitCode != 0);
     checkOutput(out, "", true);
-    checkOutput(err, "Failed to delete tenant 'dev'", false);
+    checkOutput(err, "Tenant 'dev' is not empty. All accessIds associated "
+        + "to this tenant must be revoked before the tenant can be deleted. "
+        + "See `ozone tenant user revoke`\n", true);
 
     // Delete dev volume should fail because the volume reference count > 0L
     exitCode = execute(ozoneSh, new String[] {"volume", "delete", "dev"});
@@ -640,12 +656,14 @@ public class TestOzoneTenantShell {
     executeHA(tenantShell, new String[] {
         "user", "revoke", "dev$bob"});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked accessId", false);
+    checkOutput(err, "", true);
 
     // Then delete tenant, should succeed
-    executeHA(tenantShell, new String[] {"delete", "dev"});
-    checkOutput(out, "Deleted tenant 'dev'.\n", false);
-    checkOutput(err, "", true);
+    executeHA(tenantShell, new String[] {"--verbose", "delete", "dev"});
+    checkOutput(out, "{\n" + "  \"tenantId\": \"dev\",\n"
+        + "  \"volumeName\": \"dev\",\n" + "  \"volumeRefCount\": 0\n" + "}\n",
+        true, true);
+    checkOutput(err, "Deleted tenant 'dev'.\n", false);
     deleteVolume("dev");
 
     // Sanity check: tenant list should be empty
@@ -656,12 +674,13 @@ public class TestOzoneTenantShell {
 
   @Test
   public void testListTenantUsers() throws IOException {
-    executeHA(tenantShell, new String[] {"create", "tenant1"});
-    checkOutput(out, "Created tenant 'tenant1'.\n", true);
+    executeHA(tenantShell, new String[] {"--verbose", "create", "tenant1"});
+    checkOutput(out, "{\n" +
+        "  \"tenantId\": \"tenant1\"\n" + "}\n", true, true);
     checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
-        "user", "assign", "alice", "--tenant=tenant1"});
+        "--verbose", "user", "assign", "alice", "--tenant=tenant1"});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='tenant1$alice'\n"
         + "export AWS_SECRET_ACCESS_KEY='", false);
     checkOutput(err, "Assigned 'alice' to 'tenant1'" +
@@ -671,40 +690,54 @@ public class TestOzoneTenantShell {
         "user", "assign", "bob", "--tenant=tenant1"});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='tenant1$bob'\n"
         + "export AWS_SECRET_ACCESS_KEY='", false);
-    checkOutput(err, "Assigned 'bob' to 'tenant1'" +
-        " with accessId 'tenant1$bob'.\n", true);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
-        "user", "list", "--tenant=tenant1"});
+        "user", "list", "tenant1"});
     checkOutput(out, "- User 'bob' with accessId 'tenant1$bob'\n" +
         "- User 'alice' with accessId 'tenant1$alice'\n", true);
     checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
-        "user", "list", "--tenant=tenant1", "--prefix=b"});
+        "user", "list", "tenant1", "--json"});
+    checkOutput(out, "[\n" + "  {\n" + "    \"user\": \"bob\",\n"
+        + "    \"accessId\": \"tenant1$bob\"\n" + "  },\n" + "  {\n"
+        + "    \"user\": \"alice\",\n" + "    \"accessId\": \"tenant1$alice\"\n"
+        + "  }\n" + "]\n", true);
+    checkOutput(err, "", true);
+
+    executeHA(tenantShell, new String[] {
+        "user", "list", "tenant1", "--prefix=b"});
     checkOutput(out, "- User 'bob' with accessId " +
         "'tenant1$bob'\n", true);
     checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
-        "user", "list", "--tenant=unknown"});
-    checkOutput(err, "Failed to Get Users in tenant 'unknown': " +
-        "Tenant 'unknown' doesn't exist.\n", true);
+        "user", "list", "tenant1", "--prefix=b", "--json"});
+    checkOutput(out, "[\n" + "  {\n" + "    \"user\": \"bob\",\n"
+        + "    \"accessId\": \"tenant1$bob\"\n" + "  }\n" + "]\n", true);
+    checkOutput(err, "", true);
+
+    int exitCode = executeHA(tenantShell, new String[] {
+        "user", "list", "unknown"});
+    Assert.assertTrue("Expected non-zero exit code", exitCode != 0);
+    checkOutput(out, "", true);
+    checkOutput(err, "Tenant 'unknown' doesn't exist.\n", true);
 
     // Clean up
     executeHA(tenantShell, new String[] {
         "user", "revoke", "tenant1$alice"});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked accessId", false);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
-        "user", "revoke", "tenant1$bob"});
+        "--verbose", "user", "revoke", "tenant1$bob"});
     checkOutput(out, "", true);
     checkOutput(err, "Revoked accessId", false);
 
     executeHA(tenantShell, new String[] {"delete", "tenant1"});
-    checkOutput(out, "Deleted tenant 'tenant1'.\n", false);
-    checkOutput(err, "", true);
+    checkOutput(out, "", true);
+    checkOutput(err, "Deleted tenant 'tenant1'.\n", false);
     deleteVolume("tenant1");
 
     // Sanity check: tenant list should be empty
@@ -720,28 +753,26 @@ public class TestOzoneTenantShell {
 
     // Create test tenant
     executeHA(tenantShell, new String[] {"create", tenantName});
-    checkOutput(out, "Created tenant '" + tenantName + "'.\n", true);
+    checkOutput(out, "", true);
     checkOutput(err, "", true);
 
     // Set secret for non-existent accessId. Expect failure
     executeHA(tenantShell, new String[] {
         "user", "set-secret", tenantName + "$alice", "--secret=somesecret0"});
     checkOutput(out, "", true);
-    checkOutput(err, "AccessId '" + tenantName + "$alice' doesn't exist\n",
-        true);
+    checkOutput(err, "accessId '" + tenantName + "$alice' not found.\n", true);
 
     // Assign a user to the tenant so that we have an accessId entry
     executeHA(tenantShell, new String[] {
         "user", "assign", "alice", "--tenant=" + tenantName});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$alice'\n" +
         "export AWS_SECRET_ACCESS_KEY='", false);
-    checkOutput(err, "Assigned 'alice' to '" + tenantName + "'" +
-        " with accessId '" + tenantName + "$alice'.\n", true);
+    checkOutput(err, "", true);
 
     // Set secret as OM admin should succeed
     executeHA(tenantShell, new String[] {
         "user", "setsecret", tenantName + "$alice",
-        "--secret=somesecret1", "--export"});
+        "--secret=somesecret1"});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$alice'\n" +
         "export AWS_SECRET_ACCESS_KEY='somesecret1'\n", true);
     checkOutput(err, "", true);
@@ -749,8 +780,8 @@ public class TestOzoneTenantShell {
     // Set empty secret key should fail
     int exitCode = executeHA(tenantShell, new String[] {
         "user", "setsecret", tenantName + "$alice",
-        "--secret=short", "--export"});
-    Assert.assertTrue("Command should have non-zero exit code!", exitCode != 0);
+        "--secret=short"});
+    Assert.assertTrue("Expected non-zero exit code", exitCode != 0);
     checkOutput(out, "", true);
     checkOutput(err, "Secret key length should be at least 8 characters\n",
         true);
@@ -768,7 +799,7 @@ public class TestOzoneTenantShell {
     ugiAlice.doAs((PrivilegedExceptionAction<Void>) () -> {
       executeHA(tenantShell, new String[] {
           "user", "setsecret", tenantName + "$alice",
-          "--secret=somesecret2", "--export"});
+          "--secret=somesecret2"});
       checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$alice'\n" +
           "export AWS_SECRET_ACCESS_KEY='somesecret2'\n", true);
       checkOutput(err, "", true);
@@ -780,8 +811,7 @@ public class TestOzoneTenantShell {
         "user", "assign", "bob", "--tenant=" + tenantName});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$bob'\n" +
         "export AWS_SECRET_ACCESS_KEY='", false);
-    checkOutput(err, "Assigned 'bob' to '" + tenantName + "'" +
-        " with accessId '" + tenantName + "$bob'.\n", true);
+    checkOutput(err, "", true);
 
     final UserGroupInformation ugiBob = UserGroupInformation
         .createUserForTesting("bob",  new String[] {"usergroup"});
@@ -789,7 +819,7 @@ public class TestOzoneTenantShell {
     ugiBob.doAs((PrivilegedExceptionAction<Void>) () -> {
       int exitC = executeHA(tenantShell, new String[] {
           "user", "setsecret", tenantName + "$alice",
-          "--secret=somesecret2", "--export"});
+          "--secret=somesecret2"});
       Assert.assertTrue("Should return non-zero exit code!", exitC != 0);
       checkOutput(out, "", true);
       checkOutput(err, "Permission denied. Requested accessId "
@@ -808,13 +838,13 @@ public class TestOzoneTenantShell {
         tenantName + "$" + ugiBob.getShortUserName(),
         "--tenant=" + tenantName, "--delegated=false"});
     checkOutput(out, "", true);
-    checkOutput(err, "Assigned admin", false);
+    checkOutput(err, "", true);
 
     // Set secret should succeed now
     ugiBob.doAs((PrivilegedExceptionAction<Void>) () -> {
       executeHA(tenantShell, new String[] {
           "user", "setsecret", tenantName + "$alice",
-          "--secret=somesecret2", "--export"});
+          "--secret=somesecret2"});
       checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$alice'\n" +
           "export AWS_SECRET_ACCESS_KEY='somesecret2'\n", true);
       checkOutput(err, "", true);
@@ -825,21 +855,21 @@ public class TestOzoneTenantShell {
     executeHA(tenantShell, new String[] {"user", "revoke-admin",
         tenantName + "$" + ugiBob.getShortUserName()});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked admin", false);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
         "user", "revoke", tenantName + "$bob"});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked accessId", false);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
         "user", "revoke", tenantName + "$alice"});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked accessId", false);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {"delete", tenantName});
-    checkOutput(out, "Deleted tenant '" + tenantName + "'.\n", false);
-    checkOutput(err, "", true);
+    checkOutput(out, "", true);
+    checkOutput(err, "Deleted tenant '" + tenantName + "'.\n", false);
     deleteVolume(tenantName);
 
     // Sanity check: tenant list should be empty
@@ -863,7 +893,7 @@ public class TestOzoneTenantShell {
 
     // Create test tenant
     executeHA(tenantShell, new String[] {"create", tenantName});
-    checkOutput(out, "Created tenant '" + tenantName + "'.\n", true);
+    checkOutput(out, "", true);
     checkOutput(err, "", true);
 
     // Assign alice and bob as tenant users
@@ -871,29 +901,27 @@ public class TestOzoneTenantShell {
         "user", "assign", "alice", "--tenant=" + tenantName});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$alice'\n" +
         "export AWS_SECRET_ACCESS_KEY='", false);
-    checkOutput(err, "Assigned 'alice' to '" + tenantName + "'" +
-        " with accessId '" + tenantName + "$alice'.\n", true);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
         "user", "assign", "bob", "--tenant=" + tenantName});
     checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$bob'\n" +
         "export AWS_SECRET_ACCESS_KEY='", false);
-    checkOutput(err, "Assigned 'bob' to '" + tenantName + "'" +
-        " with accessId '" + tenantName + "$bob'.\n", true);
+    checkOutput(err, "", true);
 
     // Make alice a delegated tenant admin
     executeHA(tenantShell, new String[] {"user", "assign-admin",
         tenantName + "$" + ugiAlice.getShortUserName(),
         "--tenant=" + tenantName, "--delegated=true"});
     checkOutput(out, "", true);
-    checkOutput(err, "Assigned admin", false);
+    checkOutput(err, "", true);
 
     // Make bob a non-delegated tenant admin
     executeHA(tenantShell, new String[] {"user", "assign-admin",
         tenantName + "$" + ugiBob.getShortUserName(),
         "--tenant=" + tenantName, "--delegated=false"});
     checkOutput(out, "", true);
-    checkOutput(err, "Assigned admin", false);
+    checkOutput(err, "", true);
 
     // Start test matrix
 
@@ -907,13 +935,12 @@ public class TestOzoneTenantShell {
           "user", "assign", "carol", "--tenant=" + tenantName});
       checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$carol'\n"
           + "export AWS_SECRET_ACCESS_KEY='", false);
-      checkOutput(err, "Assigned 'carol' to '" + tenantName + "' with accessId"
-          + " '" + tenantName + "$carol'.\n", true);
+      checkOutput(err, "", true);
 
       // Set secret should work
       executeHA(tenantShell, new String[] {
           "user", "setsecret", tenantName + "$alice",
-          "--secret=somesecret2", "--export"});
+          "--secret=somesecret2"});
       checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$alice'\n" +
           "export AWS_SECRET_ACCESS_KEY='somesecret2'\n", true);
       checkOutput(err, "", true);
@@ -923,32 +950,32 @@ public class TestOzoneTenantShell {
           tenantName + "$carol",
           "--tenant=" + tenantName, "--delegated=true"});
       checkOutput(out, "", true);
-      checkOutput(err, "Assigned admin", false);
+      checkOutput(err, "", true);
 
       // Revoke carol's tenant admin privilege
       executeHA(tenantShell, new String[] {"user", "revoke-admin",
           tenantName + "$carol"});
       checkOutput(out, "", true);
-      checkOutput(err, "Revoked admin", false);
+      checkOutput(err, "", true);
 
       // Make carol a tenant non-delegated tenant admin
       executeHA(tenantShell, new String[] {"user", "assign-admin",
           tenantName + "$carol",
           "--tenant=" + tenantName, "--delegated=false"});
       checkOutput(out, "", true);
-      checkOutput(err, "Assigned admin", false);
+      checkOutput(err, "", true);
 
       // Revoke carol's tenant admin privilege
       executeHA(tenantShell, new String[] {"user", "revoke-admin",
           tenantName + "$carol"});
       checkOutput(out, "", true);
-      checkOutput(err, "Revoked admin", false);
+      checkOutput(err, "", true);
 
       // Revoke carol's accessId from this tenant
       executeHA(tenantShell, new String[] {
           "user", "revoke", tenantName + "$carol"});
       checkOutput(out, "", true);
-      checkOutput(err, "Revoked accessId", false);
+      checkOutput(err, "", true);
       return null;
     });
 
@@ -964,13 +991,12 @@ public class TestOzoneTenantShell {
           "user", "assign", "carol", "--tenant=" + tenantName});
       checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$carol'\n"
           + "export AWS_SECRET_ACCESS_KEY='", false);
-      checkOutput(err, "Assigned 'carol' to '" + tenantName + "' with accessId"
-          + " '" + tenantName + "$carol'.\n", true);
+      checkOutput(err, "", true);
 
       // Set secret should work, even for a non-delegated admin
       executeHA(tenantShell, new String[] {
           "user", "setsecret", tenantName + "$alice",
-          "--secret=somesecret2", "--export"});
+          "--secret=somesecret2"});
       checkOutput(out, "export AWS_ACCESS_KEY_ID='" + tenantName + "$alice'\n" +
           "export AWS_SECRET_ACCESS_KEY='somesecret2'\n", true);
       checkOutput(err, "", true);
@@ -980,27 +1006,29 @@ public class TestOzoneTenantShell {
           tenantName + "$carol",
           "--tenant=" + tenantName, "--delegated=true"});
       checkOutput(out, "", true);
-      checkOutput(err, "Failed to assign admin", false);
+      checkOutput(err, "User 'bob' is neither an Ozone admin "
+          + "nor a delegated admin of tenant", false);
 
       // Attempt to make carol a tenant non-delegated tenant admin, should fail
       executeHA(tenantShell, new String[] {"user", "assign-admin",
           tenantName + "$carol",
           "--tenant=" + tenantName, "--delegated=false"});
       checkOutput(out, "", true);
-      checkOutput(err, "Failed to assign admin", false);
+      checkOutput(err, "User 'bob' is neither an Ozone admin "
+          + "nor a delegated admin of tenant", false);
 
       // Attempt to revoke tenant admin, should fail at the permission check
       executeHA(tenantShell, new String[] {"user", "revoke-admin",
           tenantName + "$carol"});
       checkOutput(out, "", true);
-      checkOutput(err, "User 'bob' is neither an Ozone admin nor a delegated "
-          + "admin of tenant", false);
+      checkOutput(err, "User 'bob' is neither an Ozone admin "
+          + "nor a delegated admin of tenant", false);
 
       // Revoke carol's accessId from this tenant
       executeHA(tenantShell, new String[] {
           "user", "revoke", tenantName + "$carol"});
       checkOutput(out, "", true);
-      checkOutput(err, "Revoked accessId", false);
+      checkOutput(err, "", true);
       return null;
     });
 
@@ -1008,26 +1036,26 @@ public class TestOzoneTenantShell {
     executeHA(tenantShell, new String[] {"user", "revoke-admin",
         tenantName + "$" + ugiAlice.getShortUserName()});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked admin", false);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
         "user", "revoke", tenantName + "$" + ugiAlice.getShortUserName()});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked accessId", false);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {"user", "revoke-admin",
         tenantName + "$" + ugiBob.getShortUserName()});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked admin", false);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {
         "user", "revoke", tenantName + "$" + ugiBob.getShortUserName()});
     checkOutput(out, "", true);
-    checkOutput(err, "Revoked accessId", false);
+    checkOutput(err, "", true);
 
     executeHA(tenantShell, new String[] {"delete", tenantName});
-    checkOutput(out, "Deleted tenant '" + tenantName + "'.\n", false);
-    checkOutput(err, "", true);
+    checkOutput(out, "", true);
+    checkOutput(err, "Deleted tenant '" + tenantName + "'.\n", false);
     deleteVolume(tenantName);
   }
 }
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
index 6c816cbb89..c9b58064fb 100644
--- 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
@@ -17,16 +17,19 @@
  */
 package org.apache.hadoop.ozone.shell.tenant;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
 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.ExtendedUserAccessIdInfo;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
+import org.jooq.tools.StringUtils;
 import picocli.CommandLine;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -39,56 +42,69 @@ 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<>();
+  @CommandLine.Parameters(description = "User name (principal)", arity = "1..1")
+  private String userPrincipal;
 
-  // TODO: HDDS-6340. Add an option to print JSON result
-
-  private boolean isEmptyList(List<String> list) {
-    return list == null || list.size() == 0;
-  }
+  @CommandLine.Option(names = {"--json", "-j"},
+      description = "Print result in JSON")
+  private boolean printJson;
 
   @Override
-  protected void execute(OzoneClient client, OzoneAddress address) {
-    final ObjectStore objStore = client.getObjectStore();
+  protected void execute(OzoneClient client, OzoneAddress address)
+      throws IOException {
 
-    if (isEmptyList(userPrincipals)) {
+    if (StringUtils.isEmpty(userPrincipal)) {
       GenericCli.missingSubcommand(spec);
       return;
     }
 
-    for (final String userPrincipal : userPrincipals) {
-      try {
-        final TenantUserInfoValue tenantUserInfo =
-            objStore.tenantGetUserInfo(userPrincipal);
-        List<ExtendedUserAccessIdInfo> 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:");
+    final TenantUserInfoValue tenantUserInfo =
+        client.getObjectStore().tenantGetUserInfo(userPrincipal);
+    final List<ExtendedUserAccessIdInfo> accessIdInfoList =
+        tenantUserInfo.getAccessIdInfoList();
+    if (accessIdInfoList.size() == 0) {
+      err().println("User '" + userPrincipal +
+          "' is not assigned to any tenant.");
+      return;
+    }
 
-        for (ExtendedUserAccessIdInfo accessIdInfo : accessIdInfoList) {
-          // Get admin info
-          final String adminInfoString;
-          if (accessIdInfo.getIsAdmin()) {
-            adminInfoString = accessIdInfo.getIsDelegatedAdmin() ?
-                " delegated admin" : " admin";
-          } else {
-            adminInfoString = "";
-          }
-          out().format("- Tenant '%s'%s with accessId '%s'%n",
-              accessIdInfo.getTenantId(),
-              adminInfoString,
-              accessIdInfo.getAccessId());
+    if (!printJson) {
+      out().println("User '" + userPrincipal + "' is assigned to:");
+      accessIdInfoList.forEach(accessIdInfo -> {
+        // Get admin info
+        final String adminInfoString;
+        if (accessIdInfo.getIsAdmin()) {
+          adminInfoString = accessIdInfo.getIsDelegatedAdmin() ?
+              " delegated admin" : " admin";
+        } else {
+          adminInfoString = "";
         }
+        out().format("- Tenant '%s'%s with accessId '%s'%n",
+            accessIdInfo.getTenantId(),
+            adminInfoString,
+            accessIdInfo.getAccessId());
+      });
+    } else {
 
-      } catch (IOException e) {
-        err().println("Failed to GetUserInfo of user '" + userPrincipal
-            + "': " + e.getMessage());
-      }
+      final JsonObject resObj = new JsonObject();
+      resObj.addProperty("user", userPrincipal);
+
+      final JsonArray arr = new JsonArray();
+      accessIdInfoList.forEach(accessIdInfo -> {
+        final JsonObject tenantObj = new JsonObject();
+        tenantObj.addProperty("accessId", accessIdInfo.getAccessId());
+        tenantObj.addProperty("tenantId", accessIdInfo.getTenantId());
+        tenantObj.addProperty("isAdmin", accessIdInfo.getIsAdmin());
+        tenantObj.addProperty("isDelegatedAdmin",
+            accessIdInfo.getIsDelegatedAdmin());
+        arr.add(tenantObj);
+      });
+
+      resObj.add("tenants", arr);
+
+      final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+      out().println(gson.toJson(resObj));
     }
+
   }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantAssignAdminHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantAssignAdminHandler.java
index 2d9ec62a0d..1ec0096865 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantAssignAdminHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantAssignAdminHandler.java
@@ -17,30 +17,27 @@
  */
 package org.apache.hadoop.ozone.shell.tenant;
 
-import org.apache.hadoop.ozone.client.ObjectStore;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
 import org.apache.hadoop.ozone.client.OzoneClient;
-import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
 import picocli.CommandLine;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.PERMISSION_DENIED;
 
 /**
  * ozone tenant user assign-admin.
  *
- * User must already be assigned to tenant with, will be rejected otherwise.
+ * User must already be assigned to the tenant, will be rejected otherwise.
  */
 @CommandLine.Command(name = "assign-admin",
     aliases = {"assignadmin"},
     description = "Assign admin role to accessIds in a tenant")
 public class TenantAssignAdminHandler extends TenantHandler {
 
-  @CommandLine.Parameters(description = "List of accessIds", arity = "1..")
-  private List<String> accessIds = new ArrayList<>();
+  @CommandLine.Parameters(description = "Access ID", arity = "1..1")
+  private String accessId;
 
   @CommandLine.Option(names = {"-t", "--tenant"},
       description = "Tenant name")
@@ -50,30 +47,21 @@ public class TenantAssignAdminHandler extends TenantHandler {
       description = "Set to true (default) to assign delegated admin")
   private boolean delegated;
 
-  // TODO: HDDS-6340. Add an option to print JSON result
-
   @Override
-  protected void execute(OzoneClient client, OzoneAddress address) {
-    final ObjectStore objStore = client.getObjectStore();
+  protected void execute(OzoneClient client, OzoneAddress address)
+      throws IOException {
 
-    for (final String accessId : accessIds) {
-      try {
-        objStore.tenantAssignAdmin(accessId, tenantId, delegated);
-        // TODO: Make tenantAssignAdmin return accessId, tenantId, user later.
-        err().println("Assigned admin to '" + accessId +
-            (tenantId != null ? "' in tenant '" + tenantId : "") + "'");
-      } catch (IOException e) {
-        err().println("Failed to assign admin to '" + accessId +
-            (tenantId != null ? "' in tenant '" + tenantId : "") + "': " +
-            e.getMessage());
-        if (e instanceof OMException) {
-          final OMException omEx = (OMException) e;
-          // Don't bother continuing the loop if current user isn't Ozone admin
-          if (omEx.getResult().equals(PERMISSION_DENIED)) {
-            break;
-          }
-        }
-      }
+    client.getObjectStore().tenantAssignAdmin(accessId, tenantId, delegated);
+
+    if (isVerbose()) {
+      final JsonObject obj = new JsonObject();
+      obj.addProperty("accessId", accessId);
+      obj.addProperty("tenantId", tenantId);
+      obj.addProperty("isAdmin", true);
+      obj.addProperty("isDelegatedAdmin", delegated);
+      final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+      out().println(gson.toJson(obj));
     }
+
   }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantAssignUserAccessIdHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantAssignUserAccessIdHandler.java
index 8ef65d434f..498afefcb1 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantAssignUserAccessIdHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantAssignUserAccessIdHandler.java
@@ -18,16 +18,12 @@
 package org.apache.hadoop.ozone.shell.tenant;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneClient;
-import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
 import picocli.CommandLine;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 
 import static org.apache.hadoop.ozone.OzoneConsts.TENANT_ID_USERNAME_DELIMITER;
 
@@ -41,9 +37,8 @@ public class TenantAssignUserAccessIdHandler extends TenantHandler {
   @CommandLine.Spec
   private CommandLine.Model.CommandSpec spec;
 
-  @CommandLine.Parameters(description = "List of user principals",
-      arity = "1..")
-  private List<String> userPrincipals = new ArrayList<>();
+  @CommandLine.Parameters(description = "User name", arity = "1..1")
+  private String userPrincipal;
 
   @CommandLine.Option(names = {"-t", "--tenant"},
       description = "Tenant name", required = true)
@@ -54,56 +49,35 @@ public class TenantAssignUserAccessIdHandler extends TenantHandler {
           + "If unspecified, accessId would be in the form of "
           + "TenantName$Principal.",
       hidden = true)
-  // This option is intentionally hidden for now. Because accessId isn't
-  //  restricted in any way so far and this could cause some conflict with
-  //  `s3 getsecret` and leak the secret if an admin isn't careful.
+  // This option is intentionally hidden for now. Because if accessId isn't
+  //  restricted in any way this might cause `ozone s3 getsecret` to
+  //  unintentionally leak secret if an admin isn't careful.
   private String accessId;
 
-  // TODO: HDDS-6340. Add an option to print JSON result
-
-  private String getDefaultAccessId(String userPrincipal) {
-    return tenantId + TENANT_ID_USERNAME_DELIMITER + userPrincipal;
+  private String getDefaultAccessId(String userPrinc) {
+    return tenantId + TENANT_ID_USERNAME_DELIMITER + userPrinc;
   }
 
   @Override
-  protected void execute(OzoneClient client, OzoneAddress address) {
-    final ObjectStore objStore = client.getObjectStore();
+  protected void execute(OzoneClient client, OzoneAddress address)
+      throws IOException {
 
     if (StringUtils.isEmpty(accessId)) {
-      accessId = getDefaultAccessId(userPrincipals.get(0));
-    } else if (userPrincipals.size() > 1) {
-      err().println("Manually specifying accessId is only supported when there "
-          + "is one user principal in the command line. Reduce the number of "
-          + "principal to one and try again.");
-      return;
+      accessId = getDefaultAccessId(userPrincipal);
     }
 
-    for (int i = 0; i < userPrincipals.size(); i++) {
-      final String principal = userPrincipals.get(i);
-      try {
-        if (i >= 1) {
-          accessId = getDefaultAccessId(principal);
-        }
-        final S3SecretValue resp =
-            objStore.tenantAssignUserAccessId(principal, tenantId, accessId);
-        err().println("Assigned '" + principal + "' to '" + tenantId +
-            "' with accessId '" + accessId + "'.");
-        out().println("export AWS_ACCESS_KEY_ID='" +
-            resp.getAwsAccessKey() + "'");
-        out().println("export AWS_SECRET_ACCESS_KEY='" +
-            resp.getAwsSecret() + "'");
-      } catch (IOException e) {
-        err().println("Failed to assign '" + principal + "' to '" +
-            tenantId + "': " + e.getMessage());
-        if (e instanceof OMException) {
-          final OMException omException = (OMException) e;
-          if (omException.getResult().equals(
-              OMException.ResultCodes.TENANT_NOT_FOUND)) {
-            // If tenant does not exist, don't bother continuing the loop
-            break;
-          }
-        }
-      }
+    final S3SecretValue resp = client.getObjectStore()
+        .tenantAssignUserAccessId(userPrincipal, tenantId, accessId);
+
+    out().println(
+        "export AWS_ACCESS_KEY_ID='" + resp.getAwsAccessKey() + "'");
+    out().println(
+        "export AWS_SECRET_ACCESS_KEY='" + resp.getAwsSecret() + "'");
+
+    if (isVerbose()) {
+      err().println("Assigned '" + userPrincipal + "' to '" + tenantId +
+          "' with accessId '" + accessId + "'.");
     }
+
   }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantBucketLinkHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantBucketLinkHandler.java
index 3a03600ff8..39911b987a 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantBucketLinkHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantBucketLinkHandler.java
@@ -50,8 +50,6 @@ public class TenantBucketLinkHandler extends TenantHandler {
       converter = BucketUri.class)
   private OzoneAddress target;
 
-  // TODO: HDDS-6340. Add an option to print JSON result
-
   @Override
   protected void execute(OzoneClient client, OzoneAddress address)
       throws IOException {
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantCreateHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantCreateHandler.java
index a550bf9120..0789124412 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantCreateHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantCreateHandler.java
@@ -17,6 +17,9 @@
  */
 package org.apache.hadoop.ozone.shell.tenant;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
 import picocli.CommandLine;
@@ -34,18 +37,19 @@ public class TenantCreateHandler extends TenantHandler {
   @CommandLine.Parameters(description = "Tenant name", arity = "1..1")
   private String tenantId;
 
-  // TODO: HDDS-6340. Add an option to print JSON result
-
   @Override
   protected void execute(OzoneClient client, OzoneAddress address)
       throws IOException {
-    try {
-      client.getObjectStore().createTenant(tenantId);
-      // Note: RpcClient#createTenant prints volume name in info level LOG
-      out().println("Created tenant '" + tenantId + "'.");
-    } catch (IOException e) {
-      // Throw exception to make client exit code non-zero
-      throw new IOException("Failed to create tenant '" + tenantId + "'", e);
+
+    client.getObjectStore().createTenant(tenantId);
+    // RpcClient#createTenant prints INFO level log of tenant and volume name
+
+    if (isVerbose()) {
+      final JsonObject obj = new JsonObject();
+      obj.addProperty("tenantId", tenantId);
+      final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+      out().println(gson.toJson(obj));
     }
+
   }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantDeleteHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantDeleteHandler.java
index 22c3cb24ae..9924ac827a 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantDeleteHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantDeleteHandler.java
@@ -17,6 +17,9 @@
  */
 package org.apache.hadoop.ozone.shell.tenant;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
@@ -35,30 +38,36 @@ public class TenantDeleteHandler extends TenantHandler {
   @CommandLine.Parameters(description = "Tenant name", arity = "1..1")
   private String tenantId;
 
-  // TODO: HDDS-6340. Add an option to print JSON result
-
   @Override
   protected void execute(OzoneClient client, OzoneAddress address)
       throws IOException {
-    try {
-      final DeleteTenantState resp =
-          client.getObjectStore().deleteTenant(tenantId);
-      out().println("Deleted tenant '" + tenantId + "'.");
-      long volumeRefCount = resp.getVolRefCount();
-      assert (volumeRefCount >= 0L);
-      final String volumeName = resp.getVolumeName();
-      final String extraPrompt =
-          "But the associated volume '" + volumeName + "' is not removed. ";
-      if (volumeRefCount == 0L) {
-        out().println(extraPrompt + "To delete it, run"
-            + "\n    ozone sh volume delete " + volumeName + "\n");
-      } else {
-        out().println(extraPrompt + "And it is still referenced by some other "
-            + "Ozone features (refCount is " + volumeRefCount + ").");
-      }
-    } catch (IOException e) {
-      // Throw exception to make client exit code non-zero
-      throw new IOException("Failed to delete tenant '" + tenantId + "'", e);
+
+    final DeleteTenantState resp =
+        client.getObjectStore().deleteTenant(tenantId);
+
+    err().println("Deleted tenant '" + tenantId + "'.");
+    long volumeRefCount = resp.getVolRefCount();
+    assert (volumeRefCount >= 0L);
+    final String volumeName = resp.getVolumeName();
+    final String extraPrompt =
+        "But the associated volume '" + volumeName + "' is not removed. ";
+    if (volumeRefCount == 0L) {
+      err().println(extraPrompt + "To delete it, run"
+          + "\n    ozone sh volume delete " + volumeName + "\n");
+    } else {
+      err().println(extraPrompt + "And it is still referenced by some "
+          + "other Ozone features (refCount is " + volumeRefCount + ").");
     }
+
+    if (isVerbose()) {
+      final JsonObject obj = new JsonObject();
+      obj.addProperty("tenantId", tenantId);
+      obj.addProperty("volumeName", resp.getVolumeName());
+      obj.addProperty("volumeRefCount", resp.getVolRefCount());
+      final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+      // Print raw response to stderr if verbose
+      out().println(gson.toJson(obj));
+    }
+
   }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantGetSecretHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantGetSecretHandler.java
index a700cdf8d1..1897ad403d 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantGetSecretHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantGetSecretHandler.java
@@ -17,65 +17,37 @@
  */
 package org.apache.hadoop.ozone.shell.tenant;
 
-import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneClient;
-import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
 import picocli.CommandLine;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.ACCESS_ID_NOT_FOUND;
 
 /**
  * ozone tenant user get-secret.
  */
 @CommandLine.Command(name = "get-secret",
     aliases = {"getsecret"},
-    description = "Get secret for tenant user accessIds. " +
+    description = "Get secret given tenant user accessId. " +
         "This differs from `ozone s3 getsecret` that this would not " +
-        "generate a key/secret pair when the accessId doesn't exist.")
+        "generate secret when the given access ID doesn't exist.")
 public class TenantGetSecretHandler extends TenantHandler {
 
-  @CommandLine.Parameters(description = "List of accessIds", arity = "1..")
-  private List<String> accessIds = new ArrayList<>();
-
-  @CommandLine.Option(names = {"-e", "--export"},
-      description = "Print out variables together with 'export' prefix")
-  private boolean export;
+  @CommandLine.Parameters(description = "Access ID", arity = "1..1")
+  private String accessId;
 
   @Override
   protected void execute(OzoneClient client, OzoneAddress address)
       throws IOException {
-    final ObjectStore objectStore = client.getObjectStore();
-
-    for (final String accessId : accessIds) {
 
-      try {
-        final S3SecretValue accessIdSecretKeyPair =
-            objectStore.getS3Secret(accessId, false);
-        if (export) {
-          out().println("export AWS_ACCESS_KEY_ID='" +
-              accessIdSecretKeyPair.getAwsAccessKey() + "'");
-          out().println("export AWS_SECRET_ACCESS_KEY='" +
-              accessIdSecretKeyPair.getAwsSecret() + "'");
-        } else {
-          out().println(accessIdSecretKeyPair);
-        }
-      } catch (OMException omEx) {
-        if (omEx.getResult().equals(ACCESS_ID_NOT_FOUND)) {
-          // Print to stderr here in order not to contaminate stdout just in
-          // case -e is specified.
-          err().println("AccessId '" + accessId + "' doesn't exist");
-          // Continue the loop if it's just ACCESSID_NOT_FOUND
-        } else {
-          throw omEx;
-        }
-      }
+    final S3SecretValue accessIdSecretKeyPair =
+        client.getObjectStore().getS3Secret(accessId, false);
+    // Always print in export format
+    out().printf("export AWS_ACCESS_KEY_ID='%s'%n",
+        accessIdSecretKeyPair.getAwsAccessKey());
+    out().printf("export AWS_SECRET_ACCESS_KEY='%s'%n",
+        accessIdSecretKeyPair.getAwsSecret());
 
-    }
   }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantHandler.java
index e5a7a1e82b..a76ab7420a 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantHandler.java
@@ -36,8 +36,6 @@ public abstract class TenantHandler extends Handler {
           " cluster")
   private String omServiceID;
 
-  // TODO: HDDS-6340. Add an option to print JSON result
-
   public String getOmServiceID() {
     return omServiceID;
   }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantListHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantListHandler.java
index 1661781e52..6f0428bd7b 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantListHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantListHandler.java
@@ -17,7 +17,10 @@
  */
 package org.apache.hadoop.ozone.shell.tenant;
 
-import org.apache.hadoop.ozone.client.ObjectStore;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.om.helpers.TenantStateList;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
@@ -33,56 +36,37 @@ import java.io.IOException;
     description = "List tenants")
 public class TenantListHandler extends TenantHandler {
 
-  @CommandLine.Option(names = {"--long"},
-      // Not using -l here as it potentially collides with -l inside ListOptions
-      //  if we do need pagination at some point.
-      description = "List in long format")
-  private boolean longFormat;
-
-  @CommandLine.Option(names = {"--header", "-H"},
-      description = "Print header")
-  private boolean printHeader;
-
-  // TODO: HDDS-6340. Add an option to print JSON result
-//  @CommandLine.Option(names = {"--json", "-j"},
-//      description = "Print the result in JSON.")
-//  private boolean printJson;
+  @CommandLine.Option(names = {"--json", "-j"},
+      description = "Print detailed result in JSON")
+  private boolean printJson;
 
   @Override
-  protected void execute(OzoneClient client, OzoneAddress address) {
-    final ObjectStore objStore = client.getObjectStore();
-    try {
-      TenantStateList tenantStateList = objStore.listTenant();
+  protected void execute(OzoneClient client, OzoneAddress address)
+      throws IOException {
 
-      if (printHeader) {
-        // Assuming default terminal width 120 / 6 ~= 20. +1 for space
-        out().format(longFormat ? "%-21s" : "%s%n",
-            "Tenant");
-        if (longFormat) {
-          out().format("%-21s%-21s%-21s%-21s%s%n",
-              "BucketNS",
-              "UserRole",
-              "AdminRole",
-              "BucketNSPolicy",
-              "BucketPolicy");
-        }
-      }
+    TenantStateList tenantStateList = client.getObjectStore().listTenant();
 
+    if (!printJson) {
+      tenantStateList.getTenantStateList().forEach(tenantState ->
+          out().println(tenantState.getTenantId()));
+    } else {
+      final JsonArray resArray = new JsonArray();
       tenantStateList.getTenantStateList().forEach(tenantState -> {
-        out().format(longFormat ? "%-21s" : "%s%n",
-            tenantState.getTenantId());
-        if (longFormat) {
-          out().format("%-21s%-21s%-21s%-21s%s%n",
-              tenantState.getBucketNamespaceName(),
-              tenantState.getUserRoleName(),
-              tenantState.getAdminRoleName(),
-              tenantState.getBucketNamespacePolicyName(),
-              tenantState.getBucketPolicyName());
-        }
+        final JsonObject obj = new JsonObject();
+        obj.addProperty("tenantId", tenantState.getTenantId());
+        obj.addProperty("bucketNamespaceName",
+            tenantState.getBucketNamespaceName());
+        obj.addProperty("userRoleName", tenantState.getUserRoleName());
+        obj.addProperty("adminRoleName", tenantState.getAdminRoleName());
+        obj.addProperty("bucketNamespacePolicyName",
+            tenantState.getBucketNamespacePolicyName());
+        obj.addProperty("bucketPolicyName",
+            tenantState.getBucketPolicyName());
+        resArray.add(obj);
       });
-
-    } catch (IOException e) {
-      LOG.error("Failed to list tenants: {}", e.getMessage());
+      final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+      out().println(gson.toJson(resArray));
     }
+
   }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantListUsersHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantListUsersHandler.java
index 71acdedfbc..e27a8cecd8 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantListUsersHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantListUsersHandler.java
@@ -20,11 +20,12 @@ package org.apache.hadoop.ozone.shell.tenant;
 
 import java.io.IOException;
 
-import org.apache.commons.lang3.StringUtils;
-import org.apache.hadoop.ozone.client.ObjectStore;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.om.helpers.TenantUserList;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.UserAccessIdInfo;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
 import org.apache.hadoop.ozone.shell.s3.S3Handler;
 
@@ -41,34 +42,40 @@ public class TenantListUsersHandler extends S3Handler {
   @CommandLine.Spec
   private CommandLine.Model.CommandSpec spec;
 
-  @CommandLine.Option(names = {"-t", "--tenant"},
-      description = "Tenant name")
+  @CommandLine.Parameters(description = "Tenant name", arity = "1..1")
   private String tenantId;
 
-  @CommandLine.Option(names = {"-p", "--prefix"},
+  @CommandLine.Option(names = {"--prefix", "-p"},
       description = "Filter users with this prefix.")
   private String prefix;
 
-  // TODO: HDDS-6340. Add an option to print JSON result
+  @CommandLine.Option(names = {"--json", "-j"},
+      description = "Print detailed result in JSON")
+  private boolean printJson;
 
   @Override
-  protected void execute(OzoneClient client, OzoneAddress address) {
-    final ObjectStore objStore = client.getObjectStore();
+  protected void execute(OzoneClient client, OzoneAddress address)
+      throws IOException {
 
-    if (StringUtils.isEmpty(tenantId)) {
-      err().println("Please specify a tenant name with -t.");
-      return;
-    }
-    try {
-      TenantUserList usersInTenant =
-          objStore.listUsersInTenant(tenantId, prefix);
-      for (UserAccessIdInfo accessIdInfo : usersInTenant.getUserAccessIds()) {
+    final TenantUserList usersInTenant =
+        client.getObjectStore().listUsersInTenant(tenantId, prefix);
+
+    if (!printJson) {
+      usersInTenant.getUserAccessIds().forEach(accessIdInfo -> {
         out().println("- User '" + accessIdInfo.getUserPrincipal() +
             "' with accessId '" + accessIdInfo.getAccessId() + "'");
-      }
-    } catch (IOException e) {
-      err().println("Failed to Get Users in tenant '" + tenantId
-          + "': " + e.getMessage());
+      });
+    } else {
+      final JsonArray resArray = new JsonArray();
+      usersInTenant.getUserAccessIds().forEach(accessIdInfo -> {
+        final JsonObject obj = new JsonObject();
+        obj.addProperty("user", accessIdInfo.getUserPrincipal());
+        obj.addProperty("accessId", accessIdInfo.getAccessId());
+        resArray.add(obj);
+      });
+      final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+      out().println(gson.toJson(resArray));
     }
+
   }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantRevokeAdminHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantRevokeAdminHandler.java
index 21d40f50b8..419628246f 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantRevokeAdminHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantRevokeAdminHandler.java
@@ -17,17 +17,14 @@
  */
 package org.apache.hadoop.ozone.shell.tenant;
 
-import org.apache.hadoop.ozone.client.ObjectStore;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
 import org.apache.hadoop.ozone.client.OzoneClient;
-import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
 import picocli.CommandLine;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.PERMISSION_DENIED;
 
 /**
  * ozone tenant user revoke-admin.
@@ -37,37 +34,28 @@ import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.PERM
     description = "Revoke admin role from accessIds in a tenant")
 public class TenantRevokeAdminHandler extends TenantHandler {
 
-  @CommandLine.Parameters(description = "List of accessIds", arity = "1..")
-  private List<String> accessIds = new ArrayList<>();
+  @CommandLine.Parameters(description = "Access ID", arity = "1..1")
+  private String accessId;
 
   @CommandLine.Option(names = {"-t", "--tenant"},
       description = "Tenant name")
   private String tenantId;
 
-  // TODO: HDDS-6340. Add an option to print JSON result
-
   @Override
-  protected void execute(OzoneClient client, OzoneAddress address) {
-    final ObjectStore objStore = client.getObjectStore();
-
-    for (final String accessId : accessIds) {
-      try {
-        // TODO: Make tenantRevokeAdmin return accessId, tenantId, user later.
-        objStore.tenantRevokeAdmin(accessId, tenantId);
-        err().println("Revoked admin role of '" + accessId +
-            (tenantId != null ? "' from tenant '" + tenantId : "") + "'.");
-      } catch (IOException e) {
-        err().println("Failed to revoke admin role of '" + accessId +
-            (tenantId != null ? "' from tenant '" + tenantId : "") + "'" +
-            ": " + e.getMessage());
-        if (e instanceof OMException) {
-          final OMException omEx = (OMException) e;
-          // Don't bother continuing the loop if current user isn't Ozone admin
-          if (omEx.getResult().equals(PERMISSION_DENIED)) {
-            break;
-          }
-        }
-      }
+  protected void execute(OzoneClient client, OzoneAddress address)
+      throws IOException {
+
+    client.getObjectStore().tenantRevokeAdmin(accessId, tenantId);
+
+    if (isVerbose()) {
+      final JsonObject obj = new JsonObject();
+      obj.addProperty("accessId", accessId);
+      obj.addProperty("tenantId", tenantId);
+      obj.addProperty("isAdmin", false);
+      obj.addProperty("isDelegatedAdmin", false);
+      final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+      out().println(gson.toJson(obj));
     }
+
   }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantRevokeUserAccessIdHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantRevokeUserAccessIdHandler.java
index 008a64c7ac..0a26f83be9 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantRevokeUserAccessIdHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantRevokeUserAccessIdHandler.java
@@ -17,14 +17,11 @@
  */
 package org.apache.hadoop.ozone.shell.tenant;
 
-import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneClient;
 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 revoke.
@@ -33,26 +30,16 @@ import java.util.List;
     description = "Revoke user accessId to tenant")
 public class TenantRevokeUserAccessIdHandler extends TenantHandler {
 
-  @CommandLine.Spec
-  private CommandLine.Model.CommandSpec spec;
-
-  @CommandLine.Parameters(description = "List of user accessIds", arity = "1..")
-  private List<String> accessIds = new ArrayList<>();
-
-  // TODO: HDDS-6340. Add an option to print JSON result
+  @CommandLine.Parameters(description = "Access ID", arity = "1..1")
+  private String accessId;
 
   @Override
-  protected void execute(OzoneClient client, OzoneAddress address) {
-    final ObjectStore objStore = client.getObjectStore();
+  protected void execute(OzoneClient client, OzoneAddress address)
+      throws IOException {
 
-    accessIds.forEach(accessId -> {
-      try {
-        objStore.tenantRevokeUserAccessId(accessId);
-        err().format("Revoked accessId '%s'.%n", accessId);
-      } catch (IOException e) {
-        err().format("Failed to revoke accessId '%s': %s%n",
-            accessId, e.getMessage());
-      }
-    });
+    client.getObjectStore().tenantRevokeUserAccessId(accessId);
+    if (isVerbose()) {
+      err().format("Revoked accessId '%s'.%n", accessId);
+    }
   }
 }
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantSetSecretHandler.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantSetSecretHandler.java
index cec53732ca..1ae4a8e571 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantSetSecretHandler.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/tenant/TenantSetSecretHandler.java
@@ -17,17 +17,13 @@
  */
 package org.apache.hadoop.ozone.shell.tenant;
 
-import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneClient;
-import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
 import org.apache.hadoop.ozone.shell.OzoneAddress;
 import picocli.CommandLine;
 
 import java.io.IOException;
 
-import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.ACCESS_ID_NOT_FOUND;
-
 /**
  * ozone tenant user set-secret.
  */
@@ -43,36 +39,17 @@ public class TenantSetSecretHandler extends TenantHandler {
           description = "Secret key", required = true)
   private String secretKey;
 
-  @CommandLine.Option(names = {"-e", "--export"},
-          description = "Print out variables together with 'export' prefix")
-  private boolean export;
-
   @Override
   protected void execute(OzoneClient client, OzoneAddress address)
           throws IOException {
 
-    final ObjectStore objectStore = client.getObjectStore();
+    final S3SecretValue accessIdSecretKeyPair =
+        client.getObjectStore().setS3Secret(accessId, secretKey);
+
+    out().println("export AWS_ACCESS_KEY_ID='" +
+            accessIdSecretKeyPair.getAwsAccessKey() + "'");
+    out().println("export AWS_SECRET_ACCESS_KEY='" +
+            accessIdSecretKeyPair.getAwsSecret() + "'");
 
-    try {
-      final S3SecretValue accessIdSecretKeyPair =
-              objectStore.setS3Secret(accessId, secretKey);
-      if (export) {
-        out().println("export AWS_ACCESS_KEY_ID='" +
-                accessIdSecretKeyPair.getAwsAccessKey() + "'");
-        out().println("export AWS_SECRET_ACCESS_KEY='" +
-                accessIdSecretKeyPair.getAwsSecret() + "'");
-      } else {
-        out().println(accessIdSecretKeyPair);
-      }
-    } catch (OMException omEx) {
-      if (omEx.getResult().equals(ACCESS_ID_NOT_FOUND)) {
-        // Print to stderr here in order not to contaminate stdout just in
-        // case -e is specified.
-        throw new IOException("AccessId '" + accessId + "' doesn't exist",
-            omEx);
-      } else {
-        throw omEx;
-      }
-    }
   }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org