Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c6cf77b
HDDS-14386. Support bucket CORS configuration
ivandika3 Apr 23, 2026
ffdfa49
HDDS-14386. Run bucket CORS Robot smoke test
ivandika3 Apr 23, 2026
9f7074e
HDDS-14386. Fix S3 CORS test regressions
ivandika3 Apr 24, 2026
9965b59
HDDS-14386. Fix checkstyle in S3GatewayService
ivandika3 Apr 24, 2026
1296381
HDDS-14386. Fix PMD and SpotBugs warnings
ivandika3 Apr 24, 2026
49f4273
HDDS-14386. Fix acceptance regressions
ivandika3 Apr 24, 2026
481a291
HDDS-14386. Exclude bucket CORS from bucket-type sweeps
ivandika3 Apr 24, 2026
47023f2
HDDS-14386. Exclude bucket CORS from EC bucket sweeps
ivandika3 Apr 24, 2026
ec5dd5a
HDDS-14386. Restore OmBucketArgs tests
ivandika3 Apr 25, 2026
009e98e
HDDS-14386. Verify PutBucketCors owner checks
ivandika3 Apr 25, 2026
c7f9258
Fix checkstyle
ivandika3 Apr 25, 2026
8f187ab
HDDS-14386. Streamline S3 bucket caching
ivandika3 Apr 25, 2026
2932163
HDDS-14386. Gate bucket CORS on OM version
ivandika3 Apr 25, 2026
3d4ab61
Fix findbugs
ivandika3 Apr 25, 2026
bf439ea
HDDS-14386. Restore S3 sweep coverage
ivandika3 Apr 25, 2026
2e5c985
HDDS-14386. Reuse request volume when caching buckets
ivandika3 Apr 25, 2026
e5754dd
HDDS-14386. Fix S3 gateway test mocks
ivandika3 Apr 25, 2026
801a33b
HDDS-14386. Cover bucket CORS delete edge cases
ivandika3 Apr 26, 2026
bb2ce47
HDDS-14386. Avoid caching buckets for non-CORS requests
ivandika3 Apr 26, 2026
3cf01ec
HDDS-14386. Remove S3 bucket helper from EndpointBase
ivandika3 May 2, 2026
5672dd5
HDDS-14386. Fix object tagging test mocks
ivandika3 May 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ public enum OzoneManagerVersion implements ComponentVersion {

ATOMIC_CREATE_IF_NOT_EXISTS(12,
"OzoneManager version that supports explicit create-if-not-exists key semantics"),


S3_BUCKET_CORS(13,
"OzoneManager version that supports bucket CORS configuration"),

FUTURE_VERSION(-1, "Used internally in the client when the server side is "
+ " newer and an unknown server version has arrived to the client.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.hadoop.ozone.OzoneAcl;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
import org.apache.hadoop.ozone.om.helpers.CorsConfiguration;

/**
* This class encapsulates the arguments that are
Expand Down Expand Up @@ -68,6 +69,7 @@ public final class BucketArgs {
private final long quotaInNamespace;

private final String owner;
private final CorsConfiguration corsConfiguration;

/**
* Bucket Layout.
Expand All @@ -87,6 +89,7 @@ private BucketArgs(Builder b) {
bucketLayout = b.bucketLayout;
owner = b.owner;
defaultReplicationConfig = b.defaultReplicationConfig;
corsConfiguration = b.corsConfiguration;
}

/**
Expand Down Expand Up @@ -185,6 +188,10 @@ public String getOwner() {
return owner;
}

public CorsConfiguration getCorsConfiguration() {
return corsConfiguration;
}

/**
* Builder for OmBucketInfo.
*/
Expand All @@ -201,6 +208,7 @@ public static class Builder {
private BucketLayout bucketLayout;
private String owner;
private DefaultReplicationConfig defaultReplicationConfig;
private CorsConfiguration corsConfiguration;

public Builder() {
quotaInBytes = OzoneConsts.QUOTA_RESET;
Expand Down Expand Up @@ -274,6 +282,12 @@ public BucketArgs.Builder setDefaultReplicationConfig(
return this;
}

public BucketArgs.Builder setCorsConfiguration(
CorsConfiguration corsConfig) {
corsConfiguration = corsConfig;
return this;
}

/**
* Constructs the BucketArgs.
* @return instance of BucketArgs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.BasicOmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
import org.apache.hadoop.ozone.om.helpers.CorsConfiguration;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmMultipartInfo;
Expand Down Expand Up @@ -151,6 +152,7 @@ public class OzoneBucket extends WithMetadata {
* Bucket Owner.
*/
private String owner;
private CorsConfiguration corsConfiguration;
/**
* Pending deletion bytes (Includes bytes retained by snapshots).
*/
Expand Down Expand Up @@ -201,6 +203,7 @@ protected OzoneBucket(Builder builder) {
this.bucketLayout = builder.bucketLayout;
}
this.owner = builder.owner;
this.corsConfiguration = builder.corsConfiguration;
}

/**
Expand Down Expand Up @@ -1148,6 +1151,21 @@ public boolean setOwner(String userName) throws IOException {
return result;
}

public CorsConfiguration getCorsConfiguration() {
return corsConfiguration;
}

public void setCorsConfiguration(
CorsConfiguration newCorsConfiguration) throws IOException {
proxy.setBucketCors(volumeName, name, newCorsConfiguration);
this.corsConfiguration = newCorsConfiguration;
}

public void deleteCorsConfiguration() throws IOException {
proxy.deleteBucketCors(volumeName, name);
this.corsConfiguration = null;
}

/**
* Builder for OmBucketInfo.
/**
Expand Down Expand Up @@ -1231,6 +1249,7 @@ public static class Builder extends WithMetadata.Builder {
private String owner;
private long pendingDeleteBytes;
private long pendingDeleteNamespace;
private CorsConfiguration corsConfiguration;

protected Builder() {
}
Expand Down Expand Up @@ -1327,6 +1346,12 @@ public Builder setOwner(String owner) {
return this;
}

public Builder setCorsConfiguration(
CorsConfiguration corsConfig) {
this.corsConfiguration = corsConfig;
return this;
}

public Builder setPendingDeleteBytes(long pendingDeleteBytes) {
this.pendingDeleteBytes = pendingDeleteBytes;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
import org.apache.hadoop.ozone.om.OMConfigKeys;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.CorsConfiguration;
import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
import org.apache.hadoop.ozone.om.helpers.LeaseKeyInfo;
Expand Down Expand Up @@ -277,6 +278,26 @@ void setBucketStorageType(String volumeName, String bucketName,
StorageType storageType)
throws IOException;

/**
* Sets the CORS configuration of a Bucket.
* @param volumeName Name of the Volume
* @param bucketName Name of the Bucket
* @param corsConfiguration CORS configuration to set
* @throws IOException
*/
void setBucketCors(String volumeName, String bucketName,
CorsConfiguration corsConfiguration)
throws IOException;

/**
* Clears the CORS configuration of a Bucket.
* @param volumeName Name of the Volume
* @param bucketName Name of the Bucket
* @throws IOException
*/
void deleteBucketCors(String volumeName, String bucketName)
throws IOException;

/**
* Deletes a bucket if it is empty.
* @param volumeName Name of the Volume
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
import org.apache.hadoop.ozone.om.helpers.BasicOmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.BucketEncryptionKeyInfo;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
import org.apache.hadoop.ozone.om.helpers.CorsConfiguration;
import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
import org.apache.hadoop.ozone.om.helpers.KeyInfoWithVolumeContext;
Expand Down Expand Up @@ -622,6 +623,9 @@ public void createBucket(
+ " not support Erasure Coded replication.");
}
}
if (bucketArgs.getCorsConfiguration() != null) {
checkBucketCorsFeatureEnabled();
}

final String owner;
// If S3 auth exists, set owner name to the short user name derived from the
Expand Down Expand Up @@ -656,7 +660,8 @@ public void createBucket(
.setQuotaInBytes(bucketArgs.getQuotaInBytes())
.setQuotaInNamespace(bucketArgs.getQuotaInNamespace())
.setBucketLayout(bucketLayout)
.setOwner(owner);
.setOwner(owner)
.setCorsConfiguration(bucketArgs.getCorsConfiguration());

if (bucketArgs.getAcls() != null) {
builder.acls().addAll(bucketArgs.getAcls());
Expand Down Expand Up @@ -1232,6 +1237,40 @@ public void setBucketStorageType(
ozoneManagerClient.setBucketProperty(builder.build());
}

@Override
public void setBucketCors(String volumeName, String bucketName,
CorsConfiguration corsConfiguration) throws IOException {
verifyVolumeName(volumeName);
verifyBucketName(bucketName);
Objects.requireNonNull(corsConfiguration, "corsConfiguration == null");
checkBucketCorsFeatureEnabled();
OmBucketArgs.Builder builder = OmBucketArgs.newBuilder();
builder.setVolumeName(volumeName)
.setBucketName(bucketName)
.setCorsConfiguration(corsConfiguration);
ozoneManagerClient.setBucketProperty(builder.build());
}

@Override
public void deleteBucketCors(String volumeName, String bucketName)
throws IOException {
verifyVolumeName(volumeName);
verifyBucketName(bucketName);
checkBucketCorsFeatureEnabled();
OmBucketArgs.Builder builder = OmBucketArgs.newBuilder();
builder.setVolumeName(volumeName)
.setBucketName(bucketName)
.setClearCorsConfiguration(true);
ozoneManagerClient.setBucketProperty(builder.build());
}

private void checkBucketCorsFeatureEnabled() throws IOException {
if (omVersion.compareTo(OzoneManagerVersion.S3_BUCKET_CORS) < 0) {
throw new IOException("OzoneManager does not support bucket CORS "
+ "configuration.");
}
}

@Override
public void setBucketQuota(String volumeName, String bucketName,
long quotaInNamespace, long quotaInBytes) throws IOException {
Expand Down Expand Up @@ -1340,6 +1379,7 @@ public OzoneBucket getBucketDetails(
.setBucketLayout(bucketInfo.getBucketLayout())
.setOwner(bucketInfo.getOwner())
.setDefaultReplicationConfig(bucketInfo.getDefaultReplicationConfig())
.setCorsConfiguration(bucketInfo.getCorsConfiguration())
.build();
}

Expand Down Expand Up @@ -1374,6 +1414,7 @@ public List<OzoneBucket> listBuckets(String volumeName, String bucketPrefix,
.setOwner(bucket.getOwner())
.setDefaultReplicationConfig(
bucket.getDefaultReplicationConfig())
.setCorsConfiguration(bucket.getCorsConfiguration())
.build())
.collect(Collectors.toList());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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.helpers;

import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CORSConfiguration;

/**
* S3 bucket CORS configuration.
*/
public final class CorsConfiguration {
private final ImmutableList<CorsRule> rules;

private CorsConfiguration(Builder builder) {
this.rules = ImmutableList.copyOf(builder.rules);
}

public static Builder newBuilder() {
return new Builder();
}

public List<CorsRule> getRules() {
return rules;
}

public CORSConfiguration getProtobuf() {
return CORSConfiguration.newBuilder()
.addAllCorsRule(rules.stream()
.map(CorsRule::getProtobuf)
.collect(Collectors.toList()))
.build();
}

public static CorsConfiguration getFromProtobuf(
CORSConfiguration proto) {
return newBuilder()
.setRules(proto.getCorsRuleList().stream()
.map(CorsRule::getFromProtobuf)
.collect(Collectors.toList()))
.build();
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CorsConfiguration)) {
return false;
}
CorsConfiguration that = (CorsConfiguration) obj;
return Objects.equals(rules, that.rules);
}

@Override
public int hashCode() {
return Objects.hash(rules);
}

@Override
public String toString() {
return "CorsConfiguration{"
+ "rules=" + rules
+ '}';
}

/**
* Builder for {@link CorsConfiguration}.
*/
public static final class Builder {
private List<CorsRule> rules = ImmutableList.of();

private Builder() {
}

public Builder setRules(List<CorsRule> corsRules) {
this.rules = corsRules == null ? ImmutableList.of() : corsRules;
return this;
}

public Builder addRule(CorsRule rule) {
ImmutableList.Builder<CorsRule> builder = ImmutableList.builder();
builder.addAll(rules);
builder.add(rule);
this.rules = builder.build();
return this;
}

public CorsConfiguration build() {
return new CorsConfiguration(this);
}
}
}
Loading