embulk-input-ftp 0.1.6 → 0.2.0
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/classpath/embulk-input-ftp-0.2.0.jar +0 -0
- data/classpath/embulk-util-ftp-0.2.0.jar +0 -0
- data/src/main/java/org/embulk/input/FtpFileInputPlugin.java +124 -104
- data/src/test/java/org/embulk/input/TestFtpFileInputPlugin.java +308 -0
- data/src/test/resources/sample_01.csv +5 -0
- data/src/test/resources/sample_02.csv +5 -0
- metadata +6 -4
- data/classpath/embulk-input-ftp-0.1.6.jar +0 -0
- data/classpath/embulk-util-ftp-0.1.6.jar +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27845469df3d11bb23cf8ef4a815733383a34768
|
4
|
+
data.tar.gz: 391a4e30104f40bb79d34b1780863bbac9fc32f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43298f5fba9022feb85f0523f99e703f69553fb47c1f1e41a0a34c78584bb162ff84291de2117367e26dae290d865bc90122e806baa8a10b1a3833ea2a5f3d2a
|
7
|
+
data.tar.gz: 663e55ceb928b929b00e8c53566617932b45dcdda8d4cb81d1c2b5a67ba2ed99387fbdc1d6fcd03b6e82e89c6615bacd27eb5d561b536f4be789659b1bc3cfe6
|
Binary file
|
Binary file
|
@@ -1,49 +1,52 @@
|
|
1
1
|
package org.embulk.input;
|
2
2
|
|
3
|
-
import java.util.List;
|
4
|
-
import java.util.ArrayList;
|
5
|
-
import java.util.Collections;
|
6
|
-
import java.util.concurrent.Executors;
|
7
|
-
import java.util.concurrent.ExecutorService;
|
8
|
-
import java.io.IOException;
|
9
|
-
import java.io.InterruptedIOException;
|
10
|
-
import java.io.InputStream;
|
11
|
-
import java.nio.channels.Channels;
|
12
|
-
import org.slf4j.Logger;
|
13
|
-
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
14
|
-
import com.google.common.collect.ImmutableList;
|
15
|
-
import com.google.common.base.Optional;
|
16
|
-
import com.google.common.base.Throwables;
|
17
3
|
import com.google.common.base.Function;
|
4
|
+
import com.google.common.base.Throwables;
|
5
|
+
import com.google.common.collect.ImmutableList;
|
6
|
+
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
7
|
+
import it.sauronsoftware.ftp4j.FTPAbortedException;
|
18
8
|
import it.sauronsoftware.ftp4j.FTPClient;
|
19
|
-
import it.sauronsoftware.ftp4j.FTPFile;
|
20
|
-
import it.sauronsoftware.ftp4j.FTPConnector;
|
21
9
|
import it.sauronsoftware.ftp4j.FTPCommunicationListener;
|
10
|
+
import it.sauronsoftware.ftp4j.FTPConnector;
|
11
|
+
import it.sauronsoftware.ftp4j.FTPDataTransferException;
|
22
12
|
import it.sauronsoftware.ftp4j.FTPDataTransferListener;
|
23
13
|
import it.sauronsoftware.ftp4j.FTPException;
|
14
|
+
import it.sauronsoftware.ftp4j.FTPFile;
|
24
15
|
import it.sauronsoftware.ftp4j.FTPIllegalReplyException;
|
25
|
-
import it.sauronsoftware.ftp4j.FTPDataTransferException;
|
26
|
-
import it.sauronsoftware.ftp4j.FTPAbortedException;
|
27
16
|
import it.sauronsoftware.ftp4j.FTPListParseException;
|
28
|
-
import org.embulk.config.TaskReport;
|
29
17
|
import org.embulk.config.Config;
|
30
|
-
import org.embulk.config.ConfigInject;
|
31
18
|
import org.embulk.config.ConfigDefault;
|
32
19
|
import org.embulk.config.ConfigDiff;
|
20
|
+
import org.embulk.config.ConfigInject;
|
33
21
|
import org.embulk.config.ConfigSource;
|
34
22
|
import org.embulk.config.Task;
|
23
|
+
import org.embulk.config.TaskReport;
|
35
24
|
import org.embulk.config.TaskSource;
|
36
25
|
import org.embulk.spi.BufferAllocator;
|
37
26
|
import org.embulk.spi.Exec;
|
38
27
|
import org.embulk.spi.FileInputPlugin;
|
39
28
|
import org.embulk.spi.TransactionalFileInput;
|
40
29
|
import org.embulk.spi.util.InputStreamFileInput;
|
30
|
+
import org.embulk.spi.util.InputStreamFileInput.InputStreamWithHints;
|
41
31
|
import org.embulk.spi.util.ResumableInputStream;
|
42
|
-
import org.embulk.spi.util.RetryExecutor.Retryable;
|
43
32
|
import org.embulk.spi.util.RetryExecutor.RetryGiveupException;
|
33
|
+
import org.embulk.spi.util.RetryExecutor.Retryable;
|
44
34
|
import org.embulk.util.ftp.BlockingTransfer;
|
45
35
|
import org.embulk.util.ftp.SSLPlugins;
|
46
36
|
import org.embulk.util.ftp.SSLPlugins.SSLPluginConfig;
|
37
|
+
import org.slf4j.Logger;
|
38
|
+
|
39
|
+
import java.io.IOException;
|
40
|
+
import java.io.InputStream;
|
41
|
+
import java.io.InterruptedIOException;
|
42
|
+
import java.nio.channels.Channels;
|
43
|
+
import java.util.ArrayList;
|
44
|
+
import java.util.Collections;
|
45
|
+
import java.util.List;
|
46
|
+
import java.util.Optional;
|
47
|
+
import java.util.concurrent.ExecutorService;
|
48
|
+
import java.util.concurrent.Executors;
|
49
|
+
|
47
50
|
import static org.embulk.spi.util.RetryExecutor.retryExecutor;
|
48
51
|
|
49
52
|
public class FtpFileInputPlugin
|
@@ -58,55 +61,55 @@ public class FtpFileInputPlugin
|
|
58
61
|
extends Task, SSLPlugins.SSLPluginTask
|
59
62
|
{
|
60
63
|
@Config("path_prefix")
|
61
|
-
|
64
|
+
String getPathPrefix();
|
62
65
|
|
63
66
|
@Config("last_path")
|
64
67
|
@ConfigDefault("null")
|
65
|
-
|
68
|
+
Optional<String> getLastPath();
|
66
69
|
|
67
70
|
@Config("incremental")
|
68
71
|
@ConfigDefault("true")
|
69
|
-
|
72
|
+
boolean getIncremental();
|
70
73
|
|
71
74
|
@Config("host")
|
72
|
-
|
75
|
+
String getHost();
|
73
76
|
|
74
77
|
@Config("port")
|
75
78
|
@ConfigDefault("null")
|
76
|
-
|
79
|
+
Optional<Integer> getPort();
|
77
80
|
|
78
81
|
@Config("user")
|
79
82
|
@ConfigDefault("null")
|
80
|
-
|
83
|
+
Optional<String> getUser();
|
81
84
|
|
82
85
|
@Config("password")
|
83
86
|
@ConfigDefault("null")
|
84
|
-
|
87
|
+
Optional<String> getPassword();
|
85
88
|
|
86
89
|
@Config("passive_mode")
|
87
90
|
@ConfigDefault("true")
|
88
|
-
|
91
|
+
boolean getPassiveMode();
|
89
92
|
|
90
93
|
@Config("ascii_mode")
|
91
94
|
@ConfigDefault("false")
|
92
|
-
|
95
|
+
boolean getAsciiMode();
|
93
96
|
|
94
97
|
@Config("ssl")
|
95
98
|
@ConfigDefault("false")
|
96
|
-
|
99
|
+
boolean getSsl();
|
97
100
|
|
98
101
|
@Config("ssl_explicit")
|
99
102
|
@ConfigDefault("true")
|
100
|
-
|
103
|
+
boolean getSslExplicit();
|
101
104
|
|
102
|
-
|
103
|
-
|
105
|
+
List<String> getFiles();
|
106
|
+
void setFiles(List<String> files);
|
104
107
|
|
105
|
-
|
106
|
-
|
108
|
+
SSLPluginConfig getSSLConfig();
|
109
|
+
void setSSLConfig(SSLPluginConfig config);
|
107
110
|
|
108
111
|
@ConfigInject
|
109
|
-
|
112
|
+
BufferAllocator getBufferAllocator();
|
110
113
|
}
|
111
114
|
|
112
115
|
@Override
|
@@ -146,7 +149,8 @@ public class FtpFileInputPlugin
|
|
146
149
|
if (task.getLastPath().isPresent()) {
|
147
150
|
configDiff.set("last_path", task.getLastPath().get());
|
148
151
|
}
|
149
|
-
}
|
152
|
+
}
|
153
|
+
else {
|
150
154
|
List<String> files = new ArrayList<String>(task.getFiles());
|
151
155
|
Collections.sort(files);
|
152
156
|
configDiff.set("last_path", files.get(files.size() - 1));
|
@@ -182,7 +186,7 @@ public class FtpFileInputPlugin
|
|
182
186
|
log.info("Using FTPS(FTPS/implicit) mode");
|
183
187
|
}
|
184
188
|
}
|
185
|
-
int port = task.getPort().isPresent()? task.getPort().get() : defaultPort;
|
189
|
+
int port = task.getPort().isPresent() ? task.getPort().get() : defaultPort;
|
186
190
|
|
187
191
|
client.addCommunicationListener(new LoggingCommunicationListner(log));
|
188
192
|
|
@@ -202,11 +206,11 @@ public class FtpFileInputPlugin
|
|
202
206
|
//client.setAutodetectUTF8
|
203
207
|
|
204
208
|
client.connect(task.getHost(), port);
|
205
|
-
log.info("Connecting to {}:{}",task.getHost(),port);
|
209
|
+
log.info("Connecting to {}:{}", task.getHost(), port);
|
206
210
|
|
207
211
|
if (task.getUser().isPresent()) {
|
208
|
-
log.info("Logging in with user "+task.getUser().get());
|
209
|
-
client.login(task.getUser().get(), task.getPassword().
|
212
|
+
log.info("Logging in with user " + task.getUser().get());
|
213
|
+
client.login(task.getUser().get(), task.getPassword().orElse(""));
|
210
214
|
}
|
211
215
|
|
212
216
|
log.info("Using passive mode");
|
@@ -215,7 +219,8 @@ public class FtpFileInputPlugin
|
|
215
219
|
if (task.getAsciiMode()) {
|
216
220
|
log.info("Using ASCII mode");
|
217
221
|
client.setType(FTPClient.TYPE_TEXTUAL);
|
218
|
-
}
|
222
|
+
}
|
223
|
+
else {
|
219
224
|
log.info("Using binary mode");
|
220
225
|
client.setType(FTPClient.TYPE_BINARY);
|
221
226
|
}
|
@@ -228,20 +233,20 @@ public class FtpFileInputPlugin
|
|
228
233
|
FTPClient connected = client;
|
229
234
|
client = null;
|
230
235
|
return connected;
|
231
|
-
|
232
|
-
|
233
|
-
log.info("FTP command failed: "+ex.getCode()+" "+ex.getMessage());
|
236
|
+
}
|
237
|
+
catch (FTPException ex) {
|
238
|
+
log.info("FTP command failed: " + ex.getCode() + " " + ex.getMessage());
|
234
239
|
throw Throwables.propagate(ex);
|
235
|
-
|
236
|
-
|
240
|
+
}
|
241
|
+
catch (FTPIllegalReplyException ex) {
|
237
242
|
log.info("FTP protocol error");
|
238
243
|
throw Throwables.propagate(ex);
|
239
|
-
|
240
|
-
|
241
|
-
log.info("FTP network error: "+ex);
|
244
|
+
}
|
245
|
+
catch (IOException ex) {
|
246
|
+
log.info("FTP network error: " + ex);
|
242
247
|
throw Throwables.propagate(ex);
|
243
|
-
|
244
|
-
|
248
|
+
}
|
249
|
+
finally {
|
245
250
|
if (client != null) {
|
246
251
|
disconnectClient(client);
|
247
252
|
}
|
@@ -253,11 +258,14 @@ public class FtpFileInputPlugin
|
|
253
258
|
if (client.isConnected()) {
|
254
259
|
try {
|
255
260
|
client.disconnect(false);
|
256
|
-
}
|
261
|
+
}
|
262
|
+
catch (FTPException ex) {
|
257
263
|
// do nothing
|
258
|
-
}
|
264
|
+
}
|
265
|
+
catch (FTPIllegalReplyException ex) {
|
259
266
|
// do nothing
|
260
|
-
}
|
267
|
+
}
|
268
|
+
catch (IOException ex) {
|
261
269
|
// do nothing
|
262
270
|
}
|
263
271
|
}
|
@@ -268,7 +276,8 @@ public class FtpFileInputPlugin
|
|
268
276
|
FTPClient client = newFTPClient(log, task);
|
269
277
|
try {
|
270
278
|
return listFilesByPrefix(log, client, task.getPathPrefix(), task.getLastPath());
|
271
|
-
}
|
279
|
+
}
|
280
|
+
finally {
|
272
281
|
disconnectClient(client);
|
273
282
|
}
|
274
283
|
}
|
@@ -281,12 +290,14 @@ public class FtpFileInputPlugin
|
|
281
290
|
if (prefix.isEmpty()) {
|
282
291
|
directory = "";
|
283
292
|
fileNamePrefix = "";
|
284
|
-
}
|
293
|
+
}
|
294
|
+
else {
|
285
295
|
int pos = prefix.lastIndexOf("/");
|
286
296
|
if (pos < 0) {
|
287
297
|
directory = "";
|
288
298
|
fileNamePrefix = prefix;
|
289
|
-
}
|
299
|
+
}
|
300
|
+
else {
|
290
301
|
directory = prefix.substring(0, pos + 1); // include last "/"
|
291
302
|
fileNamePrefix = prefix.substring(pos + 1);
|
292
303
|
}
|
@@ -308,29 +319,29 @@ public class FtpFileInputPlugin
|
|
308
319
|
listFilesRecursive(client, currentDirectory, file, lastPath, builder);
|
309
320
|
}
|
310
321
|
}
|
311
|
-
|
312
|
-
|
322
|
+
}
|
323
|
+
catch (FTPListParseException ex) {
|
313
324
|
log.info("FTP listing files failed");
|
314
325
|
throw Throwables.propagate(ex);
|
315
|
-
|
316
|
-
|
326
|
+
}
|
327
|
+
catch (FTPAbortedException ex) {
|
317
328
|
log.info("FTP listing files failed");
|
318
329
|
throw Throwables.propagate(ex);
|
319
|
-
|
320
|
-
|
330
|
+
}
|
331
|
+
catch (FTPDataTransferException ex) {
|
321
332
|
log.info("FTP data transfer failed");
|
322
333
|
throw Throwables.propagate(ex);
|
323
|
-
|
324
|
-
|
325
|
-
log.info("FTP command failed: "+ex.getCode()+" "+ex.getMessage());
|
334
|
+
}
|
335
|
+
catch (FTPException ex) {
|
336
|
+
log.info("FTP command failed: " + ex.getCode() + " " + ex.getMessage());
|
326
337
|
throw Throwables.propagate(ex);
|
327
|
-
|
328
|
-
|
338
|
+
}
|
339
|
+
catch (FTPIllegalReplyException ex) {
|
329
340
|
log.info("FTP protocol error");
|
330
341
|
throw Throwables.propagate(ex);
|
331
|
-
|
332
|
-
|
333
|
-
log.info("FTP network error: "+ex);
|
342
|
+
}
|
343
|
+
catch (IOException ex) {
|
344
|
+
log.info("FTP network error: " + ex);
|
334
345
|
throw Throwables.propagate(ex);
|
335
346
|
}
|
336
347
|
|
@@ -386,7 +397,7 @@ public class FtpFileInputPlugin
|
|
386
397
|
|
387
398
|
public void received(String statement)
|
388
399
|
{
|
389
|
-
log.info("< "+statement);
|
400
|
+
log.info("< " + statement);
|
390
401
|
}
|
391
402
|
|
392
403
|
public void sent(String statement)
|
@@ -395,7 +406,7 @@ public class FtpFileInputPlugin
|
|
395
406
|
// don't show password
|
396
407
|
return;
|
397
408
|
}
|
398
|
-
log.info("> "+statement);
|
409
|
+
log.info("> " + statement);
|
399
410
|
}
|
400
411
|
}
|
401
412
|
|
@@ -424,14 +435,14 @@ public class FtpFileInputPlugin
|
|
424
435
|
{
|
425
436
|
totalTransfer += length;
|
426
437
|
if (totalTransfer > nextTransferNotice) {
|
427
|
-
log.info("Transferred "+totalTransfer+" bytes");
|
428
|
-
nextTransferNotice = ((totalTransfer / transferNoticeBytes)+1) * transferNoticeBytes;
|
438
|
+
log.info("Transferred " + totalTransfer + " bytes");
|
439
|
+
nextTransferNotice = ((totalTransfer / transferNoticeBytes) + 1) * transferNoticeBytes;
|
429
440
|
}
|
430
441
|
}
|
431
442
|
|
432
443
|
public void completed()
|
433
444
|
{
|
434
|
-
log.info("Transfer completed "+totalTransfer+" bytes");
|
445
|
+
log.info("Transfer completed " + totalTransfer + " bytes");
|
435
446
|
}
|
436
447
|
|
437
448
|
public void aborted()
|
@@ -445,7 +456,7 @@ public class FtpFileInputPlugin
|
|
445
456
|
}
|
446
457
|
}
|
447
458
|
|
448
|
-
private static final long TRANSFER_NOTICE_BYTES = 100*1024*1024;
|
459
|
+
private static final long TRANSFER_NOTICE_BYTES = 100 * 1024 * 1024;
|
449
460
|
|
450
461
|
private static InputStream startDownload(final Logger log, final FTPClient client,
|
451
462
|
final String path, final long offset, ExecutorService executor)
|
@@ -460,30 +471,31 @@ public class FtpFileInputPlugin
|
|
460
471
|
{
|
461
472
|
try {
|
462
473
|
client.download(path, Channels.newOutputStream(transfer.getWriterChannel()), offset, new LoggingTransferListener(log, TRANSFER_NOTICE_BYTES));
|
463
|
-
|
464
|
-
|
465
|
-
log.info("FTP command failed: "+ex.getCode()+" "+ex.getMessage());
|
474
|
+
}
|
475
|
+
catch (FTPException ex) {
|
476
|
+
log.info("FTP command failed: " + ex.getCode() + " " + ex.getMessage());
|
466
477
|
throw Throwables.propagate(ex);
|
467
|
-
|
468
|
-
|
478
|
+
}
|
479
|
+
catch (FTPDataTransferException ex) {
|
469
480
|
log.info("FTP data transfer failed");
|
470
481
|
throw Throwables.propagate(ex);
|
471
|
-
|
472
|
-
|
482
|
+
}
|
483
|
+
catch (FTPAbortedException ex) {
|
473
484
|
log.info("FTP listing files failed");
|
474
485
|
throw Throwables.propagate(ex);
|
475
|
-
|
476
|
-
|
486
|
+
}
|
487
|
+
catch (FTPIllegalReplyException ex) {
|
477
488
|
log.info("FTP protocol error");
|
478
489
|
throw Throwables.propagate(ex);
|
479
|
-
|
480
|
-
|
490
|
+
}
|
491
|
+
catch (IOException ex) {
|
481
492
|
throw Throwables.propagate(ex);
|
482
|
-
|
483
|
-
|
493
|
+
}
|
494
|
+
finally {
|
484
495
|
try {
|
485
496
|
transfer.getWriterChannel().close();
|
486
|
-
}
|
497
|
+
}
|
498
|
+
catch (IOException ex) {
|
487
499
|
throw new RuntimeException(ex);
|
488
500
|
}
|
489
501
|
}
|
@@ -517,7 +529,7 @@ public class FtpFileInputPlugin
|
|
517
529
|
return retryExecutor()
|
518
530
|
.withRetryLimit(3)
|
519
531
|
.withInitialRetryWait(500)
|
520
|
-
.withMaxRetryWait(30*1000)
|
532
|
+
.withMaxRetryWait(30 * 1000)
|
521
533
|
.runInterruptible(new Retryable<InputStream>() {
|
522
534
|
@Override
|
523
535
|
public InputStream call() throws InterruptedIOException
|
@@ -537,10 +549,11 @@ public class FtpFileInputPlugin
|
|
537
549
|
throws RetryGiveupException
|
538
550
|
{
|
539
551
|
String message = String.format("FTP GET request failed. Retrying %d/%d after %d seconds. Message: %s",
|
540
|
-
retryCount, retryLimit, retryWait/1000, exception.getMessage());
|
552
|
+
retryCount, retryLimit, retryWait / 1000, exception.getMessage());
|
541
553
|
if (retryCount % 3 == 0) {
|
542
554
|
log.warn(message, exception);
|
543
|
-
}
|
555
|
+
}
|
556
|
+
else {
|
544
557
|
log.warn(message);
|
545
558
|
}
|
546
559
|
}
|
@@ -551,10 +564,12 @@ public class FtpFileInputPlugin
|
|
551
564
|
{
|
552
565
|
}
|
553
566
|
});
|
554
|
-
}
|
567
|
+
}
|
568
|
+
catch (RetryGiveupException ex) {
|
555
569
|
Throwables.propagateIfInstanceOf(ex.getCause(), IOException.class);
|
556
570
|
throw Throwables.propagate(ex.getCause());
|
557
|
-
}
|
571
|
+
}
|
572
|
+
catch (InterruptedException ex) {
|
558
573
|
throw new InterruptedIOException();
|
559
574
|
}
|
560
575
|
}
|
@@ -583,16 +598,18 @@ public class FtpFileInputPlugin
|
|
583
598
|
}
|
584
599
|
|
585
600
|
@Override
|
586
|
-
public
|
601
|
+
public InputStreamWithHints openNextWithHints() throws IOException
|
587
602
|
{
|
588
603
|
if (opened) {
|
589
604
|
return null;
|
590
605
|
}
|
591
606
|
opened = true;
|
592
607
|
|
593
|
-
return new
|
594
|
-
|
595
|
-
|
608
|
+
return new InputStreamWithHints(
|
609
|
+
new ResumableInputStream(
|
610
|
+
startDownload(log, client, path, 0L, executor),
|
611
|
+
new FtpInputStreamReopener(log, client, executor, path)), path
|
612
|
+
);
|
596
613
|
}
|
597
614
|
|
598
615
|
@Override
|
@@ -600,7 +617,8 @@ public class FtpFileInputPlugin
|
|
600
617
|
{
|
601
618
|
try {
|
602
619
|
executor.shutdownNow();
|
603
|
-
}
|
620
|
+
}
|
621
|
+
finally {
|
604
622
|
disconnectClient(client);
|
605
623
|
}
|
606
624
|
}
|
@@ -615,7 +633,9 @@ public class FtpFileInputPlugin
|
|
615
633
|
super(task.getBufferAllocator(), new SingleFileProvider(log, task, taskIndex));
|
616
634
|
}
|
617
635
|
|
618
|
-
public void abort()
|
636
|
+
public void abort()
|
637
|
+
{
|
638
|
+
}
|
619
639
|
|
620
640
|
public TaskReport commit()
|
621
641
|
{
|
@@ -1,5 +1,313 @@
|
|
1
1
|
package org.embulk.input;
|
2
2
|
|
3
|
+
import com.google.common.collect.ImmutableList;
|
4
|
+
import com.google.common.collect.ImmutableMap;
|
5
|
+
import com.google.common.collect.Lists;
|
6
|
+
import org.embulk.EmbulkTestRuntime;
|
7
|
+
import org.embulk.config.ConfigDiff;
|
8
|
+
import org.embulk.config.ConfigSource;
|
9
|
+
import org.embulk.config.TaskReport;
|
10
|
+
import org.embulk.config.TaskSource;
|
11
|
+
import org.embulk.input.FtpFileInputPlugin.PluginTask;
|
12
|
+
import org.embulk.spi.Exec;
|
13
|
+
import org.embulk.spi.FileInputPlugin;
|
14
|
+
import org.embulk.spi.FileInputRunner;
|
15
|
+
import org.embulk.spi.InputPlugin;
|
16
|
+
import org.embulk.spi.Schema;
|
17
|
+
import org.embulk.spi.TestPageBuilderReader;
|
18
|
+
import org.embulk.spi.TestPageBuilderReader.MockPageOutput;
|
19
|
+
import org.embulk.spi.util.Pages;
|
20
|
+
import org.embulk.standards.CsvParserPlugin;
|
21
|
+
import org.embulk.util.ftp.SSLPlugins;
|
22
|
+
import org.junit.Before;
|
23
|
+
import org.junit.BeforeClass;
|
24
|
+
import org.junit.Rule;
|
25
|
+
import org.junit.Test;
|
26
|
+
import org.slf4j.Logger;
|
27
|
+
|
28
|
+
import java.lang.reflect.Method;
|
29
|
+
import java.util.ArrayList;
|
30
|
+
import java.util.Arrays;
|
31
|
+
import java.util.List;
|
32
|
+
import java.util.Map;
|
33
|
+
|
34
|
+
import static org.hamcrest.CoreMatchers.is;
|
35
|
+
import static org.junit.Assert.assertThat;
|
36
|
+
|
3
37
|
public class TestFtpFileInputPlugin
|
4
38
|
{
|
39
|
+
private static String FTP_TEST_HOST;
|
40
|
+
private static Integer FTP_TEST_PORT;
|
41
|
+
private static Integer FTP_TEST_SSL_PORT;
|
42
|
+
private static String FTP_TEST_USER;
|
43
|
+
private static String FTP_TEST_PASSWORD;
|
44
|
+
private static String FTP_TEST_SSL_TRUSTED_CA_CERT_FILE;
|
45
|
+
private static String FTP_TEST_SSL_TRUSTED_CA_CERT_DATA;
|
46
|
+
private static String FTP_TEST_DIRECTORY;
|
47
|
+
private static String FTP_TEST_PATH_PREFIX;
|
48
|
+
private FileInputRunner runner;
|
49
|
+
private TestPageBuilderReader.MockPageOutput output;
|
50
|
+
|
51
|
+
/*
|
52
|
+
* This test case requires environment variables
|
53
|
+
* FTP_TEST_HOST
|
54
|
+
* FTP_TEST_USER
|
55
|
+
* FTP_TEST_PASSWORD
|
56
|
+
* FTP_TEST_SSL_TRUSTED_CA_CERT_FILE
|
57
|
+
*/
|
58
|
+
@BeforeClass
|
59
|
+
public static void initializeConstant()
|
60
|
+
{
|
61
|
+
final Map<String, String> env = System.getenv();
|
62
|
+
FTP_TEST_HOST = env.getOrDefault("FTP_TEST_HOST", "localhost");
|
63
|
+
FTP_TEST_PORT = Integer.valueOf(env.getOrDefault("FTP_TEST_PORT", "11021"));
|
64
|
+
FTP_TEST_SSL_PORT = Integer.valueOf(env.getOrDefault("FTP_TEST_SSL_PORT", "990"));
|
65
|
+
FTP_TEST_USER = env.getOrDefault("FTP_TEST_USER", "scott");
|
66
|
+
FTP_TEST_PASSWORD = env.getOrDefault("FTP_TEST_PASSWORD", "tiger");
|
67
|
+
FTP_TEST_SSL_TRUSTED_CA_CERT_FILE = env.getOrDefault("FTP_TEST_SSL_TRUSTED_CA_CERT_FILE", "dummy");
|
68
|
+
FTP_TEST_SSL_TRUSTED_CA_CERT_DATA = env.getOrDefault("FTP_TEST_SSL_TRUSTED_CA_CERT_DATA", "dummy");
|
69
|
+
|
70
|
+
FTP_TEST_DIRECTORY = getDirectory(env.getOrDefault("FTP_TEST_DIRECTORY", "/unittest/"));
|
71
|
+
FTP_TEST_PATH_PREFIX = FTP_TEST_DIRECTORY + "sample_";
|
72
|
+
}
|
73
|
+
|
74
|
+
@Rule
|
75
|
+
public EmbulkTestRuntime runtime = new EmbulkTestRuntime();
|
76
|
+
private FtpFileInputPlugin plugin;
|
77
|
+
|
78
|
+
@Before
|
79
|
+
public void createResources()
|
80
|
+
{
|
81
|
+
plugin = new FtpFileInputPlugin();
|
82
|
+
runner = new FileInputRunner(runtime.getInstance(FtpFileInputPlugin.class));
|
83
|
+
output = new MockPageOutput();
|
84
|
+
}
|
85
|
+
|
86
|
+
@Test(expected = RuntimeException.class) // TODO ConfigException should be thrown
|
87
|
+
public void testTransactionWithInvalidHost()
|
88
|
+
{
|
89
|
+
ConfigSource config = config().deepCopy()
|
90
|
+
.set("host", "non-exists.example.com");
|
91
|
+
|
92
|
+
runner.transaction(config, new Control());
|
93
|
+
}
|
94
|
+
|
95
|
+
@Test
|
96
|
+
public void testResume()
|
97
|
+
{
|
98
|
+
PluginTask task = config().loadConfig(PluginTask.class);
|
99
|
+
task.setSSLConfig(sslConfig(task));
|
100
|
+
task.setFiles(Arrays.asList("in/aa/a"));
|
101
|
+
ConfigDiff configDiff = plugin.resume(task.dump(), 0, new FileInputPlugin.Control()
|
102
|
+
{
|
103
|
+
@Override
|
104
|
+
public List<TaskReport> run(TaskSource taskSource, int taskCount)
|
105
|
+
{
|
106
|
+
return emptyTaskReports(taskCount);
|
107
|
+
}
|
108
|
+
});
|
109
|
+
assertThat(configDiff.get(String.class, "last_path"), is("in/aa/a"));
|
110
|
+
}
|
111
|
+
|
112
|
+
@Test
|
113
|
+
public void testCleanup()
|
114
|
+
{
|
115
|
+
PluginTask task = config().loadConfig(PluginTask.class);
|
116
|
+
plugin.cleanup(task.dump(), 0, Lists.<TaskReport>newArrayList()); // no errors happens
|
117
|
+
}
|
118
|
+
|
119
|
+
@Test
|
120
|
+
@SuppressWarnings("unchecked")
|
121
|
+
public void testListFilesWithNonExistPath() throws Exception
|
122
|
+
{
|
123
|
+
ConfigSource config = config().deepCopy()
|
124
|
+
.set("path_prefix", "non-exists-path");
|
125
|
+
PluginTask task = config.loadConfig(PluginTask.class);
|
126
|
+
plugin.transaction(config, new FileInputPlugin.Control() {
|
127
|
+
@Override
|
128
|
+
public List<TaskReport> run(TaskSource taskSource, int taskCount)
|
129
|
+
{
|
130
|
+
assertThat(taskCount, is(0));
|
131
|
+
return emptyTaskReports(taskCount);
|
132
|
+
}
|
133
|
+
});
|
134
|
+
|
135
|
+
Method method = FtpFileInputPlugin.class.getDeclaredMethod("listFiles", Logger.class, PluginTask.class);
|
136
|
+
method.setAccessible(true);
|
137
|
+
Logger logger = Exec.getLogger(FtpFileInputPlugin.class);
|
138
|
+
List<String> fileList = (List<String>) method.invoke(plugin, logger, task);
|
139
|
+
assertThat(fileList.size(), is(0));
|
140
|
+
}
|
141
|
+
|
142
|
+
@Test
|
143
|
+
@SuppressWarnings("unchecked")
|
144
|
+
public void testListFiles() throws Exception
|
145
|
+
{
|
146
|
+
List<String> expected = Arrays.asList(
|
147
|
+
FTP_TEST_PATH_PREFIX + "01.csv",
|
148
|
+
FTP_TEST_PATH_PREFIX + "02.csv"
|
149
|
+
);
|
150
|
+
|
151
|
+
ConfigSource config = config();
|
152
|
+
final PluginTask task = config.loadConfig(PluginTask.class);
|
153
|
+
ConfigDiff configDiff = plugin.transaction(config, new FileInputPlugin.Control() {
|
154
|
+
@Override
|
155
|
+
public List<TaskReport> run(TaskSource taskSource, int taskCount)
|
156
|
+
{
|
157
|
+
assertThat(taskCount, is(2));
|
158
|
+
return emptyTaskReports(taskCount);
|
159
|
+
}
|
160
|
+
});
|
161
|
+
|
162
|
+
Method method = FtpFileInputPlugin.class.getDeclaredMethod("listFiles", Logger.class, PluginTask.class);
|
163
|
+
method.setAccessible(true);
|
164
|
+
Logger logger = Exec.getLogger(FtpFileInputPlugin.class);
|
165
|
+
List<String> fileList = (List<String>) method.invoke(plugin, logger, task);
|
166
|
+
assertThat(fileList.get(0), is(expected.get(0)));
|
167
|
+
assertThat(fileList.get(1), is(expected.get(1)));
|
168
|
+
assertThat(configDiff.get(String.class, "last_path"), is(FTP_TEST_PATH_PREFIX + "02.csv"));
|
169
|
+
}
|
170
|
+
|
171
|
+
@Test
|
172
|
+
public void testListFilesByPrefixIncrementalFalse()
|
173
|
+
{
|
174
|
+
ConfigSource config = config()
|
175
|
+
.deepCopy()
|
176
|
+
.set("incremental", false);
|
177
|
+
|
178
|
+
ConfigDiff configDiff = runner.transaction(config, new Control());
|
179
|
+
|
180
|
+
assertThat(configDiff.toString(), is("{}"));
|
181
|
+
}
|
182
|
+
|
183
|
+
@Test
|
184
|
+
@SuppressWarnings("unchecked")
|
185
|
+
public void testFtpFileInputByOpen() throws Exception
|
186
|
+
{
|
187
|
+
ConfigSource config = config();
|
188
|
+
PluginTask task = config().loadConfig(PluginTask.class);
|
189
|
+
|
190
|
+
runner.transaction(config, new Control());
|
191
|
+
|
192
|
+
Method method = FtpFileInputPlugin.class.getDeclaredMethod("listFiles", Logger.class, PluginTask.class);
|
193
|
+
method.setAccessible(true);
|
194
|
+
Logger logger = Exec.getLogger(FtpFileInputPlugin.class);
|
195
|
+
List<String> fileList = (List<String>) method.invoke(plugin, logger, task);
|
196
|
+
task.setFiles(fileList);
|
197
|
+
|
198
|
+
assertRecords(config, output);
|
199
|
+
}
|
200
|
+
|
201
|
+
private static List<TaskReport> emptyTaskReports(int taskCount)
|
202
|
+
{
|
203
|
+
ImmutableList.Builder<TaskReport> reports = new ImmutableList.Builder<>();
|
204
|
+
for (int i = 0; i < taskCount; i++) {
|
205
|
+
reports.add(Exec.newTaskReport());
|
206
|
+
}
|
207
|
+
return reports.build();
|
208
|
+
}
|
209
|
+
|
210
|
+
private class Control
|
211
|
+
implements InputPlugin.Control
|
212
|
+
{
|
213
|
+
@Override
|
214
|
+
public List<TaskReport> run(TaskSource taskSource, Schema schema, int taskCount)
|
215
|
+
{
|
216
|
+
List<TaskReport> reports = new ArrayList<>();
|
217
|
+
for (int i = 0; i < taskCount; i++) {
|
218
|
+
reports.add(runner.run(taskSource, schema, i, output));
|
219
|
+
}
|
220
|
+
return reports;
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
private ConfigSource config()
|
225
|
+
{
|
226
|
+
return Exec.newConfigSource()
|
227
|
+
.set("host", FTP_TEST_HOST)
|
228
|
+
.set("port", FTP_TEST_PORT)
|
229
|
+
.set("user", FTP_TEST_USER)
|
230
|
+
.set("password", FTP_TEST_PASSWORD)
|
231
|
+
.set("path_prefix", FTP_TEST_PATH_PREFIX)
|
232
|
+
.set("last_path", "")
|
233
|
+
.set("file_ext", ".csv")
|
234
|
+
.set("max_connection_retry", 3)
|
235
|
+
.set("ssl", false)
|
236
|
+
.set("ssl_verify", false)
|
237
|
+
.set("ssl_verify_hostname", false)
|
238
|
+
.set("ssl_trusted_ca_cert_data", FTP_TEST_SSL_TRUSTED_CA_CERT_DATA)
|
239
|
+
.set("parser", parserConfig(schemaConfig()));
|
240
|
+
}
|
241
|
+
|
242
|
+
private ImmutableMap<String, Object> parserConfig(ImmutableList<Object> schemaConfig)
|
243
|
+
{
|
244
|
+
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
245
|
+
builder.put("type", "csv");
|
246
|
+
builder.put("newline", "CRLF");
|
247
|
+
builder.put("delimiter", ",");
|
248
|
+
builder.put("quote", "\"");
|
249
|
+
builder.put("escape", "\"");
|
250
|
+
builder.put("trim_if_not_quoted", false);
|
251
|
+
builder.put("skip_header_lines", 1);
|
252
|
+
builder.put("allow_extra_columns", false);
|
253
|
+
builder.put("allow_optional_columns", false);
|
254
|
+
builder.put("columns", schemaConfig);
|
255
|
+
return builder.build();
|
256
|
+
}
|
257
|
+
|
258
|
+
private ImmutableList<Object> schemaConfig()
|
259
|
+
{
|
260
|
+
ImmutableList.Builder<Object> builder = new ImmutableList.Builder<>();
|
261
|
+
builder.add(ImmutableMap.of("name", "id", "type", "long"));
|
262
|
+
builder.add(ImmutableMap.of("name", "account", "type", "long"));
|
263
|
+
builder.add(ImmutableMap.of("name", "time", "type", "timestamp", "format", "%Y-%m-%d %H:%M:%S"));
|
264
|
+
builder.add(ImmutableMap.of("name", "purchase", "type", "timestamp", "format", "%Y%m%d"));
|
265
|
+
builder.add(ImmutableMap.of("name", "comment", "type", "string"));
|
266
|
+
return builder.build();
|
267
|
+
}
|
268
|
+
|
269
|
+
public SSLPlugins.SSLPluginConfig sslConfig(PluginTask task)
|
270
|
+
{
|
271
|
+
return SSLPlugins.configure(task);
|
272
|
+
}
|
273
|
+
|
274
|
+
private void assertRecords(ConfigSource config, MockPageOutput output)
|
275
|
+
{
|
276
|
+
List<Object[]> records = getRecords(config, output);
|
277
|
+
assertThat(records.size(), is(8));
|
278
|
+
{
|
279
|
+
Object[] record = records.get(0);
|
280
|
+
assertThat((long) record[0], is(1L));
|
281
|
+
assertThat((long) record[1], is(32864L));
|
282
|
+
assertThat(record[2].toString(), is("2015-01-27 19:23:49 UTC"));
|
283
|
+
assertThat(record[3].toString(), is("2015-01-27 00:00:00 UTC"));
|
284
|
+
assertThat(record[4].toString(), is("embulk"));
|
285
|
+
}
|
286
|
+
|
287
|
+
{
|
288
|
+
Object[] record = records.get(1);
|
289
|
+
assertThat((long) record[0], is(2L));
|
290
|
+
assertThat((long) record[1], is(14824L));
|
291
|
+
assertThat(record[2].toString(), is("2015-01-27 19:01:23 UTC"));
|
292
|
+
assertThat(record[3].toString(), is("2015-01-27 00:00:00 UTC"));
|
293
|
+
assertThat(record[4].toString(), is("embulk jruby"));
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
private List<Object[]> getRecords(ConfigSource config, MockPageOutput output)
|
298
|
+
{
|
299
|
+
Schema schema = config.getNested("parser").loadConfig(CsvParserPlugin.PluginTask.class).getSchemaConfig().toSchema();
|
300
|
+
return Pages.toObjects(schema, output.pages);
|
301
|
+
}
|
302
|
+
|
303
|
+
private static String getDirectory(String dir)
|
304
|
+
{
|
305
|
+
if (dir != null && !dir.endsWith("/")) {
|
306
|
+
dir = dir + "/";
|
307
|
+
}
|
308
|
+
if (dir.startsWith("/")) {
|
309
|
+
dir = dir.replaceFirst("/", "");
|
310
|
+
}
|
311
|
+
return dir;
|
312
|
+
}
|
5
313
|
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-input-ftp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sadayuki Furuhashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-12-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -49,11 +49,13 @@ files:
|
|
49
49
|
- lib/embulk/input/ftp.rb
|
50
50
|
- src/main/java/org/embulk/input/FtpFileInputPlugin.java
|
51
51
|
- src/test/java/org/embulk/input/TestFtpFileInputPlugin.java
|
52
|
+
- src/test/resources/sample_01.csv
|
53
|
+
- src/test/resources/sample_02.csv
|
52
54
|
- classpath/bcpkix-jdk15on-1.52.jar
|
53
|
-
- classpath/embulk-
|
55
|
+
- classpath/embulk-util-ftp-0.2.0.jar
|
54
56
|
- classpath/bcprov-jdk15on-1.52.jar
|
55
57
|
- classpath/ftp4j-1.7.2.jar
|
56
|
-
- classpath/embulk-
|
58
|
+
- classpath/embulk-input-ftp-0.2.0.jar
|
57
59
|
homepage: https://github.com/embulk/embulk-input-ftp
|
58
60
|
licenses:
|
59
61
|
- Apache 2.0
|
Binary file
|
Binary file
|