|
20 | 20 | package org.elasticsearch.repositories.azure;
|
21 | 21 |
|
22 | 22 | import com.microsoft.azure.storage.AccessCondition;
|
| 23 | +import com.microsoft.azure.storage.BatchException; |
23 | 24 | import com.microsoft.azure.storage.CloudStorageAccount;
|
| 25 | +import com.microsoft.azure.storage.Constants; |
24 | 26 | import com.microsoft.azure.storage.OperationContext;
|
25 | 27 | import com.microsoft.azure.storage.RetryExponentialRetry;
|
26 | 28 | import com.microsoft.azure.storage.RetryPolicy;
|
27 | 29 | import com.microsoft.azure.storage.RetryPolicyFactory;
|
28 | 30 | import com.microsoft.azure.storage.StorageErrorCodeStrings;
|
29 | 31 | import com.microsoft.azure.storage.StorageException;
|
| 32 | +import com.microsoft.azure.storage.blob.BlobDeleteBatchOperation; |
30 | 33 | import com.microsoft.azure.storage.blob.BlobInputStream;
|
31 | 34 | import com.microsoft.azure.storage.blob.BlobListingDetails;
|
32 | 35 | import com.microsoft.azure.storage.blob.BlobProperties;
|
|
42 | 45 | import org.apache.logging.log4j.LogManager;
|
43 | 46 | import org.apache.logging.log4j.Logger;
|
44 | 47 | import org.apache.logging.log4j.message.ParameterizedMessage;
|
45 |
| -import org.elasticsearch.action.support.PlainActionFuture; |
46 | 48 | import org.elasticsearch.common.blobstore.BlobMetaData;
|
47 | 49 | import org.elasticsearch.common.blobstore.BlobPath;
|
48 | 50 | import org.elasticsearch.common.blobstore.DeleteResult;
|
|
53 | 55 | import org.elasticsearch.common.settings.SettingsException;
|
54 | 56 | import org.elasticsearch.common.unit.ByteSizeUnit;
|
55 | 57 | import org.elasticsearch.common.unit.ByteSizeValue;
|
56 |
| -import org.elasticsearch.common.util.concurrent.AbstractRunnable; |
57 | 58 |
|
58 | 59 | import java.io.IOException;
|
59 | 60 | import java.io.InputStream;
|
|
67 | 68 | import java.util.Collections;
|
68 | 69 | import java.util.EnumSet;
|
69 | 70 | import java.util.HashSet;
|
| 71 | +import java.util.Iterator; |
| 72 | +import java.util.List; |
70 | 73 | import java.util.Map;
|
71 | 74 | import java.util.Set;
|
72 |
| -import java.util.concurrent.Executor; |
73 | 75 | import java.util.concurrent.atomic.AtomicLong;
|
74 | 76 | import java.util.function.Supplier;
|
75 | 77 |
|
@@ -188,72 +190,61 @@ public boolean blobExists(String account, String container, String blob) throws
|
188 | 190 | });
|
189 | 191 | }
|
190 | 192 |
|
191 |
| - public void deleteBlob(String account, String container, String blob) throws URISyntaxException, StorageException { |
| 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)); |
192 | 196 | final Tuple<CloudBlobClient, Supplier<OperationContext>> client = client(account);
|
193 | 197 | // Container name must be lower case.
|
194 | 198 | final CloudBlobContainer blobContainer = client.v1().getContainerReference(container);
|
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 |
| - }); |
| 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 | + } |
201 | 220 | }
|
202 | 221 |
|
203 |
| - DeleteResult deleteBlobDirectory(String account, String container, String path, Executor executor) |
| 222 | + DeleteResult deleteBlobDirectory(String account, String container, String path) |
204 | 223 | throws URISyntaxException, StorageException, IOException {
|
205 | 224 | final Tuple<CloudBlobClient, Supplier<OperationContext>> client = client(account);
|
206 | 225 | 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(); |
210 | 226 | final AtomicLong blobsDeleted = new AtomicLong();
|
211 | 227 | final AtomicLong bytesDeleted = new AtomicLong();
|
| 228 | + final List<String> blobsToDelete = new ArrayList<>(); |
212 | 229 | SocketAccess.doPrivilegedVoidException(() -> {
|
213 |
| - for (final ListBlobItem blobItem : blobContainer.listBlobs(path, true)) { |
| 230 | + for (ListBlobItem blobItem : blobContainer.listBlobs(path, true)) { |
214 | 231 | // uri.getPath is of the form /container/keyPath.* and we want to strip off the /container/
|
215 | 232 | // this requires 1 + container.length() + 1, with each 1 corresponding to one of the /
|
216 | 233 | final String blobPath = blobItem.getUri().getPath().substring(1 + container.length() + 1);
|
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 |
| - }); |
| 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 | + } |
246 | 245 | }
|
247 | 246 | });
|
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 |
| - } |
| 247 | + deleteBlobsIgnoringIfNotExists(account, container, blobsToDelete); |
257 | 248 | return new DeleteResult(blobsDeleted.get(), bytesDeleted.get());
|
258 | 249 | }
|
259 | 250 |
|
|
0 commit comments