Repository: googleapis/java-storage
Pull request #678 (2021-01-12 13:05:09)

deps: update dependency com.google.cloud:google-cloud-shared-dependencies to v0.17.1
[![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [com.google.cloud:google-cloud-shared-dependencies](https://togithub.com/googleapis/java-shared-dependencies) | patch | `0.17.0` -> `0.17.1` |

---

### Release Notes

<details>
<summary>googleapis/java-shared-dependencies</summary>

### [`v0.17.1`](https://togithub.com/googleapis/java-shared-dependencies/blob/master/CHANGELOG.md#&#8203;0171-httpswwwgithubcomgoogleapisjava-shared-dependenciescompare0170v0171-2021-01-12)

[Compare Source](https://togithub.com/googleapis/java-shared-dependencies/compare/v0.17.0...v0.17.1)

</details>

---

### Renovate configuration

:date: **Schedule**: At any time (no schedule defined).

:vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

:recycle: **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

:no_bell: **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-storage).

codecov[bot]: # [Codecov](https://codecov.io/gh/googleapis/java-storage/pull/678?src=pr&el=h1) Report
> Merging [#678](https://codecov.io/gh/googleapis/java-storage/pull/678?src=pr&el=desc) (accc9c5) into [master](https://codecov.io/gh/googleapis/java-storage/commit/ebb5fb29e7dc51699b1d522838f1106b42d79397?el=desc) (ebb5fb2) will **decrease** coverage by `0.15%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/googleapis/java-storage/pull/678/graphs/tree.svg?width=650&height=150&src=pr&token=ssm139YOdp)](https://codecov.io/gh/googleapis/java-storage/pull/678?src=pr&el=tree)

```diff
@@             Coverage Diff              @@
##             master     #678      +/-   ##
============================================
- Coverage     64.26%   64.10%   -0.16%     
+ Complexity      621      620       -1     
============================================
  Files            32       32              
  Lines          5322     5305      -17     
  Branches        521      488      -33     
============================================
- Hits           3420     3401      -19     
+ Misses         1740     1738       -2     
- Partials        162      166       +4     
```


| [Impacted Files](https://codecov.io/gh/googleapis/java-storage/pull/678?src=pr&el=tree) | Coverage Δ | Complexity Δ | |
|---|---|---|---|
| [...in/java/com/google/cloud/storage/StorageBatch.java](https://codecov.io/gh/googleapis/java-storage/pull/678/diff?src=pr&el=tree#diff-Z29vZ2xlLWNsb3VkLXN0b3JhZ2Uvc3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvU3RvcmFnZUJhdGNoLmphdmE=) | `88.00% <0.00%> (-4.00%)` | `13.00% <0.00%> (ø%)` | |
| [...gle/cloud/storage/testing/RemoteStorageHelper.java](https://codecov.io/gh/googleapis/java-storage/pull/678/diff?src=pr&el=tree#diff-Z29vZ2xlLWNsb3VkLXN0b3JhZ2Uvc3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvdGVzdGluZy9SZW1vdGVTdG9yYWdlSGVscGVyLmphdmE=) | `63.41% <0.00%> (-0.59%)` | `9.00% <0.00%> (ø%)` | |
| [...main/java/com/google/cloud/storage/BucketInfo.java](https://codecov.io/gh/googleapis/java-storage/pull/678/diff?src=pr&el=tree#diff-Z29vZ2xlLWNsb3VkLXN0b3JhZ2Uvc3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvQnVja2V0SW5mby5qYXZh) | `83.64% <0.00%> (-0.34%)` | `86.00% <0.00%> (ø%)` | |
| [...ain/java/com/google/cloud/storage/StorageImpl.java](https://codecov.io/gh/googleapis/java-storage/pull/678/diff?src=pr&el=tree#diff-Z29vZ2xlLWNsb3VkLXN0b3JhZ2Uvc3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvU3RvcmFnZUltcGwuamF2YQ==) | `81.18% <0.00%> (-0.14%)` | `138.00% <0.00%> (ø%)` | |
| [...e/src/main/java/com/google/cloud/storage/Blob.java](https://codecov.io/gh/googleapis/java-storage/pull/678/diff?src=pr&el=tree#diff-Z29vZ2xlLWNsb3VkLXN0b3JhZ2Uvc3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvQmxvYi5qYXZh) | `83.61% <0.00%> (-0.10%)` | `29.00% <0.00%> (-1.00%)` | |
| [...in/java/com/google/cloud/storage/PostPolicyV4.java](https://codecov.io/gh/googleapis/java-storage/pull/678/diff?src=pr&el=tree#diff-Z29vZ2xlLWNsb3VkLXN0b3JhZ2Uvc3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvUG9zdFBvbGljeVY0LmphdmE=) | `86.26% <0.00%> (-0.08%)` | `5.00% <0.00%> (ø%)` | |
| [...om/google/cloud/storage/spi/v1/HttpStorageRpc.java](https://codecov.io/gh/googleapis/java-storage/pull/678/diff?src=pr&el=tree#diff-Z29vZ2xlLWNsb3VkLXN0b3JhZ2Uvc3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2Uvc3BpL3YxL0h0dHBTdG9yYWdlUnBjLmphdmE=) | `1.54% <0.00%> (+<0.01%)` | `2.00% <0.00%> (ø%)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/googleapis/java-storage/pull/678?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/googleapis/java-storage/pull/678?src=pr&el=footer). Last update [ebb5fb2...accc9c5](https://codecov.io/gh/googleapis/java-storage/pull/678?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java
index 2e9c0a38..ca87933c 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java
@@ -120,6 +120,14 @@ class BlobWriteChannel extends BaseWriteChannel<StorageOptions, BlobInfo> {
                     // For completeness, this case is not possible because it would require retrying
                     // a 400 status code which is not allowed.
                     //
+                    // Case 7: remoteNextByteOffset==-1 && last == true
+                    // Upload is complete and retry occurred in the "last" chunk. Data sent was
+                    // received by the service.
+                    //
+                    // Case 8: remoteNextByteOffset==-1 && last == false
+                    // Upload was completed by another client because this retry did not occur
+                    // during the last chunk.
+                    //
                     // Get remote offset from API
                     long remoteNextByteOffset =
                         getOptions().getStorageRpcV1().getCurrentUploadOffset(getUploadId());
@@ -154,7 +162,8 @@ class BlobWriteChannel extends BaseWriteChannel<StorageOptions, BlobInfo> {
                       // Continue to next chunk
                       retrying = false;
                       return;
-                    } else {
+                    } else if (localNextByteOffset < remoteNextByteOffset
+                        && driftOffset > getChunkSize()) {
                       // Case 5
                       StringBuilder sb = new StringBuilder();
                       sb.append(
@@ -167,6 +176,13 @@ class BlobWriteChannel extends BaseWriteChannel<StorageOptions, BlobInfo> {
                       sb.append("remoteNextByteOffset: ").append(remoteNextByteOffset).append('\n');
                       sb.append("driftOffset: ").append(driftOffset).append("\n\n");
                       throw new StorageException(0, sb.toString());
+                    } else if (remoteNextByteOffset == -1 && last) {
+                      // Case 7
+                      retrying = false;
+                      return;
+                    } else if (remoteNextByteOffset == -1 && !last) {
+                      // Case 8
+                      throw new StorageException(0, "Resumable upload is already complete.");
                     }
                   }
                   // Request was successful and retrying state is now disabled.
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
index abdfa33c..37aff273 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
@@ -766,7 +766,7 @@ public class HttpStorageRpc implements StorageRpc {
         response = httpRequest.execute();
         int code = response.getStatusCode();
         if (code == 201 || code == 200) {
-          throw new StorageException(0, "Resumable upload is already complete.");
+          return -1;
         }
         StringBuilder sb = new StringBuilder();
         sb.append("Not sure what occurred. Here's debugging information:\n");
diff --git a/pom.xml b/pom.xml
index f9ac84c0..51864ccf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,7 +63,7 @@
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     <github.global.server>github</github.global.server>
     <site.installationModule>google-cloud-storage-parent</site.installationModule>
-    <google.cloud.shared-dependencies.version>0.17.0</google.cloud.shared-dependencies.version>
+    <google.cloud.shared-dependencies.version>0.17.1</google.cloud.shared-dependencies.version>
   </properties>
 
   <dependencyManagement>


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java
index b6d7bbf3..eefee772 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java
@@ -202,6 +202,64 @@ public class BlobWriteChannelTest {
     assertArrayEquals(buffer.array(), capturedBuffer.getValue());
   }
 
+  @Test
+  public void testWriteWithLastFlushRetryChunkButCompleted() throws IOException {
+    StorageException exception = new StorageException(new SocketException("Socket closed"));
+    ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
+    Capture<byte[]> capturedBuffer = Capture.newInstance();
+    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.writeWithResponse(
+                eq(UPLOAD_ID),
+                capture(capturedBuffer),
+                eq(0),
+                eq(0L),
+                eq(MIN_CHUNK_SIZE),
+                eq(true)))
+        .andThrow(exception);
+    expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L);
+    replay(storageRpcMock);
+    writer = new BlobWriteChannel(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
+    assertEquals(MIN_CHUNK_SIZE, writer.write(buffer));
+    writer.close();
+    assertFalse(writer.isRetrying());
+    assertFalse(writer.isOpen());
+    // Capture captures entire buffer of a chunk even when not completely used.
+    // Making assert selective up to the size of MIN_CHUNK_SIZE
+    assertArrayEquals(Arrays.copyOf(capturedBuffer.getValue(), MIN_CHUNK_SIZE), buffer.array());
+  }
+
+  @Test
+  public void testWriteWithFlushRetryChunkButCompletedByAnotherClient() throws IOException {
+    StorageException exception = new StorageException(new SocketException("Socket closed"));
+    StorageException completedException =
+        new StorageException(0, "Resumable upload is already complete.");
+    ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
+    Capture<byte[]> capturedBuffer = Capture.newInstance();
+    expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
+    expect(
+            storageRpcMock.writeWithResponse(
+                eq(UPLOAD_ID),
+                capture(capturedBuffer),
+                eq(0),
+                eq(0L),
+                eq(MIN_CHUNK_SIZE),
+                eq(false)))
+        .andThrow(exception);
+    expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L);
+    replay(storageRpcMock);
+    writer = new BlobWriteChannel(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
+    writer.setChunkSize(MIN_CHUNK_SIZE);
+    try {
+      writer.write(buffer);
+      fail("Expected completed exception.");
+    } catch (StorageException ex) {
+      assertEquals(ex, completedException);
+    }
+    assertTrue(writer.isRetrying());
+    assertTrue(writer.isOpen());
+  }
+
   @Test
   public void testWriteWithFlush() throws IOException {
     expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);