blob: c89b32ee7347e41b3d2eda0670ddc25cdfa7451e [file] [log] [blame]
/**
* 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;
import com.google.common.base.Strings;
import org.apache.hadoop.ozone.OzoneAcl;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
import org.apache.hadoop.ozone.om.helpers.OmPrefixInfo;
import org.apache.hadoop.ozone.om.helpers.OzoneAclUtil;
import org.apache.hadoop.ozone.security.acl.OzoneObj;
import org.apache.hadoop.ozone.security.acl.RequestContext;
import org.apache.hadoop.ozone.util.RadixNode;
import org.apache.hadoop.ozone.util.RadixTree;
import org.apache.hadoop.hdds.utils.db.Table.KeyValue;
import org.apache.hadoop.hdds.utils.db.TableIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.BUCKET_NOT_FOUND;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.PREFIX_NOT_FOUND;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_NOT_FOUND;
import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.PREFIX_LOCK;
import static org.apache.hadoop.ozone.security.acl.OzoneObj.ResourceType.PREFIX;
/**
* Implementation of PrefixManager.
*/
public class PrefixManagerImpl implements PrefixManager {
private static final Logger LOG =
LoggerFactory.getLogger(PrefixManagerImpl.class);
private static final List<OzoneAcl> EMPTY_ACL_LIST = new ArrayList<>();
private final OMMetadataManager metadataManager;
// In-memory prefix tree to optimize ACL evaluation
private RadixTree<OmPrefixInfo> prefixTree;
// TODO: This isRatisEnabled check will be removed as part of HDDS-1909,
// where we integrate both HA and Non-HA code.
private boolean isRatisEnabled;
public PrefixManagerImpl(OMMetadataManager metadataManager,
boolean isRatisEnabled) {
this.isRatisEnabled = isRatisEnabled;
this.metadataManager = metadataManager;
loadPrefixTree();
}
private void loadPrefixTree() {
prefixTree = new RadixTree<>();
try (TableIterator<String, ? extends
KeyValue<String, OmPrefixInfo>> iterator =
getMetadataManager().getPrefixTable().iterator()) {
iterator.seekToFirst();
while (iterator.hasNext()) {
KeyValue<String, OmPrefixInfo> kv = iterator.next();
prefixTree.insert(kv.getKey(), kv.getValue());
}
} catch (IOException ex) {
LOG.error("Fail to load prefix tree");
}
}
@Override
public OMMetadataManager getMetadataManager() {
return metadataManager;
}
/**
* Add acl for Ozone object. Return true if acl is added successfully else
* false.
*
* @param obj Ozone object for which acl should be added.
* @param acl ozone acl top be added.
* @throws IOException if there is error.
*/
@Override
public boolean addAcl(OzoneObj obj, OzoneAcl acl) throws IOException {
validateOzoneObj(obj);
String prefixPath = obj.getPath();
metadataManager.getLock().acquireLock(PREFIX_LOCK, prefixPath);
try {
OmPrefixInfo prefixInfo =
metadataManager.getPrefixTable().get(prefixPath);
OMPrefixAclOpResult omPrefixAclOpResult = addAcl(obj, acl, prefixInfo);
return omPrefixAclOpResult.isOperationsResult();
} catch (IOException ex) {
if (!(ex instanceof OMException)) {
LOG.error("Add acl operation failed for prefix path:{} acl:{}",
prefixPath, acl, ex);
}
throw ex;
} finally {
metadataManager.getLock().releaseLock(PREFIX_LOCK, prefixPath);
}
}
/**
* Remove acl for Ozone object. Return true if acl is removed successfully
* else false.
*
* @param obj Ozone object.
* @param acl Ozone acl to be removed.
* @throws IOException if there is error.
*/
@Override
public boolean removeAcl(OzoneObj obj, OzoneAcl acl) throws IOException {
validateOzoneObj(obj);
String prefixPath = obj.getPath();
metadataManager.getLock().acquireLock(PREFIX_LOCK, prefixPath);
try {
OmPrefixInfo prefixInfo =
metadataManager.getPrefixTable().get(prefixPath);
OMPrefixAclOpResult omPrefixAclOpResult = removeAcl(obj, acl, prefixInfo);
if (!omPrefixAclOpResult.isOperationsResult()) {
if (LOG.isDebugEnabled()) {
LOG.debug("acl {} does not exist for prefix path {} ",
acl, prefixPath);
}
return false;
}
return omPrefixAclOpResult.isOperationsResult();
} catch (IOException ex) {
if (!(ex instanceof OMException)) {
LOG.error("Remove prefix acl operation failed for prefix path:{}" +
" acl:{}", prefixPath, acl, ex);
}
throw ex;
} finally {
metadataManager.getLock().releaseLock(PREFIX_LOCK, prefixPath);
}
}
/**
* Acls to be set for given Ozone object. This operations reset ACL for given
* object to list of ACLs provided in argument.
*
* @param obj Ozone object.
* @param acls List of acls.
* @throws IOException if there is error.
*/
@Override
public boolean setAcl(OzoneObj obj, List<OzoneAcl> acls) throws IOException {
validateOzoneObj(obj);
String prefixPath = obj.getPath();
metadataManager.getLock().acquireLock(PREFIX_LOCK, prefixPath);
try {
OmPrefixInfo prefixInfo =
metadataManager.getPrefixTable().get(prefixPath);
OMPrefixAclOpResult omPrefixAclOpResult = setAcl(obj, acls, prefixInfo);
return omPrefixAclOpResult.isOperationsResult();
} catch (IOException ex) {
if (!(ex instanceof OMException)) {
LOG.error("Set prefix acl operation failed for prefix path:{} acls:{}",
prefixPath, acls, ex);
}
throw ex;
} finally {
metadataManager.getLock().releaseLock(PREFIX_LOCK, prefixPath);
}
}
/**
* Returns list of ACLs for given Ozone object.
*
* @param obj Ozone object.
* @throws IOException if there is error.
*/
@Override
public List<OzoneAcl> getAcl(OzoneObj obj) throws IOException {
validateOzoneObj(obj);
String prefixPath = obj.getPath();
metadataManager.getLock().acquireLock(PREFIX_LOCK, prefixPath);
try {
String longestPrefix = prefixTree.getLongestPrefix(prefixPath);
if (prefixPath.equals(longestPrefix)) {
RadixNode<OmPrefixInfo> lastNode =
prefixTree.getLastNodeInPrefixPath(prefixPath);
if (lastNode != null && lastNode.getValue() != null) {
return lastNode.getValue().getAcls();
}
}
} finally {
metadataManager.getLock().releaseLock(PREFIX_LOCK, prefixPath);
}
return EMPTY_ACL_LIST;
}
/**
* Check access for given ozoneObject.
*
* @param ozObject object for which access needs to be checked.
* @param context Context object encapsulating all user related information.
* @return true if user has access else false.
*/
@Override
public boolean checkAccess(OzoneObj ozObject, RequestContext context)
throws OMException {
Objects.requireNonNull(ozObject);
Objects.requireNonNull(context);
String prefixPath = ozObject.getPath();
metadataManager.getLock().acquireLock(PREFIX_LOCK, prefixPath);
try {
String longestPrefix = prefixTree.getLongestPrefix(prefixPath);
if (prefixPath.equals(longestPrefix)) {
RadixNode<OmPrefixInfo> lastNode =
prefixTree.getLastNodeInPrefixPath(prefixPath);
if (lastNode != null && lastNode.getValue() != null) {
boolean hasAccess = OzoneAclUtil.checkAclRights(lastNode.getValue().
getAcls(), context);
if (LOG.isDebugEnabled()) {
LOG.debug("user:{} has access rights for ozObj:{} ::{} ",
context.getClientUgi(), ozObject, hasAccess);
}
return hasAccess;
} else {
return true;
}
} else {
return true;
}
} finally {
metadataManager.getLock().releaseLock(PREFIX_LOCK, prefixPath);
}
}
@Override
public List<OmPrefixInfo> getLongestPrefixPath(String path) {
String prefixPath = prefixTree.getLongestPrefix(path);
metadataManager.getLock().acquireLock(PREFIX_LOCK, prefixPath);
try {
return getLongestPrefixPathHelper(prefixPath);
} finally {
metadataManager.getLock().releaseLock(PREFIX_LOCK, prefixPath);
}
}
/**
* Get longest prefix path assuming caller take prefix lock.
* @param prefixPath
* @return list of prefix info.
*/
private List<OmPrefixInfo> getLongestPrefixPathHelper(String prefixPath) {
return prefixTree.getLongestPrefixPath(prefixPath).stream()
.map(c -> c.getValue()).collect(Collectors.toList());
}
/**
* Helper method to validate ozone object.
* @param obj
* */
public void validateOzoneObj(OzoneObj obj) throws OMException {
Objects.requireNonNull(obj);
if (!obj.getResourceType().equals(PREFIX)) {
throw new IllegalArgumentException("Unexpected argument passed to " +
"PrefixManager. OzoneObj type:" + obj.getResourceType());
}
String volume = obj.getVolumeName();
String bucket = obj.getBucketName();
String prefixName = obj.getPrefixName();
if (Strings.isNullOrEmpty(volume)) {
throw new OMException("Volume name is required.", VOLUME_NOT_FOUND);
}
if (Strings.isNullOrEmpty(bucket)) {
throw new OMException("Bucket name is required.", BUCKET_NOT_FOUND);
}
if (Strings.isNullOrEmpty(prefixName)) {
throw new OMException("Prefix name is required.", PREFIX_NOT_FOUND);
}
if (!prefixName.endsWith("/")) {
throw new OMException("Invalid prefix name: " + prefixName,
PREFIX_NOT_FOUND);
}
}
public OMPrefixAclOpResult addAcl(OzoneObj ozoneObj, OzoneAcl ozoneAcl,
OmPrefixInfo prefixInfo) throws IOException {
if (prefixInfo == null) {
prefixInfo = new OmPrefixInfo.Builder().setName(ozoneObj
.getPath()).build();
}
boolean changed = prefixInfo.addAcl(ozoneAcl);
if (changed) {
// update the in-memory prefix tree
prefixTree.insert(ozoneObj.getPath(), prefixInfo);
if (!isRatisEnabled) {
metadataManager.getPrefixTable().put(ozoneObj.getPath(), prefixInfo);
}
}
return new OMPrefixAclOpResult(prefixInfo, changed);
}
public OMPrefixAclOpResult removeAcl(OzoneObj ozoneObj, OzoneAcl ozoneAcl,
OmPrefixInfo prefixInfo) throws IOException {
boolean removed = false;
if (prefixInfo != null) {
removed = prefixInfo.removeAcl(ozoneAcl);
}
// Nothing is matching to remove.
if (removed) {
// Update in-memory prefix tree.
if (prefixInfo.getAcls().isEmpty()) {
prefixTree.removePrefixPath(ozoneObj.getPath());
if (!isRatisEnabled) {
metadataManager.getPrefixTable().delete(ozoneObj.getPath());
}
} else {
prefixTree.insert(ozoneObj.getPath(), prefixInfo);
if (!isRatisEnabled) {
metadataManager.getPrefixTable().put(ozoneObj.getPath(), prefixInfo);
}
}
}
return new OMPrefixAclOpResult(prefixInfo, removed);
}
public OMPrefixAclOpResult setAcl(OzoneObj ozoneObj, List<OzoneAcl> ozoneAcls,
OmPrefixInfo prefixInfo) throws IOException {
if (prefixInfo == null) {
prefixInfo = new OmPrefixInfo.Builder().setName(ozoneObj
.getPath()).build();
}
boolean changed = prefixInfo.setAcls(ozoneAcls);
if (changed) {
List<OzoneAcl> aclsToBeSet = prefixInfo.getAcls();
// Inherit DEFAULT acls from prefix.
boolean prefixParentFound = false;
List<OmPrefixInfo> prefixList = getLongestPrefixPathHelper(
prefixTree.getLongestPrefix(ozoneObj.getPath()));
if (prefixList.size() > 0) {
// Add all acls from direct parent to key.
OmPrefixInfo parentPrefixInfo = prefixList.get(prefixList.size() - 1);
if (parentPrefixInfo != null) {
prefixParentFound = OzoneAclUtil.inheritDefaultAcls(aclsToBeSet,
parentPrefixInfo.getAcls());
}
}
// If no parent prefix is found inherit DEFAULT acls from bucket.
if (!prefixParentFound) {
String bucketKey = metadataManager.getBucketKey(ozoneObj
.getVolumeName(), ozoneObj.getBucketName());
OmBucketInfo bucketInfo = metadataManager.getBucketTable().
get(bucketKey);
if (bucketInfo != null) {
OzoneAclUtil.inheritDefaultAcls(aclsToBeSet, bucketInfo.getAcls());
}
}
prefixTree.insert(ozoneObj.getPath(), prefixInfo);
if (!isRatisEnabled) {
metadataManager.getPrefixTable().put(ozoneObj.getPath(), prefixInfo);
}
}
return new OMPrefixAclOpResult(prefixInfo, changed);
}
/**
* Result of the prefix acl operation.
*/
public static class OMPrefixAclOpResult {
private OmPrefixInfo omPrefixInfo;
private boolean operationsResult;
public OMPrefixAclOpResult(OmPrefixInfo omPrefixInfo,
boolean operationsResult) {
this.omPrefixInfo = omPrefixInfo;
this.operationsResult = operationsResult;
}
public OmPrefixInfo getOmPrefixInfo() {
return omPrefixInfo;
}
public boolean isOperationsResult() {
return operationsResult;
}
}
}