embulk-output-azure_blob_storage 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +2 -2
- data/build.gradle +1 -1
- data/src/main/java/org/embulk/output/azure_blob_storage/AzureBlobStorageFileOutputPlugin.java +51 -22
- data/src/test/java/org/embulk/output/azure_blob_storage/TestAzureBlobStorageFileOutputPlugin.java +85 -9
- data/src/test/resources/sample_01.csv +6 -5
- data/src/test/resources/sample_02.csv +6 -5
- metadata +4 -4
- data/ChangeLog +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5cc1a4b0540a0db2bb640ffff57daef127b63eec
|
4
|
+
data.tar.gz: 09115e5d83c014bac1b3d8d19b12fb8adad3a4be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b167e589e571d77ed3b33552505090aa58bdc1c4470fd36fc66bbc90b8dca8129a0b2f031b1832640a2b0154855fd207a2100477f26ef34fec7ac6a8c4ee818
|
7
|
+
data.tar.gz: 9aedb87b4cdb6750ded5ef47108fd9574392f3a244ac11c051c287595c04195abd54ccbccb0c84f92a961e0aa48f8f015a6bfd94110252865dc437238e2515f5
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
## 0.1.3 - 2015-03-15
|
2
|
+
|
3
|
+
* [maintenance] Add retry logic using expotential backoff [#6](https://github.com/sakama/embulk-output-azure_blob_storage/pull/6)
|
4
|
+
* [maintenance] Change local file output path to temporary directory
|
5
|
+
[#5](https://github.com/sakama/embulk-output-azure_blob_storage/pull/5)
|
6
|
+
|
7
|
+
## 0.1.2 - 2015-02-02
|
8
|
+
|
9
|
+
* [maintenance] Upgrade embulk version to v0.8.2 [#3](https://github.com/sakama/embulk-output-azure_blob_storage/pull/3)
|
10
|
+
|
11
|
+
## 0.1.1 - 2015-11-16
|
12
|
+
|
13
|
+
* [maintenance] Added unit tests [#2](https://github.com/sakama/embulk-output-azure_blob_storage/pull/2)
|
14
|
+
* [maintenance] Refactored code [#1](https://github.com/sakama/embulk-output-azure_blob_storage/pull/1)
|
data/README.md
CHANGED
@@ -70,7 +70,7 @@ AZURE_CONTAINER_DIRECTORY (optional, if needed)
|
|
70
70
|
```
|
71
71
|
|
72
72
|
If you're using Mac OS X El Capitan and GUI Applications(IDE), like as follows.
|
73
|
-
```
|
73
|
+
```xml
|
74
74
|
$ vi ~/Library/LaunchAgents/environment.plist
|
75
75
|
<?xml version="1.0" encoding="UTF-8"?>
|
76
76
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
@@ -98,4 +98,4 @@ $ launchctl load ~/Library/LaunchAgents/environment.plist
|
|
98
98
|
$ launchctl getenv AZURE_ACCOUNT_NAME //try to get value.
|
99
99
|
|
100
100
|
Then start your applications.
|
101
|
-
```
|
101
|
+
```
|
data/build.gradle
CHANGED
data/src/main/java/org/embulk/output/azure_blob_storage/AzureBlobStorageFileOutputPlugin.java
CHANGED
@@ -56,6 +56,10 @@ public class AzureBlobStorageFileOutputPlugin
|
|
56
56
|
@Config("sequence_format")
|
57
57
|
@ConfigDefault("\"%03d.%02d\"")
|
58
58
|
String getSequenceFormat();
|
59
|
+
|
60
|
+
@Config("max_connection_retry")
|
61
|
+
@ConfigDefault("5") // 5 times retry to connect sftp server if failed.
|
62
|
+
int getMaxConnectionRetry();
|
59
63
|
}
|
60
64
|
|
61
65
|
private static final Logger log = Exec.getLogger(AzureBlobStorageFileOutputPlugin.class);
|
@@ -71,7 +75,7 @@ public class AzureBlobStorageFileOutputPlugin
|
|
71
75
|
String containerName = task.getContainer();
|
72
76
|
CloudBlobContainer container = blobClient.getContainerReference(containerName);
|
73
77
|
if (!container.exists()) {
|
74
|
-
log.info(
|
78
|
+
log.info("container {} doesn't exists and created.", containerName);
|
75
79
|
container.createIfNotExists();
|
76
80
|
}
|
77
81
|
}
|
@@ -124,6 +128,7 @@ public class AzureBlobStorageFileOutputPlugin
|
|
124
128
|
private final String sequenceFormat;
|
125
129
|
private final String pathSuffix;
|
126
130
|
private final CloudBlobClient client;
|
131
|
+
private final int maxConnectionRetry;
|
127
132
|
private CloudBlobContainer container = null;
|
128
133
|
private BufferedOutputStream output = null;
|
129
134
|
private int fileIndex;
|
@@ -138,6 +143,7 @@ public class AzureBlobStorageFileOutputPlugin
|
|
138
143
|
this.sequenceFormat = task.getSequenceFormat();
|
139
144
|
this.pathSuffix = task.getFileNameExtension();
|
140
145
|
this.client = newAzureClient(task.getAccountName(), task.getAccountKey());
|
146
|
+
this.maxConnectionRetry = task.getMaxConnectionRetry();
|
141
147
|
try {
|
142
148
|
this.container = client.getContainerReference(task.getContainer());
|
143
149
|
}
|
@@ -157,17 +163,11 @@ public class AzureBlobStorageFileOutputPlugin
|
|
157
163
|
suffix = "." + suffix;
|
158
164
|
}
|
159
165
|
filePath = pathPrefix + String.format(sequenceFormat, taskIndex, fileIndex) + suffix;
|
160
|
-
file =
|
161
|
-
|
162
|
-
|
163
|
-
File dir = new File(parentPath);
|
164
|
-
if (!dir.exists()) {
|
165
|
-
dir.mkdir();
|
166
|
-
}
|
167
|
-
log.info(String.format("Writing local file [%s]", filePath));
|
168
|
-
output = new BufferedOutputStream(new FileOutputStream(filePath));
|
166
|
+
file = File.createTempFile(filePath, ".tmp");
|
167
|
+
log.info("Writing local file {}", file.getAbsolutePath());
|
168
|
+
output = new BufferedOutputStream(new FileOutputStream(file));
|
169
169
|
}
|
170
|
-
catch (
|
170
|
+
catch (IOException ex) {
|
171
171
|
throw Throwables.propagate(ex);
|
172
172
|
}
|
173
173
|
}
|
@@ -202,18 +202,47 @@ public class AzureBlobStorageFileOutputPlugin
|
|
202
202
|
@Override
|
203
203
|
public void finish()
|
204
204
|
{
|
205
|
-
|
205
|
+
close();
|
206
|
+
try {
|
207
|
+
Thread.sleep(1000 * 10);
|
208
|
+
}
|
209
|
+
catch (Exception ex) {
|
210
|
+
// null;
|
211
|
+
}
|
206
212
|
if (filePath != null) {
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
213
|
+
int count = 0;
|
214
|
+
while (true) {
|
215
|
+
try {
|
216
|
+
CloudBlockBlob blob = container.getBlockBlobReference(filePath);
|
217
|
+
log.info("Upload start {} to {}", file.getAbsolutePath(), filePath);
|
218
|
+
blob.upload(new FileInputStream(file), file.length());
|
219
|
+
log.info("Upload completed {} to {}", file.getAbsolutePath(), filePath);
|
220
|
+
log.info("Delete completed local file {}", file.getAbsolutePath());
|
221
|
+
if (!file.delete()) {
|
222
|
+
throw new ConfigException("Couldn't delete local file " + file.getAbsolutePath());
|
223
|
+
}
|
224
|
+
break;
|
225
|
+
}
|
226
|
+
catch (FileNotFoundException | URISyntaxException ex) {
|
227
|
+
throw new ConfigException(ex);
|
228
|
+
}
|
229
|
+
catch (StorageException | IOException ex) {
|
230
|
+
if (++count == maxConnectionRetry) {
|
231
|
+
Throwables.propagate(ex);
|
232
|
+
}
|
233
|
+
log.warn("failed to connect SFTP server: " + ex.getMessage(), ex);
|
234
|
+
|
235
|
+
try {
|
236
|
+
long sleepTime = ((long) Math.pow(2, count) * 1000);
|
237
|
+
log.warn("sleep in next connection retry: {} milliseconds", sleepTime);
|
238
|
+
Thread.sleep(sleepTime); // milliseconds
|
239
|
+
}
|
240
|
+
catch (InterruptedException ex2) {
|
241
|
+
// Ignore this exception because this exception is just about `sleep`.
|
242
|
+
log.warn(ex2.getMessage(), ex2);
|
243
|
+
}
|
244
|
+
log.warn("retrying to connect SFTP server: " + count + " times");
|
245
|
+
}
|
217
246
|
}
|
218
247
|
}
|
219
248
|
}
|
data/src/test/java/org/embulk/output/azure_blob_storage/TestAzureBlobStorageFileOutputPlugin.java
CHANGED
@@ -3,11 +3,13 @@ package org.embulk.output.azure_blob_storage;
|
|
3
3
|
import com.google.common.collect.ImmutableList;
|
4
4
|
import com.google.common.collect.ImmutableMap;
|
5
5
|
import com.google.common.collect.Lists;
|
6
|
+
import com.google.common.io.Resources;
|
6
7
|
import com.microsoft.azure.storage.blob.CloudBlob;
|
7
8
|
import com.microsoft.azure.storage.blob.CloudBlobClient;
|
8
9
|
import com.microsoft.azure.storage.blob.CloudBlobContainer;
|
9
10
|
import org.embulk.EmbulkTestRuntime;
|
10
11
|
import org.embulk.config.ConfigDiff;
|
12
|
+
import org.embulk.config.ConfigException;
|
11
13
|
import org.embulk.config.ConfigSource;
|
12
14
|
import org.embulk.config.TaskReport;
|
13
15
|
import org.embulk.config.TaskSource;
|
@@ -30,10 +32,12 @@ import static org.junit.Assume.assumeNotNull;
|
|
30
32
|
|
31
33
|
import java.io.BufferedReader;
|
32
34
|
import java.io.ByteArrayOutputStream;
|
35
|
+
import java.io.File;
|
33
36
|
import java.io.FileInputStream;
|
34
37
|
import java.io.IOException;
|
35
38
|
import java.io.InputStream;
|
36
39
|
import java.io.InputStreamReader;
|
40
|
+
import java.lang.reflect.Field;
|
37
41
|
import java.lang.reflect.Method;
|
38
42
|
import java.security.GeneralSecurityException;
|
39
43
|
import java.util.Arrays;
|
@@ -67,7 +71,7 @@ public class TestAzureBlobStorageFileOutputPlugin
|
|
67
71
|
|
68
72
|
AZURE_CONTAINER_DIRECTORY = System.getenv("AZURE_CONTAINER_DIRECTORY") != null ? getDirectory(System.getenv("AZURE_CONTAINER_DIRECTORY")) : getDirectory("");
|
69
73
|
AZURE_PATH_PREFIX = AZURE_CONTAINER_DIRECTORY + "sample_";
|
70
|
-
LOCAL_PATH_PREFIX =
|
74
|
+
LOCAL_PATH_PREFIX = Resources.getResource("sample_01.csv").getPath();
|
71
75
|
}
|
72
76
|
|
73
77
|
@Rule
|
@@ -96,7 +100,7 @@ public class TestAzureBlobStorageFileOutputPlugin
|
|
96
100
|
.set("formatter", formatterConfig());
|
97
101
|
|
98
102
|
PluginTask task = config.loadConfig(PluginTask.class);
|
99
|
-
assertEquals(AZURE_ACCOUNT_NAME, task.getAccountName()
|
103
|
+
assertEquals(AZURE_ACCOUNT_NAME, task.getAccountName());
|
100
104
|
}
|
101
105
|
|
102
106
|
@Test
|
@@ -210,6 +214,64 @@ public class TestAzureBlobStorageFileOutputPlugin
|
|
210
214
|
assertRecords(remotePath);
|
211
215
|
}
|
212
216
|
|
217
|
+
@Test(expected = ConfigException.class)
|
218
|
+
public void testAzureFileOutputByOpenWithNonReadableFile() throws Exception
|
219
|
+
{
|
220
|
+
ConfigSource configSource = config();
|
221
|
+
PluginTask task = configSource.loadConfig(PluginTask.class);
|
222
|
+
Schema schema = configSource.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
223
|
+
runner.transaction(configSource, schema, 0, new Control());
|
224
|
+
|
225
|
+
TransactionalFileOutput output = plugin.open(task.dump(), 0);
|
226
|
+
|
227
|
+
output.nextFile();
|
228
|
+
|
229
|
+
FileInputStream is = new FileInputStream(LOCAL_PATH_PREFIX);
|
230
|
+
byte[] bytes = convertInputStreamToByte(is);
|
231
|
+
Buffer buffer = Buffer.wrap(bytes);
|
232
|
+
output.add(buffer);
|
233
|
+
|
234
|
+
Field field = AzureBlobStorageFileOutputPlugin.AzureFileOutput.class.getDeclaredField("file");
|
235
|
+
field.setAccessible(true);
|
236
|
+
File file = File.createTempFile("non-readable-file", ".tmp");
|
237
|
+
file.setReadable(false);
|
238
|
+
field.set(output, file);
|
239
|
+
output.finish();
|
240
|
+
}
|
241
|
+
|
242
|
+
@Test(expected = RuntimeException.class)
|
243
|
+
public void testAzureFileOutputByOpenWithRetry() throws Exception
|
244
|
+
{
|
245
|
+
ConfigSource configSource = config();
|
246
|
+
PluginTask task = configSource.loadConfig(PluginTask.class);
|
247
|
+
Schema schema = configSource.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
248
|
+
runner.transaction(configSource, schema, 0, new Control());
|
249
|
+
|
250
|
+
TransactionalFileOutput output = plugin.open(task.dump(), 0);
|
251
|
+
|
252
|
+
output.nextFile();
|
253
|
+
|
254
|
+
FileInputStream is = new FileInputStream(LOCAL_PATH_PREFIX);
|
255
|
+
byte[] bytes = convertInputStreamToByte(is);
|
256
|
+
Buffer buffer = Buffer.wrap(bytes);
|
257
|
+
output.add(buffer);
|
258
|
+
|
259
|
+
// set maxConnectionRetry = 1 for Test
|
260
|
+
Field maxConnectionRetry = AzureBlobStorageFileOutputPlugin.AzureFileOutput.class.getDeclaredField("maxConnectionRetry");
|
261
|
+
maxConnectionRetry.setAccessible(true);
|
262
|
+
maxConnectionRetry.set(output, 1);
|
263
|
+
|
264
|
+
// set non-existing-container for Test
|
265
|
+
Method method = AzureBlobStorageFileOutputPlugin.class.getDeclaredMethod("newAzureClient", String.class, String.class);
|
266
|
+
method.setAccessible(true);
|
267
|
+
CloudBlobClient client = (CloudBlobClient) method.invoke(plugin, AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY);
|
268
|
+
|
269
|
+
Field container = AzureBlobStorageFileOutputPlugin.AzureFileOutput.class.getDeclaredField("container");
|
270
|
+
container.setAccessible(true);
|
271
|
+
container.set(output, client.getContainerReference("non-exists-container"));
|
272
|
+
output.finish();
|
273
|
+
}
|
274
|
+
|
213
275
|
public ConfigSource config()
|
214
276
|
{
|
215
277
|
return Exec.newConfigSource()
|
@@ -268,6 +330,7 @@ public class TestAzureBlobStorageFileOutputPlugin
|
|
268
330
|
builder.add(ImmutableMap.of("name", "time", "type", "timestamp", "format", "%Y-%m-%d %H:%M:%S"));
|
269
331
|
builder.add(ImmutableMap.of("name", "purchase", "type", "timestamp", "format", "%Y%m%d"));
|
270
332
|
builder.add(ImmutableMap.of("name", "comment", "type", "string"));
|
333
|
+
builder.add(ImmutableMap.of("name", "json_column", "type", "json"));
|
271
334
|
return builder.build();
|
272
335
|
}
|
273
336
|
|
@@ -283,7 +346,7 @@ public class TestAzureBlobStorageFileOutputPlugin
|
|
283
346
|
private void assertRecords(String azurePath) throws Exception
|
284
347
|
{
|
285
348
|
ImmutableList<List<String>> records = getFileContentsFromAzure(azurePath);
|
286
|
-
assertEquals(
|
349
|
+
assertEquals(6, records.size());
|
287
350
|
{
|
288
351
|
List<String> record = records.get(1);
|
289
352
|
assertEquals("1", record.get(0));
|
@@ -291,6 +354,7 @@ public class TestAzureBlobStorageFileOutputPlugin
|
|
291
354
|
assertEquals("2015-01-27 19:23:49", record.get(2));
|
292
355
|
assertEquals("20150127", record.get(3));
|
293
356
|
assertEquals("embulk", record.get(4));
|
357
|
+
assertEquals("{\"k\":true}", record.get(5));
|
294
358
|
}
|
295
359
|
|
296
360
|
{
|
@@ -300,6 +364,22 @@ public class TestAzureBlobStorageFileOutputPlugin
|
|
300
364
|
assertEquals("2015-01-27 19:01:23", record.get(2));
|
301
365
|
assertEquals("20150127", record.get(3));
|
302
366
|
assertEquals("embulk jruby", record.get(4));
|
367
|
+
assertEquals("{\"k\":1}", record.get(5));
|
368
|
+
}
|
369
|
+
|
370
|
+
{
|
371
|
+
List<String> record = records.get(3);
|
372
|
+
assertEquals("{\"k\":1.23}", record.get(5));
|
373
|
+
}
|
374
|
+
|
375
|
+
{
|
376
|
+
List<String> record = records.get(4);
|
377
|
+
assertEquals("{\"k\":\"v\"}", record.get(5));
|
378
|
+
}
|
379
|
+
|
380
|
+
{
|
381
|
+
List<String> record = records.get(5);
|
382
|
+
assertEquals("{\"k\":\"2015-02-03 08:13:45\"}", record.get(5));
|
303
383
|
}
|
304
384
|
}
|
305
385
|
|
@@ -330,11 +410,7 @@ public class TestAzureBlobStorageFileOutputPlugin
|
|
330
410
|
method.setAccessible(true);
|
331
411
|
CloudBlobClient client = (CloudBlobClient) method.invoke(plugin, AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY);
|
332
412
|
CloudBlobContainer container = client.getContainerReference(containerName);
|
333
|
-
|
334
|
-
return true;
|
335
|
-
}
|
336
|
-
|
337
|
-
return false;
|
413
|
+
return container.exists();
|
338
414
|
}
|
339
415
|
|
340
416
|
private void deleteContainerIfExists(String containerName) throws Exception
|
@@ -352,7 +428,7 @@ public class TestAzureBlobStorageFileOutputPlugin
|
|
352
428
|
|
353
429
|
private static String getDirectory(String dir)
|
354
430
|
{
|
355
|
-
if (dir
|
431
|
+
if (!dir.isEmpty() && !dir.endsWith("/")) {
|
356
432
|
dir = dir + "/";
|
357
433
|
}
|
358
434
|
if (dir.startsWith("/")) {
|
@@ -1,5 +1,6 @@
|
|
1
|
-
id,account,time,purchase,comment
|
2
|
-
1,32864,2015-01-27 19:23:49,20150127,embulk
|
3
|
-
2,14824,2015-01-27 19:01:23,20150127,embulk jruby
|
4
|
-
3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin"
|
5
|
-
4,11270,2015-01-29 11:54:36,20150129,NULL
|
1
|
+
id,account,time,purchase,comment,json_column
|
2
|
+
1,32864,2015-01-27 19:23:49,20150127,embulk,{"k":true}
|
3
|
+
2,14824,2015-01-27 19:01:23,20150127,embulk jruby,{"k":1}
|
4
|
+
3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin",{"k":1.23}
|
5
|
+
4,11270,2015-01-29 11:54:36,20150129,NULL,{"k":"v"}
|
6
|
+
5,53231,2015-01-30 13:48:12,20150130,NULL,{"k":"2015-02-03 08:13:45"}
|
@@ -1,5 +1,6 @@
|
|
1
|
-
id,account,time,purchase,comment
|
2
|
-
1,32864,2015-01-27 19:23:49,20150127,embulk
|
3
|
-
2,14824,2015-01-27 19:01:23,20150127,embulk jruby
|
4
|
-
3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin"
|
5
|
-
4,11270,2015-01-29 11:54:36,20150129,NULL
|
1
|
+
id,account,time,purchase,comment,json_column
|
2
|
+
1,32864,2015-01-27 19:23:49,20150127,embulk,{"k":true}
|
3
|
+
2,14824,2015-01-27 19:01:23,20150127,embulk jruby,{"k":1}
|
4
|
+
3,27559,2015-01-28 02:20:02,20150128,"Embulk ""csv"" parser plugin",{"k":1.23}
|
5
|
+
4,11270,2015-01-29 11:54:36,20150129,NULL,{"k":"v"}
|
6
|
+
5,53231,2015-01-30 13:48:12,20150130,NULL,{"k":"2015-02-03 08:13:45"}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-output-azure_blob_storage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Satoshi Akama
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-03-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -47,7 +47,7 @@ extra_rdoc_files: []
|
|
47
47
|
files:
|
48
48
|
- .gitignore
|
49
49
|
- .travis.yml
|
50
|
-
-
|
50
|
+
- CHANGELOG.md
|
51
51
|
- README.md
|
52
52
|
- build.gradle
|
53
53
|
- config/checkstyle/checkstyle.xml
|
@@ -63,7 +63,7 @@ files:
|
|
63
63
|
- src/test/resources/sample_02.csv
|
64
64
|
- classpath/azure-storage-4.0.0.jar
|
65
65
|
- classpath/commons-lang3-3.4.jar
|
66
|
-
- classpath/embulk-output-azure_blob_storage-0.1.
|
66
|
+
- classpath/embulk-output-azure_blob_storage-0.1.3.jar
|
67
67
|
- classpath/jackson-core-2.6.0.jar
|
68
68
|
homepage: https://github.com/sakama/embulk-output-azure_blob_storage
|
69
69
|
licenses:
|
data/ChangeLog
DELETED