blob: f2c343e0d16186f9645a002760d99ea364d5f3ca [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.request.bucket;
import org.apache.hadoop.crypto.key.KeyProviderCryptoExtension;
import org.apache.hadoop.hdds.client.DefaultReplicationConfig;
import org.apache.ratis.server.protocol.TermIndex;
import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
import org.apache.hadoop.ozone.ClientVersion;
import org.apache.hadoop.ozone.OmUtils;
import org.apache.hadoop.ozone.OzoneAcl;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.audit.AuditLogger;
import org.apache.hadoop.ozone.audit.OMAction;
import org.apache.hadoop.ozone.om.OMConfigKeys;
import org.apache.hadoop.ozone.common.BekInfoUtils;
import org.apache.hadoop.ozone.om.OMMetadataManager;
import org.apache.hadoop.ozone.om.OMMetrics;
import org.apache.hadoop.ozone.om.OzoneManager;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
import org.apache.hadoop.ozone.om.helpers.OzoneAclUtil;
import org.apache.hadoop.ozone.om.request.OMClientRequest;
import org.apache.hadoop.ozone.om.request.util.OmResponseUtil;
import org.apache.hadoop.ozone.om.request.validation.RequestFeatureValidator;
import org.apache.hadoop.ozone.om.request.validation.RequestProcessingPhase;
import org.apache.hadoop.ozone.om.request.validation.ValidationCondition;
import org.apache.hadoop.ozone.om.request.validation.ValidationContext;
import org.apache.hadoop.ozone.om.response.OMClientResponse;
import org.apache.hadoop.ozone.om.response.bucket.OMBucketCreateResponse;
import org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.BucketInfo;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateBucketRequest;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CreateBucketResponse;
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.Type;
import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
import org.apache.hadoop.ozone.security.acl.OzoneObj;
import org.apache.hadoop.util.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.util.ArrayList;
import java.util.List;
import static org.apache.hadoop.ozone.OzoneAcl.AclScope.ACCESS;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.BUCKET_ALREADY_EXISTS;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_NOT_FOUND;
import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.BUCKET_LOCK;
import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.VOLUME_LOCK;
/**
* Handles CreateBucket Request.
*/
public class OMBucketCreateRequest extends OMClientRequest {
private static final Logger LOG =
LoggerFactory.getLogger(OMBucketCreateRequest.class);
public OMBucketCreateRequest(OMRequest omRequest) {
super(omRequest);
}
@Override
public OMRequest preExecute(OzoneManager ozoneManager) throws IOException {
// Get original request.
CreateBucketRequest createBucketRequest =
getOmRequest().getCreateBucketRequest();
BucketInfo bucketInfo = createBucketRequest.getBucketInfo();
// Verify resource name
OmUtils.validateBucketName(bucketInfo.getBucketName(),
ozoneManager.isStrictS3());
validateMaxBucket(ozoneManager);
// Get KMS provider.
KeyProviderCryptoExtension kmsProvider =
ozoneManager.getKmsProvider();
// Create new Bucket request with new bucket info.
CreateBucketRequest.Builder newCreateBucketRequest =
createBucketRequest.toBuilder();
BucketInfo.Builder newBucketInfo = bucketInfo.toBuilder();
// Set creation time & modification time.
long initialTime = Time.now();
newBucketInfo.setCreationTime(initialTime)
.setModificationTime(initialTime);
if (bucketInfo.hasBeinfo()) {
newBucketInfo.setBeinfo(
BekInfoUtils.getBekInfo(kmsProvider, bucketInfo.getBeinfo()));
}
boolean hasSourceVolume = bucketInfo.hasSourceVolume();
boolean hasSourceBucket = bucketInfo.hasSourceBucket();
if (hasSourceBucket != hasSourceVolume) {
throw new OMException("Both source volume and source bucket are " +
"required for bucket links",
OMException.ResultCodes.INVALID_REQUEST);
}
if (hasSourceBucket && bucketInfo.hasBeinfo()) {
throw new OMException("Encryption cannot be set for bucket links",
OMException.ResultCodes.INVALID_REQUEST);
}
if (bucketInfo.hasDefaultReplicationConfig()) {
DefaultReplicationConfig drc = DefaultReplicationConfig.fromProto(
bucketInfo.getDefaultReplicationConfig());
ozoneManager.validateReplicationConfig(drc.getReplicationConfig());
}
newCreateBucketRequest.setBucketInfo(newBucketInfo.build());
return getOmRequest().toBuilder().setUserInfo(getUserInfo())
.setCreateBucketRequest(newCreateBucketRequest.build()).build();
}
private static void validateMaxBucket(OzoneManager ozoneManager)
throws IOException {
int maxBucket = ozoneManager.getConfiguration().getInt(
OMConfigKeys.OZONE_OM_MAX_BUCKET,
OMConfigKeys.OZONE_OM_MAX_BUCKET_DEFAULT);
if (maxBucket <= 0) {
maxBucket = OMConfigKeys.OZONE_OM_MAX_BUCKET_DEFAULT;
}
long nrOfBuckets = ozoneManager.getMetadataManager().getBucketTable()
.getEstimatedKeyCount();
if (nrOfBuckets >= maxBucket) {
throw new OMException("Cannot create more than " + maxBucket
+ " buckets",
ResultCodes.TOO_MANY_BUCKETS);
}
}
@Override
public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, TermIndex termIndex) {
final long transactionLogIndex = termIndex.getIndex();
OMMetrics omMetrics = ozoneManager.getMetrics();
omMetrics.incNumBucketCreates();
OMMetadataManager metadataManager = ozoneManager.getMetadataManager();
CreateBucketRequest createBucketRequest = getOmRequest()
.getCreateBucketRequest();
BucketInfo bucketInfo = createBucketRequest.getBucketInfo();
String volumeName = bucketInfo.getVolumeName();
String bucketName = bucketInfo.getBucketName();
OMResponse.Builder omResponse = OmResponseUtil.getOMResponseBuilder(
getOmRequest());
OmBucketInfo omBucketInfo = null;
// bucketInfo.hasBucketLayout() would be true when the request proto
// specifies a bucket layout.
// When the value is not specified in the proto, OM will use
// "ozone.default.bucket.layout".
if (!bucketInfo.hasBucketLayout()) {
BucketLayout defaultBucketLayout =
ozoneManager.getOMDefaultBucketLayout();
omBucketInfo =
OmBucketInfo.getFromProtobuf(bucketInfo, defaultBucketLayout);
} else {
omBucketInfo = OmBucketInfo.getFromProtobuf(bucketInfo);
}
if (omBucketInfo.getBucketLayout().isFileSystemOptimized()) {
omMetrics.incNumFSOBucketCreates();
}
AuditLogger auditLogger = ozoneManager.getAuditLogger();
OzoneManagerProtocolProtos.UserInfo userInfo = getOmRequest().getUserInfo();
String volumeKey = metadataManager.getVolumeKey(volumeName);
String bucketKey = metadataManager.getBucketKey(volumeName, bucketName);
Exception exception = null;
boolean acquiredBucketLock = false;
boolean acquiredVolumeLock = false;
OMClientResponse omClientResponse = null;
try {
// check Acl
if (ozoneManager.getAclsEnabled()) {
checkAcls(ozoneManager, OzoneObj.ResourceType.BUCKET,
OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.CREATE,
volumeName, bucketName, null);
}
mergeOmLockDetails(
metadataManager.getLock().acquireReadLock(VOLUME_LOCK, volumeName));
acquiredVolumeLock = getOmLockDetails().isLockAcquired();
mergeOmLockDetails(metadataManager.getLock().acquireWriteLock(
BUCKET_LOCK, volumeName, bucketName));
acquiredBucketLock = getOmLockDetails().isLockAcquired();
OmVolumeArgs omVolumeArgs =
metadataManager.getVolumeTable().getReadCopy(volumeKey);
//Check if the volume exists
if (omVolumeArgs == null) {
LOG.debug("volume: {} not found ", volumeName);
throw new OMException("Volume doesn't exist", VOLUME_NOT_FOUND);
}
//Check if bucket already exists
if (metadataManager.getBucketTable().isExist(bucketKey)) {
LOG.debug("bucket: {} already exists ", bucketName);
throw new OMException("Bucket already exist", BUCKET_ALREADY_EXISTS);
}
//Check quotaInBytes to update
if (!bucketInfo.hasSourceBucket()) {
checkQuotaBytesValid(metadataManager, omVolumeArgs, omBucketInfo,
volumeKey);
}
// Add objectID and updateID
omBucketInfo.setObjectID(
ozoneManager.getObjectIdFromTxId(transactionLogIndex));
omBucketInfo.setUpdateID(transactionLogIndex,
ozoneManager.isRatisEnabled());
// Add default acls from volume.
addDefaultAcls(omBucketInfo, omVolumeArgs);
// check namespace quota
checkQuotaInNamespace(omVolumeArgs, 1L);
// update used namespace for volume
omVolumeArgs.incrUsedNamespace(1L);
// Update table cache.
metadataManager.getVolumeTable().addCacheEntry(new CacheKey<>(volumeKey),
CacheValue.get(transactionLogIndex, omVolumeArgs));
metadataManager.getBucketTable().addCacheEntry(new CacheKey<>(bucketKey),
CacheValue.get(transactionLogIndex, omBucketInfo));
omResponse.setCreateBucketResponse(
CreateBucketResponse.newBuilder().build());
omClientResponse = new OMBucketCreateResponse(omResponse.build(),
omBucketInfo, omVolumeArgs.copyObject());
} catch (IOException | InvalidPathException ex) {
exception = ex;
omClientResponse = new OMBucketCreateResponse(
createErrorOMResponse(omResponse, exception));
} finally {
if (acquiredBucketLock) {
mergeOmLockDetails(
metadataManager.getLock().releaseWriteLock(BUCKET_LOCK, volumeName,
bucketName));
}
if (acquiredVolumeLock) {
mergeOmLockDetails(
metadataManager.getLock().releaseReadLock(VOLUME_LOCK, volumeName));
}
if (omClientResponse != null) {
omClientResponse.setOmLockDetails(getOmLockDetails());
}
}
// Performing audit logging outside of the lock.
auditLog(auditLogger, buildAuditMessage(OMAction.CREATE_BUCKET,
omBucketInfo.toAuditMap(), exception, userInfo));
// return response.
if (exception == null) {
LOG.info("created bucket: {} of layout {} in volume: {}", bucketName,
omBucketInfo.getBucketLayout(), volumeName);
omMetrics.incNumBuckets();
if (isECBucket(bucketInfo)) {
omMetrics.incEcBucketsTotal();
}
return omClientResponse;
} else {
omMetrics.incNumBucketCreateFails();
if (isECBucket(bucketInfo)) {
omMetrics.incEcBucketCreateFailsTotal();
}
LOG.error("Bucket creation failed for bucket:{} in volume:{}",
bucketName, volumeName, exception);
return omClientResponse;
}
}
private boolean isECBucket(BucketInfo bucketInfo) {
return bucketInfo.hasDefaultReplicationConfig() && bucketInfo
.getDefaultReplicationConfig().hasEcReplicationConfig();
}
/**
* Add default acls for bucket. These acls are inherited from volume
* default acl list.
*
* @param omBucketInfo
* @param omVolumeArgs
*/
private void addDefaultAcls(OmBucketInfo omBucketInfo,
OmVolumeArgs omVolumeArgs) {
// Add default acls for bucket creator.
List<OzoneAcl> acls = new ArrayList<>();
if (omBucketInfo.getAcls() != null) {
acls.addAll(omBucketInfo.getAcls());
}
// Add default acls from volume.
List<OzoneAcl> defaultVolumeAcls = omVolumeArgs.getDefaultAcls();
OzoneAclUtil.inheritDefaultAcls(acls, defaultVolumeAcls, ACCESS);
omBucketInfo.setAcls(acls);
}
/**
* Check namespace quota.
*/
private void checkQuotaInNamespace(OmVolumeArgs omVolumeArgs,
long allocatedNamespace) throws IOException {
if (omVolumeArgs.getQuotaInNamespace() > 0) {
long usedNamespace = omVolumeArgs.getUsedNamespace();
long quotaInNamespace = omVolumeArgs.getQuotaInNamespace();
long toUseNamespaceInTotal = usedNamespace + allocatedNamespace;
if (quotaInNamespace < toUseNamespaceInTotal) {
throw new OMException("The namespace quota of Volume:"
+ omVolumeArgs.getVolume() + " exceeded: quotaInNamespace: "
+ quotaInNamespace + " but namespace consumed: "
+ toUseNamespaceInTotal + ".",
OMException.ResultCodes.QUOTA_EXCEEDED);
}
}
}
public boolean checkQuotaBytesValid(OMMetadataManager metadataManager,
OmVolumeArgs omVolumeArgs, OmBucketInfo omBucketInfo, String volumeKey)
throws IOException {
long quotaInBytes = omBucketInfo.getQuotaInBytes();
long volumeQuotaInBytes = omVolumeArgs.getQuotaInBytes();
// When volume quota is set, then its mandatory to have bucket quota
if (volumeQuotaInBytes > 0) {
if (quotaInBytes <= 0) {
throw new OMException("Bucket space quota in this volume " +
"should be set as volume space quota is already set.",
OMException.ResultCodes.QUOTA_ERROR);
}
}
long totalBucketQuota = 0;
if (quotaInBytes > 0) {
totalBucketQuota = quotaInBytes;
} else {
return false;
}
List<OmBucketInfo> bucketList = metadataManager.listBuckets(
omVolumeArgs.getVolume(), null, null, Integer.MAX_VALUE, false);
for (OmBucketInfo bucketInfo : bucketList) {
long nextQuotaInBytes = bucketInfo.getQuotaInBytes();
if (nextQuotaInBytes > OzoneConsts.QUOTA_RESET) {
totalBucketQuota += nextQuotaInBytes;
}
}
if (volumeQuotaInBytes < totalBucketQuota
&& volumeQuotaInBytes != OzoneConsts.QUOTA_RESET) {
throw new OMException("Total buckets quota in this volume " +
"should not be greater than volume quota : the total space quota is" +
" set to:" + totalBucketQuota + ". But the volume space quota is:" +
volumeQuotaInBytes, OMException.ResultCodes.QUOTA_EXCEEDED);
}
return true;
}
@RequestFeatureValidator(
conditions = ValidationCondition.CLUSTER_NEEDS_FINALIZATION,
processingPhase = RequestProcessingPhase.PRE_PROCESS,
requestType = Type.CreateBucket
)
public static OMRequest disallowCreateBucketWithECReplicationConfig(
OMRequest req, ValidationContext ctx) throws OMException {
if (!ctx.versionManager()
.isAllowed(OMLayoutFeature.ERASURE_CODED_STORAGE_SUPPORT)) {
if (req.getCreateBucketRequest()
.getBucketInfo().hasDefaultReplicationConfig()
&& req.getCreateBucketRequest().getBucketInfo()
.getDefaultReplicationConfig().hasEcReplicationConfig()) {
throw new OMException("Cluster does not have the Erasure Coded"
+ " Storage support feature finalized yet, but the request contains"
+ " an Erasure Coded replication type. Rejecting the request,"
+ " please finalize the cluster upgrade and then try again.",
OMException.ResultCodes.NOT_SUPPORTED_OPERATION_PRIOR_FINALIZATION);
}
}
return req;
}
@RequestFeatureValidator(
conditions = ValidationCondition.CLUSTER_NEEDS_FINALIZATION,
processingPhase = RequestProcessingPhase.PRE_PROCESS,
requestType = Type.CreateBucket
)
public static OMRequest handleCreateBucketWithBucketLayoutDuringPreFinalize(
OMRequest req, ValidationContext ctx) throws OMException {
if (!ctx.versionManager()
.isAllowed(OMLayoutFeature.BUCKET_LAYOUT_SUPPORT)) {
if (req.getCreateBucketRequest()
.getBucketInfo().hasBucketLayout()) {
// If the client explicitly specified a bucket layout while OM is
// pre-finalized, reject the request.
if (!BucketLayout.fromProto(req.getCreateBucketRequest().getBucketInfo()
.getBucketLayout()).isLegacy()) {
throw new OMException("Cluster does not have the Bucket Layout"
+ " support feature finalized yet, but the request contains"
+ " a non LEGACY bucket type. Rejecting the request,"
+ " please finalize the cluster upgrade and then try again.",
ResultCodes.NOT_SUPPORTED_OPERATION_PRIOR_FINALIZATION);
}
} else {
// The client did not specify a bucket layout, meaning the OM's
// default bucket layout would be used.
// Since the OM is pre-finalized for bucket layout support,
// explicitly override the server default by specifying this request
// as a legacy bucket.
return changeBucketLayout(req, BucketLayout.LEGACY);
}
}
return req;
}
/**
* When a client that does not support bucket layout types issues a create
* bucket command, it will leave the bucket layout field empty. For these
* old clients, they should create legacy buckets so that they can read and
* write to them, instead of using the server default which may be in a layout
* they do not understand.
*/
@RequestFeatureValidator(
conditions = ValidationCondition.OLDER_CLIENT_REQUESTS,
processingPhase = RequestProcessingPhase.PRE_PROCESS,
requestType = Type.CreateBucket
)
public static OMRequest setDefaultBucketLayoutForOlderClients(OMRequest req,
ValidationContext ctx) {
if (ClientVersion.fromProtoValue(req.getVersion())
.compareTo(ClientVersion.BUCKET_LAYOUT_SUPPORT) < 0) {
// Older client will default bucket layout to LEGACY to
// make its operations backward compatible.
return changeBucketLayout(req, BucketLayout.LEGACY);
} else {
return req;
}
}
private static OMRequest changeBucketLayout(OMRequest originalRequest,
BucketLayout newLayout) {
CreateBucketRequest createBucketRequest =
originalRequest.getCreateBucketRequest();
BucketInfo newBucketInfo = createBucketRequest.getBucketInfo().toBuilder()
.setBucketLayout(newLayout.toProto()).build();
CreateBucketRequest newCreateRequest = createBucketRequest.toBuilder()
.setBucketInfo(newBucketInfo).build();
return originalRequest.toBuilder()
.setCreateBucketRequest(newCreateRequest).build();
}
}