Skip to content

Commit 9024bcc

Browse files
author
Liudmila Molkova
authored
upload failures (Azure#34277)
1 parent 8136061 commit 9024bcc

File tree

6 files changed

+189
-7
lines changed

6 files changed

+189
-7
lines changed

sdk/containerregistry/azure-containers-containerregistry/TROUBLESHOOTING.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,35 @@ Refer to [Troubleshoot network issues with registry](https://docs.microsoft.com/
145145
```text
146146
AcrErrorsException: Status code 403, "{"errors":[{"code":"DENIED","message":"client with IP <> is not allowed access. Refer https://aka.ms/acr/firewall to grant access."}]}
147147
```
148-
148+
149+
## Service errors
150+
151+
When working with `ContainerRegistryContentClient` and `ContainerRegistryAsyncContentClient` you may get an `HttpResponseException` exception with
152+
message containing additional information and [Docker error code](https://docs.docker.com/registry/spec/api/#errors-2).
153+
154+
### Getting BLOB_UPLOAD_INVALID
155+
156+
In rare cases, transient error (such as connection reset) can happen during blob upload which may lead to `ResourceNotFoundException` being thrown with message similar to
157+
`{"errors":[{"code":"BLOB_UPLOAD_INVALID","message":"blob upload invalid"}]}` resulting in a failed upload. In this case upload should to be restarted from the beginning.
158+
159+
The following code snippet shows how to access detailed error information:
160+
```java com.azure.containers.containerregistry.uploadBlobErrorHandling
161+
BinaryData configContent = BinaryData.fromObject(Collections.singletonMap("hello", "world"));
162+
163+
try {
164+
UploadRegistryBlobResult uploadResult = contentClient.uploadBlob(configContent);
165+
System.out.printf("Uploaded blob: digest - '%s', size - %s\n", uploadResult.getDigest(),
166+
uploadResult.getSizeInBytes());
167+
} catch (HttpResponseException ex) {
168+
if (ex.getCause() instanceof AcrErrorsException) {
169+
AcrErrorsException acrErrors = (AcrErrorsException) ex.getCause();
170+
for (AcrErrorInfo info : acrErrors.getValue().getErrors()) {
171+
System.out.printf("Uploaded blob failed: code '%s'\n", info.getCode());
172+
}
173+
}
174+
}
175+
```
176+
149177
## Dependency Conflicts
150178

151179
If you see `NoSuchMethodError` or `NoClassDefFoundError` during your application runtime, this is due to a

sdk/containerregistry/azure-containers-containerregistry/src/main/java/com/azure/containers/containerregistry/ContainerRegistryContentAsyncClient.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,33 @@ public Mono<Response<SetManifestResult>> setManifestWithResponse(SetManifestOpti
182182
* </pre>
183183
* <!-- end com.azure.containers.containerregistry.uploadBlobAsync -->
184184
*
185+
* <!-- src_embed com.azure.containers.containerregistry.uploadStreamAsync -->
186+
* <pre>
187+
* Flux.using&#40;
188+
* &#40;&#41; -&gt; new FileInputStream&#40;&quot;artifact.tar.gz&quot;&#41;,
189+
* fileStream -&gt; contentClient.uploadBlob&#40;FluxUtil.toFluxByteBuffer&#40;fileStream, CHUNK_SIZE&#41;&#41;,
190+
* this::closeStream&#41;
191+
* .subscribe&#40;uploadResult -&gt;
192+
* System.out.printf&#40;&quot;Uploaded blob: digest - '%s', size - %s&#92;n&quot;,
193+
* uploadResult.getDigest&#40;&#41;, uploadResult.getSizeInBytes&#40;&#41;&#41;&#41;;
194+
* </pre>
195+
* <!-- end com.azure.containers.containerregistry.uploadStreamAsync -->
196+
*
197+
* <!-- src_embed com.azure.containers.containerregistry.uploadBlobAsyncErrorHandling -->
198+
* <pre>
199+
* layerContent
200+
* .flatMap&#40;content -&gt; contentClient.uploadBlob&#40;content&#41;&#41;
201+
* .doOnError&#40;HttpResponseException.class, &#40;ex&#41; -&gt; &#123;
202+
* if &#40;ex.getCause&#40;&#41; instanceof AcrErrorsException&#41; &#123;
203+
* AcrErrorsException acrErrors = &#40;AcrErrorsException&#41; ex.getCause&#40;&#41;;
204+
* for &#40;AcrErrorInfo info : acrErrors.getValue&#40;&#41;.getErrors&#40;&#41;&#41; &#123;
205+
* System.out.printf&#40;&quot;Uploaded blob failed: code '%s'&#92;n&quot;, info.getCode&#40;&#41;&#41;;
206+
* &#125;
207+
* &#125;
208+
* &#125;&#41;;
209+
* </pre>
210+
* <!-- end com.azure.containers.containerregistry.uploadBlobAsyncErrorHandling -->
211+
*
185212
* Content is uploaded in chunks of up to 4MB size. Chunk size depends on passed {@link ByteBuffer}
186213
* sizes. Buffers that are bigger than 4MB are broken down into smaller chunks, but small buffers are not aggregated.
187214
* To decrease number of chunks for big content, use buffers of 4MB size.

sdk/containerregistry/azure-containers-containerregistry/src/main/java/com/azure/containers/containerregistry/ContainerRegistryContentClient.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,25 @@ public Response<SetManifestResult> setManifestWithResponse(SetManifestOptions op
175175
* </pre>
176176
* <!-- end com.azure.containers.containerregistry.uploadBlob -->
177177
*
178+
* <!-- src_embed com.azure.containers.containerregistry.uploadBlobErrorHandling -->
179+
* <pre>
180+
* BinaryData configContent = BinaryData.fromObject&#40;Collections.singletonMap&#40;&quot;hello&quot;, &quot;world&quot;&#41;&#41;;
181+
*
182+
* try &#123;
183+
* UploadRegistryBlobResult uploadResult = contentClient.uploadBlob&#40;configContent&#41;;
184+
* System.out.printf&#40;&quot;Uploaded blob: digest - '%s', size - %s&#92;n&quot;, uploadResult.getDigest&#40;&#41;,
185+
* uploadResult.getSizeInBytes&#40;&#41;&#41;;
186+
* &#125; catch &#40;HttpResponseException ex&#41; &#123;
187+
* if &#40;ex.getCause&#40;&#41; instanceof AcrErrorsException&#41; &#123;
188+
* AcrErrorsException acrErrors = &#40;AcrErrorsException&#41; ex.getCause&#40;&#41;;
189+
* for &#40;AcrErrorInfo info : acrErrors.getValue&#40;&#41;.getErrors&#40;&#41;&#41; &#123;
190+
* System.out.printf&#40;&quot;Uploaded blob failed: code '%s'&#92;n&quot;, info.getCode&#40;&#41;&#41;;
191+
* &#125;
192+
* &#125;
193+
* &#125;
194+
* </pre>
195+
* <!-- end com.azure.containers.containerregistry.uploadBlobErrorHandling -->
196+
*
178197
* @param content The blob content. The content may be loaded into memory depending on how {@link BinaryData} is created.
179198
* @return The upload response.
180199
* @throws ClientAuthenticationException thrown if the client's credentials do not have access to modify the namespace.

sdk/containerregistry/azure-containers-containerregistry/src/samples/java/com/azure/containers/containerregistry/UploadImage.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
package com.azure.containers.containerregistry;
55

6+
import com.azure.containers.containerregistry.implementation.models.AcrErrorInfo;
7+
import com.azure.containers.containerregistry.implementation.models.AcrErrorsException;
68
import com.azure.containers.containerregistry.models.ManifestMediaType;
79
import com.azure.containers.containerregistry.models.OciDescriptor;
810
import com.azure.containers.containerregistry.models.OciImageManifest;
911
import com.azure.containers.containerregistry.models.UploadRegistryBlobResult;
1012
import com.azure.containers.containerregistry.models.SetManifestOptions;
1113
import com.azure.containers.containerregistry.models.SetManifestResult;
14+
import com.azure.core.exception.HttpResponseException;
1215
import com.azure.core.http.rest.Response;
1316
import com.azure.core.util.BinaryData;
1417
import com.azure.core.util.Context;
@@ -78,6 +81,31 @@ private void uploadBlobBinaryData() {
7881
// END: com.azure.containers.containerregistry.uploadBlob
7982
}
8083

84+
private void uploadBlobFails() {
85+
ContainerRegistryContentClient contentClient = new ContainerRegistryContentClientBuilder()
86+
.endpoint(ENDPOINT)
87+
.repositoryName(REPOSITORY)
88+
.credential(CREDENTIAL)
89+
.buildClient();
90+
91+
// BEGIN: com.azure.containers.containerregistry.uploadBlobErrorHandling
92+
BinaryData configContent = BinaryData.fromObject(Collections.singletonMap("hello", "world"));
93+
94+
try {
95+
UploadRegistryBlobResult uploadResult = contentClient.uploadBlob(configContent);
96+
System.out.printf("Uploaded blob: digest - '%s', size - %s\n", uploadResult.getDigest(),
97+
uploadResult.getSizeInBytes());
98+
} catch (HttpResponseException ex) {
99+
if (ex.getCause() instanceof AcrErrorsException) {
100+
AcrErrorsException acrErrors = (AcrErrorsException) ex.getCause();
101+
for (AcrErrorInfo info : acrErrors.getValue().getErrors()) {
102+
System.out.printf("Uploaded blob failed: code '%s'\n", info.getCode());
103+
}
104+
}
105+
}
106+
// END: com.azure.containers.containerregistry.uploadBlobErrorHandling
107+
}
108+
81109
private void uploadStream() throws IOException {
82110
ContainerRegistryContentClient contentClient = new ContainerRegistryContentClientBuilder()
83111
.endpoint(ENDPOINT)

sdk/containerregistry/azure-containers-containerregistry/src/samples/java/com/azure/containers/containerregistry/UploadImageAsync.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33

44
package com.azure.containers.containerregistry;
55

6+
import com.azure.containers.containerregistry.implementation.models.AcrErrorInfo;
7+
import com.azure.containers.containerregistry.implementation.models.AcrErrorsException;
68
import com.azure.containers.containerregistry.models.ManifestMediaType;
79
import com.azure.containers.containerregistry.models.OciDescriptor;
810
import com.azure.containers.containerregistry.models.OciImageManifest;
911
import com.azure.containers.containerregistry.models.SetManifestOptions;
1012
import com.azure.containers.containerregistry.models.SetManifestResult;
13+
import com.azure.core.exception.HttpResponseException;
1114
import com.azure.core.util.BinaryData;
1215
import com.azure.core.util.FluxUtil;
1316
import com.azure.identity.DefaultAzureCredential;
@@ -200,4 +203,28 @@ private void closeStream(InputStream stream) {
200203
throw new RuntimeException(e);
201204
}
202205
}
206+
207+
private void uploadBlobFails() {
208+
ContainerRegistryContentAsyncClient contentClient = new ContainerRegistryContentClientBuilder()
209+
.endpoint(ENDPOINT)
210+
.repositoryName(REPOSITORY)
211+
.credential(CREDENTIAL)
212+
.buildAsyncClient();
213+
214+
long dataLength = 1024 * 1024 * 1024;
215+
Mono<BinaryData> layerContent = BinaryData.fromFlux(getData(dataLength), dataLength, false); // 1 GB
216+
217+
// BEGIN: com.azure.containers.containerregistry.uploadBlobAsyncErrorHandling
218+
layerContent
219+
.flatMap(content -> contentClient.uploadBlob(content))
220+
.doOnError(HttpResponseException.class, (ex) -> {
221+
if (ex.getCause() instanceof AcrErrorsException) {
222+
AcrErrorsException acrErrors = (AcrErrorsException) ex.getCause();
223+
for (AcrErrorInfo info : acrErrors.getValue().getErrors()) {
224+
System.out.printf("Uploaded blob failed: code '%s'\n", info.getCode());
225+
}
226+
}
227+
});
228+
// END: com.azure.containers.containerregistry.uploadBlobAsyncErrorHandling
229+
}
203230
}

sdk/containerregistry/azure-containers-containerregistry/src/test/java/com/azure/containers/containerregistry/ContainerRegistryContentClientTests.java

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
package com.azure.containers.containerregistry;
55

66
import com.azure.containers.containerregistry.implementation.UtilsImpl;
7+
import com.azure.containers.containerregistry.implementation.models.AcrErrorsException;
78
import com.azure.containers.containerregistry.models.GetManifestResult;
89
import com.azure.containers.containerregistry.models.ManifestMediaType;
910
import com.azure.containers.containerregistry.models.OciImageManifest;
11+
import com.azure.core.exception.ResourceNotFoundException;
1012
import com.azure.core.exception.ServiceResponseException;
1113
import com.azure.core.http.HttpClient;
1214
import com.azure.core.http.HttpHeaderName;
@@ -49,6 +51,7 @@
4951
import java.util.Random;
5052
import java.util.concurrent.atomic.AtomicInteger;
5153
import java.util.concurrent.atomic.AtomicLong;
54+
import java.util.function.BiFunction;
5255
import java.util.function.Function;
5356
import java.util.function.Supplier;
5457

@@ -61,6 +64,7 @@
6164
import static com.azure.core.util.CoreUtils.bytesToHexString;
6265
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
6366
import static org.junit.jupiter.api.Assertions.assertEquals;
67+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
6468
import static org.junit.jupiter.api.Assertions.assertNotNull;
6569
import static org.junit.jupiter.api.Assertions.assertThrows;
6670
import static org.junit.jupiter.api.Assertions.fail;
@@ -350,6 +354,46 @@ public void uploadFromStream() throws IOException {
350354
}
351355
}
352356

357+
@SyncAsyncTest
358+
public void uploadFails() {
359+
Flux<ByteBuffer> flux = Flux.create(sink -> {
360+
sha256.update(CHUNK);
361+
sink.next(ByteBuffer.wrap(CHUNK));
362+
363+
sha256.update(CHUNK);
364+
sink.next(ByteBuffer.wrap(CHUNK));
365+
366+
sink.complete();
367+
});
368+
369+
BiFunction<HttpRequest, Integer, HttpResponse> onChunk = (r, c) -> {
370+
if (c == 3) {
371+
HttpHeaders responseHeaders = new HttpHeaders().add("Content-Type", String.valueOf("application/json"));
372+
String error = "{\"errors\":[{\"code\":\"BLOB_UPLOAD_INVALID\",\"message\":\"blob upload invalid\"}]}";
373+
return new MockHttpResponse(r, 404, responseHeaders, error.getBytes(StandardCharsets.UTF_8));
374+
}
375+
return null;
376+
};
377+
Supplier<String> calculateDigest = () -> "sha256:" + bytesToHexString(sha256.digest());
378+
BinaryData content = BinaryData.fromFlux(flux, (long) CHUNK_SIZE * 2, false).block();
379+
380+
ContainerRegistryContentClient client = createSyncClient(createUploadContentClient(calculateDigest, onChunk));
381+
ContainerRegistryContentAsyncClient asyncClient = createAsyncClient(createUploadContentClient(calculateDigest, onChunk));
382+
383+
ResourceNotFoundException ex = assertThrows(ResourceNotFoundException.class, () -> SyncAsyncExtension.execute(
384+
() -> client.uploadBlob(content),
385+
() -> asyncClient.uploadBlob(content)));
386+
387+
assertAcrException(ex, "BLOB_UPLOAD_INVALID");
388+
}
389+
390+
private void assertAcrException(Exception ex, String code) {
391+
assertInstanceOf(AcrErrorsException.class, ex.getCause());
392+
AcrErrorsException acrErrors = (AcrErrorsException) ex.getCause();
393+
assertEquals(1, acrErrors.getValue().getErrors().size());
394+
assertEquals(code, acrErrors.getValue().getErrors().get(0).getCode());
395+
}
396+
353397
@Test
354398
public void downloadToFile() throws IOException {
355399
File output = File.createTempFile("temp", "in");
@@ -410,17 +454,22 @@ public static HttpClient createClientManifests(BinaryData content, String digest
410454
});
411455
}
412456

413-
public static HttpClient createUploadContentClient(Supplier<String> calculateDigest) {
414-
AtomicInteger chunkNumber = new AtomicInteger();
457+
public static HttpClient createUploadContentClient(Supplier<String> calculateDigest, BiFunction<HttpRequest, Integer, HttpResponse> onChunk) {
458+
AtomicInteger callNumber = new AtomicInteger();
415459
return new MockHttpClient(request -> {
416-
String expectedReceivedLocation = String.valueOf(chunkNumber.getAndIncrement());
417-
HttpHeaders responseHeaders = new HttpHeaders().add("Location", String.valueOf(chunkNumber.get()));
460+
String expectedReceivedLocation = String.valueOf(callNumber.getAndIncrement());
461+
HttpHeaders responseHeaders = new HttpHeaders().add("Location", String.valueOf(callNumber.get()));
418462
if (request.getHttpMethod() == HttpMethod.POST) { // start upload
419-
assertEquals(0, chunkNumber.get() - 1);
463+
assertEquals(0, callNumber.get() - 1);
420464
return new MockHttpResponse(request, 202, responseHeaders);
421465
} else if (request.getHttpMethod() == HttpMethod.PATCH) { // upload chunk
422466
assertEquals("/" + expectedReceivedLocation, request.getUrl().getPath());
423-
return new MockHttpResponse(request, 202, responseHeaders);
467+
HttpResponse response = onChunk.apply(request, callNumber.get());
468+
if (response == null) {
469+
response = new MockHttpResponse(request, 202, responseHeaders);
470+
}
471+
472+
return response;
424473
} else if (request.getHttpMethod() == HttpMethod.PUT) { // complete upload
425474
String expectedDigest = calculateDigest.get();
426475
try {
@@ -438,6 +487,10 @@ public static HttpClient createUploadContentClient(Supplier<String> calculateDig
438487
});
439488
}
440489

490+
public static HttpClient createUploadContentClient(Supplier<String> calculateDigest) {
491+
return createUploadContentClient(calculateDigest, (r, c) -> null);
492+
}
493+
441494
private BinaryData getDataSync(int size, MessageDigest sha256) {
442495
return BinaryData.fromFlux(getDataAsync(size, CHUNK_SIZE, sha256)).block();
443496
}

0 commit comments

Comments
 (0)