private static StorageException translate(IOException exception) { return new StorageException(exception); }
private static StorageException translate(GoogleJsonError exception) { return new StorageException(exception); }
@Override public byte[] load(StorageObject storageObject, Map<Option, ?> options) throws StorageException { String key = fullname(storageObject); if (!contents.containsKey(key)) { throw new StorageException(404, "File not found: " + key); } return contents.get(key); }
@Override public StorageObject create(StorageObject object, InputStream content, Map<Option, ?> options) throws StorageException { potentiallyThrow(options); String key = fullname(object); metadata.put(key, object); try { contents.put(key, com.google.common.io.ByteStreams.toByteArray(content)); } catch (IOException e) { throw new StorageException(e); } // TODO: crc, etc return object; }
/** * Downloads this blob to the given file path using specified blob read options. * * @param path destination * @param options blob read options * @throws StorageException upon failure */ public void downloadTo(Path path, BlobSourceOption... options) { try (OutputStream outputStream = Files.newOutputStream(path); ReadChannel reader = reader(options)) { WritableByteChannel channel = Channels.newChannel(outputStream); ByteBuffer bytes = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE); while (reader.read(bytes) > 0) { bytes.flip(); channel.write(bytes); bytes.clear(); } } catch (IOException e) { throw new StorageException(e); } }
/** * Throw if we're asking for generation 0 and the file exists, or if the requested generation * number doesn't match what is asked. * * @param key * @param generationMatch */ private void checkGeneration(String key, Long generationMatch) { if (null == generationMatch) { return; } if (generationMatch == 0 && metadata.containsKey(key)) { throw new StorageException(new FileAlreadyExistsException(key)); } if (generationMatch != 0) { Long generation = metadata.get(key).getGeneration(); if (!generationMatch.equals(generation)) { throw new StorageException( 404, "Generation mismatch. Requested " + generationMatch + " but got " + generation); } } }
/** * Records a reopen attempt for the given StorageException, sleeping for an amount of time * dependent on the attempt number. Throws a StorageException if we've exhausted all reopens. * * @param exs The StorageException error that prompted this reopen attempt. */ private void handleReopenForStorageException(final StorageException exs) throws StorageException { reopens++; if (reopens > maxReopens) { throw new StorageException( exs.getCode(), "All " + maxReopens + " reopens failed. Waited a total of " + totalWaitTime + " ms between attempts", exs); } sleepForAttempt(reopens); }
@Override public RewriteResponse openRewrite(RewriteRequest rewriteRequest) throws StorageException { String sourceKey = fullname(rewriteRequest.source); // a little hackish, just good enough for the tests to work. if (!contents.containsKey(sourceKey)) { throw new StorageException(404, "File not found: " + sourceKey); } // if non-null, then we check the file's at that generation. Long generationMatch = null; for (Option option : rewriteRequest.targetOptions.keySet()) { // this is a bit of a hack, since we don't implement generations. if (option == Option.IF_GENERATION_MATCH) { generationMatch = (Long) rewriteRequest.targetOptions.get(option); } } String destKey = fullname(rewriteRequest.target); checkGeneration(destKey, generationMatch); metadata.put(destKey, rewriteRequest.target); byte[] data = contents.get(sourceKey); contents.put(destKey, Arrays.copyOf(data, data.length)); return new RewriteResponse( rewriteRequest, rewriteRequest.target, data.length, true, "rewriteToken goes here", data.length); }
/** * Records a retry attempt for the given StorageException, sleeping for an amount of time * dependent on the attempt number. Throws a StorageException if we've exhausted all retries. * * @param exs The StorageException error that prompted this retry attempt. */ private void handleRetryForStorageException(final StorageException exs) throws StorageException { retries++; if (retries > maxRetries) { throw new StorageException( exs.getCode(), "All " + maxRetries + " retries failed. Waited a total of " + totalWaitTime + " ms between attempts", exs); } sleepForAttempt(retries); }
/** * Translate RetryHelperException to the StorageException that caused the error. This method will * always throw an exception. * * @throws StorageException when {@code ex} was caused by a {@code StorageException} */ public static StorageException translateAndThrow(RetryHelperException ex) { BaseServiceException.translate(ex); throw new StorageException(UNKNOWN_CODE, ex.getMessage(), ex.getCause()); } }
@Test public void testReadRetryEventuallyGivesUp() throws IOException { ByteBuffer buffer = ByteBuffer.allocate(1); when(gcsChannel.read(eq(buffer))) .thenThrow( new StorageException( new IOException( "Connection closed prematurely: bytesRead = 33554432, Content-Length = 41943040"))) .thenThrow( new StorageException( new IOException( "Connection closed prematurely: bytesRead = 33554432, Content-Length = 41943040"))) .thenReturn(1); assertThat(chan.position()).isEqualTo(0L); thrown.expect(StorageException.class); chan.read(buffer); }
@Test public void testReadRetry() throws IOException { ByteBuffer buffer = ByteBuffer.allocate(1); when(gcsChannel.read(eq(buffer))) .thenThrow( new StorageException( new IOException( "outer", new IOException( "Connection closed prematurely: bytesRead = 33554432, Content-Length = 41943040")))) .thenReturn(1); assertThat(chan.position()).isEqualTo(0L); assertThat(chan.read(buffer)).isEqualTo(1); assertThat(chan.position()).isEqualTo(1L); verify(gcsChannel, times(2)).read(any(ByteBuffer.class)); }
@Test public void testReadRetrySSLHandshake() throws IOException { ByteBuffer buffer = ByteBuffer.allocate(1); when(gcsChannel.read(eq(buffer))) .thenThrow( new StorageException( new IOException( "something", new IOException( "thing", new SSLHandshakeException("connection closed due to throttling"))))) .thenReturn(1); assertThat(chan.position()).isEqualTo(0L); assertThat(chan.read(buffer)).isEqualTo(1); assertThat(chan.position()).isEqualTo(1L); verify(gcsChannel, times(2)).read(any(ByteBuffer.class)); }
@Test public void testCreateRetryableError() { StorageException exception = new StorageException(new SocketException("Socket closed")); expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andThrow(exception); expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID); replay(storageRpcMock); writer = new BlobWriteChannel(options, BLOB_INFO, EMPTY_RPC_OPTIONS); assertTrue(writer.isOpen()); }
@Test public void testIsRetryable() throws Exception { CloudStorageConfiguration config = CloudStorageConfiguration.builder().retryableHttpCodes(ImmutableList.of(1, 2, 3)).build(); CloudStorageRetryHandler retrier = new CloudStorageRetryHandler(config); assertThat(retrier.isRetryable(new StorageException(1, "pretend error code 1"))).isTrue(); } }
@Test public void testNotifyError() { StorageException ex = new StorageException(new IOException("some error")); assertFalse(result.completed()); BatchResult.Callback<Boolean, StorageException> callback = EasyMock.createStrictMock(BatchResult.Callback.class); callback.error(ex); EasyMock.replay(callback); result.notify(callback); result.error(ex); try { result.notify(callback); fail("The batch has been completed."); } catch (IllegalStateException exception) { // expected } EasyMock.verify(callback); }
@Test public void testError() { assertFalse(result.completed()); try { result.get(); fail("This was not completed yet."); } catch (IllegalStateException ex) { // expected } StorageException ex = new StorageException(new IOException("some error")); result.error(ex); try { result.get(); fail("This is a failed operation and should have thrown a StorageException."); } catch (StorageException real) { assertSame(ex, real); } }
@Test public void testRetryableException() { BlobId blob = BlobId.of(BUCKET_NAME1, BLOB_NAME1); EasyMock.expect(storageRpcMock.get(blob.toPb(), EMPTY_RPC_OPTIONS)) .andThrow(new StorageException(500, "internalError")) .andReturn(BLOB_INFO1.toPb()); EasyMock.replay(storageRpcMock); storage = options .toBuilder() .setRetrySettings(ServiceOptions.getDefaultRetrySettings()) .build() .getService(); initializeServiceDependentObjects(); Blob readBlob = storage.get(blob); assertEquals(expectedBlob1, readBlob); }
@Test public void testNonRetryableException() { BlobId blob = BlobId.of(BUCKET_NAME1, BLOB_NAME1); String exceptionMessage = "Not Implemented"; EasyMock.expect(storageRpcMock.get(blob.toPb(), EMPTY_RPC_OPTIONS)) .andThrow(new StorageException(501, exceptionMessage)); EasyMock.replay(storageRpcMock); storage = options .toBuilder() .setRetrySettings(ServiceOptions.getDefaultRetrySettings()) .build() .getService(); initializeServiceDependentObjects(); thrown.expect(StorageException.class); thrown.expectMessage(exceptionMessage); storage.get(blob); }
@Test public void testCreateBlobFromStreamRetryableException() throws IOException { Capture<ByteArrayInputStream> capturedStream = Capture.newInstance(); ByteArrayInputStream fileStream = new ByteArrayInputStream(BLOB_CONTENT); BlobInfo.Builder infoBuilder = BLOB_INFO1.toBuilder(); BlobInfo infoWithHashes = infoBuilder.setMd5(CONTENT_MD5).setCrc32c(CONTENT_CRC32C).build(); BlobInfo infoWithoutHashes = infoBuilder.setMd5(null).setCrc32c(null).build(); EasyMock.expect( storageRpcMock.create( EasyMock.eq(infoWithoutHashes.toPb()), EasyMock.capture(capturedStream), EasyMock.eq(EMPTY_RPC_OPTIONS))) .andThrow(new StorageException(500, "internalError")) .once(); EasyMock.replay(storageRpcMock); storage = options .toBuilder() .setRetrySettings(ServiceOptions.getDefaultRetrySettings()) .build() .getService(); // Even though this exception is retryable, storage.create(BlobInfo, InputStream) // shouldn't retry. thrown.expect(StorageException.class); thrown.expectMessage("internalError"); storage.create(infoWithHashes, fileStream); }