blob: 8a581bbe259bc14b1a4b38473206de267f7bc732 [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
*
* 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 com.google.common.base.Preconditions;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.StorageType;
import org.apache.hadoop.ipc.ProtobufRpcEngine;
import org.apache.hadoop.ozone.OzoneAcl;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
import org.apache.hadoop.security.UserGroupInformation;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_ALREADY_EXISTS;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import static org.apache.hadoop.ozone.OzoneConsts.OM_S3_VOLUME_PREFIX;
import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.S3_BUCKET_LOCK;
/**
* S3 Bucket Manager, this class maintains a mapping between S3 Bucket and Ozone
* Volume/bucket.
*/
public class S3BucketManagerImpl implements S3BucketManager {
private static final Logger LOG =
LoggerFactory.getLogger(S3BucketManagerImpl.class);
private static final String S3_ADMIN_NAME = "OzoneS3Manager";
private final OzoneConfiguration configuration;
private final OMMetadataManager omMetadataManager;
private final VolumeManager volumeManager;
private final BucketManager bucketManager;
/**
* Construct an S3 Bucket Manager Object.
*
* @param configuration - Ozone Configuration.
* @param omMetadataManager - Ozone Metadata Manager.
*/
public S3BucketManagerImpl(
OzoneConfiguration configuration,
OMMetadataManager omMetadataManager,
VolumeManager volumeManager,
BucketManager bucketManager) {
this.configuration = configuration;
this.omMetadataManager = omMetadataManager;
this.volumeManager = volumeManager;
this.bucketManager = bucketManager;
}
@Override
public void createS3Bucket(String userName, String bucketName)
throws IOException {
Preconditions.checkArgument(Strings.isNotBlank(bucketName), "Bucket" +
" name cannot be null or empty.");
Preconditions.checkArgument(Strings.isNotBlank(userName), "User name " +
"cannot be null or empty.");
Preconditions.checkArgument(bucketName.length() >=3 &&
bucketName.length() < 64, "Length of the S3 Bucket is not correct.");
// TODO: Decide if we want to enforce S3 Bucket Creation Rules in this
// code path?
// https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
// Generate an Ozone volume name. For the time being, we are going to use
// s3userName as the Ozone volume name. Since S3 advices 100 buckets max
// for a user and we have no limit to the number of Ozone buckets under a
// volume we will stick to very simple model.
//
// s3Bucket -> ozoneVolume/OzoneBucket name
// s3BucketName ->s3userName/s3Bucketname
//
// You might wonder if all names map to this pattern, why we need to
// store the S3 bucketName in a table at all. This is to support
// anonymous access to bucket where the user name is absent.
String ozoneVolumeName = formatOzoneVolumeName(userName);
omMetadataManager.getLock().acquireLock(S3_BUCKET_LOCK, bucketName);
try {
String bucket = omMetadataManager.getS3Table().get(bucketName);
if (bucket != null) {
LOG.debug("Bucket already exists. {}", bucketName);
throw new OMException(
"Unable to create S3 bucket. " + bucketName + " already exists.",
OMException.ResultCodes.S3_BUCKET_ALREADY_EXISTS);
}
String ozoneBucketName = bucketName;
createOzoneBucket(ozoneVolumeName, ozoneBucketName);
String finalName = String.format("%s/%s", ozoneVolumeName,
ozoneBucketName);
omMetadataManager.getS3Table().put(bucketName, finalName);
} finally {
omMetadataManager.getLock().releaseLock(S3_BUCKET_LOCK, bucketName);
}
}
@Override
public void deleteS3Bucket(String bucketName) throws IOException {
Preconditions.checkArgument(
Strings.isNotBlank(bucketName), "Bucket name cannot be null or empty");
omMetadataManager.getLock().acquireLock(S3_BUCKET_LOCK, bucketName);
try {
String map = omMetadataManager.getS3Table().get(bucketName);
if (map == null) {
throw new OMException("No such S3 bucket. " + bucketName,
OMException.ResultCodes.S3_BUCKET_NOT_FOUND);
}
bucketManager.deleteBucket(getOzoneVolumeName(bucketName), bucketName);
omMetadataManager.getS3Table().delete(bucketName);
} catch(IOException ex) {
throw ex;
} finally {
omMetadataManager.getLock().releaseLock(S3_BUCKET_LOCK, bucketName);
}
}
@Override
public String formatOzoneVolumeName(String userName) {
return String.format(OM_S3_VOLUME_PREFIX + "%s", userName);
}
@Override
public boolean createOzoneVolumeIfNeeded(String userName)
throws IOException {
// We don't have to time of check. time of use problem here because
// this call is invoked while holding the s3Bucket lock.
boolean newVolumeCreate = true;
String ozoneVolumeName = formatOzoneVolumeName(userName);
try {
OmVolumeArgs.Builder builder =
OmVolumeArgs.newBuilder()
.setAdminName(S3_ADMIN_NAME)
.setOwnerName(userName)
.setVolume(ozoneVolumeName)
.setQuotaInBytes(OzoneConsts.MAX_QUOTA_IN_BYTES);
for (OzoneAcl acl : getDefaultAcls(userName)) {
builder.addOzoneAcls(OzoneAcl.toProtobuf(acl));
}
OmVolumeArgs args = builder.build();
volumeManager.createVolume(args);
} catch (OMException exp) {
newVolumeCreate = false;
if (exp.getResult().compareTo(VOLUME_ALREADY_EXISTS) == 0) {
if (LOG.isDebugEnabled()) {
LOG.debug("Volume already exists. {}", exp.getMessage());
}
} else {
throw exp;
}
}
return newVolumeCreate;
}
/**
* Get default acls.
* */
private List<OzoneAcl> getDefaultAcls(String userName) {
UserGroupInformation ugi = ProtobufRpcEngine.Server.getRemoteUser();
return OzoneAcl.parseAcls("user:" + (ugi == null ? userName :
ugi.getUserName()) + ":a,user:" + S3_ADMIN_NAME + ":a");
}
private void createOzoneBucket(String volumeName, String bucketName)
throws IOException {
OmBucketInfo.Builder builder = OmBucketInfo.newBuilder();
OmBucketInfo bucketInfo =
builder
.setVolumeName(volumeName)
.setBucketName(bucketName)
.setIsVersionEnabled(Boolean.FALSE)
.setStorageType(StorageType.DEFAULT)
.setAcls(getDefaultAcls(null))
.build();
bucketManager.createBucket(bucketInfo);
}
@Override
public String getOzoneBucketMapping(String s3BucketName) throws IOException {
Preconditions.checkArgument(
Strings.isNotBlank(s3BucketName),
"Bucket name cannot be null or empty.");
Preconditions.checkArgument(s3BucketName.length() >=3 &&
s3BucketName.length() < 64,
"Length of the S3 Bucket is not correct.");
omMetadataManager.getLock().acquireLock(S3_BUCKET_LOCK, s3BucketName);
try {
String mapping = omMetadataManager.getS3Table().get(s3BucketName);
if (mapping != null) {
return mapping;
}
throw new OMException("No such S3 bucket.",
OMException.ResultCodes.S3_BUCKET_NOT_FOUND);
} finally {
omMetadataManager.getLock().releaseLock(S3_BUCKET_LOCK, s3BucketName);
}
}
@Override
public String getOzoneVolumeName(String s3BucketName) throws IOException {
String mapping = getOzoneBucketMapping(s3BucketName);
return mapping.split("/")[0];
}
@Override
public String getOzoneBucketName(String s3BucketName) throws IOException {
String mapping = getOzoneBucketMapping(s3BucketName);
return mapping.split("/")[1];
}
@Override
public String getOzoneVolumeNameForUser(String userName) throws IOException {
Objects.requireNonNull(userName, "UserName cannot be null");
return formatOzoneVolumeName(userName);
}
}