avijayanhwx commented on a change in pull request #2059: URL: https://github.com/apache/ozone/pull/2059#discussion_r605264802
########## File path: hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/BucketNameSpace.java ########## @@ -0,0 +1,91 @@ +/* + * 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 java.util.List; + +import org.apache.hadoop.hdds.annotation.InterfaceAudience; +import org.apache.hadoop.hdds.annotation.InterfaceStability; +import org.apache.hadoop.hdds.client.OzoneQuota; +import org.apache.hadoop.hdds.fs.SpaceUsageSource; +import org.apache.hadoop.ozone.security.acl.OzoneObj; + [email protected]({"HDFS", "Yarn", "Ranger", "Hive", "HBase"}) [email protected] +public interface BucketNameSpace { + /** + * A Tenant will typically have his own BucketNameSpace to isolate the + * the buckets of this Tenancy from that of others. + * A BucketNameSpace can have different attributes and + * restrictions that could apply to this BucketNameSpace. + * Example of BucketNameSpace attributes can include + * - Collective Space usage information across all the buckets in this + * BucketNameSpace. + * - Collective Quota restrictions across all the buckets of this + * BucketNamespace. + * + * BucketNameSpace can be determined from the user context. Alternatively + * APIs can use "BucketNameSpace:bucketName" naming convention. + * Public buckets require unique bucket-names across all bucket-NameSpaces. + * + * Later, we can provide an API to create/set a public bucket by linking + * the bucket in Tenant's bucketNameSpace to a globally unique bucket in + * S3V(default-bucketNameSpace). + * + * @return BucketNameSpace-ID. + */ + String getBucketNameSpaceID(); + + /** + * Returns all the top level Ozone objects that belong to a BucketNameSpace. + * Some implementation can choose to represent it by Single Volume. Nothing + * prevents any future extension where a bucketNameSpace can be multiple + * volumes as well (Example Use case: one for each user). + * + * @return List of Ozone Volumes. + */ + List<OzoneObj> getBucketNameSpaceObjects(); + + /** + * Add one or more volumes to this BucketNameSpace. + * @param bucketNamespaceObject + */ + void addBucketNameSpaceObjects(OzoneObj bucketNamespaceObject); Review comment: nit. addBucketNameSpaceObject (without the 's') ########## File path: hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/AccountNameSpace.java ########## @@ -0,0 +1,75 @@ +/* + * 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.hdds.annotation.InterfaceAudience; +import org.apache.hadoop.hdds.annotation.InterfaceStability; +import org.apache.hadoop.hdds.client.OzoneQuota; +import org.apache.hadoop.hdds.fs.SpaceUsageSource; + [email protected]({"HDFS", "Yarn", "Ranger", "Hive", "HBase"}) [email protected] + +public interface AccountNameSpace { + /** + * A Tenant will typically have his own AccountNameSpace to isolate the + * the users of this Tenancy from that of the others. + * An AccountNameSpace can have different attributes and + * restrictions/additional-privileges that could apply to the users of this + * AccountNameSpace. + * Example of AccountNameSpace attribute can include + * - Space usage information across all the users of this account NameSpace + * - Quota restrictions across all the users of this namespace + * - Collective Bandwidth(Network usage information) usage across users of + * this NameSpace over a time window. + * - Any QOS restrictions. + * + * Note that users can be explicitly given access to another Tenant's + * BucketNameSpace. It is the AccountNameSpace QOS restriction of + * user's Tenancy that will come into play when it comes to QOS enforcement. + * Similarly Quotas can be enforce both/either for AccountNameSpace and/or + * BucketNameSpace. + * + * All the users of this Tenant can be identified with an accessID + * AccountNameSpaceID$Username. + * @return AccountNameSpaceID + */ + String getAccountNameSpaceID(); + + /** + * Get Space Usage Information for this AccountNameSpace. This can be + * used for billing purpose. Such Aggregation can also be done lazily + * by a Recon job. Implementations can decide. + * @return + */ + SpaceUsageSource getSpaceUsage(); + + /** + * Sets quota for this AccountNameSpace. Quota enforcement can also be done + * Lazily by a Recon job but that would be a soft quota enforcement. Choice + * of quota enforcement style is left to Implementation. + * @param quota + */ + void setQuota(OzoneQuota quota); Review comment: Is there a plan to allow updation of quota that was assigned to this Account Namepsace? If not, IMO we can get rid of this setter. ########## File path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserCreateHandler.java ########## @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.ozone.shell.s3; + +import org.apache.hadoop.hdds.cli.GenericCli; +import org.apache.hadoop.ozone.client.ObjectStore; +import org.apache.hadoop.ozone.client.OzoneClient; +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; + +/** + * ozone s3 user create. + */ [email protected](name = "create", + description = "Create one or more tenant users") +public class TenantUserCreateHandler extends S3Handler { + + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @CommandLine.Parameters(description = "List of tenant user short names") + private List<String> usernames = new ArrayList<>(); + + @CommandLine.Option(names = "-t", + description = "Tenant name") + private String tenantName; + + @Override + protected void execute(OzoneClient client, OzoneAddress address) { + final ObjectStore objStore = client.getObjectStore(); + if (tenantName == null || tenantName.length() == 0) { + tenantName = objStore.getS3VolumeName(); + } + if (usernames.size() > 0) { + for (String username : usernames) { + try { + S3SecretValue res = objStore.createTenantUser(username, tenantName); + out().println("Successfully created tenant " + username + ":"); Review comment: nit. Successfully created **user** ########## File path: hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/multitenant/RangerAccessPolicy.java ########## @@ -0,0 +1,250 @@ +/* + * 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.ACLType; +import org.apache.hadoop.ozone.security.acl.OzoneObj; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static org.apache.hadoop.ozone.om.multitenant.AccessPolicy.AccessPolicyType.RANGER_POLICY; +import static org.apache.hadoop.ozone.om.multitenant.OzoneMultiTenantPrincipal.OzonePrincipalType.GROUP_PRINCIPAL; + +public class RangerAccessPolicy implements AccessPolicy { + + // For now RangerAccessPolicy supports only one object per policy + private OzoneObj accessObject; + private Map<String, List<AccessPolicyElem>> policyMap; + private String policyID; + private String policyJsonString; + private String policyName; + + public RangerAccessPolicy(String name) { + policyMap = new ConcurrentHashMap<>(); + policyName = name; + } + + public void setPolicyID(String id) { + policyID = id; + } + + public String getPolicyID() { + return policyID; + } + + public String getPolicyName() { + return policyName; + } + + @Override + public String getPolicyJsonString() throws Exception { + updatePolicyJsonString(); + return policyJsonString; + } + + @Override + public AccessPolicyType getAccessPolicyType() { + return RANGER_POLICY; + } + + @Override + public void addAccessPolicyElem(OzoneObj object, + OzoneMultiTenantPrincipal principal, + ACLType acl, AccessGrantType grant) + throws IOException { + if (accessObject == null) { + accessObject = object; + } else if (!object.toString().equals(accessObject.toString())) { + throw new IOException( + "RangerAccessPolicy supports only one object per" + " policy"); + } + AccessPolicyElem elem = new AccessPolicyElem(object, principal, acl, grant); + if (!policyMap.containsKey(principal.toString())) { + List<AccessPolicyElem> elemList = new ArrayList<>(); + elemList.add(elem); + policyMap.put(principal.toString(), elemList); + return; + } + List<AccessPolicyElem> elemList = policyMap.get(principal.toString()); + for (AccessPolicyElem e : elemList) { + if (e.getAclType() == acl) { + throw new IOException( + "RangerAccessPolicy: Principal " + principal.toString() + + " already exists with access " + acl); + } + } + elemList.add(elem); + } + + @Override + public List<AccessPolicyElem> getAccessPolicyElem() { + List<AccessPolicyElem> list = new ArrayList<>(); + for (Map.Entry<String, List<AccessPolicyElem>> entry : policyMap + .entrySet()) { + list.addAll(entry.getValue()); + } + return list; + } + + @Override public void removeAccessPolicyElem(OzoneObj object, + OzoneMultiTenantPrincipal principal, ACLType acl, AccessGrantType grant) + throws IOException { + if (accessObject == null) { + throw new IOException("removeAccessPolicyElem: Invalid Arguments."); + } else if (!object.toString().equals(accessObject.toString())) { + throw new IOException( + "removeAccessPolicyElem: Object not found." + object.toString()); + } + if (!policyMap.containsKey(principal.toString())) { + throw new IOException( + "removeAccessPolicyElem: Principal not found." + object.toString()); + } + List<AccessPolicyElem> elemList = policyMap.get(principal.toString()); + for (AccessPolicyElem e : elemList) { + if (e.getAclType() == acl) { + elemList.remove(e); + } + } + if (elemList.isEmpty()) { + policyMap.remove(principal.toString()); + } + throw new IOException( + "removeAccessPolicyElem: aclType not found." + object.toString()); + } + + private String createRangerResourceItems() throws IOException { + StringBuilder resourceItems = new StringBuilder(); + resourceItems.append("\"resources\":{" + + "\"volume\":{" + + "\"values\":[\""); + resourceItems.append(accessObject.getVolumeName()); + resourceItems.append("\"]," + + "\"isRecursive\":false," + + "\"isExcludes\":false" + + "}"); + if ((accessObject.getResourceType() == OzoneObj.ResourceType.BUCKET) || + (accessObject.getResourceType() == OzoneObj.ResourceType.KEY)) { + resourceItems.append( + ",\"bucket\":{" + + "\"values\":[\""); + resourceItems.append(accessObject.getBucketName()); + resourceItems.append("\"]," + + "\"isRecursive\":false," + + "\"isExcludes\":false" + + "}"); + } + if (accessObject.getResourceType() == OzoneObj.ResourceType.KEY) { + resourceItems.append(",\"key\":{" + + "\"values\":[\""); + resourceItems.append(accessObject.getKeyName()); + resourceItems.append("\"]," + + "\"isRecursive\":true," + + "\"isExcludes\":false" + + "}"); + } + resourceItems.append("},"); + return resourceItems.toString(); + } + + private String createRangerPolicyItems() throws IOException { + StringBuilder policyItems = new StringBuilder(); + policyItems.append("\"policyItems\":["); + int mapRemainingSize = policyMap.size(); + for (Map.Entry<String, List<AccessPolicyElem>> mapElem : policyMap + .entrySet()) { + mapRemainingSize--; + List<AccessPolicyElem> list = mapElem.getValue(); + if (list.isEmpty()) { + continue; + } + policyItems.append("{"); + if (list.get(0).getPrincipal().getUserPrincipalType() + == GROUP_PRINCIPAL) { + policyItems.append("\"groups\":[\"" + mapElem.getKey() + "\"],"); + } else { + policyItems.append("\"users\":[\"" + mapElem.getKey() + "\"],"); + } + policyItems.append("\"accesses\":["); + Iterator<AccessPolicyElem> iter = list.iterator(); + while (iter.hasNext()) { + AccessPolicyElem elem = iter.next(); + policyItems.append("{"); + policyItems.append("\"type\":\""); + policyItems.append(getRangerAclString(elem.getAclType())); + policyItems.append("\","); + if (elem.getAccessGrantType() == AccessGrantType.ALLOW) { + policyItems.append("\"isAllowed\":true"); + } else { + policyItems.append("\"isDenied\":true"); + } + policyItems.append("}"); + if (iter.hasNext()) { + policyItems.append(","); + } + } + policyItems.append("]"); + policyItems.append("}"); + if (mapRemainingSize > 0) { + policyItems.append(","); + } + } + policyItems.append("],"); + return policyItems.toString(); + } + + private String getRangerAclString(ACLType aclType) throws IOException { + switch (aclType) { + case ALL: + return "All"; + case LIST: + return "List"; + case READ: + return "Read"; + case WRITE: + return "Write"; + case CREATE: + return "Create"; + case DELETE: + return "Delete"; + case READ_ACL: + return "Read_ACL"; + case WRITE_ACL: + return "Write_ACL"; + case NONE: + return ""; + default: + throw new IOException("Unknown ACLType"); + } + } + + private void updatePolicyJsonString() throws Exception { + policyJsonString = + "{\"policyType\":\"0\"," + "\"name\":\"" + policyName + "\"," + + "\"isEnabled\":true," + "\"policyPriority\":0," + + "\"policyLabels\":[]," + "\"description\":\"\"," + + "\"isAuditEnabled\":true," + createRangerResourceItems() + + "\"isDenyAllElse\":false," + createRangerPolicyItems() + + "\"allowExceptions\":[]," + "\"denyPolicyItems\":[]," + + "\"denyExceptions\":[]," + "\"service\":\"cm_ozone\"" + "}"; Review comment: In the future, service name "cm_ozone" may be something that Ozone needs to figure out from Ranger. ########## File path: hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/multitenant/TestMultiTenantGateKeeperRangerPlugin.java ########## @@ -0,0 +1,221 @@ +/** + * 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 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.multitenant.AccessPolicy.AccessGrantType.ALLOW; +import static org.apache.hadoop.ozone.om.multitenant.OzoneMultiTenantPrincipal.OzonePrincipalType.GROUP_PRINCIPAL; +import static org.apache.hadoop.ozone.om.multitenant.OzoneMultiTenantPrincipal.OzonePrincipalType.USER_PRINCIPAL; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.CREATE; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.LIST; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.NONE; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.READ; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.READ_ACL; +import static org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType.BUCKET; +import static org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType.KEY; +import static org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType.VOLUME; +import static org.apache.hadoop.ozone.security.acl.OzoneObj.StoreType.OZONE; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.ozone.om.multitenantImpl.OzoneMultiTenantPrincipalImpl; +import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType; +import org.apache.hadoop.ozone.security.acl.OzoneObjInfo; +import org.apache.http.auth.BasicUserPrincipal; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tests TestMultiTenantGateKeeperImplWithRanger. + * Marking it as Ignore because it needs Ranger access point. + */ +@Ignore +public class TestMultiTenantGateKeeperRangerPlugin { + private static final Logger LOG = LoggerFactory + .getLogger(TestMultiTenantGateKeeperRangerPlugin.class); + + /** + * Set a timeout for each test. + */ + @Rule + public Timeout timeout = new Timeout(300000); + + // The following values need to be set before this test can be enabled. + private static final String RANGER_ENDPOINT = ""; + private static final String RANGER_ENDPOINT_USER = ""; + private static final String RANGER_ENDPOINT_USER_PASSWD = ""; + + private List<String> usersIdsCreated = new ArrayList<String>(); + private List<String> groupIdsCreated = new ArrayList<String>(); + private List<String> policyIdsCreated = new ArrayList<String>(); + + private static OzoneConfiguration conf; + + @BeforeClass + public static void init() throws Exception { + conf = new OzoneConfiguration(); + simulateOzoneSiteXmlConfig(); + } + + @AfterClass + public static void shutdown() { + } + + private static void simulateOzoneSiteXmlConfig() { + conf.setStrings(OZONE_RANGER_HTTPS_ADDRESS_KEY, RANGER_ENDPOINT); + conf.setStrings(OZONE_OM_RANGER_HTTPS_ADMIN_API_USER, RANGER_ENDPOINT_USER); + conf.setStrings(OZONE_OM_RANGER_HTTPS_ADMIN_API_PASSWD, + RANGER_ENDPOINT_USER_PASSWD); + } + + @Test + public void testMultiTenantGateKeeperRangerPlugin() throws Exception { + simulateOzoneSiteXmlConfig(); + MultiTenantGateKeeper omm = new MultiTenantGateKeeperRangerPlugin(); + omm.init(conf); + + try { + OzoneMultiTenantPrincipal group1Principal = getTestPrincipal("tenant1", + "groupTestAdmin", GROUP_PRINCIPAL); + OzoneMultiTenantPrincipal group2Principal = getTestPrincipal("tenant1", + "groupTestUsers", GROUP_PRINCIPAL); + groupIdsCreated.add(omm.createGroup(group1Principal)); + groupIdsCreated.add(omm.createGroup(group2Principal)); + + OzoneMultiTenantPrincipal userPrincipal = + getTestPrincipal("tenant1", "user1Test", USER_PRINCIPAL); + usersIdsCreated.add(omm.createUser(userPrincipal, groupIdsCreated)); + + AccessPolicy tenant1VolumeAccessPolicy = createVolumeAccessPolicy( + "vol1", "tenant1", "Users"); + policyIdsCreated.add(omm.createAccessPolicy(tenant1VolumeAccessPolicy)); + + AccessPolicy tenant1BucketCreatePolicy = allowCreateBucketPolicy( + "vol1", "tenant1", "Users"); + policyIdsCreated.add(omm.createAccessPolicy(tenant1BucketCreatePolicy)); + + AccessPolicy tenant1BucketAccessPolicy = allowAccessBucketPolicy( + "vol1", "tenant1", "Users", "bucket1"); + policyIdsCreated.add(omm.createAccessPolicy(tenant1BucketAccessPolicy)); + + AccessPolicy tenant1KeyAccessPolicy = allowAccessKeyPolicy( + "vol1", "tenant1", "Users", "bucket1"); + policyIdsCreated.add(omm.createAccessPolicy(tenant1KeyAccessPolicy)); + + } catch (Exception e) { + Assert.fail(e.getMessage()); + } finally { + for (String id : policyIdsCreated) { + omm.deletePolicy(id); + } + if (usersIdsCreated.size() == 1) { + omm.deleteUser(usersIdsCreated.get(0)); + } + for (String id : groupIdsCreated) { + omm.deleteGroup(id); + } + } + } + + OzoneMultiTenantPrincipal getTestPrincipal(String tenant, String id, + OzoneMultiTenantPrincipal.OzonePrincipalType type) { + OzoneMultiTenantPrincipal principal = new OzoneMultiTenantPrincipalImpl( + new BasicUserPrincipal(tenant), + new BasicUserPrincipal(id), type); + return principal; + } + + AccessPolicy createVolumeAccessPolicy(String vol, String tenant, Review comment: Nit. These may be moved to a class which catalogs the list of well known policies, and provides static reference to creating them. ########## File path: hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantUserCreateRequest.java ########## @@ -0,0 +1,285 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.hadoop.ozone.om.request.s3.tenant; + +import com.google.common.base.Optional; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.hadoop.hdds.utils.db.cache.CacheKey; +import org.apache.hadoop.hdds.utils.db.cache.CacheValue; +import org.apache.hadoop.ozone.OmUtils; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.audit.OMAction; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.OzoneManager; +import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.ozone.om.helpers.S3SecretValue; +import org.apache.hadoop.ozone.om.multitenant.OzoneMultiTenantPrincipal; +import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper; +import org.apache.hadoop.ozone.om.request.util.OmResponseUtil; +import org.apache.hadoop.ozone.om.request.volume.OMVolumeRequest; +import org.apache.hadoop.ozone.om.response.OMClientResponse; +import org.apache.hadoop.ozone.om.response.s3.tenant.OMTenantCreateResponse; +import org.apache.hadoop.ozone.om.response.s3.tenant.OMTenantUserCreateResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateTenantUserRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateTenantUserResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.S3Secret; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.UpdateGetS3SecretRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.S3_SECRET_LOCK; +import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.VOLUME_LOCK; + +/* + Ratis execution flow for OMTenantUserCreate + +- Client (UserCreateHandler , etc.) + - Check username validity: ensure no invalid characters + - Send request to server +- OMTenantUserCreateRequest + - preExecute (perform checks and init) + - Check username validity (again), check $ + - If username is invalid, throw exception to client; else continue + - Generate S3 secret for the new user + - validateAndUpdateCache (update DB) + - Permission check (checkACL need to check access key now) + - Grab VOLUME_LOCK write lock + - Check user existence + - If user doesn't exist, throw exception to client; else continue + - Check tenant existence + - If tenant doesn't exist, throw exception to client; else continue + - Grab S3_SECRET_LOCK write lock + - S3SecretTable: Flush generated S3 secret + - Key: TENANTNAME$USERNAME (equivalent to kerberosID) + - Value: <GENERATED_SECRET> + - Release S3_SECRET_LOCK write lock + - tenantUserTable: New entry + - Key: Tenant user name. e.g. finance$bob, s3v$alice + - Value: Tenant name. e.g. finance + - tenantGroupTable: Add this new user to the default tenant group. + - Key: finance$bob + - Value: finance-users + - tenantRoleTable: TBD. NoOp for prototype. + - Release VOLUME_LOCK write lock + */ + +/** + * Handles OMTenantUserCreate request. + */ +public class OMTenantUserCreateRequest extends OMVolumeRequest { Review comment: Is there a plan to provide a way get the s3 creds of a user from command line post creation? Something like an extension to the **ozone s3 getsecret** command. ########## File path: hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/s3/TenantUserCreateHandler.java ########## @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.ozone.shell.s3; + +import org.apache.hadoop.hdds.cli.GenericCli; +import org.apache.hadoop.ozone.client.ObjectStore; +import org.apache.hadoop.ozone.client.OzoneClient; +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; + +/** + * ozone s3 user create. + */ [email protected](name = "create", + description = "Create one or more tenant users") +public class TenantUserCreateHandler extends S3Handler { + + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @CommandLine.Parameters(description = "List of tenant user short names") + private List<String> usernames = new ArrayList<>(); + + @CommandLine.Option(names = "-t", + description = "Tenant name") + private String tenantName; + + @Override + protected void execute(OzoneClient client, OzoneAddress address) { + final ObjectStore objStore = client.getObjectStore(); + if (tenantName == null || tenantName.length() == 0) { + tenantName = objStore.getS3VolumeName(); + } + if (usernames.size() > 0) { + for (String username : usernames) { + try { + S3SecretValue res = objStore.createTenantUser(username, tenantName); + out().println("Successfully created tenant " + username + ":"); + out().println("export AWS_ACCESS_KEY_ID=" + res.getAwsAccessKey()); + out().println("export AWS_SECRET_ACCESS_KEY=" + res.getAwsSecret()); + } catch (IOException e) { + out().println("Failed to create tenant " + username + ": " + Review comment: nit. same as above. ########## File path: hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMultiTenantManagerImpl.java ########## @@ -0,0 +1,316 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.ozone.om; + +import static org.apache.hadoop.ozone.om.multitenant.AccessPolicy.AccessGrantType.ALLOW; +import static org.apache.hadoop.ozone.om.multitenant.OzoneMultiTenantPrincipal.OzonePrincipalType.GROUP_PRINCIPAL; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.CREATE; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.LIST; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.NONE; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.READ; +import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.READ_ACL; +import static org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType.BUCKET; +import static org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType.KEY; +import static org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType.VOLUME; +import static org.apache.hadoop.ozone.security.acl.OzoneObj.StoreType.OZONE; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.ozone.om.multitenant.AccessPolicy; +import org.apache.hadoop.ozone.om.multitenant.AccountNameSpace; +import org.apache.hadoop.ozone.om.multitenant.BucketNameSpace; +import org.apache.hadoop.ozone.om.multitenant.CephCompatibleTenantImpl; +import org.apache.hadoop.ozone.om.multitenant.MultiTenantGateKeeper; +import org.apache.hadoop.ozone.om.multitenant.MultiTenantGateKeeperRangerPlugin; +import org.apache.hadoop.ozone.om.multitenant.OzoneMultiTenantPrincipal; +import org.apache.hadoop.ozone.om.multitenant.RangerAccessPolicy; +import org.apache.hadoop.ozone.om.multitenant.Tenant; +import org.apache.hadoop.ozone.om.multitenantImpl.OzoneMultiTenantPrincipalImpl; +import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType; +import org.apache.hadoop.ozone.security.acl.OzoneObj; +import org.apache.hadoop.ozone.security.acl.OzoneObjInfo; +import org.apache.http.auth.BasicUserPrincipal; + +public class OMMultiTenantManagerImpl implements OMMultiTenantManager { + private MultiTenantGateKeeper gateKeeper; + private OMMetadataManager omMetadataManager; + private OzoneConfiguration conf; + private Map<String, Tenant> allTenants; + + OMMultiTenantManagerImpl(OMMetadataManager mgr, OzoneConfiguration conf) + throws IOException { + this.conf = conf; + allTenants = new ConcurrentHashMap<>(); + omMetadataManager = mgr; + start(conf); + } + + @Override + public void start(OzoneConfiguration configuration) throws IOException { + gateKeeper = new MultiTenantGateKeeperRangerPlugin(); Review comment: nit. Type of plugin instance can be config driven in the future. ########## File path: hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantCreateRequest.java ########## @@ -0,0 +1,300 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.hadoop.ozone.om.request.s3.tenant; + +import com.google.common.base.Optional; +import org.apache.hadoop.hdds.utils.db.cache.CacheKey; +import org.apache.hadoop.hdds.utils.db.cache.CacheValue; +import org.apache.hadoop.ozone.OmUtils; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.audit.OMAction; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.OzoneManager; +import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.ozone.om.helpers.OmDBTenantInfo; +import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; +import org.apache.hadoop.ozone.om.multitenant.Tenant; +import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper; +import org.apache.hadoop.ozone.om.request.util.OmResponseUtil; +import org.apache.hadoop.ozone.om.request.volume.OMVolumeRequest; +import org.apache.hadoop.ozone.om.response.OMClientResponse; +import org.apache.hadoop.ozone.om.response.s3.tenant.OMTenantCreateResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateTenantRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateTenantResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateVolumeRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.VolumeInfo; +import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; +import org.apache.hadoop.ozone.security.acl.OzoneObj; +import org.apache.hadoop.ozone.storage.proto.OzoneManagerStorageProtos.PersistedUserVolumeInfo; +import org.apache.hadoop.util.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.USER_LOCK; +import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.VOLUME_LOCK; + +/* + Ratis execution flow for OMTenantCreate + +- preExecute (perform checks and init) + - Check tenant name validity (again) + - If name is invalid, throw exception to client; else continue +- validateAndUpdateCache (update DB) + - Grab VOLUME_LOCK write lock + - Check volume existence + - If tenant already exists, throw exception to client; else continue + - Check tenant existence by checking tenantStateTable keys + - If tenant already exists, throw exception to client; else continue + - tenantStateTable: New entry + - Key: tenant name. e.g. finance + - Value: new OmDBTenantInfo for the tenant + - tenantName: finance + - bucketNamespaceName: finance + - accountNamespaceName: finance + - userPolicyGroupName: finance-users + - bucketPolicyGroupName: finance-buckets + - tenantPolicyTable: Generate default policies for the new tenant + - K: finance-Users, V: finance-users-default + - K: finance-Buckets, V: finance-buckets-default + - Grab USER_LOCK write lock + - Create volume finance (See OMVolumeCreateRequest) + - Release VOLUME_LOCK write lock + - Release USER_LOCK write lock + - Queue Ranger policy sync that pushes default policies: + OMMultiTenantManager#createTenant + */ + +/** + * Handles OMTenantCreate request. + */ +public class OMTenantCreateRequest extends OMVolumeRequest { + private static final Logger LOG = + LoggerFactory.getLogger(OMTenantCreateRequest.class); + + public OMTenantCreateRequest(OMRequest omRequest) { + super(omRequest); + } + + @Override + public OMRequest preExecute(OzoneManager ozoneManager) throws IOException { + final CreateTenantRequest request = getOmRequest().getCreateTenantRequest(); + final String tenantName = request.getTenantName(); + + // Check tenantName validity + if (tenantName.contains(OzoneConsts.TENANT_NAME_USER_NAME_DELIMITER)) { + throw new OMException("Invalid tenant name " + tenantName + + ". Tenant name should not contain delimiter.", + OMException.ResultCodes.INVALID_VOLUME_NAME); + } + + // getUserName returns: + // - Kerberos principal when Kerberos security is enabled + // - User's login name when security is not enabled + // - AWS_ACCESS_KEY_ID if the original request comes from S3 Gateway. + // Not Applicable to TenantCreateRequest. + final String owner = getOmRequest().getUserInfo().getUserName(); + final String volumeName = tenantName; // TODO: Configurable + final VolumeInfo volumeInfo = VolumeInfo.newBuilder() + .setVolume(volumeName) + .setAdminName(owner) + .setOwnerName(owner) + .build(); + // Verify volume name + OmUtils.validateVolumeName(volumeInfo.getVolume()); + + // Generate volume modification time + long initialTime = Time.now(); + final VolumeInfo updatedVolumeInfo = volumeInfo.toBuilder() + .setCreationTime(initialTime) + .setModificationTime(initialTime) + .build(); + + final OMRequest.Builder omRequestBuilder = getOmRequest().toBuilder() + .setCreateTenantRequest( + CreateTenantRequest.newBuilder().setTenantName(tenantName)) + .setCreateVolumeRequest( + CreateVolumeRequest.newBuilder().setVolumeInfo(updatedVolumeInfo)) + .setUserInfo(getUserInfo()) + .setCmdType(getOmRequest().getCmdType()) + .setClientId(getOmRequest().getClientId()); + + if (getOmRequest().hasTraceID()) { + omRequestBuilder.setTraceID(getOmRequest().getTraceID()); + } + + return omRequestBuilder.build(); + } + + @Override + public OMClientResponse validateAndUpdateCache( + OzoneManager ozoneManager, long transactionLogIndex, + OzoneManagerDoubleBufferHelper ozoneManagerDoubleBufferHelper) { + + OMClientResponse omClientResponse = null; + OMResponse.Builder omResponse = OmResponseUtil.getOMResponseBuilder( + getOmRequest()); + OmVolumeArgs omVolumeArgs; + boolean acquiredVolumeLock = false, acquiredUserLock = false; + Tenant tenant = null; + final String owner = getOmRequest().getUserInfo().getUserName(); + LOG.info("owner: {}", owner); // TODO: owner should be access_key_id + Map<String, String> auditMap = new HashMap<>(); + OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager(); + CreateTenantRequest request = getOmRequest().getCreateTenantRequest(); + final String tenantName = request.getTenantName(); + final VolumeInfo volumeInfo = + getOmRequest().getCreateVolumeRequest().getVolumeInfo(); + final String volumeName = volumeInfo.getVolume(); + final String dbVolumeKey = omMetadataManager.getVolumeKey(volumeName); + IOException exception = null; + try { + // Check ACL: requires volume create permission. TODO: tenant create perm? Review comment: Can any user who has volume create permissions on Ranger (or admin) be allowed to create a tenant? ########## File path: hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantCreateRequest.java ########## @@ -0,0 +1,300 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.hadoop.ozone.om.request.s3.tenant; + +import com.google.common.base.Optional; +import org.apache.hadoop.hdds.utils.db.cache.CacheKey; +import org.apache.hadoop.hdds.utils.db.cache.CacheValue; +import org.apache.hadoop.ozone.OmUtils; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.audit.OMAction; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.OzoneManager; +import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.ozone.om.helpers.OmDBTenantInfo; +import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; +import org.apache.hadoop.ozone.om.multitenant.Tenant; +import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper; +import org.apache.hadoop.ozone.om.request.util.OmResponseUtil; +import org.apache.hadoop.ozone.om.request.volume.OMVolumeRequest; +import org.apache.hadoop.ozone.om.response.OMClientResponse; +import org.apache.hadoop.ozone.om.response.s3.tenant.OMTenantCreateResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateTenantRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateTenantResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateVolumeRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.VolumeInfo; +import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; +import org.apache.hadoop.ozone.security.acl.OzoneObj; +import org.apache.hadoop.ozone.storage.proto.OzoneManagerStorageProtos.PersistedUserVolumeInfo; +import org.apache.hadoop.util.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.USER_LOCK; +import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.VOLUME_LOCK; + +/* + Ratis execution flow for OMTenantCreate + +- preExecute (perform checks and init) + - Check tenant name validity (again) + - If name is invalid, throw exception to client; else continue +- validateAndUpdateCache (update DB) + - Grab VOLUME_LOCK write lock + - Check volume existence + - If tenant already exists, throw exception to client; else continue + - Check tenant existence by checking tenantStateTable keys + - If tenant already exists, throw exception to client; else continue + - tenantStateTable: New entry + - Key: tenant name. e.g. finance + - Value: new OmDBTenantInfo for the tenant + - tenantName: finance + - bucketNamespaceName: finance + - accountNamespaceName: finance + - userPolicyGroupName: finance-users + - bucketPolicyGroupName: finance-buckets + - tenantPolicyTable: Generate default policies for the new tenant + - K: finance-Users, V: finance-users-default + - K: finance-Buckets, V: finance-buckets-default + - Grab USER_LOCK write lock + - Create volume finance (See OMVolumeCreateRequest) + - Release VOLUME_LOCK write lock + - Release USER_LOCK write lock + - Queue Ranger policy sync that pushes default policies: + OMMultiTenantManager#createTenant + */ + +/** + * Handles OMTenantCreate request. + */ +public class OMTenantCreateRequest extends OMVolumeRequest { + private static final Logger LOG = + LoggerFactory.getLogger(OMTenantCreateRequest.class); + + public OMTenantCreateRequest(OMRequest omRequest) { + super(omRequest); + } + + @Override + public OMRequest preExecute(OzoneManager ozoneManager) throws IOException { + final CreateTenantRequest request = getOmRequest().getCreateTenantRequest(); + final String tenantName = request.getTenantName(); + + // Check tenantName validity + if (tenantName.contains(OzoneConsts.TENANT_NAME_USER_NAME_DELIMITER)) { + throw new OMException("Invalid tenant name " + tenantName + + ". Tenant name should not contain delimiter.", + OMException.ResultCodes.INVALID_VOLUME_NAME); + } + + // getUserName returns: + // - Kerberos principal when Kerberos security is enabled + // - User's login name when security is not enabled + // - AWS_ACCESS_KEY_ID if the original request comes from S3 Gateway. + // Not Applicable to TenantCreateRequest. + final String owner = getOmRequest().getUserInfo().getUserName(); + final String volumeName = tenantName; // TODO: Configurable + final VolumeInfo volumeInfo = VolumeInfo.newBuilder() + .setVolume(volumeName) + .setAdminName(owner) + .setOwnerName(owner) + .build(); + // Verify volume name + OmUtils.validateVolumeName(volumeInfo.getVolume()); + + // Generate volume modification time + long initialTime = Time.now(); + final VolumeInfo updatedVolumeInfo = volumeInfo.toBuilder() + .setCreationTime(initialTime) + .setModificationTime(initialTime) + .build(); + + final OMRequest.Builder omRequestBuilder = getOmRequest().toBuilder() + .setCreateTenantRequest( + CreateTenantRequest.newBuilder().setTenantName(tenantName)) + .setCreateVolumeRequest( + CreateVolumeRequest.newBuilder().setVolumeInfo(updatedVolumeInfo)) + .setUserInfo(getUserInfo()) + .setCmdType(getOmRequest().getCmdType()) + .setClientId(getOmRequest().getClientId()); + + if (getOmRequest().hasTraceID()) { + omRequestBuilder.setTraceID(getOmRequest().getTraceID()); + } + + return omRequestBuilder.build(); + } + + @Override + public OMClientResponse validateAndUpdateCache( + OzoneManager ozoneManager, long transactionLogIndex, + OzoneManagerDoubleBufferHelper ozoneManagerDoubleBufferHelper) { + + OMClientResponse omClientResponse = null; + OMResponse.Builder omResponse = OmResponseUtil.getOMResponseBuilder( + getOmRequest()); + OmVolumeArgs omVolumeArgs; + boolean acquiredVolumeLock = false, acquiredUserLock = false; + Tenant tenant = null; + final String owner = getOmRequest().getUserInfo().getUserName(); + LOG.info("owner: {}", owner); // TODO: owner should be access_key_id + Map<String, String> auditMap = new HashMap<>(); + OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager(); + CreateTenantRequest request = getOmRequest().getCreateTenantRequest(); + final String tenantName = request.getTenantName(); + final VolumeInfo volumeInfo = + getOmRequest().getCreateVolumeRequest().getVolumeInfo(); + final String volumeName = volumeInfo.getVolume(); + final String dbVolumeKey = omMetadataManager.getVolumeKey(volumeName); + IOException exception = null; + try { + // Check ACL: requires volume create permission. TODO: tenant create perm? + if (ozoneManager.getAclsEnabled()) { + checkAcls(ozoneManager, OzoneObj.ResourceType.VOLUME, + OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.CREATE, + tenantName, null, null); + } + + acquiredVolumeLock = omMetadataManager.getLock().acquireWriteLock( + VOLUME_LOCK, volumeName); + // Check volume existence + if (omMetadataManager.getVolumeTable().isExist(volumeName)) { + LOG.debug("volume: {} already exists", volumeName); + throw new OMException("Volume already exists", + OMException.ResultCodes.VOLUME_ALREADY_EXISTS); + } + // Check tenant existence in tenantStateTable + if (omMetadataManager.getTenantStateTable().isExist(tenantName)) { + LOG.debug("tenant: {} already exists", tenantName); + throw new OMException("Tenant already exists", + OMException.ResultCodes.TENANT_ALREADY_EXISTS); + } + + // Add to tenantStateTable. Redundant assignment for clarity + final String bucketNamespaceName = tenantName; + final String accountNamespaceName = tenantName; + final String userPolicyGroupName = + tenantName + OzoneConsts.DEFAULT_TENANT_USER_POLICY_SUFFIX; + final String bucketPolicyGroupName = + tenantName + OzoneConsts.DEFAULT_TENANT_BUCKET_POLICY_SUFFIX; + final OmDBTenantInfo omDBTenantInfo = new OmDBTenantInfo( + tenantName, bucketNamespaceName, accountNamespaceName, + userPolicyGroupName, bucketPolicyGroupName); + omMetadataManager.getTenantStateTable().addCacheEntry( + new CacheKey<>(tenantName), + new CacheValue<>(Optional.of(omDBTenantInfo), transactionLogIndex)); + + // Call OMMultiTenantManager +// tenant = ozoneManager.getMultiTenantManager().createTenant(tenantName); Review comment: This would be a no go in actual (non prototype) code. We are making a Ranger call with volume lock, and from inside Ratis applyTxn. ########## File path: hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/tenant/OMTenantCreateRequest.java ########## @@ -0,0 +1,300 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.hadoop.ozone.om.request.s3.tenant; + +import com.google.common.base.Optional; +import org.apache.hadoop.hdds.utils.db.cache.CacheKey; +import org.apache.hadoop.hdds.utils.db.cache.CacheValue; +import org.apache.hadoop.ozone.OmUtils; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.audit.OMAction; +import org.apache.hadoop.ozone.om.OMMetadataManager; +import org.apache.hadoop.ozone.om.OzoneManager; +import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.ozone.om.helpers.OmDBTenantInfo; +import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs; +import org.apache.hadoop.ozone.om.multitenant.Tenant; +import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerDoubleBufferHelper; +import org.apache.hadoop.ozone.om.request.util.OmResponseUtil; +import org.apache.hadoop.ozone.om.request.volume.OMVolumeRequest; +import org.apache.hadoop.ozone.om.response.OMClientResponse; +import org.apache.hadoop.ozone.om.response.s3.tenant.OMTenantCreateResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateTenantRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateTenantResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateVolumeRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.VolumeInfo; +import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; +import org.apache.hadoop.ozone.security.acl.OzoneObj; +import org.apache.hadoop.ozone.storage.proto.OzoneManagerStorageProtos.PersistedUserVolumeInfo; +import org.apache.hadoop.util.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.USER_LOCK; +import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.VOLUME_LOCK; + +/* + Ratis execution flow for OMTenantCreate + +- preExecute (perform checks and init) + - Check tenant name validity (again) + - If name is invalid, throw exception to client; else continue +- validateAndUpdateCache (update DB) + - Grab VOLUME_LOCK write lock + - Check volume existence + - If tenant already exists, throw exception to client; else continue + - Check tenant existence by checking tenantStateTable keys + - If tenant already exists, throw exception to client; else continue + - tenantStateTable: New entry + - Key: tenant name. e.g. finance + - Value: new OmDBTenantInfo for the tenant + - tenantName: finance + - bucketNamespaceName: finance + - accountNamespaceName: finance + - userPolicyGroupName: finance-users + - bucketPolicyGroupName: finance-buckets + - tenantPolicyTable: Generate default policies for the new tenant + - K: finance-Users, V: finance-users-default + - K: finance-Buckets, V: finance-buckets-default + - Grab USER_LOCK write lock + - Create volume finance (See OMVolumeCreateRequest) + - Release VOLUME_LOCK write lock + - Release USER_LOCK write lock + - Queue Ranger policy sync that pushes default policies: + OMMultiTenantManager#createTenant + */ + +/** + * Handles OMTenantCreate request. + */ +public class OMTenantCreateRequest extends OMVolumeRequest { + private static final Logger LOG = + LoggerFactory.getLogger(OMTenantCreateRequest.class); + + public OMTenantCreateRequest(OMRequest omRequest) { + super(omRequest); + } + + @Override + public OMRequest preExecute(OzoneManager ozoneManager) throws IOException { + final CreateTenantRequest request = getOmRequest().getCreateTenantRequest(); + final String tenantName = request.getTenantName(); + + // Check tenantName validity + if (tenantName.contains(OzoneConsts.TENANT_NAME_USER_NAME_DELIMITER)) { + throw new OMException("Invalid tenant name " + tenantName + + ". Tenant name should not contain delimiter.", + OMException.ResultCodes.INVALID_VOLUME_NAME); + } + + // getUserName returns: + // - Kerberos principal when Kerberos security is enabled + // - User's login name when security is not enabled + // - AWS_ACCESS_KEY_ID if the original request comes from S3 Gateway. + // Not Applicable to TenantCreateRequest. + final String owner = getOmRequest().getUserInfo().getUserName(); + final String volumeName = tenantName; // TODO: Configurable + final VolumeInfo volumeInfo = VolumeInfo.newBuilder() + .setVolume(volumeName) + .setAdminName(owner) + .setOwnerName(owner) + .build(); + // Verify volume name + OmUtils.validateVolumeName(volumeInfo.getVolume()); + + // Generate volume modification time + long initialTime = Time.now(); + final VolumeInfo updatedVolumeInfo = volumeInfo.toBuilder() + .setCreationTime(initialTime) + .setModificationTime(initialTime) + .build(); + + final OMRequest.Builder omRequestBuilder = getOmRequest().toBuilder() + .setCreateTenantRequest( + CreateTenantRequest.newBuilder().setTenantName(tenantName)) + .setCreateVolumeRequest( + CreateVolumeRequest.newBuilder().setVolumeInfo(updatedVolumeInfo)) + .setUserInfo(getUserInfo()) + .setCmdType(getOmRequest().getCmdType()) + .setClientId(getOmRequest().getClientId()); + + if (getOmRequest().hasTraceID()) { + omRequestBuilder.setTraceID(getOmRequest().getTraceID()); + } + + return omRequestBuilder.build(); + } + + @Override + public OMClientResponse validateAndUpdateCache( + OzoneManager ozoneManager, long transactionLogIndex, + OzoneManagerDoubleBufferHelper ozoneManagerDoubleBufferHelper) { + + OMClientResponse omClientResponse = null; + OMResponse.Builder omResponse = OmResponseUtil.getOMResponseBuilder( + getOmRequest()); + OmVolumeArgs omVolumeArgs; + boolean acquiredVolumeLock = false, acquiredUserLock = false; + Tenant tenant = null; + final String owner = getOmRequest().getUserInfo().getUserName(); + LOG.info("owner: {}", owner); // TODO: owner should be access_key_id + Map<String, String> auditMap = new HashMap<>(); + OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager(); + CreateTenantRequest request = getOmRequest().getCreateTenantRequest(); + final String tenantName = request.getTenantName(); + final VolumeInfo volumeInfo = + getOmRequest().getCreateVolumeRequest().getVolumeInfo(); + final String volumeName = volumeInfo.getVolume(); + final String dbVolumeKey = omMetadataManager.getVolumeKey(volumeName); + IOException exception = null; + try { + // Check ACL: requires volume create permission. TODO: tenant create perm? + if (ozoneManager.getAclsEnabled()) { + checkAcls(ozoneManager, OzoneObj.ResourceType.VOLUME, + OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.CREATE, + tenantName, null, null); + } + + acquiredVolumeLock = omMetadataManager.getLock().acquireWriteLock( + VOLUME_LOCK, volumeName); + // Check volume existence + if (omMetadataManager.getVolumeTable().isExist(volumeName)) { + LOG.debug("volume: {} already exists", volumeName); + throw new OMException("Volume already exists", + OMException.ResultCodes.VOLUME_ALREADY_EXISTS); + } + // Check tenant existence in tenantStateTable + if (omMetadataManager.getTenantStateTable().isExist(tenantName)) { + LOG.debug("tenant: {} already exists", tenantName); + throw new OMException("Tenant already exists", + OMException.ResultCodes.TENANT_ALREADY_EXISTS); + } + + // Add to tenantStateTable. Redundant assignment for clarity + final String bucketNamespaceName = tenantName; + final String accountNamespaceName = tenantName; + final String userPolicyGroupName = + tenantName + OzoneConsts.DEFAULT_TENANT_USER_POLICY_SUFFIX; + final String bucketPolicyGroupName = + tenantName + OzoneConsts.DEFAULT_TENANT_BUCKET_POLICY_SUFFIX; + final OmDBTenantInfo omDBTenantInfo = new OmDBTenantInfo( + tenantName, bucketNamespaceName, accountNamespaceName, + userPolicyGroupName, bucketPolicyGroupName); + omMetadataManager.getTenantStateTable().addCacheEntry( + new CacheKey<>(tenantName), + new CacheValue<>(Optional.of(omDBTenantInfo), transactionLogIndex)); + + // Call OMMultiTenantManager +// tenant = ozoneManager.getMultiTenantManager().createTenant(tenantName); Review comment: We can have a policy sync queue (and persistence) that pushes the policies to the external plugin in an async manner. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
