embulk-input-gcs 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,145 @@
1
+ package org.embulk.input.gcs;
2
+
3
+ import com.google.cloud.ReadChannel;
4
+ import com.google.cloud.RestorableState;
5
+ import com.google.cloud.storage.Blob;
6
+ import com.google.cloud.storage.Storage;
7
+ import com.google.common.base.Charsets;
8
+ import com.google.common.io.Files;
9
+ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
10
+ import org.embulk.EmbulkTestRuntime;
11
+ import org.embulk.spi.util.ResumableInputStream;
12
+ import org.junit.Rule;
13
+ import org.junit.Test;
14
+ import org.mockito.Mockito;
15
+
16
+ import java.io.ByteArrayOutputStream;
17
+ import java.io.File;
18
+ import java.io.IOException;
19
+ import java.nio.ByteBuffer;
20
+ import java.nio.channels.FileChannel;
21
+ import java.nio.file.Paths;
22
+
23
+ import static org.junit.Assert.assertEquals;
24
+ import static org.junit.Assert.fail;
25
+ import static org.mockito.ArgumentMatchers.any;
26
+ import static org.mockito.ArgumentMatchers.eq;
27
+
28
+ public class TestInputStreamReopener
29
+ {
30
+ private static class MockReadChannel implements ReadChannel
31
+ {
32
+ private FileChannel ch;
33
+
34
+ MockReadChannel(FileChannel ch)
35
+ {
36
+ this.ch = ch;
37
+ }
38
+
39
+ @Override
40
+ public boolean isOpen()
41
+ {
42
+ return this.ch.isOpen();
43
+ }
44
+
45
+ @Override
46
+ public void close()
47
+ {
48
+ try {
49
+ this.ch.close();
50
+ }
51
+ catch (IOException ignored) {
52
+ }
53
+ }
54
+
55
+ @Override
56
+ public void seek(long position) throws IOException
57
+ {
58
+ this.ch.position(position);
59
+ }
60
+
61
+ @Override
62
+ public void setChunkSize(int chunkSize)
63
+ {
64
+ // no-op
65
+ }
66
+
67
+ @Override
68
+ public RestorableState<ReadChannel> capture()
69
+ {
70
+ return null;
71
+ }
72
+
73
+ @Override
74
+ public int read(ByteBuffer dst) throws IOException
75
+ {
76
+ return this.ch.read(dst);
77
+ }
78
+ }
79
+
80
+ private static final String SAMPLE_PATH = TestInputStreamReopener.class.getResource("/sample_01.csv").getPath();
81
+
82
+ @SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
83
+ @Rule
84
+ public EmbulkTestRuntime runtime = new EmbulkTestRuntime();
85
+
86
+ @Test
87
+ public void testResume()
88
+ {
89
+ final String bucket = "any_bucket";
90
+ final String key = "any_file";
91
+
92
+ final Storage client = mockStorage();
93
+
94
+ final SingleFileProvider.InputStreamReopener reopener = new SingleFileProvider.InputStreamReopener(client, bucket, key);
95
+ final byte[] buf = new byte[200];
96
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
97
+ try (final ResumableInputStream ris = new ResumableInputStream(reopener)) {
98
+ int len;
99
+ while ((len = ris.read(buf)) != -1) {
100
+ out.write(buf, 0, len);
101
+ }
102
+ // read from resumable input stream
103
+ String content = out.toString("UTF-8");
104
+ // assert content
105
+ assertString(content);
106
+ }
107
+ catch (IOException e) {
108
+ e.printStackTrace();
109
+ fail("Should not throw");
110
+ }
111
+ }
112
+
113
+ private Storage mockStorage()
114
+ {
115
+ Blob blob = Mockito.mock(Blob.class);
116
+ // mock Storage to return ReadChannel
117
+ Storage client = Mockito.mock(Storage.class);
118
+ Mockito.doReturn(blob).when(client).get(eq("any_bucket"), eq("any_file"));
119
+ // to return new instance every time (can't re-use channel, because it'll be closed)
120
+ Mockito.doAnswer(invocation -> new MockReadChannel(mockChannel())).when(blob).reader();
121
+ return client;
122
+ }
123
+
124
+ /**
125
+ * Return a mock FileChannel, with simulated error during reads
126
+ *
127
+ * @return
128
+ * @throws IOException
129
+ */
130
+ private static FileChannel mockChannel() throws IOException
131
+ {
132
+ FileChannel ch = Mockito.spy(FileChannel.open(Paths.get(SAMPLE_PATH)));
133
+ // success -> error -> success -> error...
134
+ Mockito.doCallRealMethod()
135
+ .doThrow(new IOException("Fake IOException, going to resume"))
136
+ .when(ch).read(any(ByteBuffer.class));
137
+ return ch;
138
+ }
139
+
140
+ private static void assertString(final String actual) throws IOException
141
+ {
142
+ final String expected = Files.asCharSource(new File(SAMPLE_PATH), Charsets.UTF_8).read();
143
+ assertEquals(expected, actual);
144
+ }
145
+ }
@@ -0,0 +1,164 @@
1
+ package org.embulk.input.gcs;
2
+
3
+ import com.google.api.client.auth.oauth2.TokenResponseException;
4
+ import com.google.api.client.googleapis.json.GoogleJsonError;
5
+ import com.google.api.client.googleapis.json.GoogleJsonResponseException;
6
+ import com.google.api.client.http.HttpHeaders;
7
+ import com.google.api.client.http.HttpRequest;
8
+ import com.google.api.client.http.HttpTransport;
9
+ import com.google.api.client.http.LowLevelHttpRequest;
10
+ import com.google.api.client.http.LowLevelHttpResponse;
11
+ import com.google.api.client.json.Json;
12
+ import com.google.api.client.json.jackson2.JacksonFactory;
13
+ import com.google.api.client.testing.http.HttpTesting;
14
+ import com.google.api.client.testing.http.MockHttpTransport;
15
+ import com.google.api.client.testing.http.MockLowLevelHttpRequest;
16
+ import com.google.api.client.testing.http.MockLowLevelHttpResponse;
17
+ import com.google.cloud.storage.StorageException;
18
+ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19
+ import org.embulk.EmbulkTestRuntime;
20
+ import org.embulk.spi.Exec;
21
+ import org.junit.Before;
22
+ import org.junit.Rule;
23
+ import org.junit.Test;
24
+ import org.mockito.Mockito;
25
+
26
+ import java.io.IOException;
27
+
28
+ import static org.embulk.input.gcs.RetryUtils.withRetry;
29
+ import static org.hamcrest.core.IsInstanceOf.instanceOf;
30
+ import static org.junit.Assert.assertEquals;
31
+ import static org.junit.Assert.assertFalse;
32
+ import static org.junit.Assert.assertNull;
33
+ import static org.junit.Assert.assertThat;
34
+ import static org.junit.Assert.assertTrue;
35
+
36
+ public class TestRetryUtils
37
+ {
38
+ @SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
39
+ @Rule
40
+ public EmbulkTestRuntime runtime = new EmbulkTestRuntime();
41
+
42
+ private RetryUtils.DefaultRetryable<Object> mock;
43
+
44
+ @Before
45
+ public void setUp()
46
+ {
47
+ mock = new RetryUtils.DefaultRetryable<Object>()
48
+ {
49
+ @Override
50
+ public Object call()
51
+ {
52
+ return null;
53
+ }
54
+ };
55
+ }
56
+
57
+ @Test
58
+ public void testRetryable() throws IOException
59
+ {
60
+ // verify that #isRetryable() returns false for below cases:
61
+ // - GoogleJsonResponseException && details.code == 4xx
62
+ assertFalse(mock.isRetryableException(fakeJsonException(400, "fake_400_ex", null)));
63
+ // - TokenResponseException && statusCode == 4xx
64
+ assertFalse(mock.isRetryableException(fakeTokenException(400, "{}")));
65
+ assertFalse(mock.isRetryableException(fakeTokenException(401, "{\"foo\":\"bar\"}")));
66
+ assertFalse(mock.isRetryableException(fakeTokenException(403, "{ \"error_description\": \"Invalid...\"}")));
67
+ // return true
68
+ // - GoogleJsonResponseException && details.code = 5xx
69
+ assertTrue(mock.isRetryableException(fakeJsonException(500, "fake_500_ex", null)));
70
+ // - GoogleJsonResponseException && details == null && content != null
71
+ assertTrue(mock.isRetryableException(fakeJsonExceptionWithoutDetails(400, "fake_400_ex", "this content will make it retry-able")));
72
+ // - TokenResponseException && statusCode = 5xx
73
+ assertTrue(mock.isRetryableException(fakeTokenException(500, "{}")));
74
+ // - TokenResponseException && details.errorDescription contains 'Invalid JWT'
75
+ assertTrue(mock.isRetryableException(fakeTokenException(403, "{ \"error_description\": \"Invalid JWT...\"}")));
76
+ }
77
+
78
+ @Test
79
+ public void testWithRetry() throws Exception
80
+ {
81
+ mock = Mockito.spy(mock);
82
+ Exception ex = new StorageException(403, "Fake Exception");
83
+ Mockito.doThrow(ex).doThrow(ex).doReturn(null).when(mock).call();
84
+
85
+ Object result = withRetry(params(), mock);
86
+ assertNull(result);
87
+ Mockito.verify(mock, Mockito.times(3)).call();
88
+ }
89
+
90
+ @Test
91
+ public void testWithRetryGiveUp()
92
+ {
93
+ final String expectMsg = "Will retry and give up";
94
+ mock = new RetryUtils.DefaultRetryable<Object>()
95
+ {
96
+ @Override
97
+ public Object call()
98
+ {
99
+ throw new IllegalStateException(expectMsg);
100
+ }
101
+ };
102
+ try {
103
+ withRetry(params(), mock);
104
+ }
105
+ catch (RuntimeException e) {
106
+ // root cause -> RetryGiveUpException -> RuntimeException
107
+ Throwable rootCause = e.getCause().getCause();
108
+ assertEquals(expectMsg, rootCause.getMessage());
109
+ assertThat(rootCause, instanceOf(IllegalStateException.class));
110
+ }
111
+ }
112
+
113
+ private static RetryUtils.Task params()
114
+ {
115
+ return Exec.newConfigSource().set("initial_retry_interval_millis", 1).loadConfig(RetryUtils.Task.class);
116
+ }
117
+
118
+ private static GoogleJsonResponseException fakeJsonException(final int code, final String message, final String content)
119
+ {
120
+ GoogleJsonResponseException.Builder builder = new GoogleJsonResponseException.Builder(code, message, new HttpHeaders());
121
+ builder.setContent(content);
122
+ return new GoogleJsonResponseException(builder, fakeJsonError(code, message));
123
+ }
124
+
125
+ private static GoogleJsonResponseException fakeJsonExceptionWithoutDetails(final int code, final String message, final String content)
126
+ {
127
+ GoogleJsonResponseException.Builder builder = new GoogleJsonResponseException.Builder(code, message, new HttpHeaders());
128
+ builder.setContent(content);
129
+ return new GoogleJsonResponseException(builder, null);
130
+ }
131
+
132
+ private static GoogleJsonError fakeJsonError(final int code, final String message)
133
+ {
134
+ GoogleJsonError error = new GoogleJsonError();
135
+ error.setCode(code);
136
+ error.setMessage(message);
137
+ return error;
138
+ }
139
+
140
+ private static TokenResponseException fakeTokenException(final int code, final String content) throws IOException
141
+ {
142
+ HttpTransport transport = new MockHttpTransport() {
143
+ @Override
144
+ public LowLevelHttpRequest buildRequest(String method, String url)
145
+ {
146
+ return new MockLowLevelHttpRequest() {
147
+ @Override
148
+ public LowLevelHttpResponse execute()
149
+ {
150
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
151
+ response.addHeader("custom_header", "value");
152
+ response.setStatusCode(code);
153
+ response.setContentType(Json.MEDIA_TYPE);
154
+ response.setContent(content);
155
+ return response;
156
+ }
157
+ };
158
+ }
159
+ };
160
+ HttpRequest request = transport.createRequestFactory().buildGetRequest(HttpTesting.SIMPLE_GENERIC_URL);
161
+ request.setThrowExceptionOnExecuteError(false);
162
+ return TokenResponseException.from(JacksonFactory.getDefaultInstance(), request.execute());
163
+ }
164
+ }
Binary file
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: embulk-input-gcs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Satoshi Akama
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-26 00:00:00.000000000 Z
11
+ date: 2019-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - ~>
16
+ - - "~>"
17
17
  - !ruby/object:Gem::Version
18
18
  version: '1.0'
19
19
  name: bundler
@@ -21,13 +21,13 @@ dependencies:
21
21
  type: :development
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - '>='
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '10.0'
33
33
  name: rake
@@ -35,21 +35,56 @@ dependencies:
35
35
  type: :development
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
- description: Reads files stored on Google Cloud Storage (Standard, Durable Reduced Availability or Nearline)
41
+ description: Reads files stored on Google Cloud Storage (Standard, Durable Reduced
42
+ Availability or Nearline)
42
43
  email:
43
44
  - satoshiakama@gmail.com
44
45
  executables: []
45
46
  extensions: []
46
47
  extra_rdoc_files: []
47
48
  files:
48
- - .gitignore
49
- - .travis.yml
49
+ - ".gitignore"
50
+ - ".travis.yml"
50
51
  - CHANGELOG.md
51
52
  - README.md
52
53
  - build.gradle
54
+ - classpath/animal-sniffer-annotations-1.14.jar
55
+ - classpath/api-common-1.7.0.jar
56
+ - classpath/checker-compat-qual-2.5.2.jar
57
+ - classpath/commons-codec-1.10.jar
58
+ - classpath/commons-logging-1.2.jar
59
+ - classpath/embulk-input-gcs-0.3.1-shadow.jar
60
+ - classpath/error_prone_annotations-2.1.3.jar
61
+ - classpath/gax-1.35.1.jar
62
+ - classpath/gax-httpjson-0.52.1.jar
63
+ - classpath/google-api-client-1.27.0.jar
64
+ - classpath/google-api-services-storage-v1-rev20181013-1.27.0.jar
65
+ - classpath/google-auth-library-credentials-0.12.0.jar
66
+ - classpath/google-auth-library-oauth2-http-0.12.0.jar
67
+ - classpath/google-cloud-core-1.56.0.jar
68
+ - classpath/google-cloud-core-http-1.56.0.jar
69
+ - classpath/google-http-client-1.27.0.jar
70
+ - classpath/google-http-client-appengine-1.27.0.jar
71
+ - classpath/google-http-client-jackson2-1.27.0.jar
72
+ - classpath/google-oauth-client-1.27.0.jar
73
+ - classpath/grpc-context-1.12.0.jar
74
+ - classpath/gson-2.7.jar
75
+ - classpath/httpclient-4.5.5.jar
76
+ - classpath/httpcore-4.4.9.jar
77
+ - classpath/j2objc-annotations-1.1.jar
78
+ - classpath/jackson-core-2.9.6.jar
79
+ - classpath/javax.annotation-api-1.2.jar
80
+ - classpath/jsr305-3.0.2.jar
81
+ - classpath/opencensus-api-0.15.0.jar
82
+ - classpath/opencensus-contrib-http-util-0.15.0.jar
83
+ - classpath/proto-google-common-protos-1.12.0.jar
84
+ - classpath/proto-google-iam-v1-0.12.0.jar
85
+ - classpath/protobuf-java-3.6.1.jar
86
+ - classpath/protobuf-java-util-3.6.1.jar
87
+ - classpath/threetenbp-1.3.3.jar
53
88
  - config/checkstyle/checkstyle.xml
54
89
  - config/checkstyle/default.xml
55
90
  - gradle/wrapper/gradle-wrapper.jar
@@ -57,31 +92,21 @@ files:
57
92
  - gradlew
58
93
  - gradlew.bat
59
94
  - lib/embulk/input/gcs.rb
60
- - secretkeys.tar
61
- - secretkeys.tar.enc
62
95
  - settings.gradle
96
+ - src/main/java/org/embulk/input/gcs/AuthUtils.java
63
97
  - src/main/java/org/embulk/input/gcs/FileList.java
64
- - src/main/java/org/embulk/input/gcs/GcsAuthentication.java
65
98
  - src/main/java/org/embulk/input/gcs/GcsFileInput.java
66
99
  - src/main/java/org/embulk/input/gcs/GcsFileInputPlugin.java
67
100
  - src/main/java/org/embulk/input/gcs/PluginTask.java
101
+ - src/main/java/org/embulk/input/gcs/RetryUtils.java
68
102
  - src/main/java/org/embulk/input/gcs/SingleFileProvider.java
69
- - src/test/java/org/embulk/input/gcs/TestGcsAuthentication.java
103
+ - src/test/java/org/embulk/input/gcs/TestAuthUtils.java
70
104
  - src/test/java/org/embulk/input/gcs/TestGcsFileInputPlugin.java
105
+ - src/test/java/org/embulk/input/gcs/TestInputStreamReopener.java
106
+ - src/test/java/org/embulk/input/gcs/TestRetryUtils.java
71
107
  - src/test/resources/sample_01.csv
72
108
  - src/test/resources/sample_02.csv
73
- - src/test/resources/secretkeys.tar.enc
74
- - classpath/google-api-client-1.21.0.jar
75
- - classpath/httpclient-4.0.1.jar
76
- - classpath/jsr305-1.3.9.jar
77
- - classpath/commons-logging-1.1.1.jar
78
- - classpath/google-api-services-storage-v1-rev59-1.21.0.jar
79
- - classpath/google-http-client-1.21.0.jar
80
- - classpath/google-oauth-client-1.21.0.jar
81
- - classpath/commons-codec-1.3.jar
82
- - classpath/google-http-client-jackson2-1.21.0.jar
83
- - classpath/httpcore-4.0.1.jar
84
- - classpath/embulk-input-gcs-0.3.0.jar
109
+ - src/test/resources/secrets.tar.enc
85
110
  homepage: https://github.com/embulk/embulk-input-gcs
86
111
  licenses:
87
112
  - Apache-2.0
@@ -92,17 +117,17 @@ require_paths:
92
117
  - lib
93
118
  required_ruby_version: !ruby/object:Gem::Requirement
94
119
  requirements:
95
- - - '>='
120
+ - - ">="
96
121
  - !ruby/object:Gem::Version
97
122
  version: '0'
98
123
  required_rubygems_version: !ruby/object:Gem::Requirement
99
124
  requirements:
100
- - - '>='
125
+ - - ">="
101
126
  - !ruby/object:Gem::Version
102
127
  version: '0'
103
128
  requirements: []
104
129
  rubyforge_project:
105
- rubygems_version: 2.1.9
130
+ rubygems_version: 2.6.13
106
131
  signing_key:
107
132
  specification_version: 4
108
133
  summary: Google Cloud Storage input plugin for Embulk