diff --git a/hadoop-ozone/dist/src/main/smoketest/basic/links.robot b/hadoop-ozone/dist/src/main/smoketest/basic/links.robot index 41da993bae14..fcd395042098 100644 --- a/hadoop-ozone/dist/src/main/smoketest/basic/links.robot +++ b/hadoop-ozone/dist/src/main/smoketest/basic/links.robot @@ -93,6 +93,20 @@ Link to non-existent bucket Execute ozone sh bucket link ${source}/no-such-bucket ${target}/dangling-link ${result} = Execute And Ignore Error ozone sh key list ${target}/dangling-link Should Contain ${result} BUCKET_NOT_FOUND + Should Contain ${result} Cannot follow bucket link + +Link to non-existent source volume + Execute ozone sh bucket link no-such-volume/no-such-bucket ${target}/dangling-missing-vol + ${result} = Execute And Ignore Error ozone sh key list ${target}/dangling-missing-vol + Should Contain ${result} BUCKET_NOT_FOUND + Should Contain ${result} Cannot follow bucket link + +Link chain to broken source + Execute ozone sh bucket link no-such-volume/no-such-bucket ${target}/link-bucket2 + Execute ozone sh bucket link ${target}/link-bucket2 ${target}/link-bucket1 + ${result} = Execute And Ignore Error ozone sh key list ${target}/link-bucket1 + Should Contain ${result} BUCKET_NOT_FOUND + Should Contain ${result} Cannot follow bucket link Key create passthrough Ozone Shell Batch bucket link ${source}/bucket1 ${target}/link1 diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java index 9b22579ec091..97bbb26a21d5 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java @@ -95,6 +95,7 @@ import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SERVER_DEFAULT_REPLICATION_TYPE_KEY; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_SNAPSHOT_SST_FILTERING_SERVICE_INTERVAL; import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_THREAD_NUMBER_DIR_DELETION; +import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.BUCKET_NOT_FOUND; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.DETECTED_LOOP_IN_BUCKET_LINKS; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.FEATURE_NOT_ENABLED; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INTERNAL_ERROR; @@ -104,6 +105,7 @@ import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.NOT_SUPPORTED_OPERATION; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.PERMISSION_DENIED; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.TOKEN_ERROR_OTHER; +import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_NOT_FOUND; import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.LeveledResource.BUCKET_LOCK; import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.LeveledResource.VOLUME_LOCK; import static org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServer.RaftServerStatus.LEADER_AND_NOT_READY; @@ -5494,6 +5496,13 @@ private OmBucketInfo resolveBucketLink( if (allowDanglingBuckets) { return null; } + + if (!visited.isEmpty() + && (e.getResult() == VOLUME_NOT_FOUND || e.getResult() == BUCKET_NOT_FOUND)) { + throw new OMException( + "Cannot follow bucket link: linked source bucket does not exist", + BUCKET_NOT_FOUND); + } throw e; } if (!info.isLink()) { diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestBucketManagerImpl.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestBucketManagerImpl.java index 6252836b57de..08dcc3135603 100644 --- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestBucketManagerImpl.java +++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestBucketManagerImpl.java @@ -30,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; @@ -44,6 +45,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.apache.commons.lang3.tuple.Pair; import org.apache.hadoop.crypto.key.KeyProvider; import org.apache.hadoop.crypto.key.KeyProviderCryptoExtension; import org.apache.hadoop.hdds.client.DefaultReplicationConfig; @@ -660,6 +662,84 @@ private static void denySourceRead(OmMetadataReader metadataReader, String sourc }).when(metadataReader).checkAcls(any(), any(), any(), any(), any(), any()); } + @Test + void testResolveBucketLinkMissingSourceVolume() throws Exception { + String targetVolume = volumeName(); + String missingSourceVolume = volumeName(); + OmBucketInfo danglingLink = OmBucketInfo.newBuilder() + .setVolumeName(targetVolume) + .setBucketName("dangling-link") + .setSourceVolume(missingSourceVolume) + .setSourceBucket("any-bucket") + .build(); + BucketManager bucketManager = mock(BucketManager.class); + when(bucketManager.getBucketInfo(targetVolume, "dangling-link")).thenReturn(danglingLink); + when(bucketManager.getBucketInfo(missingSourceVolume, "any-bucket")) + .thenThrow(new OMException("Volume doesn't exist", ResultCodes.VOLUME_NOT_FOUND)); + OzoneManager omSpy = spy(omTestManagers.getOzoneManager()); + HddsWhiteboxTestUtils.setInternalState(omSpy, "bucketManager", bucketManager); + when(omSpy.getAclsEnabled()).thenReturn(false); + + OMException omEx = assertThrows(OMException.class, + () -> omSpy.resolveBucketLink(Pair.of(targetVolume, "dangling-link"))); + assertEquals(ResultCodes.BUCKET_NOT_FOUND, omEx.getResult()); + assertThat(omEx.getMessage()).contains("Cannot follow bucket link"); + } + + @Test + void testResolveBucketLinkMissingSourceBucket() throws Exception { + String targetVolume = volumeName(); + String sourceVolume = volumeName(); + OmBucketInfo danglingLink = OmBucketInfo.newBuilder() + .setVolumeName(targetVolume) + .setBucketName("dangling-link-bucket") + .setSourceVolume(sourceVolume) + .setSourceBucket("no-such-bucket") + .build(); + BucketManager bucketManager = mock(BucketManager.class); + when(bucketManager.getBucketInfo(targetVolume, "dangling-link-bucket")).thenReturn(danglingLink); + when(bucketManager.getBucketInfo(sourceVolume, "no-such-bucket")) + .thenThrow(new OMException("Bucket not found", ResultCodes.BUCKET_NOT_FOUND)); + OzoneManager omSpy = spy(omTestManagers.getOzoneManager()); + HddsWhiteboxTestUtils.setInternalState(omSpy, "bucketManager", bucketManager); + when(omSpy.getAclsEnabled()).thenReturn(false); + + OMException omEx = assertThrows(OMException.class, + () -> omSpy.resolveBucketLink(Pair.of(targetVolume, "dangling-link-bucket"))); + assertEquals(ResultCodes.BUCKET_NOT_FOUND, omEx.getResult()); + assertThat(omEx.getMessage()).contains("Cannot follow bucket link"); + } + + @Test + void testListKeysOnLinkWithMissingSourceVolume() throws Exception { + String targetVolume = volumeName(); + String missingSourceVolume = volumeName(); + OmBucketInfo danglingLink = OmBucketInfo.newBuilder() + .setVolumeName(targetVolume) + .setBucketName("dangling-link-list") + .setSourceVolume(missingSourceVolume) + .setSourceBucket("any-bucket") + .build(); + BucketManager bucketManager = mock(BucketManager.class); + when(bucketManager.getBucketInfo(targetVolume, "dangling-link-list")).thenReturn(danglingLink); + when(bucketManager.getBucketInfo(missingSourceVolume, "any-bucket")) + .thenThrow(new OMException("Volume doesn't exist", ResultCodes.VOLUME_NOT_FOUND)); + OzoneManager om = omTestManagers.getOzoneManager(); + OzoneManager omSpy = spy(om); + HddsWhiteboxTestUtils.setInternalState(omSpy, "bucketManager", bucketManager); + when(omSpy.getAclsEnabled()).thenReturn(false); + OmMetadataReader metadataReader = (OmMetadataReader) HddsWhiteboxTestUtils.getInternalState(om, + "omMetadataReader"); + HddsWhiteboxTestUtils.setInternalState(metadataReader, "ozoneManager", omSpy); + + OMException omEx = assertThrows(OMException.class, + () -> omSpy.listKeys(targetVolume, "dangling-link-list", null, null, 100)); + assertEquals(ResultCodes.BUCKET_NOT_FOUND, omEx.getResult()); + assertThat(omEx.getMessage()).contains("Cannot follow bucket link"); + verify(bucketManager).getBucketInfo(eq(targetVolume), eq("dangling-link-list")); + verify(bucketManager).getBucketInfo(eq(missingSourceVolume), eq("any-bucket")); + } + private OzoneManager createAclEnabledOmSpy(BucketManager bucketManager, OmMetadataReader metadataReader) { OzoneManager omSpy = spy(omTestManagers.getOzoneManager()); HddsWhiteboxTestUtils.setInternalState(omSpy, "bucketManager", bucketManager);