embulk-output-azure_blob_storage 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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