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 18:25:16 UTC

[ozone] branch HDDS-4944 updated: HDDS-5836. [Multi-Tenant] Use Ranger java client. (#3408)

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 917b079a61 HDDS-5836. [Multi-Tenant] Use Ranger java client. (#3408)
917b079a61 is described below

commit 917b079a61696c54ddf0bd31b887417fc38ad9cc
Author: Aswin Shakil Balasubramanian <as...@gmail.com>
AuthorDate: Fri May 20 11:25:11 2022 -0700

    HDDS-5836. [Multi-Tenant] Use Ranger java client. (#3408)
---
 .../multitenant/MultiTenantAccessController.java   | 213 -------
 .../RangerRestMultiTenantAccessController.java     | 636 ---------------------
 hadoop-ozone/dist/src/main/license/jar-report.txt  |  24 +
 hadoop-ozone/ozone-manager/pom.xml                 |  39 ++
 .../multitenant/MultiTenantAccessController.java   | 403 +++++++++++++
 .../RangerClientMultiTenantAccessController.java   | 324 +++++++++++
 .../InMemoryMultiTenantAccessController.java       | 154 +++++
 .../TestMultiTenantAccessController.java           | 398 +++++++++++++
 8 files changed, 1342 insertions(+), 849 deletions(-)

diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java
deleted file mode 100644
index 3b8975be45..0000000000
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java
+++ /dev/null
@@ -1,213 +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.multitenant;
-
-import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
-import org.apache.http.auth.BasicUserPrincipal;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Optional;
-
-/**
- * Defines the operations needed for multi-tenant access control.
- * Each implemented method should be atomic. A failure partway through any
- * one method call should not leave any state behind.
- */
-public interface MultiTenantAccessController {
-  /**
-   * This operation will fail if a policy with the same name already exists,
-   * or a policy for the same set of resources already exists.
-   *
-   * Roles defined in this policy that do not already exist will be created.
-   *
-   * @return The unique ID to refer to this policy.
-   */
-  long createPolicy(Policy policy) throws Exception;
-
-  void deletePolicy(long policyID) throws Exception;
-
-  Policy getPolicy(long policyID) throws Exception;
-
-  void updatePolicy(long policyID, Policy policy) throws Exception;
-
-  Map<Long, Policy> getPolicies() throws Exception;
-
-  /**
-   * This operation will fail if a role with the same name already exists.
-   *
-   * @return The unique ID to refer to this role.
-   */
-  long createRole(Role role) throws Exception;
-
-  void deleteRole(long roleID) throws Exception;
-
-  Role getRole(long roleID) throws Exception;
-
-  void updateRole(long roleID, Role role) throws Exception;
-
-  Map<Long, Role> getRoles() throws Exception;
-
-  /**
-   * Define a role to be created.
-   */
-  class Role {
-    private final String name;
-    private final Collection<BasicUserPrincipal> users;
-    private String description;
-
-    public Role(String roleName) {
-      this.name = roleName;
-      this.users = new HashSet<>();
-    }
-
-    public String getName() {
-      return name;
-    }
-
-    public void addUsers(BasicUserPrincipal... newUsers) {
-      users.addAll(Arrays.asList(newUsers));
-    }
-
-    public boolean removeUsers(BasicUserPrincipal... newUsers) {
-      return users.removeAll(Arrays.asList(newUsers));
-    }
-
-    public Collection<BasicUserPrincipal> getUsers() {
-      return Collections.unmodifiableCollection(users);
-    }
-
-    public Optional<String> getDescription() {
-      return Optional.ofNullable(description);
-    }
-
-    public void setDescription(String description) {
-      this.description = description;
-    }
-  }
-
-  /**
-   * Define an acl.
-   */
-  class Acl {
-    private final boolean isAllowed;
-    private final IAccessAuthorizer.ACLType acl;
-
-    private Acl(IAccessAuthorizer.ACLType acl, boolean isAllowed) {
-      this.isAllowed = isAllowed;
-      this.acl = acl;
-    }
-
-    public static Acl allow(IAccessAuthorizer.ACLType acl) {
-      return new Acl(acl, true);
-    }
-
-    public static Acl deny(IAccessAuthorizer.ACLType acl) {
-      return new Acl(acl, false);
-    }
-
-    public IAccessAuthorizer.ACLType getAclType() {
-      return acl;
-    }
-
-    public boolean isAllowed() {
-      return isAllowed;
-    }
-  }
-
-  /**
-   * Define a policy to be created.
-   */
-  class Policy {
-    private final String name;
-    private final Collection<String> volumes;
-    private final Collection<String> buckets;
-    private final Collection<String> keys;
-    private String description;
-    private final Map<String, Collection<Acl>> roleAcls;
-    private boolean isEnabled;
-
-    public Policy(String policyName, String... volumeName) {
-      this.name = policyName;
-      this.volumes = new ArrayList<>();
-      this.volumes.addAll(Arrays.asList(volumeName));
-      this.buckets = new ArrayList<>();
-      this.keys = new ArrayList<>();
-      this.roleAcls = new HashMap<>();
-      this.isEnabled = true;
-    }
-
-    public void setEnabled(boolean enabled) {
-      isEnabled = enabled;
-    }
-
-    public boolean isEnabled() {
-      return isEnabled;
-    }
-
-    public Collection<String> getVolumes() {
-      return Collections.unmodifiableCollection(volumes);
-    }
-
-    public Collection<String> getBuckets() {
-      return Collections.unmodifiableCollection(buckets);
-    }
-
-    public Collection<String> getKeys() {
-      return Collections.unmodifiableCollection(keys);
-    }
-
-    public void addVolumes(String... volumeNames) {
-      volumes.addAll(Arrays.asList(volumeNames));
-    }
-
-    public void addBuckets(String... bucketNames) {
-      buckets.addAll(Arrays.asList(bucketNames));
-    }
-
-    public void addKeys(String... keyNames) {
-      keys.addAll(Arrays.asList(keyNames));
-    }
-
-    public String getName() {
-      return name;
-    }
-
-    public Optional<String> getDescription() {
-      return Optional.ofNullable(description);
-    }
-
-    public void addRoleAcls(String roleName, Acl... acl) {
-      roleAcls.putIfAbsent(roleName, new ArrayList<>());
-      roleAcls.get(roleName).addAll(Arrays.asList(acl));
-    }
-
-    public Map<String, Collection<Acl>> getRoleAcls() {
-      return Collections.unmodifiableMap(roleAcls);
-    }
-
-    public void setDescription(String description) {
-      this.description = description;
-    }
-  }
-}
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerRestMultiTenantAccessController.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerRestMultiTenantAccessController.java
deleted file mode 100644
index 7184ea301d..0000000000
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerRestMultiTenantAccessController.java
+++ /dev/null
@@ -1,636 +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.multitenant;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hdds.conf.OzoneConfiguration;
-import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
-import org.apache.http.auth.BasicUserPrincipal;
-import org.apache.kerby.util.Base64;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.lang.reflect.Type;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_RANGER_HTTPS_ADMIN_API_PASSWD;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_RANGER_HTTPS_ADMIN_API_USER;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_HTTPS_ADDRESS_KEY;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT_DEFAULT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_CONNECTION_TIMEOUT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_CONNECTION_TIMEOUT_DEFAULT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_IGNORE_SERVER_CERT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_OM_IGNORE_SERVER_CERT_DEFAULT;
-import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_SERVICE;
-
-/**
- * Access controller for multi-tenancy implemented using Ranger's REST API.
- * This class is for testing and is not intended for production use.
- */
-public class RangerRestMultiTenantAccessController
-    implements MultiTenantAccessController {
-
-  public static final String OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT =
-      "/service/public/v2/api/policy/";
-  public static final String OZONE_OM_RANGER_ADMIN_ROLE_HTTP_ENDPOINT =
-      "/service/public/v2/api/roles/";
-
-  private static final Logger LOG = LoggerFactory
-      .getLogger(RangerRestMultiTenantAccessController.class);
-
-  private final OzoneConfiguration conf;
-  private boolean ignoreServerCert = false;
-  private int connectionTimeout;
-  private int connectionRequestTimeout;
-  private String authHeaderValue;
-  private final String rangerHttpsAddress;
-  private final Gson jsonConverter;
-  private final String rangerService;
-  private final Map<IAccessAuthorizer.ACLType, String> aclToString;
-  private final Map<String, IAccessAuthorizer.ACLType> stringToAcl;
-
-  public RangerRestMultiTenantAccessController(Configuration configuration)
-      throws IOException {
-    conf = new OzoneConfiguration(configuration);
-    rangerHttpsAddress = conf.get(OZONE_RANGER_HTTPS_ADDRESS_KEY);
-    rangerService = conf.get(OZONE_RANGER_SERVICE);
-
-    GsonBuilder gsonBuilder = new GsonBuilder();
-    gsonBuilder.registerTypeAdapter(Policy.class, policySerializer);
-    gsonBuilder.registerTypeAdapter(Policy.class, policyDeserializer);
-    gsonBuilder.registerTypeAdapter(Role.class, roleSerializer);
-    gsonBuilder.registerTypeAdapter(Role.class, roleDeserializer);
-    gsonBuilder.registerTypeAdapter(BasicUserPrincipal.class, userSerializer);
-    jsonConverter = gsonBuilder.create();
-
-    aclToString = new EnumMap<>(IAccessAuthorizer.ACLType.class);
-    stringToAcl = new HashMap<>();
-    fillRangerAclStrings();
-    initializeRangerConnection();
-  }
-
-  private void fillRangerAclStrings() {
-    aclToString.put(IAccessAuthorizer.ACLType.ALL, "all");
-    aclToString.put(IAccessAuthorizer.ACLType.LIST, "list");
-    aclToString.put(IAccessAuthorizer.ACLType.READ, "read");
-    aclToString.put(IAccessAuthorizer.ACLType.WRITE, "write");
-    aclToString.put(IAccessAuthorizer.ACLType.CREATE, "create");
-    aclToString.put(IAccessAuthorizer.ACLType.DELETE, "delete");
-    aclToString.put(IAccessAuthorizer.ACLType.READ_ACL, "read_acl");
-    aclToString.put(IAccessAuthorizer.ACLType.WRITE_ACL, "write_acl");
-    aclToString.put(IAccessAuthorizer.ACLType.NONE, "");
-
-    stringToAcl.put("all", IAccessAuthorizer.ACLType.ALL);
-    stringToAcl.put("list", IAccessAuthorizer.ACLType.LIST);
-    stringToAcl.put("read", IAccessAuthorizer.ACLType.READ);
-    stringToAcl.put("write", IAccessAuthorizer.ACLType.WRITE);
-    stringToAcl.put("create", IAccessAuthorizer.ACLType.CREATE);
-    stringToAcl.put("delete", IAccessAuthorizer.ACLType.DELETE);
-    stringToAcl.put("read_acl", IAccessAuthorizer.ACLType.READ_ACL);
-    stringToAcl.put("write_acl", IAccessAuthorizer.ACLType.WRITE_ACL);
-    stringToAcl.put("", IAccessAuthorizer.ACLType.NONE);
-  }
-
-  private void initializeRangerConnection() {
-    setupRangerConnectionConfig();
-    if (ignoreServerCert) {
-      setupRangerIgnoreServerCertificate();
-    }
-    setupRangerConnectionAuthHeader();
-  }
-
-  private void setupRangerConnectionConfig() {
-    connectionTimeout = (int) conf.getTimeDuration(
-        OZONE_RANGER_OM_CONNECTION_TIMEOUT,
-        conf.get(
-            OZONE_RANGER_OM_CONNECTION_TIMEOUT,
-            OZONE_RANGER_OM_CONNECTION_TIMEOUT_DEFAULT),
-        TimeUnit.MILLISECONDS);
-    connectionRequestTimeout = (int)conf.getTimeDuration(
-        OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT,
-        conf.get(
-            OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT,
-            OZONE_RANGER_OM_CONNECTION_REQUEST_TIMEOUT_DEFAULT),
-        TimeUnit.MILLISECONDS
-    );
-    ignoreServerCert = conf.getBoolean(
-        OZONE_RANGER_OM_IGNORE_SERVER_CERT,
-        OZONE_RANGER_OM_IGNORE_SERVER_CERT_DEFAULT);
-  }
-
-  private void setupRangerIgnoreServerCertificate() {
-    // Create a trust manager that does not validate certificate chains
-    TrustManager[] trustAllCerts = new TrustManager[]{
-        new X509TrustManager() {
-          public java.security.cert.X509Certificate[] getAcceptedIssuers() {
-            return null;
-          }
-          public void checkClientTrusted(
-              java.security.cert.X509Certificate[] certs, String authType) {
-          }
-          public void checkServerTrusted(
-              java.security.cert.X509Certificate[] certs, String authType) {
-          }
-        }
-    };
-
-    try {
-      SSLContext sc = SSLContext.getInstance("SSL");
-      sc.init(null, trustAllCerts, new java.security.SecureRandom());
-      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
-    } catch (Exception e) {
-      LOG.info("Setting DefaultSSLSocketFactory failed.");
-    }
-  }
-
-  private void setupRangerConnectionAuthHeader() {
-    String userName = conf.get(OZONE_OM_RANGER_HTTPS_ADMIN_API_USER);
-    String passwd = conf.get(OZONE_OM_RANGER_HTTPS_ADMIN_API_PASSWD);
-    String auth = userName + ":" + passwd;
-    byte[] encodedAuth =
-        Base64.encodeBase64(auth.getBytes(StandardCharsets.UTF_8));
-    authHeaderValue = "Basic " +
-        new String(encodedAuth, StandardCharsets.UTF_8);
-  }
-
-
-  @Override
-  public long createPolicy(Policy policy) throws IOException {
-    String rangerAdminUrl =
-        rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT;
-    HttpsURLConnection conn = makeHttpsPostCall(rangerAdminUrl,
-        jsonConverter.toJsonTree(policy).getAsJsonObject());
-    if (!successfulResponseCode(conn.getResponseCode())) {
-      throw new IOException(String.format("Failed to create policy %s. " +
-          "Http response code: %d", policy.getName(), conn.getResponseCode()));
-    }
-    String policyInfo = getResponseData(conn);
-    long policyID;
-    JsonObject jObject = new JsonParser().parse(policyInfo).getAsJsonObject();
-    policyID = jObject.get("id").getAsLong();
-    return policyID;
-  }
-
-  @Override
-  public void deletePolicy(long policyID) throws IOException {
-    String rangerAdminUrl =
-        rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT
-            + policyID;
-    HttpsURLConnection conn = makeHttpsDeleteCall(rangerAdminUrl);
-    if (!successfulResponseCode(conn.getResponseCode())) {
-      throw new IOException(String.format("Failed to delete policy %d. " +
-          "Http response code: %d", policyID, conn.getResponseCode()));
-    }
-  }
-
-  @Override
-  public Map<Long, Policy> getPolicies() throws Exception {
-    // This API gets all policies for all services. The
-    // /public/v2/api/policies/{serviceDefName}/for-resource endpoint is
-    // supposed to get policies for only a specified service, but it does not
-    // seem to work. This implementation should be ok for testing purposes as
-    // this class is intended.
-    String rangerAdminUrl =
-        rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT;
-    HttpsURLConnection conn = makeHttpsGetCall(rangerAdminUrl);
-    if (!successfulResponseCode(conn.getResponseCode())) {
-      throw new IOException(String.format("Failed to get all policies. " +
-          "Http response code: %d", conn.getResponseCode()));
-    }
-    String allPoliciesString = getResponseData(conn);
-    // Filter out policies not for Ozone service.
-    JsonArray jsonPoliciesArray = new JsonParser().parse(allPoliciesString)
-        .getAsJsonArray();
-    Map<Long, Policy> policies = new HashMap<>();
-    for (JsonElement jsonPolicy: jsonPoliciesArray) {
-      JsonObject jsonPolicyObject = jsonPolicy.getAsJsonObject();
-      String service = jsonPolicyObject.get("service").getAsString();
-      if (service.equals(rangerService)) {
-        long id = jsonPolicyObject.get("id").getAsLong();
-        policies.put(id, jsonConverter.fromJson(jsonPolicyObject,
-            Policy.class));
-      }
-    }
-
-    return policies;
-  }
-
-  @Override
-  public Policy getPolicy(long policyID) throws IOException {
-    String rangerAdminUrl = rangerHttpsAddress +
-        OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT + policyID;
-
-    HttpsURLConnection conn = makeHttpsGetCall(rangerAdminUrl);
-    if (!successfulResponseCode(conn.getResponseCode())) {
-      throw new IOException(String.format("Failed to get policy %d. " +
-          "Http response code: %d", policyID, conn.getResponseCode()));
-    }
-    String policyInfo = getResponseData(conn);
-    return jsonConverter.fromJson(policyInfo, Policy.class);
-  }
-
-  @Override
-  public void updatePolicy(long policyID, Policy policy) throws IOException {
-    String rangerAdminUrl = rangerHttpsAddress +
-        OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT + policyID;
-
-    HttpsURLConnection conn = makeHttpsPutCall(rangerAdminUrl,
-        jsonConverter.toJsonTree(policy));
-    if (!successfulResponseCode(conn.getResponseCode())) {
-      throw new IOException(String.format("Failed to update policy %d. " +
-          "Http response code: %d", policyID, conn.getResponseCode()));
-    }
-  }
-
-  @Override
-  public long createRole(Role role) throws IOException {
-    String rangerAdminUrl =
-        rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_ROLE_HTTP_ENDPOINT;
-
-    HttpsURLConnection conn = makeHttpsPostCall(rangerAdminUrl,
-        jsonConverter.toJsonTree(role).getAsJsonObject());
-    if (!successfulResponseCode(conn.getResponseCode())) {
-      throw new IOException(String.format("Failed to create role %s. " +
-          "Http response code: %d", role.getName(), conn.getResponseCode()));
-    }
-    String responseString = getResponseData(conn);
-    JsonObject jObject = new JsonParser().parse(responseString)
-        .getAsJsonObject();
-    return jObject.get("id").getAsLong();
-  }
-
-  @Override
-  public void deleteRole(long roleID) throws IOException {
-    String rangerAdminUrl =
-        rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_POLICY_HTTP_ENDPOINT
-            + roleID;
-    HttpsURLConnection conn = makeHttpsDeleteCall(rangerAdminUrl);
-    if (!successfulResponseCode(conn.getResponseCode())) {
-      throw new IOException(String.format("Failed to delete role %d. " +
-          "Http response code: %d", roleID, conn.getResponseCode()));
-    }
-  }
-
-  @Override
-  public Map<Long, Role> getRoles() throws Exception {
-    String rangerAdminUrl =
-        rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_ROLE_HTTP_ENDPOINT;
-    HttpsURLConnection conn = makeHttpsGetCall(rangerAdminUrl);
-    if (!successfulResponseCode(conn.getResponseCode())) {
-      throw new IOException(String.format("Failed to get all roles. " +
-          "Http response code: %d", conn.getResponseCode()));
-    }
-
-    String allRolesString = getResponseData(conn);
-    JsonArray rolesArrayJson =
-        new JsonParser().parse(allRolesString).getAsJsonArray();
-    Map<Long, Role> roles = new HashMap<>();
-    for (JsonElement roleJson: rolesArrayJson) {
-      long id = roleJson.getAsJsonObject().get("id").getAsLong();
-      roles.put(id, jsonConverter.fromJson(roleJson, Role.class));
-    }
-
-    return roles;
-  }
-
-  @Override
-  public Role getRole(long roleID) throws IOException {
-    String rangerAdminUrl =
-        rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_ROLE_HTTP_ENDPOINT + roleID;
-
-    HttpsURLConnection conn = makeHttpsGetCall(rangerAdminUrl);
-    if (!successfulResponseCode(conn.getResponseCode())) {
-      throw new IOException(String.format("Failed to get role %d. " +
-          "Http response code: %d", roleID, conn.getResponseCode()));
-    }
-    String roleInfo = getResponseData(conn);
-    return jsonConverter.fromJson(roleInfo, Role.class);
-  }
-
-  @Override
-  public void updateRole(long roleID, Role role) throws IOException {
-    String rangerAdminUrl =
-        rangerHttpsAddress + OZONE_OM_RANGER_ADMIN_ROLE_HTTP_ENDPOINT + roleID;
-
-    HttpsURLConnection conn = makeHttpsPutCall(rangerAdminUrl,
-        jsonConverter.toJsonTree(role));
-    if (!successfulResponseCode(conn.getResponseCode())) {
-      throw new IOException(String.format("Failed to update role %d. " +
-          "Http response code: %d", roleID, conn.getResponseCode()));
-    }
-  }
-
-  private HttpsURLConnection makeHttpsPutCall(String url, JsonElement content)
-      throws IOException {
-    HttpsURLConnection connection = makeBaseHttpsURLConnection(url);
-    connection.setRequestMethod("PUT");
-    return addJsonContentToConnection(connection, content);
-  }
-
-  private HttpsURLConnection makeHttpsPostCall(String url, JsonElement content)
-      throws IOException {
-    HttpsURLConnection connection = makeBaseHttpsURLConnection(url);
-    connection.setRequestMethod("POST");
-    return addJsonContentToConnection(connection, content);
-  }
-
-  private HttpsURLConnection addJsonContentToConnection(
-      HttpsURLConnection connection, JsonElement content) throws IOException {
-    connection.setDoOutput(true);
-    connection.setRequestProperty("Content-Type", "application/json;");
-    try (OutputStream os = connection.getOutputStream()) {
-      byte[] input = content.toString().getBytes(StandardCharsets.UTF_8);
-      os.write(input, 0, input.length);
-      os.flush();
-    }
-
-    return connection;
-  }
-
-  private HttpsURLConnection makeHttpsGetCall(String urlString)
-      throws IOException {
-    HttpsURLConnection connection = makeBaseHttpsURLConnection(urlString);
-    connection.setRequestMethod("GET");
-    return connection;
-  }
-
-  private HttpsURLConnection makeHttpsDeleteCall(String urlString)
-      throws IOException {
-    HttpsURLConnection connection = makeBaseHttpsURLConnection(urlString);
-    connection.setRequestMethod("DELETE");
-    return connection;
-  }
-
-  private HttpsURLConnection makeBaseHttpsURLConnection(String urlString)
-      throws IOException {
-    URL url = new URL(urlString);
-    HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
-    urlConnection.setConnectTimeout(connectionTimeout);
-    urlConnection.setReadTimeout(connectionRequestTimeout);
-    urlConnection.setRequestProperty("Accept", "application/json");
-    urlConnection.setRequestProperty("Authorization", authHeaderValue);
-
-    return urlConnection;
-  }
-
-  private String getResponseData(HttpsURLConnection urlConnection)
-      throws IOException {
-    StringBuilder response = new StringBuilder();
-    try (BufferedReader br = new BufferedReader(
-        new InputStreamReader(
-            urlConnection.getInputStream(), StandardCharsets.UTF_8))) {
-      String responseLine;
-      while ((responseLine = br.readLine()) != null) {
-        response.append(responseLine.trim());
-      }
-    }
-    return response.toString();
-  }
-
-  private boolean successfulResponseCode(long responseCode) {
-    return responseCode >= 200 && responseCode < 300;
-  }
-
-  /// SERIALIZATION ///
-
-  private final JsonDeserializer<Policy> policyDeserializer =
-      new JsonDeserializer<Policy>() {
-        @Override public Policy deserialize(JsonElement jsonElement, Type type,
-            JsonDeserializationContext jsonDeserializationContext)
-            throws JsonParseException {
-          JsonObject policyJson = jsonElement.getAsJsonObject();
-          String name = policyJson.get("name").getAsString();
-          Policy policy = new Policy(name);
-          if (policyJson.has("description")) {
-            policy.setDescription(policyJson.get("description").getAsString());
-          }
-          policy.setEnabled(policyJson.get("isEnabled").getAsBoolean());
-
-          // Read volume, bucket, keys from json.
-          JsonObject resourcesJson =
-              policyJson.get("resources").getAsJsonObject();
-          // All Ozone Ranger policies specify at least a volume.
-          JsonObject jsonVolumeResource =
-              resourcesJson.get("volume").getAsJsonObject();
-          JsonArray volumes = jsonVolumeResource.get("values").getAsJsonArray();
-          volumes.forEach(vol -> policy.addVolumes(vol.getAsString()));
-
-          if (resourcesJson.has("bucket")) {
-            JsonObject jsonBucketResource =
-                resourcesJson.get("bucket").getAsJsonObject();
-            JsonArray buckets =
-                jsonBucketResource.get("values").getAsJsonArray();
-            buckets.forEach(bucket -> policy.addBuckets(bucket.getAsString()));
-          }
-
-          if (resourcesJson.has("key")) {
-            JsonObject jsonKeysResource =
-                resourcesJson.get("key").getAsJsonObject();
-            JsonArray keys = jsonKeysResource.get("values").getAsJsonArray();
-            keys.forEach(key -> policy.addKeys(key.getAsString()));
-          }
-
-          // Read Roles and their ACLs.
-          JsonArray policyItemsJson = policyJson.getAsJsonArray("policyItems");
-          for (JsonElement policyItemElement : policyItemsJson) {
-            JsonObject policyItemJson = policyItemElement.getAsJsonObject();
-            JsonArray jsonRoles = policyItemJson.getAsJsonArray("roles");
-            JsonArray jsonAclArray = policyItemJson.getAsJsonArray("accesses");
-
-            for (JsonElement jsonAclElem : jsonAclArray) {
-              JsonObject jsonAcl = jsonAclElem.getAsJsonObject();
-              String aclType = jsonAcl.get("type").getAsString();
-              Acl acl;
-              if (jsonAcl.get("isAllowed").getAsBoolean()) {
-                acl = Acl.allow(stringToAcl.get(aclType));
-              } else {
-                acl = Acl.deny(stringToAcl.get(aclType));
-              }
-
-              for (JsonElement roleNameJson : jsonRoles) {
-                policy.addRoleAcls(roleNameJson.getAsString(), acl);
-              }
-            }
-          }
-
-          return policy;
-        }
-      };
-
-  private final JsonDeserializer<Role> roleDeserializer =
-      new JsonDeserializer<Role>() {
-        @Override public Role deserialize(JsonElement jsonElement, Type type,
-            JsonDeserializationContext jsonDeserializationContext)
-            throws JsonParseException {
-          JsonObject roleJson = jsonElement.getAsJsonObject();
-          String name = roleJson.get("name").getAsString();
-          Role role = new Role(name);
-          if (roleJson.has("description")) {
-            role.setDescription(roleJson.get("description").getAsString());
-          }
-          for (JsonElement jsonUser : roleJson.get("users").getAsJsonArray()) {
-            String userName =
-                jsonUser.getAsJsonObject().get("name").getAsString();
-            role.addUsers(new BasicUserPrincipal(userName));
-          }
-
-          return role;
-        }
-      };
-
-  private final JsonSerializer<Policy> policySerializer =
-      new JsonSerializer<Policy>() {
-        @Override public JsonElement serialize(Policy javaPolicy,
-            Type typeOfSrc, JsonSerializationContext context) {
-          JsonObject jsonPolicy = new JsonObject();
-          jsonPolicy.addProperty("name", javaPolicy.getName());
-          jsonPolicy.addProperty("service", rangerService);
-          jsonPolicy.addProperty("isEnabled", javaPolicy.isEnabled());
-          if (javaPolicy.getDescription().isPresent()) {
-            jsonPolicy.addProperty("description",
-                javaPolicy.getDescription().get());
-          }
-
-          // All resources under this policy are added to this object.
-          JsonObject jsonResources = new JsonObject();
-
-          // Add volumes. Ranger requires at least one volume to be specified.
-          JsonArray jsonVolumeNameArray = new JsonArray();
-          for (String volumeName : javaPolicy.getVolumes()) {
-            jsonVolumeNameArray.add(new JsonPrimitive(volumeName));
-          }
-          JsonObject jsonVolumeResource = new JsonObject();
-          jsonVolumeResource.add("values", jsonVolumeNameArray);
-          jsonVolumeResource.addProperty("isRecursive", false);
-          jsonVolumeResource.addProperty("isExcludes", false);
-          jsonResources.add("volume", jsonVolumeResource);
-
-          // Add buckets.
-          JsonArray jsonBucketNameArray = new JsonArray();
-          for (String bucketName : javaPolicy.getBuckets()) {
-            jsonBucketNameArray.add(new JsonPrimitive(bucketName));
-          }
-
-          if (jsonBucketNameArray.size() > 0) {
-            JsonObject jsonBucketResource = new JsonObject();
-            jsonBucketResource.add("values", jsonBucketNameArray);
-            jsonBucketResource.addProperty("isRecursive", false);
-            jsonBucketResource.addProperty("isExcludes", false);
-            jsonResources.add("bucket", jsonBucketResource);
-          }
-
-          // Add keys.
-          JsonArray jsonKeyNameArray = new JsonArray();
-          for (String keyName : javaPolicy.getKeys()) {
-            jsonKeyNameArray.add(new JsonPrimitive(keyName));
-          }
-          if (jsonKeyNameArray.size() > 0) {
-            JsonObject jsonKeyResource = new JsonObject();
-            jsonKeyResource.add("values", jsonKeyNameArray);
-            jsonKeyResource.addProperty("isRecursive", false);
-            jsonKeyResource.addProperty("isExcludes", false);
-            jsonResources.add("key", jsonKeyResource);
-          }
-
-          jsonPolicy.add("resources", jsonResources);
-
-          // Add roles and their acls to the policy.
-          JsonArray jsonPolicyItemArray = new JsonArray();
-
-          // Make a new policy item for each role in the map.
-          Map<String, Collection<Acl>> roleAcls = javaPolicy.getRoleAcls();
-          for (Map.Entry<String, Collection<Acl>> entry : roleAcls.entrySet()) {
-            // Add role to the policy item.
-            String roleName = entry.getKey();
-            JsonObject jsonPolicyItem = new JsonObject();
-            JsonArray jsonRoles = new JsonArray();
-            jsonRoles.add(new JsonPrimitive(roleName));
-            jsonPolicyItem.add("roles", jsonRoles);
-
-            // Add acls to the policy item.
-            JsonArray jsonAclArray = new JsonArray();
-            for (Acl acl : entry.getValue()) {
-              JsonObject jsonAcl = new JsonObject();
-              jsonAcl.addProperty("type", aclToString.get(acl.getAclType()));
-              jsonAcl.addProperty("isAllowed", acl.isAllowed());
-              jsonAclArray.add(jsonAcl);
-              jsonPolicyItem.add("accesses", jsonAclArray);
-            }
-            jsonPolicyItemArray.add(jsonPolicyItem);
-          }
-          jsonPolicy.add("policyItems", jsonPolicyItemArray);
-
-          return jsonPolicy;
-        }
-      };
-
-  private final JsonSerializer<Role> roleSerializer =
-      new JsonSerializer<Role>() {
-        @Override public JsonElement serialize(Role javaRole, Type typeOfSrc,
-            JsonSerializationContext context) {
-          JsonObject jsonRole = new JsonObject();
-          jsonRole.addProperty("name", javaRole.getName());
-
-          JsonArray jsonUserArray = new JsonArray();
-          for (BasicUserPrincipal javaUser : javaRole.getUsers()) {
-            jsonUserArray.add(jsonConverter.toJsonTree(javaUser));
-          }
-
-          jsonRole.add("users", jsonUserArray);
-          return jsonRole;
-        }
-      };
-
-  private final JsonSerializer<BasicUserPrincipal> userSerializer =
-      new JsonSerializer<BasicUserPrincipal>() {
-        @Override public JsonElement serialize(BasicUserPrincipal user,
-            Type typeOfSrc, JsonSerializationContext context) {
-          JsonObject jsonMember = new JsonObject();
-          jsonMember.addProperty("name", user.getName());
-          jsonMember.addProperty("isAdmin", false);
-          return jsonMember;
-        }
-      };
-}
diff --git a/hadoop-ozone/dist/src/main/license/jar-report.txt b/hadoop-ozone/dist/src/main/license/jar-report.txt
index da8350d4b7..7f68799785 100644
--- a/hadoop-ozone/dist/src/main/license/jar-report.txt
+++ b/hadoop-ozone/dist/src/main/license/jar-report.txt
@@ -8,6 +8,7 @@ share/ozone/lib/aopalliance-repackaged.jar
 share/ozone/lib/asm.jar
 share/ozone/lib/aspectjrt.jar
 share/ozone/lib/aspectjweaver.jar
+share/ozone/lib/aws-java-sdk-bundle.jar
 share/ozone/lib/aws-java-sdk-core.jar
 share/ozone/lib/aws-java-sdk-kms.jar
 share/ozone/lib/aws-java-sdk-s3.jar
@@ -26,6 +27,7 @@ share/ozone/lib/commons-daemon.jar
 share/ozone/lib/commons-digester.jar
 share/ozone/lib/commons-io.jar
 share/ozone/lib/commons-lang3.jar
+share/ozone/lib/commons-lang.jar
 share/ozone/lib/commons-logging.jar
 share/ozone/lib/commons-math3.jar
 share/ozone/lib/commons-net.jar
@@ -38,6 +40,7 @@ share/ozone/lib/dnsjava.jar
 share/ozone/lib/error_prone_annotations.jar
 share/ozone/lib/failureaccess.jar
 share/ozone/lib/FastInfoset.jar
+share/ozone/lib/gethostname4j.jar
 share/ozone/lib/grpc-api.jar
 share/ozone/lib/grpc-context.jar
 share/ozone/lib/grpc-core.jar
@@ -72,21 +75,29 @@ share/ozone/lib/hdds-interface-server.jar
 share/ozone/lib/hdds-server-framework.jar
 share/ozone/lib/hdds-server-scm.jar
 share/ozone/lib/hdds-tools.jar
+share/ozone/lib/hive-storage-api.jar
 share/ozone/lib/hk2-api.jar
 share/ozone/lib/hk2-locator.jar
 share/ozone/lib/hk2-utils.jar
+share/ozone/lib/hppc.jar
 share/ozone/lib/htrace-core4-incubating.jar
+share/ozone/lib/httpasyncclient.jar
 share/ozone/lib/httpclient.jar
 share/ozone/lib/httpcore.jar
+share/ozone/lib/httpcore-nio.jar
+share/ozone/lib/httpmime.jar
 share/ozone/lib/ion-java.jar
 share/ozone/lib/istack-commons-runtime.jar
 share/ozone/lib/j2objc-annotations.jar
 share/ozone/lib/jackson-annotations.jar
+share/ozone/lib/jackson-core-asl.jar
 share/ozone/lib/jackson-core.jar
 share/ozone/lib/jackson-databind.jar
 share/ozone/lib/jackson-dataformat-cbor.jar
 share/ozone/lib/jackson-dataformat-xml.jar
 share/ozone/lib/jackson-datatype-jsr310.jar
+share/ozone/lib/jackson-jaxrs.jar
+share/ozone/lib/jackson-mapper-asl.jar
 share/ozone/lib/jackson-module-jaxb-annotations.jar
 share/ozone/lib/jaeger-client.jar
 share/ozone/lib/jaeger-core.jar
@@ -110,6 +121,7 @@ share/ozone/lib/jaxb-runtime.jar
 share/ozone/lib/jcip-annotations.jar
 share/ozone/lib/jersey-cdi1x.jar
 share/ozone/lib/jersey-client.jar
+share/ozone/lib/jersey-client.jar
 share/ozone/lib/jersey-common.jar
 share/ozone/lib/jersey-container-servlet-core.jar
 share/ozone/lib/jersey-container-servlet.jar
@@ -121,6 +133,7 @@ share/ozone/lib/jersey-media-json-jackson.jar
 share/ozone/lib/jersey-server.jar
 share/ozone/lib/jersey-server.jar
 share/ozone/lib/jersey-servlet.jar
+share/ozone/lib/jetty-client.jar
 share/ozone/lib/jetty-http.jar
 share/ozone/lib/jetty-io.jar
 share/ozone/lib/jetty-security.jar
@@ -131,6 +144,8 @@ share/ozone/lib/jetty-util.jar
 share/ozone/lib/jetty-webapp.jar
 share/ozone/lib/jetty-xml.jar
 share/ozone/lib/jmespath-java.jar
+share/ozone/lib/jna.jar
+share/ozone/lib/jna-platform.jar
 share/ozone/lib/joda-time.jar
 share/ozone/lib/jooq-codegen.jar
 share/ozone/lib/jooq.jar
@@ -140,6 +155,7 @@ share/ozone/lib/json-smart.jar
 share/ozone/lib/jsp-api.jar
 share/ozone/lib/jsr305.jar
 share/ozone/lib/jsr311-api.jar
+share/ozone/lib/kafka-clients.jar
 share/ozone/lib/kerb-admin.jar
 share/ozone/lib/kerb-client.jar
 share/ozone/lib/kerb-common.jar
@@ -182,6 +198,7 @@ share/ozone/lib/opentracing-api.jar
 share/ozone/lib/opentracing-noop.jar
 share/ozone/lib/opentracing-tracerresolver.jar
 share/ozone/lib/opentracing-util.jar
+share/ozone/lib/orc-core.jar
 share/ozone/lib/osgi-resource-locator.jar
 share/ozone/lib/ozone-annotation-processing.jar
 share/ozone/lib/ozone-client.jar
@@ -206,6 +223,11 @@ share/ozone/lib/protobuf-java.jar
 share/ozone/lib/protobuf-java.jar
 share/ozone/lib/protobuf-java-util.jar
 share/ozone/lib/proto-google-common-protos.jar
+share/ozone/lib/ranger-intg.jar
+share/ozone/lib/ranger-plugin-classloader.jar
+share/ozone/lib/ranger-plugins-audit.jar
+share/ozone/lib/ranger-plugins-common.jar
+share/ozone/lib/ranger-plugins-cred.jar
 share/ozone/lib/ratis-client.jar
 share/ozone/lib/ratis-common.jar
 share/ozone/lib/ratis-grpc.jar
@@ -226,6 +248,7 @@ share/ozone/lib/slf4j-api.jar
 share/ozone/lib/slf4j-log4j12.jar
 share/ozone/lib/snakeyaml.jar
 share/ozone/lib/snappy-java.jar
+share/ozone/lib/solr-solrj.jar
 share/ozone/lib/spring-beans.RELEASE.jar
 share/ozone/lib/spring-core.RELEASE.jar
 share/ozone/lib/spring-jcl.RELEASE.jar
@@ -238,3 +261,4 @@ share/ozone/lib/token-provider.jar
 share/ozone/lib/txw2.jar
 share/ozone/lib/weld-servlet.Final.jar
 share/ozone/lib/woodstox-core.jar
+share/ozone/lib/zstd-jni.jar
diff --git a/hadoop-ozone/ozone-manager/pom.xml b/hadoop-ozone/ozone-manager/pom.xml
index 5e42414ed7..10369fc680 100644
--- a/hadoop-ozone/ozone-manager/pom.xml
+++ b/hadoop-ozone/ozone-manager/pom.xml
@@ -144,6 +144,45 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd">
       <version>0.9.11</version>
     </dependency>
 
+    <dependency>
+      <groupId>com.sun.jersey</groupId>
+      <artifactId>jersey-client</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.ranger</groupId>
+      <artifactId>ranger-intg</artifactId>
+      <version>3.0.0-SNAPSHOT</version>
+      <scope>compile</scope>
+      <!-- Workaround to prevent slf4j binding conflicts until ranger-intg is
+       fixed to not introduce this to the classpath -->
+      <exclusions>
+        <exclusion>
+          <groupId>ch.qos.logback</groupId>
+          <artifactId>logback-classic</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>com.sun.jersey</groupId>
+          <artifactId>jersey-bundle</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.lucene</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.elasticsearch</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.elasticsearch.client</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.elasticsearch.plugin</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java
new file mode 100644
index 0000000000..3d50108f78
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/MultiTenantAccessController.java
@@ -0,0 +1,403 @@
+/*
+ * 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.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.http.auth.BasicUserPrincipal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Defines the operations needed for multi-tenant access control.
+ */
+public interface MultiTenantAccessController {
+  /**
+   * This operation will fail if a policy with the same name already exists,
+   * or a policy for the same set of resources already exists.
+   *
+   * Roles defined in this policy that do not already exist will be created.
+   */
+  void createPolicy(Policy policy) throws Exception;
+
+  Policy getPolicy(String policyName) throws Exception;
+
+  List<Policy> getLabeledPolicies(String label) throws Exception;
+
+  void updatePolicy(Policy policy) throws Exception;
+
+  void deletePolicy(String policyName) throws Exception;
+
+  /**
+   * This operation will fail if a role with the same name already exists.
+   */
+  void createRole(Role role) throws Exception;
+
+  Role getRole(String roleName) throws Exception;
+
+  /**
+   * Replaces the role given by {@code roleID} with the contents of {@code
+   * role}. If {@code roleID} does not correspond to a role, an exception is
+   * thrown.
+   *
+   * The roleID of a given role can be retrieved from the {@code getRole}
+   * method.
+   */
+  void updateRole(long roleID, Role role) throws Exception;
+
+  void deleteRole(String roleName) throws Exception;
+
+  long getRangerServiceVersion() throws Exception;
+
+  static Map<IAccessAuthorizer.ACLType, String> getRangerAclStrings() {
+    Map<IAccessAuthorizer.ACLType, String> rangerAclStrings =
+        new EnumMap<>(IAccessAuthorizer.ACLType.class);
+    rangerAclStrings.put(IAccessAuthorizer.ACLType.ALL, "all");
+    rangerAclStrings.put(IAccessAuthorizer.ACLType.LIST, "list");
+    rangerAclStrings.put(IAccessAuthorizer.ACLType.READ, "read");
+    rangerAclStrings.put(IAccessAuthorizer.ACLType.WRITE, "write");
+    rangerAclStrings.put(IAccessAuthorizer.ACLType.CREATE, "create");
+    rangerAclStrings.put(IAccessAuthorizer.ACLType.DELETE, "delete");
+    rangerAclStrings.put(IAccessAuthorizer.ACLType.READ_ACL, "read_acl");
+    rangerAclStrings.put(IAccessAuthorizer.ACLType.WRITE_ACL, "write_acl");
+
+    return rangerAclStrings;
+  }
+
+  /**
+   * Define an acl.
+   */
+  class Acl {
+    private final boolean isAllowed;
+    private final IAccessAuthorizer.ACLType acl;
+
+    private Acl(IAccessAuthorizer.ACLType acl, boolean isAllowed) {
+      this.isAllowed = isAllowed;
+      this.acl = acl;
+    }
+
+    public static Acl allow(IAccessAuthorizer.ACLType acl) {
+      return new Acl(acl, true);
+    }
+
+    public static Acl deny(IAccessAuthorizer.ACLType acl) {
+      return new Acl(acl, false);
+    }
+
+    public IAccessAuthorizer.ACLType getAclType() {
+      return acl;
+    }
+
+    public boolean isAllowed() {
+      return isAllowed;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(acl);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (this == other) {
+        return true;
+      }
+      if (other == null || getClass() != other.getClass()) {
+        return false;
+      }
+      Acl otherAcl = (Acl) other;
+      return isAllowed() == otherAcl.isAllowed() && acl == otherAcl.acl;
+    }
+  }
+
+  /**
+   * Define a role to be created.
+   */
+  class Role {
+    private final String name;
+    private final Set<BasicUserPrincipal> users;
+    private final String description;
+    private final Long roleID;
+
+    private Role(Builder builder) {
+      name = builder.name;
+      users = builder.users;
+      description = builder.description;
+      roleID = builder.roleID;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public Set<BasicUserPrincipal> getUsers() {
+      return users;
+    }
+
+    public Optional<String> getDescription() {
+      return Optional.ofNullable(description);
+    }
+
+    public Optional<Long> getRoleID() {
+      return Optional.ofNullable(roleID);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(name);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (this == other) {
+        return true;
+      }
+      if (other == null || getClass() != other.getClass()) {
+        return false;
+      }
+      Role role = (Role) other;
+      // If one role does not have the ID set, still consider them equal.
+      // Role ID may not be set if the policy is being sent to Ranger for
+      // creation, but will be set if the same policy is retrieved from Ranger.
+      boolean roleIDsMatch = true;
+      if (getRoleID().isPresent() && role.getRoleID().isPresent()) {
+        roleIDsMatch = getRoleID().equals(role.getRoleID());
+      }
+      return Objects.equals(getName(), role.getName()) &&
+          Objects.equals(getUsers(), role.getUsers()) &&
+          Objects.equals(getDescription(), role.getDescription()) &&
+          roleIDsMatch;
+    }
+
+    /**
+     * Builder class for a role.
+     */
+    public static final class Builder {
+      private String name;
+      private final Set<BasicUserPrincipal> users;
+      private String description;
+      private Long roleID;
+
+      public Builder() {
+        this.users = new HashSet<>();
+      }
+
+      public Builder(Role other) {
+        this.name = other.getName();
+        this.users = new HashSet<>(other.getUsers());
+        other.getDescription().ifPresent(desc -> this.description = desc);
+        other.getRoleID().ifPresent(id -> this.roleID = id);
+      }
+
+      public Builder setName(String roleName) {
+        this.name = roleName;
+        return this;
+      }
+
+      public Builder addUser(BasicUserPrincipal user) {
+        this.users.add(user);
+        return this;
+      }
+
+      public Builder addUsers(Collection<BasicUserPrincipal> roleUsers) {
+        this.users.addAll(roleUsers);
+        return this;
+      }
+
+      public Builder setDescription(String roleDescription) {
+        this.description = roleDescription;
+        return this;
+      }
+
+      public Builder setID(long roleId) {
+        this.roleID = roleId;
+        return this;
+      }
+
+      public Role build() {
+        return new Role(this);
+      }
+    }
+  }
+
+  /**
+   * Define a policy to be created.
+   */
+  class Policy {
+    private final String name;
+    private final Set<String> volumes;
+    private final Set<String> buckets;
+    private final Set<String> keys;
+    private final String description;
+    private final Map<String, Collection<Acl>> roleAcls;
+    private final Set<String> labels;
+
+    private Policy(Builder builder) {
+      name = builder.name;
+      volumes = builder.volumes;
+      buckets = builder.buckets;
+      keys = builder.keys;
+      description = builder.description;
+      roleAcls = builder.roleAcls;
+      labels = builder.labels;
+    }
+
+    public Set<String> getVolumes() {
+      return volumes;
+    }
+
+    public Set<String> getBuckets() {
+      return buckets;
+    }
+
+    public Set<String> getKeys() {
+      return keys;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public Optional<String> getDescription() {
+      return Optional.ofNullable(description);
+    }
+
+    public Set<String> getLabels() {
+      return (labels);
+    }
+
+    public Map<String, Collection<Acl>> getRoleAcls() {
+      return roleAcls;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(name);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (this == other) {
+        return true;
+      }
+      if (other == null || getClass() != other.getClass()) {
+        return false;
+      }
+      Policy policy = (Policy) other;
+      return Objects.equals(getName(), policy.getName()) &&
+          Objects.equals(getVolumes(), policy.getVolumes()) &&
+          Objects.equals(getBuckets(), policy.getBuckets()) &&
+          Objects.equals(getKeys(), policy.getKeys()) &&
+          Objects.equals(getDescription(), policy.getDescription()) &&
+          Objects.equals(getRoleAcls(), policy.getRoleAcls()) &&
+          Objects.equals(getLabels(), policy.getLabels());
+    }
+
+    /**
+     * Builder class for a policy.
+     */
+    public static final class Builder {
+      private String name;
+      private final Set<String> volumes;
+      private final Set<String> buckets;
+      private final Set<String> keys;
+      private String description;
+      private final Map<String, Collection<Acl>> roleAcls;
+      private final Set<String> labels;
+
+      public Builder() {
+        this.volumes = new HashSet<>();
+        this.buckets = new HashSet<>();
+        this.keys = new HashSet<>();
+        this.roleAcls = new HashMap<>();
+        this.labels = new HashSet<>();
+      }
+
+      public Builder setName(String policyName) {
+        this.name = policyName;
+        return this;
+      }
+
+      public Builder addVolume(String volume) {
+        this.volumes.add(volume);
+        return this;
+      }
+
+      public Builder addBucket(String bucket) {
+        this.buckets.add(bucket);
+        return this;
+      }
+
+      public Builder addKey(String key) {
+        this.keys.add(key);
+        return this;
+      }
+
+      public Builder addVolumes(Collection<String> volumeList) {
+        this.volumes.addAll(volumeList);
+        return this;
+      }
+
+      public Builder addBuckets(Collection<String> bucketList) {
+        this.buckets.addAll(bucketList);
+        return this;
+      }
+
+      public Builder addKeys(Collection<String> keyList) {
+        this.keys.addAll(keyList);
+        return this;
+      }
+
+      public Builder setDescription(String policyDescription) {
+        this.description = policyDescription;
+        return this;
+      }
+
+      public Builder addRoleAcl(String roleName, Collection<Acl> acls) {
+        this.roleAcls.put(roleName, new ArrayList<>(acls));
+        return this;
+      }
+
+      public Builder addLabel(String label) {
+        this.labels.add(label);
+        return this;
+      }
+
+      public Builder addLabels(Collection<String> labelsList) {
+        this.labels.addAll(labelsList);
+        return this;
+      }
+
+      public Policy build() {
+        if (name == null || name.isEmpty()) {
+          throw new IllegalStateException("A policy must have a non-empty " +
+              "name.");
+        }
+        return new Policy(this);
+      }
+    }
+  }
+}
diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerClientMultiTenantAccessController.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerClientMultiTenantAccessController.java
new file mode 100644
index 0000000000..b04843789a
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerClientMultiTenantAccessController.java
@@ -0,0 +1,324 @@
+/*
+ * 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 static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_KERBEROS_KEYTAB_FILE_KEY;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_KERBEROS_PRINCIPAL_KEY;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_HTTPS_ADDRESS_KEY;
+import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_RANGER_SERVICE;
+import static org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod.KERBEROS;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import com.google.common.base.Preconditions;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.OmUtils;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.security.SecurityUtil;
+import org.apache.http.auth.BasicUserPrincipal;
+import org.apache.ranger.RangerServiceException;
+import org.apache.ranger.plugin.model.RangerPolicy;
+import org.apache.ranger.plugin.model.RangerRole;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.ranger.RangerClient;
+
+/**
+ * Implementation of {@link MultiTenantAccessController} using the
+ * {@link RangerClient} to communicate with Ranger.
+ */
+public class RangerClientMultiTenantAccessController implements
+    MultiTenantAccessController {
+
+  private static final Logger LOG = LoggerFactory
+      .getLogger(RangerClientMultiTenantAccessController.class);
+
+  private final RangerClient client;
+  private final String rangerServiceName;
+  private final Map<IAccessAuthorizer.ACLType, String> aclToString;
+  private final Map<String, IAccessAuthorizer.ACLType> stringToAcl;
+  private final String omPrincipal;
+
+  public RangerClientMultiTenantAccessController(OzoneConfiguration conf)
+      throws IOException {
+    aclToString = MultiTenantAccessController.getRangerAclStrings();
+    stringToAcl = new HashMap<>();
+    aclToString.forEach((type, string) -> stringToAcl.put(string, type));
+
+    // Should have passed the check in OMMultiTenantManager
+    String rangerHttpsAddress = conf.get(OZONE_RANGER_HTTPS_ADDRESS_KEY);
+    Preconditions.checkNotNull(rangerHttpsAddress);
+    rangerServiceName = conf.get(OZONE_RANGER_SERVICE);
+    Preconditions.checkNotNull(rangerServiceName);
+
+    String configuredOmPrincipal = conf.get(OZONE_OM_KERBEROS_PRINCIPAL_KEY);
+    Preconditions.checkNotNull(configuredOmPrincipal);
+    // Replace _HOST pattern with host name in the Kerberos principal. Ranger
+    // client currently does not do this automatically.
+    omPrincipal = SecurityUtil.getServerPrincipal(
+        configuredOmPrincipal, OmUtils.getOmAddress(conf).getHostName());
+    String keytabPath = conf.get(OZONE_OM_KERBEROS_KEYTAB_FILE_KEY);
+    Preconditions.checkNotNull(keytabPath);
+
+    client = new RangerClient(rangerHttpsAddress,
+        KERBEROS.name().toLowerCase(), omPrincipal, keytabPath,
+        rangerServiceName, OzoneConsts.OZONE);
+  }
+
+  @Override
+  public void createPolicy(Policy policy) throws RangerServiceException {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Sending create request for policy {} to Ranger.",
+          policy.getName());
+    }
+    client.createPolicy(toRangerPolicy(policy));
+  }
+
+  @Override
+  public Policy getPolicy(String policyName) throws RangerServiceException {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Sending get request for policy {} to Ranger.",
+          policyName);
+    }
+    return fromRangerPolicy(client.getPolicy(rangerServiceName, policyName));
+  }
+
+  @Override
+  public List<Policy> getLabeledPolicies(String label)
+      throws RangerServiceException {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Sending get request for policies with label {} to Ranger.",
+          label);
+    }
+    Map<String, String> filterMap = new HashMap<>();
+    filterMap.put("serviceName", rangerServiceName);
+    filterMap.put("policyLabelsPartial", label);
+    return client.findPolicies(filterMap).stream()
+        .map(this::fromRangerPolicy)
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public void updatePolicy(Policy policy) throws RangerServiceException {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Sending update request for policy {} to Ranger.",
+          policy.getName());
+    }
+    client.updatePolicy(rangerServiceName, policy.getName(),
+        toRangerPolicy(policy));
+  }
+
+  @Override
+  public void deletePolicy(String policyName) throws RangerServiceException {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Sending delete request for policy {} to Ranger.",
+          policyName);
+    }
+    client.deletePolicy(rangerServiceName, policyName);
+  }
+
+  @Override
+  public void createRole(Role role) throws RangerServiceException {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Sending create request for role {} to Ranger.",
+          role.getName());
+    }
+    client.createRole(rangerServiceName, toRangerRole(role));
+  }
+
+  @Override
+  public Role getRole(String roleName) throws RangerServiceException {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Sending get request for role {} to Ranger.",
+          roleName);
+    }
+    return fromRangerRole(client.getRole(roleName, omPrincipal,
+        rangerServiceName));
+  }
+
+  @Override
+  public void updateRole(long roleID, Role role) throws RangerServiceException {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Sending update request for role ID {} to Ranger.",
+          roleID);
+    }
+    client.updateRole(roleID, toRangerRole(role));
+  }
+
+  @Override
+  public void deleteRole(String roleName) throws RangerServiceException {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Sending delete request for role {} to Ranger.",
+          roleName);
+    }
+    client.deleteRole(roleName, omPrincipal, rangerServiceName);
+  }
+
+  @Override
+  public long getRangerServiceVersion() {
+    throw new UnsupportedOperationException("Ranger client implementation " +
+        "does not currently support this method. A workaround will be " +
+        "implemented in HDDS-6755.");
+  }
+
+  private static List<RangerRole.RoleMember> toRangerRoleMembers(
+      Collection<BasicUserPrincipal> members) {
+    return members.stream()
+            .map(princ -> new RangerRole.RoleMember(princ.getName(), false))
+            .collect(Collectors.toList());
+  }
+
+  private static List<BasicUserPrincipal> fromRangerRoleMembers(
+      Collection<RangerRole.RoleMember> members) {
+    return members.stream()
+        .map(rangerUser -> new BasicUserPrincipal(rangerUser.getName()))
+        .collect(Collectors.toList());
+  }
+
+  private static Role fromRangerRole(RangerRole rangerRole) {
+    return new Role.Builder()
+      .setID(rangerRole.getId())
+      .setName(rangerRole.getName())
+      .setDescription(rangerRole.getDescription())
+      .addUsers(fromRangerRoleMembers(rangerRole.getUsers()))
+      .build();
+  }
+
+  private static RangerRole toRangerRole(Role role) {
+    RangerRole rangerRole = new RangerRole();
+    rangerRole.setName(role.getName());
+    rangerRole.setUsers(toRangerRoleMembers(role.getUsers()));
+    if (role.getDescription().isPresent()) {
+      rangerRole.setDescription(role.getDescription().get());
+    }
+    return rangerRole;
+  }
+
+  private Policy fromRangerPolicy(RangerPolicy rangerPolicy) {
+    Policy.Builder policyBuilder = new Policy.Builder();
+
+    // Get roles and their acls from the policy.
+    for (RangerPolicy.RangerPolicyItem policyItem:
+        rangerPolicy.getPolicyItems()) {
+      Collection<Acl> acls = new ArrayList<>();
+      for (RangerPolicy.RangerPolicyItemAccess access:
+           policyItem.getAccesses()) {
+        if (access.getIsAllowed()) {
+          acls.add(Acl.allow(stringToAcl.get(access.getType())));
+        } else {
+          acls.add(Acl.deny(stringToAcl.get(access.getType())));
+        }
+      }
+
+      for (String roleName: policyItem.getRoles()) {
+        policyBuilder.addRoleAcl(roleName, acls);
+      }
+    }
+
+    // Add resources.
+    for (Map.Entry<String, RangerPolicy.RangerPolicyResource> resource:
+        rangerPolicy.getResources().entrySet()) {
+      String resourceType = resource.getKey();
+      List<String> resourceNames = resource.getValue().getValues();
+      switch (resourceType) {
+      case "volume":
+        policyBuilder.addVolumes(resourceNames);
+        break;
+      case "bucket":
+        policyBuilder.addBuckets(resourceNames);
+        break;
+      case "key":
+        policyBuilder.addKeys(resourceNames);
+        break;
+      default:
+        LOG.warn("Pulled Ranger policy with unknown resource type '{}' with" +
+            " names '{}'", resourceType, String.join(",", resourceNames));
+      }
+    }
+
+    policyBuilder.setName(rangerPolicy.getName())
+       .setDescription(rangerPolicy.getDescription())
+        .addLabels(rangerPolicy.getPolicyLabels());
+
+    return policyBuilder.build();
+  }
+
+  private RangerPolicy toRangerPolicy(Policy policy) {
+    RangerPolicy rangerPolicy = new RangerPolicy();
+    rangerPolicy.setName(policy.getName());
+    rangerPolicy.setService(rangerServiceName);
+    rangerPolicy.setPolicyLabels(new ArrayList<>(policy.getLabels()));
+
+    // Add resources.
+    Map<String, RangerPolicy.RangerPolicyResource> resource = new HashMap<>();
+    // Add volumes.
+    if (!policy.getVolumes().isEmpty()) {
+      RangerPolicy.RangerPolicyResource volumeResources =
+          new RangerPolicy.RangerPolicyResource();
+      volumeResources.setValues(new ArrayList<>(policy.getVolumes()));
+      resource.put("volume", volumeResources);
+    }
+    // Add buckets.
+    if (!policy.getBuckets().isEmpty()) {
+      RangerPolicy.RangerPolicyResource bucketResources =
+          new RangerPolicy.RangerPolicyResource();
+      bucketResources.setValues(new ArrayList<>(policy.getBuckets()));
+      resource.put("bucket", bucketResources);
+    }
+    // Add keys.
+    if (!policy.getKeys().isEmpty()) {
+      RangerPolicy.RangerPolicyResource keyResources =
+          new RangerPolicy.RangerPolicyResource();
+      keyResources.setValues(new ArrayList<>(policy.getKeys()));
+      resource.put("key", keyResources);
+    }
+    rangerPolicy.setService(rangerServiceName);
+    rangerPolicy.setResources(resource);
+    if (policy.getDescription().isPresent()) {
+      rangerPolicy.setDescription(policy.getDescription().get());
+    }
+
+    // Add roles to the policy.
+    for (Map.Entry<String, Collection<Acl>> roleAcls:
+        policy.getRoleAcls().entrySet()) {
+      RangerPolicy.RangerPolicyItem item = new RangerPolicy.RangerPolicyItem();
+      item.setRoles(Collections.singletonList(roleAcls.getKey()));
+
+      for (Acl acl: roleAcls.getValue()) {
+        RangerPolicy.RangerPolicyItemAccess access =
+            new RangerPolicy.RangerPolicyItemAccess();
+        access.setIsAllowed(acl.isAllowed());
+        access.setType(aclToString.get(acl.getAclType()));
+        item.getAccesses().add(access);
+      }
+
+      rangerPolicy.getPolicyItems().add(item);
+    }
+
+    return rangerPolicy;
+  }
+}
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/InMemoryMultiTenantAccessController.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/InMemoryMultiTenantAccessController.java
new file mode 100644
index 0000000000..cfb2a05e2f
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/InMemoryMultiTenantAccessController.java
@@ -0,0 +1,154 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * In Memory version of MultiTenantAccessController.
+ */
+public class InMemoryMultiTenantAccessController
+    implements MultiTenantAccessController {
+
+  private final Map<String, Policy> policies;
+  private final Map<String, Role> roles;
+  private long nextRoleID;
+  private long serviceVersion;
+
+  public InMemoryMultiTenantAccessController() {
+    nextRoleID = 0;
+    policies = new HashMap<>();
+    roles = new HashMap<>();
+    serviceVersion = 0;
+  }
+
+  @Override
+  public void createPolicy(Policy policy) throws Exception {
+    if (policies.containsKey(policy.getName())) {
+      throw new Exception("Policy already exists.");
+    }
+    // Multiple policies for the same resource should not be allowed.
+    for (Policy existingPolicy: policies.values()) {
+      if (existingPolicy.getVolumes().equals(policy.getVolumes()) &&
+          existingPolicy.getBuckets().equals(policy.getBuckets()) &&
+          existingPolicy.getKeys().equals(policy.getKeys())) {
+        throw new Exception("Policy for the same resource already defined.");
+      }
+    }
+    policies.put(policy.getName(), policy);
+    // Ranger will create roles if specified with policy creation.
+    for (String roleName: policy.getRoleAcls().keySet()) {
+      if (!roles.containsKey(roleName)) {
+        createRole(new Role.Builder().setName(roleName).build());
+      }
+    }
+
+    serviceVersion++;
+  }
+
+  @Override
+  public Policy getPolicy(String policyName) throws Exception {
+    if (!policies.containsKey(policyName)) {
+      throw new Exception("Policy does not exist.");
+    }
+    return policies.get(policyName);
+  }
+
+  @Override
+  public List<Policy> getLabeledPolicies(String label) throws Exception {
+    List<Policy> result = new ArrayList<>();
+    for (Policy policy: policies.values()) {
+      if (policy.getLabels().contains(label)) {
+        result.add(policy);
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public void updatePolicy(Policy policy) throws Exception {
+    if (!policies.containsKey(policy.getName())) {
+      throw new Exception("Policy does not exist.");
+    }
+    policies.put(policy.getName(), policy);
+    serviceVersion++;
+  }
+
+  @Override
+  public void deletePolicy(String policyName) throws Exception {
+    if (!policies.containsKey(policyName)) {
+      throw new Exception("Policy does not exist.");
+    }
+    policies.remove(policyName);
+    serviceVersion++;
+  }
+
+  @Override
+  public void createRole(Role role) throws Exception {
+    if (roles.containsKey(role.getName())) {
+      throw new Exception("Role already exists.");
+    }
+    Role newRole = new Role.Builder(role)
+        .setID(nextRoleID)
+        .build();
+    nextRoleID++;
+    roles.put(newRole.getName(), newRole);
+    serviceVersion++;
+  }
+
+  @Override
+  public Role getRole(String roleName) throws Exception {
+    if (!roles.containsKey(roleName)) {
+      throw new Exception("Role does not exist.");
+    }
+    return roles.get(roleName);
+  }
+
+  @Override
+  public void updateRole(long roleID, Role role) throws Exception {
+    Optional<Role> originalRole = roles.values().stream()
+        .filter(r -> r.getRoleID().isPresent() && r.getRoleID().get() == roleID)
+        .findFirst();
+    if (!originalRole.isPresent()) {
+      throw new Exception("Role does not exist.");
+    }
+    // New role may have same ID but different name.
+    roles.remove(originalRole.get().getName());
+    roles.put(role.getName(), role);
+    serviceVersion++;
+  }
+
+  @Override
+  public void deleteRole(String roleName) throws Exception {
+    if (!roles.containsKey(roleName)) {
+      throw new Exception("Role does not exist.");
+    }
+    roles.remove(roleName);
+    serviceVersion++;
+  }
+
+  @Override
+  public long getRangerServiceVersion() {
+    return serviceVersion;
+  }
+}
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantAccessController.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantAccessController.java
new file mode 100644
index 0000000000..8e0e871fca
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantAccessController.java
@@ -0,0 +1,398 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.om.multitenant.MultiTenantAccessController.Acl;
+import org.apache.hadoop.ozone.om.multitenant.MultiTenantAccessController.Policy;
+import org.apache.hadoop.ozone.om.multitenant.MultiTenantAccessController.Role;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType;
+import org.apache.http.auth.BasicUserPrincipal;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/**
+ * To test MultiTenantAccessController with Ranger Client.
+ */
+public class TestMultiTenantAccessController {
+  private MultiTenantAccessController controller;
+  private List<BasicUserPrincipal> users;
+
+  @Before
+  public void setupUsers() {
+    // If testing against a real cluster, users must already be added to Ranger.
+    users = new ArrayList<>();
+    users.add(new BasicUserPrincipal("om"));
+    users.add(new BasicUserPrincipal("hdfs"));
+  }
+
+  /**
+   * Use this setup to test against a simulated Ranger instance.
+   */
+  @Before
+  public void setupUnitTest() {
+    controller = new InMemoryMultiTenantAccessController();
+  }
+
+  /**
+   * Use this setup to test against a live Ranger instance.
+   */
+  //  @Before
+  public void setupClusterTest() throws Exception {
+    // These config keys must be set when the test is run:
+    // OZONE_RANGER_HTTPS_ADDRESS_KEY
+    // OZONE_RANGER_SERVICE
+    // OZONE_OM_KERBEROS_PRINCIPAL_KEY
+    // OZONE_OM_KERBEROS_KEYTAB_FILE_KEY
+    OzoneConfiguration conf = new OzoneConfiguration();
+    controller = new RangerClientMultiTenantAccessController(conf);
+  }
+
+  @Test
+  public void testCreateGetDeletePolicies() throws Exception {
+    // load a policy with everything possible except roles.
+    final String policyName = "test-policy";
+
+    MultiTenantAccessController.Policy originalPolicy =
+        new MultiTenantAccessController.Policy.Builder()
+            .setName(policyName)
+            .addVolume("vol1")
+            .addVolume("vol2")
+            .addBucket("vol1/bucket1")
+            .addBucket("vol2/bucket2")
+            .addKey("vol1/bucket1/key1")
+            .addKey("vol2/bucket2/key2")
+            .setDescription("description")
+            .addLabel("label1")
+            .addLabel("label2")
+            .build();
+
+    // create in ranger.
+    controller.createPolicy(originalPolicy);
+    // get to check it's there with all attributes.
+    MultiTenantAccessController.Policy retrievedPolicy =
+        controller.getPolicy(policyName);
+    Assert.assertEquals(originalPolicy, retrievedPolicy);
+
+    // delete policy.
+    controller.deletePolicy(policyName);
+    // get to check it is deleted.
+    try {
+      controller.getPolicy(policyName);
+      Assert.fail("Expected exception for missing policy.");
+    } catch (Exception ex) {
+       // Expected since policy is not there.
+    }
+  }
+
+  @Test
+  public void testCreateDuplicatePolicy() throws Exception {
+    final String policyName = "test-policy";
+    final String volumeName = "vol1";
+    MultiTenantAccessController.Policy originalPolicy =
+        new MultiTenantAccessController.Policy.Builder()
+            .setName(policyName)
+            .addVolume(volumeName)
+            .build();
+    // create in ranger.
+    controller.createPolicy(originalPolicy);
+    Assert.assertEquals(originalPolicy, controller.getPolicy(policyName));
+
+    // Create a policy with the same name but different resource.
+    // Check for error.
+    MultiTenantAccessController.Policy sameNamePolicy =
+        new MultiTenantAccessController.Policy.Builder()
+            .setName(policyName)
+            .addVolume(volumeName + "2")
+            .build();
+    try {
+      controller.createPolicy(sameNamePolicy);
+      Assert.fail("Expected exception for duplicate policy.");
+    } catch (Exception ex) {
+      // Expected since a policy with the same name should not be allowed.
+    }
+
+    // Create a policy with different name but same resource.
+    // Check for error.
+    MultiTenantAccessController.Policy sameResourcePolicy =
+        new MultiTenantAccessController.Policy.Builder()
+            .setName(policyName + "2")
+            .addVolume(volumeName)
+            .build();
+    try {
+      controller.createPolicy(sameResourcePolicy);
+      Assert.fail("Expected exception for duplicate policy.");
+    } catch (Exception ex) {
+      // Expected since a policy with the same resource should not be allowed.
+    }
+
+    // delete policy.
+    controller.deletePolicy(policyName);
+  }
+
+  @Test
+  public void testGetLabeledPolicies() throws Exception  {
+    final String label = "label";
+    Policy labeledPolicy1 = new Policy.Builder()
+        .setName("policy1")
+        .addVolume(UUID.randomUUID().toString())
+        .addLabel(label)
+        .build();
+    Policy labeledPolicy2 = new Policy.Builder()
+        .setName("policy2")
+        .addVolume(UUID.randomUUID().toString())
+        .addLabel(label)
+        .build();
+
+    List<Policy> labeledPolicies = new ArrayList<>();
+    labeledPolicies.add(labeledPolicy1);
+    labeledPolicies.add(labeledPolicy2);
+    Policy unlabeledPolicy = new Policy.Builder()
+        .setName("policy3")
+        .addVolume(UUID.randomUUID().toString())
+        .build();
+
+    for (Policy policy: labeledPolicies) {
+      controller.createPolicy(policy);
+    }
+    controller.createPolicy(unlabeledPolicy);
+
+    // Get should only return policies with the specified label.
+    List<Policy> retrievedLabeledPolicies =
+        controller.getLabeledPolicies(label);
+    Assert.assertEquals(labeledPolicies.size(),
+        retrievedLabeledPolicies.size());
+    Assert.assertTrue(retrievedLabeledPolicies.containsAll(labeledPolicies));
+
+    // Get of a specific policy should also succeed.
+    Policy retrievedPolicy = controller.getPolicy(unlabeledPolicy.getName());
+    Assert.assertEquals(unlabeledPolicy, retrievedPolicy);
+
+    // Get of policies with nonexistent label should give an empty list.
+    Assert.assertTrue(controller.getLabeledPolicies(label + "1").isEmpty());
+
+    // Cleanup
+    for (Policy policy: labeledPolicies) {
+      controller.deletePolicy(policy.getName());
+    }
+    controller.deletePolicy(unlabeledPolicy.getName());
+  }
+
+  @Test
+  public void testUpdatePolicy() throws Exception {
+    String policyName = "policy";
+    // Since the roles will not exist when the policy is created, Ranger
+    // should create them.
+    Policy originalPolicy = new Policy.Builder()
+        .setName(policyName)
+        .addVolume("vol1")
+        .addLabel("label1")
+        .addRoleAcl("role1",
+            Collections.singletonList(Acl.allow(ACLType.READ_ACL)))
+        .build();
+    controller.createPolicy(originalPolicy);
+    Assert.assertEquals(originalPolicy,
+        controller.getPolicy(policyName));
+
+    Policy updatedPolicy = new Policy.Builder()
+        .setName(policyName)
+        .addVolume("vol2")
+        .addLabel("label2")
+        .addRoleAcl("role1",
+            Collections.singletonList(Acl.allow(ACLType.WRITE_ACL)))
+        .addRoleAcl("role2",
+            Collections.singletonList(Acl.allow(ACLType.READ_ACL)))
+        .build();
+    controller.updatePolicy(updatedPolicy);
+    Assert.assertEquals(updatedPolicy,
+        controller.getPolicy(policyName));
+
+    // Cleanup
+    controller.deletePolicy(policyName);
+  }
+
+  @Test
+  public void testCreatePolicyWithRoles() throws Exception {
+    // Create a policy with role acls.
+    final String roleName = "role1";
+    Policy policy = new Policy.Builder()
+        .setName("policy1")
+        .addVolume("volume")
+        .addRoleAcl(roleName,
+            Collections.singletonList(Acl.allow(ACLType.ALL)))
+        .build();
+    // This should create the role as well.
+    controller.createPolicy(policy);
+
+    // Test the acls set on the role for the policy.
+    Policy retrievedPolicy = controller.getPolicy(policy.getName());
+    Map<String, Collection<Acl>> retrievedRoleAcls =
+        retrievedPolicy.getRoleAcls();
+    Assert.assertEquals(1, retrievedRoleAcls.size());
+    List<Acl> roleAcls = new ArrayList<>(retrievedRoleAcls.get(roleName));
+    Assert.assertEquals(1, roleAcls.size());
+    Assert.assertEquals(ACLType.ALL, roleAcls.get(0).getAclType());
+    Assert.assertTrue(roleAcls.get(0).isAllowed());
+
+    // get one of the roles to check it is there but empty.
+    Role retrievedRole = controller.getRole(roleName);
+    Assert.assertFalse(retrievedRole.getDescription().isPresent());
+    Assert.assertTrue(retrievedRole.getUsers().isEmpty());
+    Assert.assertTrue(retrievedRole.getRoleID().isPresent());
+
+    // Add a user to the role.
+    retrievedRole.getUsers().add(users.get(0));
+    controller.updateRole(retrievedRole.getRoleID().get(), retrievedRole);
+
+    // Create a new policy containing the role. This should not overwrite the
+    // role.
+    Policy policy2 = new Policy.Builder()
+        .setName("policy2")
+        .addVolume("volume2")
+        .addRoleAcl(roleName,
+            Collections.singletonList(Acl.allow(ACLType.READ)))
+        .build();
+    controller.createPolicy(policy2);
+    Assert.assertEquals(controller.getRole(roleName), retrievedRole);
+
+    controller.deletePolicy("policy1");
+    controller.deletePolicy("policy2");
+    controller.deleteRole(roleName);
+  }
+
+  @Test
+  public void testCreateGetDeleteRoles() throws Exception {
+    // load a role with everything possible.
+    final String roleName = "test-role";
+
+    Role originalRole =
+        new Role.Builder()
+            .setName(roleName)
+            .addUsers(users)
+            .build();
+
+    // create in ranger.
+    controller.createRole(originalRole);
+    // get to check it's there with all attributes.
+    Role retrievedRole = controller.getRole(roleName);
+    // Role ID should have been added by Ranger.
+    Assert.assertTrue(retrievedRole.getRoleID().isPresent());
+    Assert.assertEquals(originalRole, retrievedRole);
+
+    // delete role.
+    controller.deleteRole(roleName);
+    // get to check it is deleted.
+    try {
+      controller.getRole(roleName);
+      Assert.fail("Expected exception for missing role.");
+    } catch (Exception ex) {
+      // Expected since policy is not there.
+    }
+  }
+
+  @Test
+  public void testCreateDuplicateRole() throws Exception {
+    final String roleName = "test-role";
+    Role originalRole = new Role.Builder()
+            .setName(roleName)
+            .build();
+    // create in Ranger.
+    controller.createRole(originalRole);
+    Assert.assertEquals(originalRole, controller.getRole(roleName));
+
+    // Create a role with the same name and check for error.
+    Role sameNameRole = new Role.Builder()
+            .setName(roleName)
+            .build();
+    try {
+      controller.createRole(sameNameRole);
+      Assert.fail("Expected exception for duplicate role.");
+    } catch (Exception ex) {
+      // Expected since a policy with the same name should not be allowed.
+    }
+
+    // delete role.
+    controller.deleteRole(roleName);
+  }
+
+  @Test
+  public void testUpdateRole() throws Exception {
+    final String roleName = "test-role";
+    Role originalRole = new Role.Builder()
+        .setName(roleName)
+        .addUsers(users)
+        .build();
+    // create in Ranger.
+    controller.createRole(originalRole);
+
+    Role retrievedRole = controller.getRole(roleName);
+    Assert.assertEquals(originalRole, retrievedRole);
+    Assert.assertTrue(retrievedRole.getRoleID().isPresent());
+    long roleID = retrievedRole.getRoleID().get();
+
+    // Remove a user from the role and update it.
+    retrievedRole.getUsers().remove(users.get(0));
+    Assert.assertEquals(originalRole.getUsers().size() - 1,
+        retrievedRole.getUsers().size());
+    controller.updateRole(roleID, retrievedRole);
+    Role retrievedUpdatedRole = controller.getRole(roleName);
+    Assert.assertEquals(retrievedRole, retrievedUpdatedRole);
+    Assert.assertEquals(originalRole.getUsers().size() - 1,
+        retrievedUpdatedRole.getUsers().size());
+
+    // Cleanup.
+    controller.deleteRole(roleName);
+  }
+
+  /**
+   * Test that Acl types are correctly converted to strings that Ranger can
+   * understand. An exception will be thrown if Ranger does not recognize the
+   * Acl.
+   */
+  @Test
+  public void testRangerAclStrings() throws Exception {
+    // Create a policy that uses all possible acl types.
+    List<Acl> acls = Arrays.stream(ACLType.values())
+        .map(Acl::allow)
+        .collect(Collectors.toList());
+    // Ranger does not support the NONE acl type.
+    Acl noneRemove = Acl.allow(ACLType.NONE);
+    acls.remove(noneRemove);
+    Policy policy = new Policy.Builder()
+        .setName("policy")
+        .addVolume("volume")
+        .addRoleAcl("role", acls)
+        .build();
+    // Converting from ACLType to Ranger strings should not produce an error.
+    controller.createPolicy(policy);
+    // Converting from Ranger strings to ACLType should not produce an error.
+    controller.getPolicy(policy.getName());
+    // cleanup.
+    controller.deletePolicy(policy.getName());
+  }
+}
\ No newline at end of file


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