diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml b/hadoop-hdds/common/src/main/resources/ozone-default.xml index 2cb1f52ede15..78e8148b5bf4 100644 --- a/hadoop-hdds/common/src/main/resources/ozone-default.xml +++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml @@ -3936,6 +3936,16 @@ + + ozone.om.snapshot.rename.allowed + false + OZONE, OM + + Allows the Ozone snapshot rename operation if set to true on the OM side. + Blocks it otherwise. + + + ozone.snapshot.deleting.service.timeout 300s diff --git a/hadoop-hdds/docs/content/feature/Snapshot-Configuration-Properties.md b/hadoop-hdds/docs/content/feature/Snapshot-Configuration-Properties.md index 998a798d9c09..90c5d0ca6163 100644 --- a/hadoop-hdds/docs/content/feature/Snapshot-Configuration-Properties.md +++ b/hadoop-hdds/docs/content/feature/Snapshot-Configuration-Properties.md @@ -36,6 +36,7 @@ These parameters, defined in `ozone-site.xml`, control how Ozone manages snapsho * `ozone.om.ratis.snapshot.max.total.sst.size`: The maximum total size of SST files to be included in a Ratis snapshot (Default: 10737418240). * `ozone.om.snapshot.load.native.lib`: Use native RocksDB library for snapshot operations (Default: true). Set to false as a workaround for native library issues. * `ozone.om.snapshot.checkpoint.dir.creation.poll.timeout`: Timeout for polling the creation of the snapshot checkpoint directory (Default: 20s). + * `ozone.om.snapshot.rename.allowed`: Allow snapshot rename operation (Default: false). * **SnapshotDiff Service** * `ozone.om.snapshot.diff.db.dir`: Directory for SnapshotDiff job data. Defaults to OM metadata dir. Use a spacious location for large diffs. diff --git a/hadoop-hdds/docs/content/feature/Snapshot.md b/hadoop-hdds/docs/content/feature/Snapshot.md index 65667efbf726..3ac1d931d497 100644 --- a/hadoop-hdds/docs/content/feature/Snapshot.md +++ b/hadoop-hdds/docs/content/feature/Snapshot.md @@ -135,7 +135,7 @@ Manage snapshots using `ozone sh` or `ozone fs` (Hadoop-compatible) commands: ```shell ozone sh snapshot rename /vol1/bucket1 ``` - Requires bucket owner or admin. + Requires `ozone.om.snapshot.rename.allowed=true` on the OM side and bucket owner or admin privileges. * **Snapshot Info:** ```shell diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java index 05083d506304..e24a9036b2d8 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java @@ -30,6 +30,9 @@ public final class OMConfigKeys { public static final String OZONE_FILESYSTEM_SNAPSHOT_ENABLED_KEY = "ozone.filesystem.snapshot.enabled"; public static final boolean OZONE_FILESYSTEM_SNAPSHOT_ENABLED_DEFAULT = true; + public static final String OZONE_OM_SNAPSHOT_RENAME_ALLOWED_KEY = + "ozone.om.snapshot.rename.allowed"; + public static final boolean OZONE_OM_SNAPSHOT_RENAME_ALLOWED_DEFAULT = false; // Location where the OM stores its DB files. In the future we may support // multiple entries for performance (sharding).. diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsSnapshot.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsSnapshot.java index 6a97796af32b..f86005399d0b 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsSnapshot.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsSnapshot.java @@ -27,13 +27,17 @@ import static org.apache.hadoop.ozone.OzoneConsts.OZONE_OFS_URI_SCHEME; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_SST_FILTERING_SERVICE_INTERVAL; import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPath; +import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.FEATURE_NOT_ENABLED; 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -45,12 +49,16 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import org.apache.commons.io.FileUtils; +import org.apache.hadoop.hdds.cli.GenericCli; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.ozone.MiniOzoneCluster; +import org.apache.hadoop.ozone.client.OzoneClient; import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.ozone.om.OmConfig; import org.apache.hadoop.ozone.om.OzoneManager; +import org.apache.hadoop.ozone.om.exceptions.OMException; import org.apache.hadoop.ozone.om.helpers.SnapshotInfo; +import org.apache.hadoop.ozone.shell.OzoneShell; import org.apache.hadoop.util.ToolRunner; import org.apache.ozone.test.GenericTestUtils; import org.junit.jupiter.api.AfterAll; @@ -68,6 +76,9 @@ */ class TestOzoneFsSnapshot { + private static final String SNAPSHOT_RENAME_NOT_ALLOWED_MESSAGE = + "Ozone snapshot rename feature is not allowed per Ozone Manager server config"; + private static MiniOzoneCluster cluster; private static final String OM_SERVICE_ID = "om-service-test1"; private static OzoneManager ozoneManager; @@ -95,6 +106,8 @@ static void initClass() throws Exception { conf.setInt(OZONE_SNAPSHOT_SST_FILTERING_SERVICE_INTERVAL, -1); conf.setInt(OmConfig.Keys.SERVER_LIST_MAX_SIZE, 20); conf.setInt(OZONE_FS_LISTING_PAGE_SIZE, 30); + // Explicitly disable snapshot rename for the test + conf.setBoolean(OMConfigKeys.OZONE_OM_SNAPSHOT_RENAME_ALLOWED_KEY, false); // Start the cluster cluster = MiniOzoneCluster.newHABuilder(conf) @@ -212,6 +225,44 @@ void testCreateSnapshotSuccess(String snapshotName) assertNotNull(snapshotInfo); } + @Test + void testSnapshotRenameBlockedWhenConfigDisallows(@TempDir Path tempDir) + throws Exception { + assertFalse(cluster.getConf().getBoolean( + OMConfigKeys.OZONE_OM_SNAPSHOT_RENAME_ALLOWED_KEY, + OMConfigKeys.OZONE_OM_SNAPSHOT_RENAME_ALLOWED_DEFAULT)); + + String oldSnapshotName = createSnapshot(); + String newSnapshotName = "snap-" + counter.incrementAndGet(); + + try (OzoneClient ozoneClient = cluster.newClient()) { + OMException omException = assertThrows(OMException.class, + () -> ozoneClient.getObjectStore().renameSnapshot( + VOLUME, BUCKET, oldSnapshotName, newSnapshotName)); + assertEquals(FEATURE_NOT_ENABLED, omException.getResult()); + assertEquals(SNAPSHOT_RENAME_NOT_ALLOWED_MESSAGE, + omException.getMessage()); + } + + Path confPath = tempDir.resolve("ozone-site.xml"); + try (OutputStream outputStream = Files.newOutputStream(confPath)) { + cluster.getConf().writeXml(outputStream); + } + + try (GenericTestUtils.SystemErrCapturer capture = + new GenericTestUtils.SystemErrCapturer()) { + OzoneShell ozoneShell = new OzoneShell(); + int res = ozoneShell.execute(new String[] { + "-conf", confPath.toString(), "snapshot", "rename", + "o3://" + OM_SERVICE_ID + BUCKET_PATH, + oldSnapshotName, newSnapshotName}); + + assertEquals(GenericCli.EXECUTION_ERROR_EXIT_CODE, res); + assertThat(capture.getOutput()).contains( + SNAPSHOT_RENAME_NOT_ALLOWED_MESSAGE); + } + } + private static Stream createSnapshotFailureScenarios() { String invalidBucketPath = "/invalid/uri"; return Stream.of( diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmMetrics.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmMetrics.java index 9d3aedd4edcd..bad68bea43b6 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmMetrics.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOmMetrics.java @@ -116,6 +116,7 @@ public void setup() throws Exception { conf.setTimeDuration(OMConfigKeys.OZONE_DIR_DELETING_SERVICE_INTERVAL, 1000, TimeUnit.MILLISECONDS); // For testing fs operations with legacy buckets. conf.setBoolean(OMConfigKeys.OZONE_OM_ENABLE_FILESYSTEM_PATHS, true); + conf.setBoolean(OMConfigKeys.OZONE_OM_SNAPSHOT_RENAME_ALLOWED_KEY, true); clusterBuilder = MiniOzoneCluster.newBuilder(conf).setNumDatanodes(5); startCluster(); } diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOzoneManagerSnapshotAcl.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOzoneManagerSnapshotAcl.java index ae6cddba7cf7..4f4508ebdc7d 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOzoneManagerSnapshotAcl.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/snapshot/TestOzoneManagerSnapshotAcl.java @@ -51,6 +51,7 @@ import org.apache.hadoop.ozone.client.VolumeArgs; import org.apache.hadoop.ozone.client.io.OzoneOutputStream; import org.apache.hadoop.ozone.om.KeyManagerImpl; +import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.ozone.om.OMStorage; import org.apache.hadoop.ozone.om.OmSnapshotManager; import org.apache.hadoop.ozone.om.OzoneManager; @@ -111,6 +112,7 @@ public static void init() throws Exception { conf.setBoolean(OZONE_TEST_AUTHORIZATION_ENABLED, true); conf.setBoolean(OZONE_ACL_ENABLED, true); conf.set(OZONE_ACL_AUTHORIZER_CLASS, OZONE_ACL_AUTHORIZER_CLASS_NATIVE); + conf.setBoolean(OMConfigKeys.OZONE_OM_SNAPSHOT_RENAME_ALLOWED_KEY, true); final String omServiceId = "om-service-test-1" + RandomStringUtils.secure().nextNumeric(32); diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotRenameRequest.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotRenameRequest.java index 2d9bd5c21ab1..2d0a0cf4bfdc 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotRenameRequest.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/snapshot/OMSnapshotRenameRequest.java @@ -17,6 +17,9 @@ package org.apache.hadoop.ozone.om.request.snapshot; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_RENAME_ALLOWED_DEFAULT; +import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_RENAME_ALLOWED_KEY; +import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.FEATURE_NOT_ENABLED; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.FILE_ALREADY_EXISTS; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.FILE_NOT_FOUND; import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.LeveledResource.BUCKET_LOCK; @@ -69,6 +72,13 @@ public OMSnapshotRenameRequest(OMRequest omRequest) { public OMRequest preExecute(OzoneManager ozoneManager) throws IOException { final OMRequest omRequest = super.preExecute(ozoneManager); + if (!ozoneManager.getConfiguration().getBoolean( + OZONE_OM_SNAPSHOT_RENAME_ALLOWED_KEY, + OZONE_OM_SNAPSHOT_RENAME_ALLOWED_DEFAULT)) { + throw new OMException("Ozone snapshot rename feature is not allowed per Ozone Manager server config", + FEATURE_NOT_ENABLED); + } + final RenameSnapshotRequest renameSnapshotRequest = omRequest.getRenameSnapshotRequest(); diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotRenameRequest.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotRenameRequest.java index 87de986a2a12..7b67b753e55e 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotRenameRequest.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/snapshot/TestOMSnapshotRenameRequest.java @@ -18,6 +18,7 @@ package org.apache.hadoop.ozone.om.request.snapshot; import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.THREE; +import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.FEATURE_NOT_ENABLED; import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.SnapshotStatus.SNAPSHOT_ACTIVE; import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.getFromProtobuf; import static org.apache.hadoop.ozone.om.helpers.SnapshotInfo.getTableKey; @@ -26,6 +27,7 @@ import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status.OK; import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type.RenameSnapshot; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -38,6 +40,7 @@ import org.apache.hadoop.hdds.client.RatisReplicationConfig; import org.apache.hadoop.hdds.utils.db.cache.CacheKey; import org.apache.hadoop.hdds.utils.db.cache.CacheValue; +import org.apache.hadoop.ozone.om.OMConfigKeys; import org.apache.hadoop.ozone.om.OzoneManager; import org.apache.hadoop.ozone.om.ResolvedBucket; import org.apache.hadoop.ozone.om.exceptions.OMException; @@ -67,6 +70,8 @@ public class TestOMSnapshotRenameRequest extends TestSnapshotRequestAndResponse public void setup() throws Exception { snapshotName1 = UUID.randomUUID().toString(); snapshotName2 = UUID.randomUUID().toString(); + getOzoneManager().getConfiguration().setBoolean( + OMConfigKeys.OZONE_OM_SNAPSHOT_RENAME_ALLOWED_KEY, true); } @ValueSource(strings = { @@ -87,6 +92,21 @@ public void testPreExecute(String toSnapshotName) throws Exception { doPreExecute(omRequest); } + @Test + public void testPreExecuteFailsWhenSnapshotRenameNotAllowed() { + assertFalse(OMConfigKeys.OZONE_OM_SNAPSHOT_RENAME_ALLOWED_DEFAULT); + getOzoneManager().getConfiguration().unset( + OMConfigKeys.OZONE_OM_SNAPSHOT_RENAME_ALLOWED_KEY); + + OzoneManagerProtocolProtos.OMRequest omRequest = renameSnapshotRequest( + getVolumeName(), getBucketName(), snapshotName1, snapshotName2); + OMException omException = assertThrows(OMException.class, + () -> doPreExecute(omRequest)); + assertEquals(FEATURE_NOT_ENABLED, omException.getResult()); + assertEquals("Ozone snapshot rename feature is not allowed per Ozone Manager server config", + omException.getMessage()); + } + @ValueSource(strings = { // '-' is allowed. "9cdf0e8a-6946-41ad-a2d1-9eb724fab126",