This is an automated email from the ASF dual-hosted git repository.
devmadhuu pushed a commit to branch HDDS-11233
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/HDDS-11233 by this push:
new 8787bc21038 HDDS-15881. Support setting storagePolicy via
SetBucketProperty (#11232)
8787bc21038 is described below
commit 8787bc21038a65e75159a7c66d6a940c7ccedf79
Author: Anurag Parvatikar <[email protected]>
AuthorDate: Mon Oct 5 20:43:30 2026 +0530
HDDS-15881. Support setting storagePolicy via SetBucketProperty (#11232)
---
.../java/org/apache/hadoop/ozone/OzoneConsts.java | 1 +
.../ozone/shell/bucket/CreateBucketHandler.java | 44 ++++
.../ozone/shell/bucket/UpdateBucketHandler.java | 59 ++++++
.../apache/hadoop/ozone/client/OzoneBucket.java | 50 +++++
.../ozone/client/protocol/ClientProtocol.java | 11 +
.../apache/hadoop/ozone/client/rpc/RpcClient.java | 8 +
.../hadoop/ozone/om/helpers/OmBucketArgs.java | 30 +++
.../ozone/om/TestOMStoragePolicyUpgrade.java | 223 +++++++++++++++++++++
.../hadoop/ozone/shell/TestOzoneShellHA.java | 156 ++++++++++++++
.../src/main/proto/OmClientProtocol.proto | 1 +
.../om/request/bucket/OMBucketCreateRequest.java | 31 +++
.../request/bucket/OMBucketSetPropertyRequest.java | 35 ++++
.../hadoop/ozone/om/upgrade/OMLayoutFeature.java | 3 +-
.../request/bucket/TestOMBucketCreateRequest.java | 64 ++++++
.../bucket/TestOMBucketSetPropertyRequest.java | 161 +++++++++++++++
.../hadoop/ozone/client/ClientProtocolStub.java | 6 +
16 files changed, 882 insertions(+), 1 deletion(-)
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
index 50559ca917a..62f57a3b12c 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
@@ -300,6 +300,7 @@ public final class OzoneConsts {
public static final String STORAGE_TYPE = "storageType";
public static final String STORAGE_POLICY = "storagePolicy";
public static final String ALLOW_FALLBACK_STORAGE_POLICY =
"allowFallbackStoragePolicy";
+ public static final String UNSET_STORAGE_POLICY = "unsetStoragePolicy";
public static final String RESOURCE_TYPE = "resourceType";
public static final String IS_VERSION_ENABLED = "isVersionEnabled";
public static final String CREATION_TIME = "creationTime";
diff --git
a/hadoop-ozone/cli-shell/src/main/java/org/apache/hadoop/ozone/shell/bucket/CreateBucketHandler.java
b/hadoop-ozone/cli-shell/src/main/java/org/apache/hadoop/ozone/shell/bucket/CreateBucketHandler.java
index ab842f184e5..a06db5cb7b2 100644
---
a/hadoop-ozone/cli-shell/src/main/java/org/apache/hadoop/ozone/shell/bucket/CreateBucketHandler.java
+++
b/hadoop-ozone/cli-shell/src/main/java/org/apache/hadoop/ozone/shell/bucket/CreateBucketHandler.java
@@ -21,6 +21,8 @@
import java.io.IOException;
import org.apache.hadoop.hdds.client.DefaultReplicationConfig;
import org.apache.hadoop.hdds.client.OzoneQuota;
+import org.apache.hadoop.hdds.client.OzoneStoragePolicy;
+import org.apache.hadoop.hdds.client.StoragePolicy;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.client.BucketArgs;
import org.apache.hadoop.ozone.client.OzoneBucket;
@@ -33,6 +35,7 @@
import org.apache.hadoop.security.UserGroupInformation;
import picocli.CommandLine;
import picocli.CommandLine.Command;
+import picocli.CommandLine.Help.Visibility;
import picocli.CommandLine.Option;
/**
@@ -67,6 +70,21 @@ public class CreateBucketHandler extends BucketHandler {
@CommandLine.Mixin
private SetSpaceQuotaOptions quotaOptions;
+ @Option(names = {"--storage-policy", "-s"},
+ description = "Bucket StoragePolicy. Allowed values: HOT, WARM, COLD,
null.",
+ defaultValue = "WARM",
+ showDefaultValue = Visibility.ALWAYS)
+ private String storagePolicyStr;
+
+ @Option(names = {"--allow-fallback-storage-policy", "-a"},
+ description = "When true, allocation may fall back to the
StoragePolicy's " +
+ "fallback tier if the creation tier is unavailable.",
+ defaultValue = "true", arity = "1",
+ showDefaultValue = Visibility.ALWAYS)
+ private boolean allowFallBackStoragePolicy;
+
+ private static final String NULL_STORAGE_POLICY = "null";
+
/**
* Executes create bucket.
*/
@@ -78,8 +96,12 @@ public void execute(OzoneClient client, OzoneAddress address)
ownerName = UserGroupInformation.getCurrentUser().getShortUserName();
}
+ StoragePolicy storagePolicy = parseStoragePolicy(storagePolicyStr);
+
BucketArgs.Builder bb =
new BucketArgs.Builder()
+ .setStoragePolicy(storagePolicy)
+ .setAllowFallbackStoragePolicy(allowFallBackStoragePolicy)
.setVersioning(false).setOwner(ownerName);
if (allowedBucketLayout != null) {
bb.setBucketLayout(allowedBucketLayout);
@@ -127,6 +149,28 @@ public void execute(OzoneClient client, OzoneAddress
address)
}
}
+ /**
+ * Parse a user-supplied {@code --storage-policy} value into a
+ * {@link StoragePolicy}. Returns {@code null} when the caller passed
+ * "null" (any case) or an empty value, to leave the bucket without an
+ * explicit StoragePolicy (the server defaults it to WARM on create).
+ *
+ * @throws IllegalArgumentException if the value is not one of HOT, WARM,
+ * COLD, or "null".
+ */
+ private static StoragePolicy parseStoragePolicy(String value) {
+ if (Strings.isNullOrEmpty(value)
+ || NULL_STORAGE_POLICY.equalsIgnoreCase(value)) {
+ return null;
+ }
+ try {
+ return OzoneStoragePolicy.valueOf(value.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Invalid storage policy: " + value
+ + ". Allowed String values are: HOT, WARM, COLD, or null.");
+ }
+ }
+
private static class LayoutConverter implements
CommandLine.ITypeConverter<BucketLayout> {
@Override
public BucketLayout convert(String value) {
diff --git
a/hadoop-ozone/cli-shell/src/main/java/org/apache/hadoop/ozone/shell/bucket/UpdateBucketHandler.java
b/hadoop-ozone/cli-shell/src/main/java/org/apache/hadoop/ozone/shell/bucket/UpdateBucketHandler.java
index 58f7190628f..9baad3a284b 100644
---
a/hadoop-ozone/cli-shell/src/main/java/org/apache/hadoop/ozone/shell/bucket/UpdateBucketHandler.java
+++
b/hadoop-ozone/cli-shell/src/main/java/org/apache/hadoop/ozone/shell/bucket/UpdateBucketHandler.java
@@ -17,7 +17,10 @@
package org.apache.hadoop.ozone.shell.bucket;
+import com.google.common.base.Strings;
import java.io.IOException;
+import org.apache.hadoop.hdds.client.OzoneStoragePolicy;
+import org.apache.hadoop.hdds.client.StoragePolicy;
import org.apache.hadoop.ozone.client.OzoneBucket;
import org.apache.hadoop.ozone.client.OzoneClient;
import org.apache.hadoop.ozone.shell.OzoneAddress;
@@ -35,10 +38,33 @@ public class UpdateBucketHandler extends BucketHandler {
description = "Owner of the bucket to set")
private String ownerName;
+ @Option(names = {"--storage-policy", "-s"},
+ description = "Bucket StoragePolicy. Allowed values: HOT, WARM, COLD,
null "
+ + "(null clears the bucket's StoragePolicy). Leave unset to keep the
"
+ + "current value.")
+ private String storagePolicyStr;
+
+ @Option(names = {"--allow-fallback-storage-policy", "-a"},
+ description = "When true, allocation may fall back to the
StoragePolicy's "
+ + "fallback tier if the creation tier is unavailable. Leave unset to
"
+ + "keep the current value.",
+ arity = "1")
+ private Boolean allowFallBackStoragePolicy;
+
+ private static final String NULL_STORAGE_POLICY = "null";
+
@Override
protected void execute(OzoneClient client, OzoneAddress address)
throws IOException {
+ if (ownerName != null && !ownerName.isEmpty()
+ && (hasStoragePolicy() || allowFallBackStoragePolicy != null)) {
+ throw new IllegalArgumentException(
+ "--user cannot be combined with --storage-policy or "
+ + "--allow-fallback-storage-policy. Run separate 'bucket "
+ + "update' commands for each property.");
+ }
+
String volumeName = address.getVolumeName();
String bucketName = address.getBucketName();
OzoneBucket bucket = client.getObjectStore().getVolume(volumeName)
@@ -52,8 +78,41 @@ protected void execute(OzoneClient client, OzoneAddress
address)
}
}
+ // Update StoragePolicy / allowFallback if requested.
+ if (hasStoragePolicy() || allowFallBackStoragePolicy != null) {
+ StoragePolicy newPolicy = hasStoragePolicy() ? getStoragePolicy() : null;
+ boolean unsetPolicy = hasStoragePolicy() && newPolicy == null;
+ bucket.setStoragePolicyProperty(newPolicy, allowFallBackStoragePolicy,
unsetPolicy);
+ }
+
OzoneBucket updatedBucket = client.getObjectStore().getVolume(volumeName)
.getBucket(bucketName);
printObjectAsJson(updatedBucket);
}
+
+ private boolean hasStoragePolicy() {
+ return !Strings.isNullOrEmpty(storagePolicyStr);
+ }
+
+ /**
+ * Parse the {@code --storagepolicy} value. Returns {@code null} when the
+ * user passed "null" (any case), meaning the policy should be cleared;
+ * otherwise the matching {@link StoragePolicy}.
+ *
+ * @throws IllegalArgumentException if the value is not HOT, WARM, COLD, or
+ * "null".
+ */
+ private StoragePolicy getStoragePolicy() {
+ if (Strings.isNullOrEmpty(storagePolicyStr)
+ || NULL_STORAGE_POLICY.equalsIgnoreCase(storagePolicyStr)) {
+ return null;
+ }
+ try {
+ return OzoneStoragePolicy.valueOf(storagePolicyStr.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Invalid storage policy: "
+ + storagePolicyStr
+ + ". Allowed String values are: HOT, WARM, COLD, or null.");
+ }
+ }
}
diff --git
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
index 1999c8144ca..42d86723c0b 100644
---
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
+++
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java
@@ -55,6 +55,7 @@
import org.apache.hadoop.ozone.om.helpers.BasicOmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
+import org.apache.hadoop.ozone.om.helpers.OmBucketArgs;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmLifecycleConfiguration;
import org.apache.hadoop.ozone.om.helpers.OmMultipartInfo;
@@ -377,6 +378,55 @@ public void setStoragePolicy(StoragePolicy
newStoragePolicy) throws IOException
storagePolicy = newStoragePolicy;
}
+ /**
+ * Sets the bucket's storage-policy properties (storage policy,
allowFallback,
+ * or unset) on this bucket. Used by the update path, where the policy may be
+ * absent or explicitly cleared.
+ * @param newPolicy new storage policy, or {@code null} to leave unchanged
+ * @param newFallback new allowFallbackStoragePolicy, or {@code null} to
leave unchanged
+ * @param unsetPolicy whether to clear the bucket's storage policy
+ * @throws IOException
+ */
+ public void setStoragePolicyProperty(
+ StoragePolicy newPolicy,
+ Boolean newFallback,
+ boolean unsetPolicy) throws IOException {
+
+ if (newPolicy != null && unsetPolicy) {
+ throw new IllegalArgumentException(
+ "Set storagePolicy and unset storagePolicy cannot "
+ + "be given at the same time");
+ }
+
+ OmBucketArgs.Builder builder = OmBucketArgs.newBuilder()
+ .setVolumeName(volumeName)
+ .setBucketName(name);
+
+ if (newPolicy != null) {
+ builder.setStoragePolicy(newPolicy);
+ }
+
+ if (newFallback != null) {
+ builder.setAllowFallbackStoragePolicy(newFallback);
+ }
+
+ if (unsetPolicy) {
+ builder.setUnsetStoragePolicy(true);
+ }
+
+ proxy.setBucketStoragePolicy(builder.build());
+
+ if (unsetPolicy) {
+ storagePolicy = null;
+ } else if (newPolicy != null) {
+ storagePolicy = newPolicy;
+ }
+
+ if (newFallback != null) {
+ allowFallbackStoragePolicy = newFallback;
+ }
+ }
+
/**
* Enable/Disable versioning of the bucket.
* @param newVersioning
diff --git
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
index 49d5b82cf3e..536a69c5a26 100644
---
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
+++
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
@@ -50,6 +50,7 @@
import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
import org.apache.hadoop.ozone.om.helpers.LeaseKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmBucketArgs;
import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
@@ -293,6 +294,16 @@ void setBucketStoragePolicy(String volumeName, String
bucketName,
StoragePolicy storagePolicy)
throws IOException;
+ /**
+ * Sets the storage-policy related properties of a bucket carried in the
+ * given {@link OmBucketArgs} (storage policy, allowFallback, or unset).
+ * Unlike the three-arg overload, the policy may be absent or explicitly
+ * cleared via {@link OmBucketArgs.Builder#setUnsetStoragePolicy}.
+ * @param args Bucket arguments carrying the properties to update.
+ * @throws IOException
+ */
+ void setBucketStoragePolicy(OmBucketArgs args) throws IOException;
+
/**
* Deletes a bucket if it is empty.
* @param volumeName Name of the Volume
diff --git
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
index 0218522b1d6..46375d190ac 100644
---
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
+++
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
@@ -1236,6 +1236,14 @@ public void setBucketStoragePolicy(
ozoneManagerClient.setBucketProperty(builder.build());
}
+ @Override
+ public void setBucketStoragePolicy(OmBucketArgs args) throws IOException {
+ Objects.requireNonNull(args, "args == null");
+ verifyVolumeName(args.getVolumeName());
+ verifyBucketName(args.getBucketName());
+ ozoneManagerClient.setBucketProperty(args);
+ }
+
@Override
public void setBucketQuota(String volumeName, String bucketName,
long quotaInNamespace, long quotaInBytes) throws IOException {
diff --git
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmBucketArgs.java
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmBucketArgs.java
index c25173e2614..9b582a32715 100644
---
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmBucketArgs.java
+++
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmBucketArgs.java
@@ -55,6 +55,11 @@ public final class OmBucketArgs extends WithMetadata
implements Auditable {
* the flag was not set.
*/
private final Boolean allowFallbackStoragePolicy;
+ /**
+ * Whether to clear (unset) the bucket's storage policy. {@code null} or
+ * {@code false} leaves the existing policy unchanged.
+ */
+ private final Boolean unsetStoragePolicy;
/**
* Bucket encryption key info if encryption is enabled.
@@ -81,6 +86,7 @@ private OmBucketArgs(Builder b) {
this.isVersionEnabled = b.isVersionEnabled;
this.storagePolicy = b.storagePolicy;
this.allowFallbackStoragePolicy = b.allowFallbackStoragePolicy;
+ this.unsetStoragePolicy = b.unsetStoragePolicy;
this.ownerName = b.ownerName;
this.defaultReplicationConfig = b.defaultReplicationConfig;
this.quotaInBytesSet = b.quotaInBytesSet;
@@ -132,6 +138,14 @@ public Boolean getAllowFallbackStoragePolicy() {
return allowFallbackStoragePolicy;
}
+ /**
+ * Returns whether the bucket's storage policy should be cleared (unset).
+ * @return unsetStoragePolicy (may be {@code null} when not set).
+ */
+ public Boolean getUnsetStoragePolicy() {
+ return unsetStoragePolicy;
+ }
+
/**
* Returns Bucket Quota in bytes.
* @return quotaInBytes.
@@ -213,6 +227,10 @@ public Map<String, String> toAuditMap() {
auditMap.put(OzoneConsts.ALLOW_FALLBACK_STORAGE_POLICY,
String.valueOf(this.allowFallbackStoragePolicy));
}
+ if (this.unsetStoragePolicy != null) {
+ auditMap.put(OzoneConsts.UNSET_STORAGE_POLICY,
+ String.valueOf(this.unsetStoragePolicy));
+ }
if (this.ownerName != null) {
auditMap.put(OzoneConsts.OWNER, this.ownerName);
}
@@ -249,6 +267,7 @@ public static class Builder extends WithMetadata.Builder {
private Boolean isVersionEnabled;
private StoragePolicy storagePolicy;
private Boolean allowFallbackStoragePolicy;
+ private Boolean unsetStoragePolicy;
private boolean quotaInBytesSet = false;
private long quotaInBytes;
private boolean quotaInNamespaceSet = false;
@@ -306,6 +325,11 @@ public Builder setAllowFallbackStoragePolicy(Boolean
allowFallback) {
return this;
}
+ public Builder setUnsetStoragePolicy(Boolean unset) {
+ this.unsetStoragePolicy = unset;
+ return this;
+ }
+
public Builder setQuotaInBytes(long quota) {
quotaInBytesSet = true;
quotaInBytes = quota;
@@ -371,6 +395,9 @@ public BucketArgs getProtobuf() {
if (allowFallbackStoragePolicy != null) {
builder.setAllowFallbackStoragePolicy(allowFallbackStoragePolicy);
}
+ if (unsetStoragePolicy != null) {
+ builder.setUnsetStoragePolicy(unsetStoragePolicy);
+ }
if (quotaInBytesSet && (
quotaInBytes > 0 || quotaInBytes == OzoneConsts.QUOTA_RESET)) {
builder.setQuotaInBytes(quotaInBytes);
@@ -416,6 +443,9 @@ public static Builder builderFromProtobuf(BucketArgs
bucketArgs) {
if (bucketArgs.hasAllowFallbackStoragePolicy()) {
builder.setAllowFallbackStoragePolicy(bucketArgs.getAllowFallbackStoragePolicy());
}
+ if (bucketArgs.hasUnsetStoragePolicy()) {
+ builder.setUnsetStoragePolicy(bucketArgs.getUnsetStoragePolicy());
+ }
if (bucketArgs.hasOwnerName()) {
builder.setOwnerName(bucketArgs.getOwnerName());
}
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMStoragePolicyUpgrade.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMStoragePolicyUpgrade.java
new file mode 100644
index 00000000000..f76faf9dea7
--- /dev/null
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMStoragePolicyUpgrade.java
@@ -0,0 +1,223 @@
+/*
+ * 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.OMUpgradeTestUtils.waitForFinalization;
+import static
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.NOT_SUPPORTED_OPERATION_PRIOR_FINALIZATION;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.UUID;
+import org.apache.hadoop.hdds.client.OzoneStoragePolicy;
+import org.apache.hadoop.hdds.client.StoragePolicy;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.utils.IOUtils;
+import org.apache.hadoop.ozone.MiniOzoneCluster;
+import org.apache.hadoop.ozone.client.OzoneClient;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.OmBucketArgs;
+import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
+import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
+import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
+import org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+
+/**
+ * Upgrade testing for the bucket storage policy feature.
+ * <p>
+ * Expected behavior:
+ * 1. Pre-Finalize:
+ * - CreateBucket requests that carry a storagePolicy and/or
+ * allowFallbackStoragePolicy field succeed, but those fields are
+ * silently stripped from the request before it is applied, so the
+ * created bucket ends up with no storage policy, as if the fields had
+ * never been sent.
+ * - SetBucketProperty requests that carry storagePolicy,
+ * allowFallbackStoragePolicy or unsetStoragePolicy are rejected.
+ * - Requests that carry none of these fields, such as those sent by an
+ * older client, are unaffected either way.
+ * <p>
+ * 2. Post-Finalize: CreateBucket honors the requested storage policy fields
+ * and SetBucketProperty requests carrying them succeed; in both cases the
+ * policy is persisted.
+ * <p>
+ * The cluster starts at the layout version immediately below
+ * {@link OMLayoutFeature#BUCKET_STORAGE_POLICY_SUPPORT} so that only this
+ * feature is un-finalized and the assertions cannot be satisfied by an
+ * unrelated validator.
+ * <p>
+ * Note this covers the pre-finalization gate, not a mixed-version OM ring:
+ * every OM here runs the same binary at the same metadata layout version.
+ * Real mixed-binary coverage lives in the non-rolling-upgrade acceptance suite
+ * under {@code hadoop-ozone/dist/src/main/compose/upgrade}.
+ */
+public class TestOMStoragePolicyUpgrade {
+
+ private static final String VOLUME_NAME = "vol-" + UUID.randomUUID();
+
+ private MiniOzoneCluster cluster;
+ private OzoneClient client;
+ private OzoneManagerProtocol omClient;
+ private final OzoneConfiguration conf = new OzoneConfiguration();
+
+ /** Bucket created before finalization, used by the update assertions. */
+ private String bucketName;
+
+ @BeforeEach
+ public void init() throws Exception {
+ conf.setInt(OMStorage.TESTING_INIT_LAYOUT_VERSION_KEY,
+ OMLayoutFeature.MPU_PARTS_TABLE_SPLIT.layoutVersion());
+
+ cluster = MiniOzoneCluster.newBuilder(conf)
+ .setNumDatanodes(1)
+ .build();
+ cluster.waitForClusterToBeReady();
+ client = cluster.newClient();
+ omClient =
client.getObjectStore().getClientProxy().getOzoneManagerClient();
+
+ omClient.createVolume(new OmVolumeArgs.Builder()
+ .setVolume(VOLUME_NAME)
+ .setOwnerName("user1")
+ .setAdminName("user1")
+ .build());
+
+ // A bucket without any storage policy field, as an older client would
+ // create it. This must be allowed while pre-finalized.
+ bucketName = createBucket(null, null);
+ }
+
+ @Test
+ public void upgrade() throws Exception {
+ preFinalizationChecks();
+ finalizeOMUpgrade();
+ postFinalizationChecks();
+ }
+
+ private void preFinalizationChecks() throws Exception {
+ assertThat(cluster.getOzoneManager().getVersionManager()
+ .getMetadataLayoutVersion())
+ .isEqualTo(OMLayoutFeature.MPU_PARTS_TABLE_SPLIT.layoutVersion());
+
+ // CreateBucket carrying BucketInfo storage policy field: the request
+ // succeeds, but the storage policy field is silently stripped.
+ String strippedPolicyBucket = createBucket(OzoneStoragePolicy.COLD, null);
+ assertThat(omClient.getBucketInfo(VOLUME_NAME, strippedPolicyBucket)
+ .getStoragePolicy()).isNull();
+
+ // SetBucketProperty carrying any of the three BucketArgs fields.
+ assertRejected(() -> setStoragePolicy(OzoneStoragePolicy.HOT, true, null));
+ assertRejected(() -> setStoragePolicy(null, null, true));
+
+ // Controls: the gate must not reject requests that carry no storage
policy field
+ createBucket(null, null);
+ omClient.setBucketProperty(OmBucketArgs.newBuilder()
+ .setVolumeName(VOLUME_NAME)
+ .setBucketName(bucketName)
+ .setIsVersionEnabled(true)
+ .build());
+ assertThat(omClient.getBucketInfo(VOLUME_NAME, bucketName)
+ .getStoragePolicy()).isNull();
+ }
+
+ /**
+ * Trigger OM upgrade finalization and block until it completes. The client
id
+ * must match the one {@link OMUpgradeTestUtils#waitForFinalization} polls
+ * with.
+ */
+ private void finalizeOMUpgrade() throws Exception {
+ omClient.finalizeUpgrade("finalize-test");
+ waitForFinalization(omClient);
+ }
+
+ private void postFinalizationChecks() throws Exception {
+ String created = createBucket(OzoneStoragePolicy.HOT, true);
+ OmBucketInfo createdInfo = omClient.getBucketInfo(VOLUME_NAME, created);
+
assertThat(createdInfo.getStoragePolicy()).isEqualTo(OzoneStoragePolicy.HOT);
+ assertThat(createdInfo.getAllowFallbackStoragePolicy()).isTrue();
+
+ setStoragePolicy(OzoneStoragePolicy.HOT, null, null);
+ assertThat(omClient.getBucketInfo(VOLUME_NAME, bucketName)
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.HOT);
+
+ setStoragePolicy(null, null, true);
+ assertThat(omClient.getBucketInfo(VOLUME_NAME, bucketName)
+ .getStoragePolicy()).isNull();
+ }
+
+ private void assertRejected(Executable executable) {
+ OMException ex = assertThrows(OMException.class, executable);
+ assertThat(ex.getResult())
+ .isEqualTo(NOT_SUPPORTED_OPERATION_PRIOR_FINALIZATION);
+ assertThat(ex.getMessage())
+ .contains("Cluster does not have the bucket storage policy support"
+ + " feature finalized yet");
+ }
+
+ /**
+ * Creates a bucket through {@link OzoneManagerProtocol} rather than the
+ * {@code OzoneBucket} API, because {@code RpcClient#createBucket}
substitutes
+ * the default storage policy for null and so cannot express a request
without
+ * the field.
+ *
+ * @return the name of the bucket created.
+ */
+ private String createBucket(StoragePolicy storagePolicy,
+ Boolean allowFallbackStoragePolicy) throws Exception {
+ String name = "buck-" + UUID.randomUUID();
+ OmBucketInfo.Builder builder = OmBucketInfo.newBuilder()
+ .setVolumeName(VOLUME_NAME)
+ .setBucketName(name);
+ if (storagePolicy != null) {
+ builder.setStoragePolicy(storagePolicy);
+ }
+ if (allowFallbackStoragePolicy != null) {
+ builder.setAllowFallbackStoragePolicy(allowFallbackStoragePolicy);
+ }
+ omClient.createBucket(builder.build());
+ return name;
+ }
+
+ private void setStoragePolicy(StoragePolicy storagePolicy,
+ Boolean allowFallbackStoragePolicy, Boolean unsetStoragePolicy)
+ throws Exception {
+ OmBucketArgs.Builder builder = OmBucketArgs.newBuilder()
+ .setVolumeName(VOLUME_NAME)
+ .setBucketName(bucketName);
+ if (storagePolicy != null) {
+ builder.setStoragePolicy(storagePolicy);
+ }
+ if (allowFallbackStoragePolicy != null) {
+ builder.setAllowFallbackStoragePolicy(allowFallbackStoragePolicy);
+ }
+ if (unsetStoragePolicy != null) {
+ builder.setUnsetStoragePolicy(unsetStoragePolicy);
+ }
+ omClient.setBucketProperty(builder.build());
+ }
+
+ @AfterEach
+ public void teardown() {
+ IOUtils.closeQuietly(client);
+ if (cluster != null) {
+ cluster.shutdown();
+ }
+ }
+}
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
index 00b070e29d2..d7697f4c3bc 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
@@ -73,7 +73,9 @@
import org.apache.hadoop.fs.ozone.OzoneTrashPolicy;
import org.apache.hadoop.hdds.JsonTestUtils;
import org.apache.hadoop.hdds.cli.GenericCli;
+import org.apache.hadoop.hdds.client.OzoneStoragePolicy;
import org.apache.hadoop.hdds.client.ReplicationType;
+import org.apache.hadoop.hdds.client.StoragePolicy;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.scm.ScmConfigKeys;
@@ -1278,6 +1280,160 @@ public void testDeleteTrashNoSkipTrash() throws
Exception {
}
+ @Test
+ public void testShCreateBucketWithStoragePolicy() throws Exception {
+ ObjectStore objectStore = client.getObjectStore();
+ execute(ozoneShell, new String[]{"volume", "create", "spvol1"});
+ out.reset();
+
+ // No -sp: server defaults to WARM and allowFallback true.
+ execute(ozoneShell, new String[]{"bucket", "create", "spvol1/bucket1"});
+ assertThat(objectStore.getVolume("spvol1").getBucket("bucket1")
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.WARM);
+ assertThat(objectStore.getVolume("spvol1").getBucket("bucket1")
+ .getAllowFallbackStoragePolicy()).isTrue();
+
+ // -sp null on create still yields a non-null policy (defaults to WARM).
+ execute(ozoneShell, new String[]{"bucket", "create", "spvol1/bucket2",
"-s", "null"});
+ assertThat(objectStore.getVolume("spvol1").getBucket("bucket2")
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.WARM);
+
+ execute(ozoneShell, new String[]{"bucket", "create", "spvol1/bucket3",
"-s", "HOT"});
+ assertThat(objectStore.getVolume("spvol1").getBucket("bucket3")
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.HOT);
+
+ execute(ozoneShell,
+ new String[]{"bucket", "create", "spvol1/bucket4", "-s", "HOT", "-a",
"false"});
+ assertThat(objectStore.getVolume("spvol1").getBucket("bucket4")
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.HOT);
+ assertThat(objectStore.getVolume("spvol1").getBucket("bucket4")
+ .getAllowFallbackStoragePolicy()).isFalse();
+
+ execute(ozoneShell, new String[]{"bucket", "create", "spvol1/bucket5",
"-s", "COLD"});
+ assertThat(objectStore.getVolume("spvol1").getBucket("bucket5")
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.COLD);
+
+ objectStore.getVolume("spvol1").deleteBucket("bucket1");
+ objectStore.getVolume("spvol1").deleteBucket("bucket2");
+ objectStore.getVolume("spvol1").deleteBucket("bucket3");
+ objectStore.getVolume("spvol1").deleteBucket("bucket4");
+ objectStore.getVolume("spvol1").deleteBucket("bucket5");
+ objectStore.deleteVolume("spvol1");
+ }
+
+ @Test
+ public void testShUpdateBucketStoragePolicy() throws Exception {
+ ObjectStore objectStore = client.getObjectStore();
+ execute(ozoneShell, new String[]{"volume", "create", "spvol2"});
+ out.reset();
+
+ execute(ozoneShell, new String[]{"bucket", "create", "spvol2/bucket1"});
+ assertThat(objectStore.getVolume("spvol2").getBucket("bucket1")
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.WARM);
+ assertThat(objectStore.getVolume("spvol2").getBucket("bucket1")
+ .getAllowFallbackStoragePolicy()).isTrue();
+
+ // Update policy only; allowFallback is left unchanged.
+ execute(ozoneShell, new String[]{"bucket", "update", "spvol2/bucket1",
"-s", "HOT"});
+ assertThat(objectStore.getVolume("spvol2").getBucket("bucket1")
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.HOT);
+ assertThat(objectStore.getVolume("spvol2").getBucket("bucket1")
+ .getAllowFallbackStoragePolicy()).isTrue();
+
+ // Update both policy and allowFallback.
+ execute(ozoneShell,
+ new String[]{"bucket", "update", "spvol2/bucket1", "-s", "COLD", "-a",
"false"});
+ assertThat(objectStore.getVolume("spvol2").getBucket("bucket1")
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.COLD);
+ assertThat(objectStore.getVolume("spvol2").getBucket("bucket1")
+ .getAllowFallbackStoragePolicy()).isFalse();
+
+ // Unset the policy via -sp null (case-insensitive).
+ execute(ozoneShell, new String[]{"bucket", "update", "spvol2/bucket1",
"-s", "NULL"});
+ assertThat(objectStore.getVolume("spvol2").getBucket("bucket1")
+ .getStoragePolicy()).isNull();
+
+ // Set it again, then unset via lowercase null.
+ execute(ozoneShell, new String[]{"bucket", "update", "spvol2/bucket1",
"-s", "HOT"});
+ assertThat(objectStore.getVolume("spvol2").getBucket("bucket1")
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.HOT);
+ execute(ozoneShell, new String[]{"bucket", "update", "spvol2/bucket1",
"-s", "null"});
+ assertThat(objectStore.getVolume("spvol2").getBucket("bucket1")
+ .getStoragePolicy()).isNull();
+
+ objectStore.getVolume("spvol2").deleteBucket("bucket1");
+ objectStore.deleteVolume("spvol2");
+ }
+
+ @Test
+ public void testShUpdateBucketStoragePolicyLocalStateStaysConsistent()
throws Exception {
+ ObjectStore objectStore = client.getObjectStore();
+ objectStore.createVolume("spvol4");
+ objectStore.getVolume("spvol4").createBucket("bucket1");
+
+ OzoneBucket bucket = objectStore.getVolume("spvol4").getBucket("bucket1");
+ bucket.setStoragePolicyProperty(OzoneStoragePolicy.COLD, false, false);
+
+ // The in-memory bucket object reflects the new values immediately,
+ // without needing to be re-fetched.
+ assertThat(bucket.getStoragePolicy()).isEqualTo(OzoneStoragePolicy.COLD);
+ assertThat(bucket.getAllowFallbackStoragePolicy()).isFalse();
+
+ // And the update was actually applied to this bucket (vol/name it was
+ // constructed for), not some other bucket.
+ OzoneBucket refetched =
objectStore.getVolume("spvol4").getBucket("bucket1");
+
assertThat(refetched.getStoragePolicy()).isEqualTo(OzoneStoragePolicy.COLD);
+ assertThat(refetched.getAllowFallbackStoragePolicy()).isFalse();
+
+ bucket.setStoragePolicyProperty(null, null, true);
+ assertThat(bucket.getStoragePolicy()).isNull();
+ assertThat(objectStore.getVolume("spvol4").getBucket("bucket1")
+ .getStoragePolicy()).isNull();
+
+ objectStore.getVolume("spvol4").deleteBucket("bucket1");
+ objectStore.deleteVolume("spvol4");
+ }
+
+ @Test
+ public void testShUpdateBucketRejectsOwnerCombinedWithStoragePolicy() throws
Exception {
+ ObjectStore objectStore = client.getObjectStore();
+ execute(ozoneShell, new String[]{"volume", "create", "spvol3"});
+ out.reset();
+
+ execute(ozoneShell, new String[]{"bucket", "create", "spvol3/bucket1"});
+ String baselineOwner =
objectStore.getVolume("spvol3").getBucket("bucket1").getOwner();
+ StoragePolicy baselinePolicy = objectStore.getVolume("spvol3")
+ .getBucket("bucket1").getStoragePolicy();
+
+ // --user combined with --storage-policy is rejected before any RPC.
+ executeWithError(ozoneShell,
+ new String[]{"bucket", "update", "spvol3/bucket1", "-u", "bob", "-s",
"COLD"},
+ "--user cannot be combined with --storage-policy");
+ assertThat(objectStore.getVolume("spvol3").getBucket("bucket1").getOwner())
+ .isEqualTo(baselineOwner);
+
assertThat(objectStore.getVolume("spvol3").getBucket("bucket1").getStoragePolicy())
+ .isEqualTo(baselinePolicy);
+
+ // --user combined with --allow-fallback-storage-policy is rejected too.
+ executeWithError(ozoneShell,
+ new String[]{"bucket", "update", "spvol3/bucket1", "-u", "bob", "-a",
"false"},
+ "--user cannot be combined with");
+ assertThat(objectStore.getVolume("spvol3").getBucket("bucket1").getOwner())
+ .isEqualTo(baselineOwner);
+ assertThat(objectStore.getVolume("spvol3").getBucket("bucket1")
+ .getAllowFallbackStoragePolicy()).isTrue();
+
+ // --user alone still works as a single RPC.
+ execute(ozoneShell, new String[]{"bucket", "update", "spvol3/bucket1",
"-u", "bob"});
+ assertThat(objectStore.getVolume("spvol3").getBucket("bucket1").getOwner())
+ .isEqualTo("bob");
+
assertThat(objectStore.getVolume("spvol3").getBucket("bucket1").getStoragePolicy())
+ .isEqualTo(baselinePolicy);
+
+ objectStore.getVolume("spvol3").deleteBucket("bucket1");
+ objectStore.deleteVolume("spvol3");
+ }
+
@Test
@SuppressWarnings("methodlength")
public void testShQuota() throws Exception {
diff --git
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 94b3020f8e3..730c8bfe467 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -901,6 +901,7 @@ message BucketArgs {
repeated hadoop.hdds.KeyValue tags = 13;
optional hadoop.hdds.StoragePolicyProto storagePolicy = 14;
optional bool allowFallbackStoragePolicy = 15;
+ optional bool unsetStoragePolicy = 16;
}
message PrefixInfo {
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketCreateRequest.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketCreateRequest.java
index 718f329aaaf..0a196004a95 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketCreateRequest.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketCreateRequest.java
@@ -433,6 +433,24 @@ public static OMRequest
disallowCreateBucketWithECReplicationConfig(
return req;
}
+ @RequestFeatureValidator(
+ conditions = ValidationCondition.CLUSTER_NEEDS_FINALIZATION,
+ processingPhase = RequestProcessingPhase.PRE_PROCESS,
+ requestType = Type.CreateBucket
+ )
+ public static OMRequest disallowCreateBucketWithStoragePolicy(
+ OMRequest req, ValidationContext ctx) throws OMException {
+ if (!ctx.versionManager()
+ .isAllowed(OMLayoutFeature.BUCKET_STORAGE_POLICY_SUPPORT)) {
+ BucketInfo bucketInfo = req.getCreateBucketRequest().getBucketInfo();
+ if (bucketInfo.hasStoragePolicy()
+ || bucketInfo.hasAllowFallbackStoragePolicy()) {
+ return stripStoragePolicyFromRequest(req);
+ }
+ }
+ return req;
+ }
+
@RequestFeatureValidator(
conditions = ValidationCondition.CLUSTER_NEEDS_FINALIZATION,
processingPhase = RequestProcessingPhase.PRE_PROCESS,
@@ -502,4 +520,17 @@ private static OMRequest changeBucketLayout(OMRequest
originalRequest,
return originalRequest.toBuilder()
.setCreateBucketRequest(newCreateRequest).build();
}
+
+ private static OMRequest stripStoragePolicyFromRequest(OMRequest
originalRequest) {
+ CreateBucketRequest createBucketRequest =
+ originalRequest.getCreateBucketRequest();
+ BucketInfo newBucketInfo = createBucketRequest.getBucketInfo().toBuilder()
+ .clearStoragePolicy()
+ .clearAllowFallbackStoragePolicy()
+ .build();
+ CreateBucketRequest newCreateRequest = createBucketRequest.toBuilder()
+ .setBucketInfo(newBucketInfo).build();
+ return originalRequest.toBuilder()
+ .setCreateBucketRequest(newCreateRequest).build();
+ }
}
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketSetPropertyRequest.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketSetPropertyRequest.java
index 2911ba47c53..ac0bc77a404 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketSetPropertyRequest.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketSetPropertyRequest.java
@@ -166,11 +166,22 @@ public OMClientResponse
validateAndUpdateCache(OzoneManager ozoneManager, Execut
//Check StoragePolicy to update
StoragePolicy storagePolicy = omBucketArgs.getStoragePolicy();
+ Boolean unsetStoragePolicy = omBucketArgs.getUnsetStoragePolicy();
+ if (storagePolicy != null && Boolean.TRUE.equals(unsetStoragePolicy)) {
+ throw new OMException("Set storagePolicy and unset storagePolicy
cannot "
+ + "be given at the same time",
+ OMException.ResultCodes.NOT_SUPPORTED_OPERATION);
+ }
if (storagePolicy != null) {
bucketInfoBuilder.setStoragePolicy(storagePolicy);
LOG.debug("Updating bucket storage policy for bucket: {} in volume:
{}",
bucketName, volumeName);
}
+ if (Boolean.TRUE.equals(unsetStoragePolicy)) {
+ bucketInfoBuilder.setStoragePolicy(null);
+ LOG.debug("Unsetting bucket storage policy for bucket: {} in volume:
{}",
+ bucketName, volumeName);
+ }
//Check allowFallbackStoragePolicy to update
Boolean allowFallbackStoragePolicy =
@@ -386,4 +397,28 @@ public static OMRequest
disallowSetBucketPropertyWithECReplicationConfig(
}
return req;
}
+
+ @RequestFeatureValidator(
+ conditions = ValidationCondition.CLUSTER_NEEDS_FINALIZATION,
+ processingPhase = RequestProcessingPhase.PRE_PROCESS,
+ requestType = Type.SetBucketProperty
+ )
+ public static OMRequest disallowSetBucketPropertyWithStoragePolicy(
+ OMRequest req, ValidationContext ctx) throws OMException {
+ if (!ctx.versionManager()
+ .isAllowed(OMLayoutFeature.BUCKET_STORAGE_POLICY_SUPPORT)) {
+ SetBucketPropertyRequest propReq = req.getSetBucketPropertyRequest();
+ if (propReq.hasBucketArgs()
+ && (propReq.getBucketArgs().hasStoragePolicy()
+ || propReq.getBucketArgs().hasAllowFallbackStoragePolicy()
+ || propReq.getBucketArgs().hasUnsetStoragePolicy())) {
+ throw new OMException("Cluster does not have the bucket storage policy"
+ + " support feature finalized yet, but the request contains
storage"
+ + " policy arguments. Rejecting the request, please finalize the"
+ + " cluster upgrade and then try again.",
+
OMException.ResultCodes.NOT_SUPPORTED_OPERATION_PRIOR_FINALIZATION);
+ }
+ }
+ return req;
+ }
}
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMLayoutFeature.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMLayoutFeature.java
index 189bb3f3e1d..71d7fe4211d 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMLayoutFeature.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/upgrade/OMLayoutFeature.java
@@ -46,7 +46,8 @@ public enum OMLayoutFeature implements LayoutFeature {
DELEGATION_TOKEN_SYMMETRIC_SIGN(8, "Delegation token signed by symmetric
key"),
SNAPSHOT_DEFRAG(9, "Supporting defragmentation of snapshot"),
S3_LIFECYCLE_SUPPORT(10, "S3 bucket lifecycle configuration support"),
- MPU_PARTS_TABLE_SPLIT(11, "Split multipart table into separate table for
parts and key");
+ MPU_PARTS_TABLE_SPLIT(11, "Split multipart table into separate table for
parts and key"),
+ BUCKET_STORAGE_POLICY_SUPPORT(12, "Bucket storage policy and fallback
storage policy support");
/////////////////////////////// /////////////////////////////
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketCreateRequest.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketCreateRequest.java
index 64ba9df4e08..a14aba549af 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketCreateRequest.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketCreateRequest.java
@@ -19,6 +19,7 @@
import static
org.apache.hadoop.ozone.om.request.OMRequestTestUtils.newBucketInfoBuilder;
import static
org.apache.hadoop.ozone.om.request.OMRequestTestUtils.newCreateBucketRequest;
+import static
org.apache.hadoop.ozone.om.request.validation.ValidationContext.of;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -27,6 +28,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
@@ -44,12 +46,15 @@
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
+import org.apache.hadoop.ozone.om.request.validation.ValidationContext;
import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
import org.apache.hadoop.ozone.security.acl.OzoneObj;
+import org.apache.hadoop.ozone.upgrade.LayoutVersionManager;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.Time;
import org.junit.jupiter.api.Test;
@@ -413,6 +418,65 @@ public void testIgnoreClientACL(boolean ignoreClientACLs)
throws Exception {
}
}
+ @Test
+ public void testStripStoragePolicyBeforeFinalization() throws Exception {
+ OMRequest request = newCreateBucketRequest(
+ newBucketInfoBuilder(UUID.randomUUID().toString(),
+ UUID.randomUUID().toString())
+ .setStoragePolicy(StoragePolicyProto.HOT)
+ .setAllowFallbackStoragePolicy(true)).build();
+
+ OMRequest result =
OMBucketCreateRequest.disallowCreateBucketWithStoragePolicy(
+ request, preFinalizedContext());
+
+ assertThat(result).isNotEqualTo(request);
+ assertThat(result.getCreateBucketRequest().getBucketInfo()
+ .hasStoragePolicy()).isFalse();
+ assertThat(result.getCreateBucketRequest().getBucketInfo()
+ .hasAllowFallbackStoragePolicy()).isFalse();
+ }
+
+ /**
+ * A request that carries no storage-policy field must pass the gate
+ * untouched, since the validator's return value is chained into the next
+ * validator by the request validation framework.
+ */
+ @Test
+ public void testAllowCreateBucketWithoutStoragePolicyBeforeFinalization()
+ throws Exception {
+ OMRequest request = newCreateBucketRequest(
+ newBucketInfoBuilder(UUID.randomUUID().toString(),
+ UUID.randomUUID().toString())
+ .clearStoragePolicy()).build();
+
+ assertThat(OMBucketCreateRequest.disallowCreateBucketWithStoragePolicy(
+ request, preFinalizedContext())).isEqualTo(request);
+ }
+
+ @Test
+ public void testAllowCreateBucketWithStoragePolicyAfterFinalization()
+ throws Exception {
+ OMRequest request = newCreateBucketRequest(
+ newBucketInfoBuilder(UUID.randomUUID().toString(),
+ UUID.randomUUID().toString())
+ .setStoragePolicy(StoragePolicyProto.COLD)
+ .setAllowFallbackStoragePolicy(true)).build();
+
+ LayoutVersionManager versionManager = mock(LayoutVersionManager.class);
+ when(versionManager.isAllowed(
+ OMLayoutFeature.BUCKET_STORAGE_POLICY_SUPPORT)).thenReturn(true);
+
+ assertThat(OMBucketCreateRequest.disallowCreateBucketWithStoragePolicy(
+ request, of(versionManager, omMetadataManager))).isEqualTo(request);
+ }
+
+ private ValidationContext preFinalizedContext() {
+ LayoutVersionManager versionManager = mock(LayoutVersionManager.class);
+ when(versionManager.isAllowed(
+ OMLayoutFeature.BUCKET_STORAGE_POLICY_SUPPORT)).thenReturn(false);
+ return of(versionManager, omMetadataManager);
+ }
+
private void acceptBucketCreationHelper(String volumeName, String bucketName)
throws Exception {
OMBucketCreateRequest omBucketCreateRequest =
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketSetPropertyRequest.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketSetPropertyRequest.java
index 2e41d4c8b17..cb1d56950ad 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketSetPropertyRequest.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketSetPropertyRequest.java
@@ -19,28 +19,38 @@
import static org.apache.hadoop.hdds.client.ReplicationType.EC;
import static org.apache.hadoop.ozone.OzoneConsts.GB;
+import static
org.apache.hadoop.ozone.om.request.validation.ValidationContext.of;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import java.util.UUID;
import org.apache.hadoop.hdds.client.DefaultReplicationConfig;
import org.apache.hadoop.hdds.client.ECReplicationConfig;
+import org.apache.hadoop.hdds.client.OzoneStoragePolicy;
+import org.apache.hadoop.hdds.client.StoragePolicy;
import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.BucketEncryptionKeyInfo;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
import org.apache.hadoop.ozone.om.helpers.OmBucketArgs;
import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
+import org.apache.hadoop.ozone.om.request.validation.ValidationContext;
import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.upgrade.OMLayoutFeature;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.BucketArgs;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SetBucketPropertyRequest;
+import org.apache.hadoop.ozone.upgrade.LayoutVersionManager;
import org.apache.ozone.test.GenericTestUtils.LogCapturer;
import org.junit.jupiter.api.Test;
@@ -496,4 +506,155 @@ public void testSettingQuotaRetainsReplication() throws
Exception {
assertEquals(20 * GB, dbBucketInfoAfter.getQuotaInBytes());
assertEquals(1000L, dbBucketInfoAfter.getQuotaInNamespace());
}
+
+ @Test
+ public void testSettingStoragePolicy() throws Exception {
+ String volumeName = UUID.randomUUID().toString();
+ String bucketName = UUID.randomUUID().toString();
+
+ OmBucketInfo.Builder bucketInfo = new OmBucketInfo.Builder()
+ .setVolumeName(volumeName)
+ .setBucketName(bucketName)
+ .setStoragePolicy(OzoneStoragePolicy.WARM);
+ OMRequestTestUtils.addVolumeToDB(volumeName, omMetadataManager);
+ OMRequestTestUtils.addBucketToDB(omMetadataManager, bucketInfo);
+
+ String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+ assertThat(omMetadataManager.getBucketTable().get(bucketKey)
+ .getStoragePolicy()).isEqualTo(OzoneStoragePolicy.WARM);
+
+ // Set the StoragePolicy to HOT and disable fallback.
+ OMClientResponse response = runSetStoragePolicy(volumeName, bucketName,
+ OzoneStoragePolicy.HOT, null, false, 1);
+ assertThat(response.getOMResponse().getSuccess()).isTrue();
+ OmBucketInfo updated = omMetadataManager.getBucketTable().get(bucketKey);
+ assertThat(updated.getStoragePolicy()).isEqualTo(OzoneStoragePolicy.HOT);
+ assertThat(updated.getAllowFallbackStoragePolicy()).isFalse();
+
+ // Set the StoragePolicy to COLD and enable fallback.
+ response = runSetStoragePolicy(volumeName, bucketName,
+ OzoneStoragePolicy.COLD, null, true, 2);
+ assertThat(response.getOMResponse().getSuccess()).isTrue();
+ updated = omMetadataManager.getBucketTable().get(bucketKey);
+ assertThat(updated.getStoragePolicy()).isEqualTo(OzoneStoragePolicy.COLD);
+ assertThat(updated.getAllowFallbackStoragePolicy()).isTrue();
+
+ // Unset the StoragePolicy.
+ response = runSetStoragePolicy(volumeName, bucketName, null, true, null,
3);
+ assertThat(response.getOMResponse().getSuccess()).isTrue();
+ updated = omMetadataManager.getBucketTable().get(bucketKey);
+ assertThat(updated.getStoragePolicy()).isNull();
+
+ // Set and unset the StoragePolicy at the same time is not allowed.
+ response = runSetStoragePolicy(volumeName, bucketName,
+ OzoneStoragePolicy.HOT, true, null, 4);
+ assertThat(response.getOMResponse().getSuccess()).isFalse();
+ assertThat(response.getOMResponse().getStatus())
+ .isEqualTo(OzoneManagerProtocolProtos.Status.NOT_SUPPORTED_OPERATION);
+ }
+
+ private OMClientResponse runSetStoragePolicy(String volumeName,
+ String bucketName, StoragePolicy storagePolicy,
+ Boolean unsetStoragePolicy, Boolean allowFallbackStoragePolicy,
+ long txnId) throws Exception {
+ OmBucketArgs.Builder argsBuilder = OmBucketArgs.newBuilder()
+ .setVolumeName(volumeName)
+ .setBucketName(bucketName);
+ if (storagePolicy != null) {
+ argsBuilder.setStoragePolicy(storagePolicy);
+ }
+ if (unsetStoragePolicy != null) {
+ argsBuilder.setUnsetStoragePolicy(unsetStoragePolicy);
+ }
+ if (allowFallbackStoragePolicy != null) {
+ argsBuilder.setAllowFallbackStoragePolicy(allowFallbackStoragePolicy);
+ }
+ OMRequest omRequest = OMRequest.newBuilder().setSetBucketPropertyRequest(
+ SetBucketPropertyRequest.newBuilder()
+ .setBucketArgs(argsBuilder.build().getProtobuf()))
+ .setCmdType(OzoneManagerProtocolProtos.Type.SetBucketProperty)
+ .setClientId(UUID.randomUUID().toString()).build();
+ return new OMBucketSetPropertyRequest(omRequest)
+ .validateAndUpdateCache(ozoneManager, txnId);
+ }
+
+ @Test
+ public void testDisallowSetStoragePolicyBeforeFinalization() {
+ assertStoragePolicyRejectedBeforeFinalization(storagePolicyRequest(
+ OzoneStoragePolicy.HOT, null, true));
+ }
+
+ @Test
+ public void testDisallowUnsetStoragePolicyBeforeFinalization() {
+ assertStoragePolicyRejectedBeforeFinalization(
+ storagePolicyRequest(null, true, null));
+ }
+
+ /**
+ * A SetBucketProperty request that carries no storage-policy field must pass
+ * the gate untouched, since the validator's return value is chained into the
+ * next validator by the request validation framework.
+ */
+ @Test
+ public void testAllowSetOtherBucketPropertyBeforeFinalization()
+ throws Exception {
+ OMRequest request = createSetBucketPropertyRequest(
+ UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ true, 100 * GB);
+
+ assertThat(OMBucketSetPropertyRequest
+ .disallowSetBucketPropertyWithStoragePolicy(
+ request, preFinalizedContext())).isEqualTo(request);
+ }
+
+ @Test
+ public void testAllowSetStoragePolicyAfterFinalization() throws Exception {
+ OMRequest request = storagePolicyRequest(OzoneStoragePolicy.COLD, null,
true);
+
+ LayoutVersionManager versionManager = mock(LayoutVersionManager.class);
+ when(versionManager.isAllowed(
+ OMLayoutFeature.BUCKET_STORAGE_POLICY_SUPPORT)).thenReturn(true);
+
+ assertThat(OMBucketSetPropertyRequest
+ .disallowSetBucketPropertyWithStoragePolicy(
+ request, of(versionManager,
omMetadataManager))).isEqualTo(request);
+ }
+
+ private void assertStoragePolicyRejectedBeforeFinalization(
+ OMRequest request) {
+ OMException ex = assertThrows(OMException.class, () ->
+ OMBucketSetPropertyRequest.disallowSetBucketPropertyWithStoragePolicy(
+ request, preFinalizedContext()));
+
+ assertThat(ex.getResult()).isEqualTo(
+ OMException.ResultCodes.NOT_SUPPORTED_OPERATION_PRIOR_FINALIZATION);
+ }
+
+ private OMRequest storagePolicyRequest(StoragePolicy storagePolicy,
+ Boolean unsetStoragePolicy, Boolean allowFallbackStoragePolicy) {
+ OmBucketArgs.Builder argsBuilder = OmBucketArgs.newBuilder()
+ .setVolumeName(UUID.randomUUID().toString())
+ .setBucketName(UUID.randomUUID().toString());
+ if (storagePolicy != null) {
+ argsBuilder.setStoragePolicy(storagePolicy);
+ }
+ if (unsetStoragePolicy != null) {
+ argsBuilder.setUnsetStoragePolicy(unsetStoragePolicy);
+ }
+ if (allowFallbackStoragePolicy != null) {
+ argsBuilder.setAllowFallbackStoragePolicy(allowFallbackStoragePolicy);
+ }
+ return OMRequest.newBuilder().setSetBucketPropertyRequest(
+ SetBucketPropertyRequest.newBuilder()
+ .setBucketArgs(argsBuilder.build().getProtobuf()))
+ .setCmdType(OzoneManagerProtocolProtos.Type.SetBucketProperty)
+ .setClientId(UUID.randomUUID().toString()).build();
+ }
+
+ private ValidationContext preFinalizedContext() {
+ LayoutVersionManager versionManager = mock(LayoutVersionManager.class);
+ when(versionManager.isAllowed(
+ OMLayoutFeature.BUCKET_STORAGE_POLICY_SUPPORT)).thenReturn(false);
+ return of(versionManager, omMetadataManager);
+ }
}
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
index 22e85954ae2..a4a516b0cca 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
@@ -40,6 +40,7 @@
import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
import org.apache.hadoop.ozone.om.helpers.LeaseKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmBucketArgs;
import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
@@ -198,6 +199,11 @@ public void setBucketStoragePolicy(String volumeName,
String bucketName,
}
+ @Override
+ public void setBucketStoragePolicy(OmBucketArgs args) throws IOException {
+
+ }
+
@Override
public void deleteBucket(String volumeName, String bucketName)
throws IOException {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]