|
20 | 20 | package org.elasticsearch.repositories.azure; |
21 | 21 |
|
22 | 22 | import com.microsoft.azure.storage.AccessCondition; |
23 | | -import com.microsoft.azure.storage.BatchException; |
24 | 23 | import com.microsoft.azure.storage.CloudStorageAccount; |
25 | | -import com.microsoft.azure.storage.Constants; |
26 | 24 | import com.microsoft.azure.storage.OperationContext; |
27 | 25 | import com.microsoft.azure.storage.RetryExponentialRetry; |
28 | 26 | import com.microsoft.azure.storage.RetryPolicy; |
29 | 27 | import com.microsoft.azure.storage.RetryPolicyFactory; |
30 | 28 | import com.microsoft.azure.storage.StorageErrorCodeStrings; |
31 | 29 | import com.microsoft.azure.storage.StorageException; |
32 | | -import com.microsoft.azure.storage.blob.BlobDeleteBatchOperation; |
33 | 30 | import com.microsoft.azure.storage.blob.BlobInputStream; |
34 | 31 | import com.microsoft.azure.storage.blob.BlobListingDetails; |
35 | 32 | import com.microsoft.azure.storage.blob.BlobProperties; |
|
45 | 42 | import org.apache.logging.log4j.LogManager; |
46 | 43 | import org.apache.logging.log4j.Logger; |
47 | 44 | import org.apache.logging.log4j.message.ParameterizedMessage; |
| 45 | +import org.elasticsearch.action.support.PlainActionFuture; |
48 | 46 | import org.elasticsearch.common.blobstore.BlobMetaData; |
49 | 47 | import org.elasticsearch.common.blobstore.BlobPath; |
50 | 48 | import org.elasticsearch.common.blobstore.DeleteResult; |
|
55 | 53 | import org.elasticsearch.common.settings.SettingsException; |
56 | 54 | import org.elasticsearch.common.unit.ByteSizeUnit; |
57 | 55 | import org.elasticsearch.common.unit.ByteSizeValue; |
| 56 | +import org.elasticsearch.common.util.concurrent.AbstractRunnable; |
58 | 57 |
|
59 | 58 | import java.io.IOException; |
60 | 59 | import java.io.InputStream; |
|
68 | 67 | import java.util.Collections; |
69 | 68 | import java.util.EnumSet; |
70 | 69 | import java.util.HashSet; |
71 | | -import java.util.Iterator; |
72 | | -import java.util.List; |
73 | 70 | import java.util.Map; |
74 | 71 | import java.util.Set; |
| 72 | +import java.util.concurrent.Executor; |
75 | 73 | import java.util.concurrent.atomic.AtomicLong; |
76 | 74 | import java.util.function.Supplier; |
77 | 75 |
|
@@ -190,61 +188,72 @@ public boolean blobExists(String account, String container, String blob) throws |
190 | 188 | }); |
191 | 189 | } |
192 | 190 |
|
193 | | - public void deleteBlobsIgnoringIfNotExists(String account, String container, Collection<String> blobs) |
194 | | - throws URISyntaxException, StorageException { |
195 | | - logger.trace(() -> new ParameterizedMessage("delete blobs for container [{}], blob [{}]", container, blobs)); |
| 191 | + public void deleteBlob(String account, String container, String blob) throws URISyntaxException, StorageException { |
196 | 192 | final Tuple<CloudBlobClient, Supplier<OperationContext>> client = client(account); |
197 | 193 | // Container name must be lower case. |
198 | 194 | final CloudBlobContainer blobContainer = client.v1().getContainerReference(container); |
199 | | - final Iterator<String> blobIterator = blobs.iterator(); |
200 | | - int currentBatchSize = 0; |
201 | | - while (blobIterator.hasNext()) { |
202 | | - final BlobDeleteBatchOperation batchDeleteOp = new BlobDeleteBatchOperation(); |
203 | | - do { |
204 | | - batchDeleteOp.addSubOperation(blobContainer.getBlockBlobReference(blobIterator.next()), |
205 | | - DeleteSnapshotsOption.NONE, null, null); |
206 | | - ++currentBatchSize; |
207 | | - } while (blobIterator.hasNext() && currentBatchSize < Constants.BATCH_MAX_REQUESTS); |
208 | | - currentBatchSize = 0; |
209 | | - try { |
210 | | - SocketAccess.doPrivilegedVoidException(() -> blobContainer.getServiceClient().executeBatch(batchDeleteOp)); |
211 | | - } catch (BatchException e) { |
212 | | - for (StorageException ex : e.getExceptions().values()) { |
213 | | - if (ex.getHttpStatusCode() != HttpURLConnection.HTTP_NOT_FOUND) { |
214 | | - logger.error("Batch exceptions [{}]", e.getExceptions()); |
215 | | - throw e; |
216 | | - } |
217 | | - } |
218 | | - } |
219 | | - } |
| 195 | + logger.trace(() -> new ParameterizedMessage("delete blob for container [{}], blob [{}]", container, blob)); |
| 196 | + SocketAccess.doPrivilegedVoidException(() -> { |
| 197 | + final CloudBlockBlob azureBlob = blobContainer.getBlockBlobReference(blob); |
| 198 | + logger.trace(() -> new ParameterizedMessage("container [{}]: blob [{}] found. removing.", container, blob)); |
| 199 | + azureBlob.delete(DeleteSnapshotsOption.NONE, null, null, client.v2().get()); |
| 200 | + }); |
220 | 201 | } |
221 | 202 |
|
222 | | - DeleteResult deleteBlobDirectory(String account, String container, String path) |
| 203 | + DeleteResult deleteBlobDirectory(String account, String container, String path, Executor executor) |
223 | 204 | throws URISyntaxException, StorageException, IOException { |
224 | 205 | final Tuple<CloudBlobClient, Supplier<OperationContext>> client = client(account); |
225 | 206 | final CloudBlobContainer blobContainer = client.v1().getContainerReference(container); |
| 207 | + final Collection<Exception> exceptions = Collections.synchronizedList(new ArrayList<>()); |
| 208 | + final AtomicLong outstanding = new AtomicLong(1L); |
| 209 | + final PlainActionFuture<Void> result = PlainActionFuture.newFuture(); |
226 | 210 | final AtomicLong blobsDeleted = new AtomicLong(); |
227 | 211 | final AtomicLong bytesDeleted = new AtomicLong(); |
228 | | - final List<String> blobsToDelete = new ArrayList<>(); |
229 | 212 | SocketAccess.doPrivilegedVoidException(() -> { |
230 | | - for (ListBlobItem blobItem : blobContainer.listBlobs(path, true)) { |
| 213 | + for (final ListBlobItem blobItem : blobContainer.listBlobs(path, true)) { |
231 | 214 | // uri.getPath is of the form /container/keyPath.* and we want to strip off the /container/ |
232 | 215 | // this requires 1 + container.length() + 1, with each 1 corresponding to one of the / |
233 | 216 | final String blobPath = blobItem.getUri().getPath().substring(1 + container.length() + 1); |
234 | | - final long len; |
235 | | - if (blobItem instanceof CloudBlob) { |
236 | | - len = ((CloudBlob) blobItem).getProperties().getLength(); |
237 | | - } else { |
238 | | - len = -1L; |
239 | | - } |
240 | | - blobsToDelete.add(blobPath); |
241 | | - blobsDeleted.incrementAndGet(); |
242 | | - if (len >= 0) { |
243 | | - bytesDeleted.addAndGet(len); |
244 | | - } |
| 217 | + outstanding.incrementAndGet(); |
| 218 | + executor.execute(new AbstractRunnable() { |
| 219 | + @Override |
| 220 | + protected void doRun() throws Exception { |
| 221 | + final long len; |
| 222 | + if (blobItem instanceof CloudBlob) { |
| 223 | + len = ((CloudBlob) blobItem).getProperties().getLength(); |
| 224 | + } else { |
| 225 | + len = -1L; |
| 226 | + } |
| 227 | + deleteBlob(account, container, blobPath); |
| 228 | + blobsDeleted.incrementAndGet(); |
| 229 | + if (len >= 0) { |
| 230 | + bytesDeleted.addAndGet(len); |
| 231 | + } |
| 232 | + } |
| 233 | + |
| 234 | + @Override |
| 235 | + public void onFailure(Exception e) { |
| 236 | + exceptions.add(e); |
| 237 | + } |
| 238 | + |
| 239 | + @Override |
| 240 | + public void onAfter() { |
| 241 | + if (outstanding.decrementAndGet() == 0) { |
| 242 | + result.onResponse(null); |
| 243 | + } |
| 244 | + } |
| 245 | + }); |
245 | 246 | } |
246 | 247 | }); |
247 | | - deleteBlobsIgnoringIfNotExists(account, container, blobsToDelete); |
| 248 | + if (outstanding.decrementAndGet() == 0) { |
| 249 | + result.onResponse(null); |
| 250 | + } |
| 251 | + result.actionGet(); |
| 252 | + if (exceptions.isEmpty() == false) { |
| 253 | + final IOException ex = new IOException("Deleting directory [" + path + "] failed"); |
| 254 | + exceptions.forEach(ex::addSuppressed); |
| 255 | + throw ex; |
| 256 | + } |
248 | 257 | return new DeleteResult(blobsDeleted.get(), bytesDeleted.get()); |
249 | 258 | } |
250 | 259 |
|
|
0 commit comments