Skip to content

Commit 510491a

Browse files
authored
Fixed a bug that caused failures when deleting empty directories (Azure#26674)
* Fixed a bug that caused failures when deleting empty directories * Updated recordings * Updated more recordings
1 parent 3fa874d commit 510491a

File tree

201 files changed

+15271
-22590
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

201 files changed

+15271
-22590
lines changed

sdk/storage/azure-storage-blob-nio/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
### Breaking Changes
88

99
### Bugs Fixed
10+
- Fixed a bug that would prevent deleting an empty directory in the case where one directory name was a prefix of the other.
1011

1112
### Other Changes
1213

sdk/storage/azure-storage-blob-nio/src/main/java/com/azure/storage/blob/nio/AzureResource.java

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.azure.storage.blob.models.BlobHttpHeaders;
1111
import com.azure.storage.blob.models.BlobItem;
1212
import com.azure.storage.blob.models.BlobListDetails;
13+
import com.azure.storage.blob.models.BlobProperties;
1314
import com.azure.storage.blob.models.BlobRequestConditions;
1415
import com.azure.storage.blob.models.BlobStorageException;
1516
import com.azure.storage.blob.models.ListBlobsOptions;
@@ -93,43 +94,44 @@ DirectoryStatus checkDirStatus() throws IOException {
9394
}
9495
BlobContainerClient containerClient = this.getContainerClient();
9596

96-
// Two blobs will give us all the info we need (see below).
97+
/*
98+
* Do a get properties first on the directory name. This will determine if it is concrete&&exists or is either
99+
* virtual or doesn't exist.
100+
*/
101+
BlobProperties props = null;
102+
boolean exists = false;
103+
try {
104+
props = this.getBlobClient().getProperties();
105+
exists = true;
106+
} catch (BlobStorageException e) {
107+
if (e.getStatusCode() != 404) {
108+
throw LoggingUtility.logError(logger, new IOException(e));
109+
}
110+
}
111+
112+
// Check if the resource is a file or directory before listing
113+
if (exists && !props.getMetadata().containsKey(AzureResource.DIR_METADATA_MARKER)) {
114+
return DirectoryStatus.NOT_A_DIRECTORY;
115+
}
116+
117+
// List on the directory name + '/' so that we only get things under the directory if any
97118
ListBlobsOptions listOptions = new ListBlobsOptions().setMaxResultsPerPage(2)
98-
.setPrefix(this.blobClient.getBlobName())
119+
.setPrefix(this.blobClient.getBlobName() + AzureFileSystem.PATH_SEPARATOR)
99120
.setDetails(new BlobListDetails().setRetrieveMetadata(true));
100121

101122
/*
102-
Do a list on prefix.
103-
Zero elements means no virtual dir. Does not exist.
104-
One element that matches this dir means empty.
105-
One element that doesn't match this dir or more than one element. Not empty.
106-
One element that matches the name but does not have a directory marker means the resource is not a directory.
107-
108-
Note that blob names that match the prefix exactly are returned in listing operations.
123+
* If listing returns anything, then it is not empty. If listing returns nothing and exists() was true, then it's
124+
* empty Else it does not exist
109125
*/
110126
try {
111127
Iterator<BlobItem> blobIterator = containerClient.listBlobsByHierarchy(AzureFileSystem.PATH_SEPARATOR,
112128
listOptions, null).iterator();
113-
if (!blobIterator.hasNext()) { // Nothing there
114-
return DirectoryStatus.DOES_NOT_EXIST;
129+
if (blobIterator.hasNext()) {
130+
return DirectoryStatus.NOT_EMPTY;
131+
} else if (exists) {
132+
return DirectoryStatus.EMPTY;
115133
} else {
116-
BlobItem item = blobIterator.next();
117-
if (!item.getName().equals(this.blobClient.getBlobName())) {
118-
/*
119-
Names do not match. Must be a virtual dir with one item. e.g. blob with name "foo/bar" means dir
120-
"foo" exists.
121-
*/
122-
return DirectoryStatus.NOT_EMPTY;
123-
}
124-
// Metadata marker
125-
if (item.getMetadata() != null && item.getMetadata().containsKey(DIR_METADATA_MARKER)) {
126-
if (blobIterator.hasNext()) { // More than one item with dir path as prefix. Must be a dir.
127-
return DirectoryStatus.NOT_EMPTY;
128-
} else {
129-
return DirectoryStatus.EMPTY;
130-
}
131-
}
132-
return DirectoryStatus.NOT_A_DIRECTORY; // There is a file (not a directory) at this location.
134+
return DirectoryStatus.DOES_NOT_EXIST;
133135
}
134136
} catch (BlobStorageException e) {
135137
throw LoggingUtility.logError(logger, new IOException(e));

sdk/storage/azure-storage-blob-nio/src/test/java/com/azure/storage/blob/nio/AzureFileSystemProviderTest.groovy

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1481,11 +1481,15 @@ class AzureFileSystemProviderTest extends APISpec {
14811481

14821482
def "CheckAccess IOException"() {
14831483
setup:
1484-
config = initializeConfigMap(new CheckAccessIoExceptionPolicy())
14851484
def fs = createFS(config)
14861485
def path = fs.getPath(generateBlobName())
14871486
def os = fs.provider().newOutputStream(path)
14881487
os.close()
1488+
fs.close()
1489+
1490+
config = initializeConfigMap(new CheckAccessIoExceptionPolicy())
1491+
fs = createFS(config)
1492+
path = fs.getPath(path.toString())
14891493

14901494
when:
14911495
fs.provider().checkAccess(path)

sdk/storage/azure-storage-blob-nio/src/test/java/com/azure/storage/blob/nio/AzureResourceTest.groovy

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ class AzureResourceTest extends APISpec {
105105
DirectoryStatus.NOT_EMPTY | false
106106
}
107107

108-
@Unroll
109108
def "Directory status files with same prefix"() {
110109
setup:
111110
def fs = createFS(config)
@@ -121,6 +120,40 @@ class AzureResourceTest extends APISpec {
121120
new AzureResource(path2).checkDirStatus() == DirectoryStatus.NOT_A_DIRECTORY
122121
}
123122

123+
def "Directory status directories with same prefix"() {
124+
setup: "Create two folders where one is a prefix of the others"
125+
def fs = createFS(config)
126+
def pathName = generateBlobName()
127+
def pathName2 = pathName + '2'
128+
Files.createDirectory(fs.getPath(pathName))
129+
Files.createDirectory(fs.getPath(pathName2))
130+
131+
expect: "Both should be empty"
132+
new AzureResource(fs.getPath(pathName)).checkDirStatus() == DirectoryStatus.EMPTY
133+
new AzureResource(fs.getPath(pathName2)).checkDirStatus() == DirectoryStatus.EMPTY
134+
}
135+
136+
def "Directory status files between prefix and child"() {
137+
setup:
138+
def fs = createFS(config)
139+
def dirPath = fs.getPath(generateBlobName())
140+
def childPath = fs.getPath(dirPath.toString(), generateBlobName())
141+
/*
142+
Under an old listing scheme, it was possible for a file with the same name as a directory but with a trailing
143+
'+' to cut in between the parent and child in the listing as we did it and the listing may not register the
144+
child and erroneously return that the directory is empty. This ensures that listing is done in such a way as to
145+
account for this and return correctly that the directory is not empty.
146+
*/
147+
def middlePath = fs.getPath(dirPath.toString() + "+")
148+
149+
Files.createDirectory(dirPath)
150+
Files.createFile(childPath)
151+
Files.createFile(middlePath)
152+
153+
expect:
154+
new AzureResource(dirPath).checkDirStatus() == DirectoryStatus.NOT_EMPTY
155+
}
156+
124157
def "Parent dir exists false"() {
125158
setup:
126159
def fs = createFS(config)

sdk/storage/azure-storage-blob-nio/src/test/java/com/azure/storage/blob/nio/CompositeTest.groovy

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,17 @@ class CompositeTest extends APISpec {
9393
then:
9494
notThrown(IOException)
9595
}
96+
97+
def "Files delete empty directory"() {
98+
setup: "Create two folders where one is a prefix of the others"
99+
def fs = createFS(config)
100+
def pathName = generateBlobName()
101+
def pathName2 = pathName + '2'
102+
Files.createDirectory(fs.getPath(pathName))
103+
Files.createDirectory(fs.getPath(pathName2))
104+
105+
expect:
106+
// Delete the one that is a prefix to ensure the other one does not interfere
107+
Files.delete(fs.getPath(pathName))
108+
}
96109
}

0 commit comments

Comments
 (0)