fluent-plugin-bigquery-custom 0.3.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.
@@ -0,0 +1,34 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'fluent/test'
15
+ unless ENV.has_key?('VERBOSE')
16
+ nulllogger = Object.new
17
+ nulllogger.instance_eval {|obj|
18
+ def method_missing(method, *args)
19
+ # pass
20
+ end
21
+ }
22
+ $log = nulllogger
23
+ end
24
+
25
+ require 'fluent/buffer'
26
+ require 'fluent/plugin/buf_memory'
27
+ require 'fluent/plugin/buf_file'
28
+
29
+ require 'fluent/plugin/out_bigquery'
30
+
31
+ require 'rr'
32
+
33
+ class Test::Unit::TestCase
34
+ end
@@ -0,0 +1,1015 @@
1
+ require 'helper'
2
+ require 'google/apis/bigquery_v2'
3
+ require 'google/api_client/auth/key_utils'
4
+ require 'googleauth'
5
+ require 'active_support/json'
6
+ require 'active_support/core_ext/hash'
7
+ require 'active_support/core_ext/object/json'
8
+
9
+
10
+ class BigQueryOutputTest < Test::Unit::TestCase
11
+ def setup
12
+ Fluent::Test.setup
13
+ end
14
+
15
+ CONFIG = %[
16
+ table foo
17
+ email foo@bar.example
18
+ private_key_path /path/to/key
19
+ project yourproject_id
20
+ dataset yourdataset_id
21
+
22
+ time_format %s
23
+ time_field time
24
+
25
+ field_integer time,status,bytes
26
+ field_string vhost,path,method,protocol,agent,referer,remote.host,remote.ip,remote.user
27
+ field_float requesttime
28
+ field_boolean bot_access,loginsession
29
+ ]
30
+
31
+ API_SCOPE = "https://www.googleapis.com/auth/bigquery"
32
+
33
+ def create_driver(conf = CONFIG)
34
+ Fluent::Test::OutputTestDriver.new(Fluent::BigQueryOutput).configure(conf)
35
+ end
36
+
37
+ def stub_client(driver)
38
+ stub(client = Object.new) do |expect|
39
+ yield expect if defined?(yield)
40
+ end
41
+ stub(driver.instance).client { client }
42
+ client
43
+ end
44
+
45
+ def mock_client(driver)
46
+ mock(client = Object.new) do |expect|
47
+ yield expect
48
+ end
49
+ stub(driver.instance).client { client }
50
+ client
51
+ end
52
+
53
+ def test_configure_table
54
+ driver = create_driver
55
+ assert_equal driver.instance.table, 'foo'
56
+ assert_nil driver.instance.tables
57
+
58
+ driver = create_driver(CONFIG.sub(/\btable\s+.*$/, 'tables foo,bar'))
59
+ assert_nil driver.instance.table
60
+ assert_equal driver.instance.tables, 'foo,bar'
61
+
62
+ assert_raise(Fluent::ConfigError, "'table' or 'tables' must be specified, and both are invalid") {
63
+ create_driver(CONFIG + "tables foo,bar")
64
+ }
65
+ end
66
+
67
+ def test_configure_auth_private_key
68
+ key = stub!
69
+ mock(Google::APIClient::KeyUtils).load_from_pkcs12('/path/to/key', 'notasecret') { key }
70
+ authorization = Object.new
71
+ stub(Signet::OAuth2::Client).new
72
+ mock(Signet::OAuth2::Client).new(
73
+ token_credential_uri: "https://accounts.google.com/o/oauth2/token",
74
+ audience: "https://accounts.google.com/o/oauth2/token",
75
+ scope: API_SCOPE,
76
+ issuer: 'foo@bar.example',
77
+ signing_key: key) { authorization }
78
+
79
+ mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args {
80
+ mock!.__send__(:authorization=, authorization) {}
81
+ }
82
+
83
+ driver = create_driver(CONFIG)
84
+ driver.instance.client()
85
+ end
86
+
87
+ def test_configure_auth_compute_engine
88
+ authorization = Object.new
89
+ mock(Google::Auth::GCECredentials).new { authorization }
90
+
91
+ mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args {
92
+ mock!.__send__(:authorization=, authorization) {}
93
+ }
94
+
95
+ driver = create_driver(%[
96
+ table foo
97
+ auth_method compute_engine
98
+ project yourproject_id
99
+ dataset yourdataset_id
100
+ field_integer time,status,bytes
101
+ ])
102
+ driver.instance.client()
103
+ end
104
+
105
+ def test_configure_auth_json_key_as_file
106
+ json_key_path = 'test/plugin/testdata/json_key.json'
107
+ authorization = Object.new
108
+ mock(Google::Auth::ServiceAccountCredentials).make_creds(json_key_io: File.open(json_key_path), scope: API_SCOPE) { authorization }
109
+
110
+ mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args {
111
+ mock!.__send__(:authorization=, authorization) {}
112
+ }
113
+
114
+ driver = create_driver(%[
115
+ table foo
116
+ auth_method json_key
117
+ json_key #{json_key_path}
118
+ project yourproject_id
119
+ dataset yourdataset_id
120
+ field_integer time,status,bytes
121
+ ])
122
+ driver.instance.client()
123
+ end
124
+
125
+ def test_configure_auth_json_key_as_string
126
+ json_key = '{"private_key": "X", "client_email": "xxx@developer.gserviceaccount.com"}'
127
+ json_key_io = StringIO.new(json_key)
128
+ mock(StringIO).new(json_key) { json_key_io }
129
+ authorization = Object.new
130
+ mock(Google::Auth::ServiceAccountCredentials).make_creds(json_key_io: json_key_io, scope: API_SCOPE) { authorization }
131
+
132
+ mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args {
133
+ mock!.__send__(:authorization=, authorization) {}
134
+ }
135
+
136
+ driver = create_driver(%[
137
+ table foo
138
+ auth_method json_key
139
+ json_key #{json_key}
140
+ project yourproject_id
141
+ dataset yourdataset_id
142
+ field_integer time,status,bytes
143
+ ])
144
+ driver.instance.client()
145
+ end
146
+
147
+ def test_configure_auth_application_default
148
+ authorization = Object.new
149
+ mock(Google::Auth).get_application_default([API_SCOPE]) { authorization }
150
+
151
+ mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args {
152
+ mock!.__send__(:authorization=, authorization) {}
153
+ }
154
+
155
+ driver = create_driver(%[
156
+ table foo
157
+ auth_method application_default
158
+ project yourproject_id
159
+ dataset yourdataset_id
160
+ field_integer time,status,bytes
161
+ ])
162
+ driver.instance.client()
163
+ end
164
+
165
+ def test_configure_fieldname_stripped
166
+ driver = create_driver(%[
167
+ table foo
168
+ email foo@bar.example
169
+ private_key_path /path/to/key
170
+ project yourproject_id
171
+ dataset yourdataset_id
172
+
173
+ time_format %s
174
+ time_field time
175
+
176
+ field_integer time , status , bytes
177
+ field_string _log_name, vhost, path, method, protocol, agent, referer, remote.host, remote.ip, remote.user
178
+ field_float requesttime
179
+ field_boolean bot_access , loginsession
180
+ ])
181
+ fields = driver.instance.instance_eval{ @fields }
182
+
183
+ assert (not fields['time ']), "tailing spaces must be stripped"
184
+ assert fields['time']
185
+ assert fields['status']
186
+ assert fields['bytes']
187
+ assert fields['_log_name']
188
+ assert fields['vhost']
189
+ assert fields['protocol']
190
+ assert fields['agent']
191
+ assert fields['referer']
192
+ assert fields['remote']['host']
193
+ assert fields['remote']['ip']
194
+ assert fields['remote']['user']
195
+ assert fields['requesttime']
196
+ assert fields['bot_access']
197
+ assert fields['loginsession']
198
+ end
199
+
200
+ def test_configure_invalid_fieldname
201
+ base = %[
202
+ table foo
203
+ email foo@bar.example
204
+ private_key_path /path/to/key
205
+ project yourproject_id
206
+ dataset yourdataset_id
207
+
208
+ time_format %s
209
+ time_field time
210
+ ]
211
+
212
+ assert_raises(Fluent::ConfigError) do
213
+ create_driver(base + "field_integer time field\n")
214
+ end
215
+ assert_raises(Fluent::ConfigError) do
216
+ create_driver(base + "field_string my name\n")
217
+ end
218
+ assert_raises(Fluent::ConfigError) do
219
+ create_driver(base + "field_string remote.host name\n")
220
+ end
221
+ assert_raises(Fluent::ConfigError) do
222
+ create_driver(base + "field_string 1column\n")
223
+ end
224
+ assert_raises(Fluent::ConfigError) do
225
+ create_driver(base + "field_string #{'tenstrings' * 12 + '123456789'}\n")
226
+ end
227
+ assert_raises(Fluent::ConfigError) do
228
+ create_driver(base + "field_float request time\n")
229
+ end
230
+ assert_raises(Fluent::ConfigError) do
231
+ create_driver(base + "field_boolean login session\n")
232
+ end
233
+ end
234
+
235
+ def test_format_stream
236
+ now = Time.now
237
+ input = [
238
+ now,
239
+ {
240
+ "status" => "1",
241
+ "bytes" => 3.0,
242
+ "vhost" => :bar,
243
+ "path" => "/path/to/baz",
244
+ "method" => "GET",
245
+ "protocol" => "HTTP/0.9",
246
+ "agent" => "libwww",
247
+ "referer" => "http://referer.example",
248
+ "requesttime" => (now - 1).to_f.to_s,
249
+ "bot_access" => true,
250
+ "loginsession" => false,
251
+ "something-else" => "would be ignored",
252
+ "yet-another" => {
253
+ "foo" => "bar",
254
+ "baz" => 1,
255
+ },
256
+ "remote" => {
257
+ "host" => "remote.example",
258
+ "ip" => "192.0.2.1",
259
+ "port" => 12345,
260
+ "user" => "tagomoris",
261
+ }
262
+ }
263
+ ]
264
+ expected = {
265
+ "json" => {
266
+ "time" => now.to_i,
267
+ "status" => 1,
268
+ "bytes" => 3,
269
+ "vhost" => "bar",
270
+ "path" => "/path/to/baz",
271
+ "method" => "GET",
272
+ "protocol" => "HTTP/0.9",
273
+ "agent" => "libwww",
274
+ "referer" => "http://referer.example",
275
+ "requesttime" => (now - 1).to_f.to_s.to_f,
276
+ "bot_access" => true,
277
+ "loginsession" => false,
278
+ "remote" => {
279
+ "host" => "remote.example",
280
+ "ip" => "192.0.2.1",
281
+ "user" => "tagomoris",
282
+ }
283
+ }
284
+ }
285
+
286
+ driver = create_driver(CONFIG)
287
+ driver.instance.start
288
+ buf = driver.instance.format_stream("my.tag", [input])
289
+ driver.instance.shutdown
290
+
291
+ assert_equal expected, MessagePack.unpack(buf)
292
+ end
293
+
294
+ [
295
+ # <time_format>, <time field type>, <time expectation generator>, <assertion>
296
+ [
297
+ "%s.%6N", "field_float",
298
+ lambda{|t| t.strftime("%s.%6N").to_f },
299
+ lambda{|recv, expected, actual|
300
+ recv.assert_in_delta(expected, actual, Float::EPSILON / 10**3)
301
+ }
302
+ ],
303
+ [
304
+ "%Y-%m-%dT%H:%M:%SZ", "field_string",
305
+ lambda{|t| t.iso8601 },
306
+ :assert_equal.to_proc
307
+ ],
308
+ [
309
+ "%a, %d %b %Y %H:%M:%S GMT", "field_string",
310
+ lambda{|t| t.httpdate },
311
+ :assert_equal.to_proc
312
+ ],
313
+ ].each do |format, type, expect_time, assert|
314
+ define_method("test_time_formats_#{format}") do
315
+ now = Time.now.utc
316
+ input = [ now, {} ]
317
+ expected = { "json" => { "time" => expect_time[now], } }
318
+
319
+ driver = create_driver(<<-CONFIG)
320
+ table foo
321
+ email foo@bar.example
322
+ private_key_path /path/to/key
323
+ project yourproject_id
324
+ dataset yourdataset_id
325
+
326
+ time_format #{format}
327
+ time_field time
328
+ #{type} time
329
+ CONFIG
330
+ stub_client(driver)
331
+
332
+ driver.instance.start
333
+ buf = driver.instance.format_stream("my.tag", [input])
334
+ driver.instance.shutdown
335
+
336
+ assert[self, expected["json"]["time"], MessagePack.unpack(buf)["json"]["time"]]
337
+ end
338
+ end
339
+
340
+ def test_format_nested_time
341
+ now = Time.now
342
+ input = [
343
+ now,
344
+ {
345
+ "metadata" => {
346
+ "node" => "mynode.example",
347
+ },
348
+ "log" => "something",
349
+ }
350
+ ]
351
+ expected = {
352
+ "json" => {
353
+ "metadata" => {
354
+ "time" => now.strftime("%s").to_i,
355
+ "node" => "mynode.example",
356
+ },
357
+ "log" => "something",
358
+ }
359
+ }
360
+
361
+ driver = create_driver(<<-CONFIG)
362
+ table foo
363
+ email foo@bar.example
364
+ private_key_path /path/to/key
365
+ project yourproject_id
366
+ dataset yourdataset_id
367
+
368
+ time_format %s
369
+ time_field metadata.time
370
+
371
+ field_integer metadata.time
372
+ field_string metadata.node,log
373
+ CONFIG
374
+ stub_client(driver)
375
+ driver.instance.start
376
+ buf = driver.instance.format_stream("my.tag", [input])
377
+ driver.instance.shutdown
378
+
379
+ assert_equal expected, MessagePack.unpack(buf)
380
+ end
381
+
382
+ def test_format_with_schema
383
+ now = Time.now
384
+ input = [
385
+ now,
386
+ {
387
+ "request" => {
388
+ "vhost" => :bar,
389
+ "path" => "/path/to/baz",
390
+ "method" => "GET",
391
+ "protocol" => "HTTP/0.9",
392
+ "agent" => "libwww",
393
+ "referer" => "http://referer.example",
394
+ "time" => (now - 1).to_f,
395
+ "bot_access" => true,
396
+ "loginsession" => false,
397
+ },
398
+ "response" => {
399
+ "status" => "1",
400
+ "bytes" => 3.0,
401
+ },
402
+ "remote" => {
403
+ "host" => "remote.example",
404
+ "ip" => "192.0.2.1",
405
+ "port" => 12345,
406
+ "user" => "tagomoris",
407
+ },
408
+ "something-else" => "would be ignored",
409
+ "yet-another" => {
410
+ "foo" => "bar",
411
+ "baz" => 1,
412
+ },
413
+ }
414
+ ]
415
+ expected = {
416
+ "json" => {
417
+ "time" => now.to_i,
418
+ "request" => {
419
+ "vhost" => "bar",
420
+ "path" => "/path/to/baz",
421
+ "method" => "GET",
422
+ "protocol" => "HTTP/0.9",
423
+ "agent" => "libwww",
424
+ "referer" => "http://referer.example",
425
+ "time" => (now - 1).to_f,
426
+ "bot_access" => true,
427
+ "loginsession" => false,
428
+ },
429
+ "remote" => {
430
+ "host" => "remote.example",
431
+ "ip" => "192.0.2.1",
432
+ "user" => "tagomoris",
433
+ },
434
+ "response" => {
435
+ "status" => 1,
436
+ "bytes" => 3,
437
+ },
438
+ }
439
+ }
440
+
441
+ driver = create_driver(<<-CONFIG)
442
+ table foo
443
+ email foo@bar.example
444
+ private_key_path /path/to/key
445
+ project yourproject_id
446
+ dataset yourdataset_id
447
+
448
+ time_format %s
449
+ time_field time
450
+
451
+ schema_path #{File.join(File.dirname(__FILE__), "testdata", "apache.schema")}
452
+ field_integer time
453
+ CONFIG
454
+ driver.instance.start
455
+ buf = driver.instance.format_stream("my.tag", [input])
456
+ driver.instance.shutdown
457
+
458
+ assert_equal expected, MessagePack.unpack(buf)
459
+ end
460
+
461
+ def test_format_repeated_field_with_schema
462
+ now = Time.now
463
+ input = [
464
+ now,
465
+ {
466
+ "tty" => nil,
467
+ "pwd" => "/home/yugui",
468
+ "user" => "fluentd",
469
+ "argv" => %w[ tail -f /var/log/fluentd/fluentd.log ]
470
+ }
471
+ ]
472
+ expected = {
473
+ "json" => {
474
+ "time" => now.to_i,
475
+ "pwd" => "/home/yugui",
476
+ "user" => "fluentd",
477
+ "argv" => %w[ tail -f /var/log/fluentd/fluentd.log ]
478
+ }
479
+ }
480
+
481
+ driver = create_driver(<<-CONFIG)
482
+ table foo
483
+ email foo@bar.example
484
+ private_key_path /path/to/key
485
+ project yourproject_id
486
+ dataset yourdataset_id
487
+
488
+ time_format %s
489
+ time_field time
490
+
491
+ schema_path #{File.join(File.dirname(__FILE__), "testdata", "sudo.schema")}
492
+ field_integer time
493
+ CONFIG
494
+ driver.instance.start
495
+ buf = driver.instance.format_stream("my.tag", [input])
496
+ driver.instance.shutdown
497
+
498
+ assert_equal expected, MessagePack.unpack(buf)
499
+ end
500
+
501
+ def test_format_fetch_from_bigquery_api
502
+ now = Time.now
503
+ input = [
504
+ now,
505
+ {
506
+ "tty" => nil,
507
+ "pwd" => "/home/yugui",
508
+ "user" => "fluentd",
509
+ "argv" => %w[ tail -f /var/log/fluentd/fluentd.log ]
510
+ }
511
+ ]
512
+ expected = {
513
+ "json" => {
514
+ "time" => now.to_i,
515
+ "pwd" => "/home/yugui",
516
+ "user" => "fluentd",
517
+ "argv" => %w[ tail -f /var/log/fluentd/fluentd.log ]
518
+ }
519
+ }
520
+
521
+ driver = create_driver(<<-CONFIG)
522
+ table foo
523
+ email foo@bar.example
524
+ private_key_path /path/to/key
525
+ project yourproject_id
526
+ dataset yourdataset_id
527
+
528
+ time_format %s
529
+ time_field time
530
+
531
+ fetch_schema true
532
+ field_integer time
533
+ CONFIG
534
+ mock_client(driver) do |expect|
535
+ expect.get_table('yourproject_id', 'yourdataset_id', 'foo') {
536
+ s = stub!
537
+ schema_stub = stub!
538
+ fields_stub = stub!
539
+ s.schema { schema_stub }
540
+ schema_stub.fields { fields_stub }
541
+ fields_stub.as_json { sudo_schema_response.deep_stringify_keys["schema"]["fields"] }
542
+ s
543
+ }
544
+ end
545
+ driver.instance.start
546
+ buf = driver.instance.format_stream("my.tag", [input])
547
+ driver.instance.shutdown
548
+
549
+ assert_equal expected, MessagePack.unpack(buf)
550
+
551
+ fields = driver.instance.instance_eval{ @fields }
552
+ assert fields["time"]
553
+ assert_equal :integer, fields["time"].type # DO NOT OVERWRITE
554
+ assert_equal :nullable, fields["time"].mode # DO NOT OVERWRITE
555
+
556
+ assert fields["tty"]
557
+ assert_equal :string, fields["tty"].type
558
+ assert_equal :nullable, fields["tty"].mode
559
+
560
+ assert fields["pwd"]
561
+ assert_equal :string, fields["pwd"].type
562
+ assert_equal :required, fields["pwd"].mode
563
+
564
+ assert fields["user"]
565
+ assert_equal :string, fields["user"].type
566
+ assert_equal :required, fields["user"].mode
567
+
568
+ assert fields["argv"]
569
+ assert_equal :string, fields["argv"].type
570
+ assert_equal :repeated, fields["argv"].mode
571
+ end
572
+
573
+ def test_format_fetch_from_bigquery_api_with_generated_table_id
574
+ now = Time.now
575
+ input = [
576
+ now,
577
+ {
578
+ "tty" => nil,
579
+ "pwd" => "/home/yugui",
580
+ "user" => "fluentd",
581
+ "argv" => %w[ tail -f /var/log/fluentd/fluentd.log ]
582
+ }
583
+ ]
584
+ expected = {
585
+ "json" => {
586
+ "time" => now.to_i,
587
+ "pwd" => "/home/yugui",
588
+ "user" => "fluentd",
589
+ "argv" => %w[ tail -f /var/log/fluentd/fluentd.log ]
590
+ }
591
+ }
592
+
593
+ driver = create_driver(<<-CONFIG)
594
+ table foo_%Y_%m_%d
595
+ email foo@bar.example
596
+ private_key_path /path/to/key
597
+ project yourproject_id
598
+ dataset yourdataset_id
599
+
600
+ time_format %s
601
+ time_field time
602
+
603
+ fetch_schema true
604
+ field_integer time
605
+ CONFIG
606
+ mock_client(driver) do |expect|
607
+ expect.get_table('yourproject_id', 'yourdataset_id', now.strftime('foo_%Y_%m_%d')) {
608
+ s = stub!
609
+ schema_stub = stub!
610
+ fields_stub = stub!
611
+ s.schema { schema_stub }
612
+ schema_stub.fields { fields_stub }
613
+ fields_stub.as_json { sudo_schema_response.deep_stringify_keys["schema"]["fields"] }
614
+ s
615
+ }
616
+ end
617
+ driver.instance.start
618
+ buf = driver.instance.format_stream("my.tag", [input])
619
+ driver.instance.shutdown
620
+
621
+ assert_equal expected, MessagePack.unpack(buf)
622
+
623
+ fields = driver.instance.instance_eval{ @fields }
624
+ assert fields["time"]
625
+ assert_equal :integer, fields["time"].type # DO NOT OVERWRITE
626
+ assert_equal :nullable, fields["time"].mode # DO NOT OVERWRITE
627
+
628
+ assert fields["tty"]
629
+ assert_equal :string, fields["tty"].type
630
+ assert_equal :nullable, fields["tty"].mode
631
+
632
+ assert fields["pwd"]
633
+ assert_equal :string, fields["pwd"].type
634
+ assert_equal :required, fields["pwd"].mode
635
+
636
+ assert fields["user"]
637
+ assert_equal :string, fields["user"].type
638
+ assert_equal :required, fields["user"].mode
639
+
640
+ assert fields["argv"]
641
+ assert_equal :string, fields["argv"].type
642
+ assert_equal :repeated, fields["argv"].mode
643
+ end
644
+
645
+ def test_format_with_insert_id
646
+ now = Time.now
647
+ input = [
648
+ now,
649
+ {
650
+ "uuid" => "9ABFF756-0267-4247-847F-0895B65F0938",
651
+ }
652
+ ]
653
+ expected = {
654
+ "insert_id" => "9ABFF756-0267-4247-847F-0895B65F0938",
655
+ "json" => {
656
+ "uuid" => "9ABFF756-0267-4247-847F-0895B65F0938",
657
+ }
658
+ }
659
+
660
+ driver = create_driver(<<-CONFIG)
661
+ table foo
662
+ email foo@bar.example
663
+ private_key_path /path/to/key
664
+ project yourproject_id
665
+ dataset yourdataset_id
666
+
667
+ insert_id_field uuid
668
+ field_string uuid
669
+ CONFIG
670
+ driver.instance.start
671
+ buf = driver.instance.format_stream("my.tag", [input])
672
+ driver.instance.shutdown
673
+
674
+ assert_equal expected, MessagePack.unpack(buf)
675
+ end
676
+
677
+ def test_format_with_nested_insert_id
678
+ now = Time.now
679
+ input = [
680
+ now,
681
+ {
682
+ "data" => {
683
+ "uuid" => "809F6BA7-1C16-44CD-9816-4B20E2C7AA2A",
684
+ },
685
+ }
686
+ ]
687
+ expected = {
688
+ "insert_id" => "809F6BA7-1C16-44CD-9816-4B20E2C7AA2A",
689
+ "json" => {
690
+ "data" => {
691
+ "uuid" => "809F6BA7-1C16-44CD-9816-4B20E2C7AA2A",
692
+ }
693
+ }
694
+ }
695
+
696
+ driver = create_driver(<<-CONFIG)
697
+ table foo
698
+ email foo@bar.example
699
+ private_key_path /path/to/key
700
+ project yourproject_id
701
+ dataset yourdataset_id
702
+
703
+ insert_id_field data.uuid
704
+ field_string data.uuid
705
+ CONFIG
706
+ driver.instance.start
707
+ buf = driver.instance.format_stream("my.tag", [input])
708
+ driver.instance.shutdown
709
+
710
+ assert_equal expected, MessagePack.unpack(buf)
711
+ end
712
+
713
+ def test_format_for_load
714
+ now = Time.now
715
+ input = [
716
+ now,
717
+ {
718
+ "uuid" => "9ABFF756-0267-4247-847F-0895B65F0938",
719
+ }
720
+ ]
721
+ expected = MultiJson.dump({
722
+ "uuid" => "9ABFF756-0267-4247-847F-0895B65F0938",
723
+ }) + "\n"
724
+
725
+ driver = create_driver(<<-CONFIG)
726
+ method load
727
+ table foo
728
+ email foo@bar.example
729
+ private_key_path /path/to/key
730
+ project yourproject_id
731
+ dataset yourdataset_id
732
+
733
+ field_string uuid
734
+ CONFIG
735
+ driver.instance.start
736
+ buf = driver.instance.format_stream("my.tag", [input])
737
+ driver.instance.shutdown
738
+
739
+ assert_equal expected, buf
740
+ end
741
+
742
+ def test_empty_value_in_required
743
+ now = Time.now
744
+ input = [
745
+ now,
746
+ {
747
+ "tty" => "pts/1",
748
+ "pwd" => "/home/yugui",
749
+ "user" => nil,
750
+ "argv" => %w[ tail -f /var/log/fluentd/fluentd.log ]
751
+ }
752
+ ]
753
+
754
+ driver = create_driver(<<-CONFIG)
755
+ table foo
756
+ email foo@bar.example
757
+ private_key_path /path/to/key
758
+ project yourproject_id
759
+ dataset yourdataset_id
760
+
761
+ time_format %s
762
+ time_field time
763
+
764
+ schema_path #{File.join(File.dirname(__FILE__), "testdata", "sudo.schema")}
765
+ field_integer time
766
+ CONFIG
767
+ driver.instance.start
768
+ assert_raises(RuntimeError.new("Required field user cannot be null")) do
769
+ driver.instance.format_stream("my.tag", [input])
770
+ end
771
+ driver.instance.shutdown
772
+ end
773
+
774
+ def test_replace_record_key
775
+ now = Time.now
776
+ input = [
777
+ now,
778
+ {
779
+ "vhost" => :bar,
780
+ "@referer" => "http://referer.example",
781
+ "bot_access" => true,
782
+ "login-session" => false
783
+ }
784
+ ]
785
+ expected = {
786
+ "json" => {
787
+ "time" => now.to_i,
788
+ "vhost" => "bar",
789
+ "referer" => "http://referer.example",
790
+ "bot_access" => true,
791
+ "login_session" => false
792
+ }
793
+ }
794
+
795
+ driver = create_driver(<<-CONFIG)
796
+ table foo
797
+ email foo@bar.example
798
+ private_key_path /path/to/key
799
+ project yourproject_id
800
+ dataset yourdataset_id
801
+
802
+ replace_record_key true
803
+ replace_record_key_regexp1 - _
804
+
805
+ time_format %s
806
+ time_field time
807
+
808
+ field_integer time
809
+ field_string vhost, referer
810
+ field_boolean bot_access, login_session
811
+ CONFIG
812
+ driver.instance.start
813
+ buf = driver.instance.format_stream("my.tag", [input])
814
+ driver.instance.shutdown
815
+
816
+ assert_equal expected, MessagePack.unpack(buf)
817
+ end
818
+
819
+ def test_write
820
+ entry = {json: {a: "b"}}, {json: {b: "c"}}
821
+ driver = create_driver(CONFIG)
822
+ mock_client(driver) do |expect|
823
+ expect.insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo', {
824
+ rows: entry
825
+ }, {}) {
826
+ s = stub!
827
+ s.insert_errors { nil }
828
+ s
829
+ }
830
+ end
831
+
832
+ chunk = Fluent::MemoryBufferChunk.new("my.tag")
833
+ entry.each do |e|
834
+ chunk << e.to_msgpack
835
+ end
836
+
837
+ driver.instance.start
838
+ driver.instance.write(chunk)
839
+ driver.instance.shutdown
840
+ end
841
+
842
+ def test_write_for_load
843
+ schema_path = File.join(File.dirname(__FILE__), "testdata", "sudo.schema")
844
+ entry = {a: "b"}, {b: "c"}
845
+ driver = create_driver(<<-CONFIG)
846
+ method load
847
+ table foo
848
+ email foo@bar.example
849
+ private_key_path /path/to/key
850
+ project yourproject_id
851
+ dataset yourdataset_id
852
+
853
+ time_format %s
854
+ time_field time
855
+
856
+ schema_path #{schema_path}
857
+ field_integer time
858
+ CONFIG
859
+ schema_fields = MultiJson.load(File.read(schema_path)).map(&:deep_symbolize_keys).tap do |h|
860
+ h[0][:type] = "INTEGER"
861
+ h[0][:mode] = "NULLABLE"
862
+ end
863
+
864
+ chunk = Fluent::MemoryBufferChunk.new("my.tag")
865
+ io = StringIO.new("hello")
866
+ mock(driver.instance).create_upload_source(chunk).yields(io)
867
+ mock_client(driver) do |expect|
868
+ expect.insert_job('yourproject_id', {
869
+ configuration: {
870
+ load: {
871
+ destination_table: {
872
+ project_id: 'yourproject_id',
873
+ dataset_id: 'yourdataset_id',
874
+ table_id: 'foo',
875
+ },
876
+ schema: {
877
+ fields: schema_fields,
878
+ },
879
+ write_disposition: "WRITE_APPEND",
880
+ source_format: "NEWLINE_DELIMITED_JSON"
881
+ }
882
+ }
883
+ }, {upload_source: io, content_type: "application/octet-stream"}) {
884
+ s = stub!
885
+ status_stub = stub!
886
+ s.status { status_stub }
887
+ status_stub.state { "DONE" }
888
+ status_stub.error_result { nil }
889
+ status_stub.errors { nil }
890
+ s
891
+ }
892
+ end
893
+
894
+ entry.each do |e|
895
+ chunk << MultiJson.dump(e) + "\n"
896
+ end
897
+
898
+ driver.instance.start
899
+ driver.instance.write(chunk)
900
+ driver.instance.shutdown
901
+ end
902
+
903
+ def test_generate_table_id
904
+ driver = create_driver
905
+ table_id_format = 'foo_%Y_%m_%d'
906
+ time = Time.local(2014, 8, 11, 21, 20, 56)
907
+ table_id = driver.instance.generate_table_id(table_id_format, time)
908
+ assert_equal 'foo_2014_08_11', table_id
909
+ end
910
+
911
+ def test_auto_create_table_by_bigquery_api
912
+ now = Time.now
913
+ message = {
914
+ "json" => {
915
+ "time" => now.to_i,
916
+ "request" => {
917
+ "vhost" => "bar",
918
+ "path" => "/path/to/baz",
919
+ "method" => "GET",
920
+ "protocol" => "HTTP/1.0",
921
+ "agent" => "libwww",
922
+ "referer" => "http://referer.example",
923
+ "time" => (now - 1).to_f,
924
+ "bot_access" => true,
925
+ "loginsession" => false,
926
+ },
927
+ "remote" => {
928
+ "host" => "remote.example",
929
+ "ip" => "192.168.1.1",
930
+ "user" => "nagachika",
931
+ },
932
+ "response" => {
933
+ "status" => 200,
934
+ "bytes" => 72,
935
+ },
936
+ }
937
+ }.deep_symbolize_keys
938
+
939
+ driver = create_driver(<<-CONFIG)
940
+ table foo
941
+ email foo@bar.example
942
+ private_key_path /path/to/key
943
+ project yourproject_id
944
+ dataset yourdataset_id
945
+
946
+ time_format %s
947
+ time_field time
948
+
949
+ auto_create_table true
950
+ schema_path #{File.join(File.dirname(__FILE__), "testdata", "apache.schema")}
951
+ CONFIG
952
+ mock_client(driver) do |expect|
953
+ expect.insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo', {
954
+ rows: [message]
955
+ }, {}) {
956
+ raise Google::Apis::ServerError.new("Not found: Table yourproject_id:yourdataset_id.foo", status_code: 404, body: "Not found: Table yourproject_id:yourdataset_id.foo")
957
+ }
958
+ expect.insert_table('yourproject_id', 'yourdataset_id', {
959
+ table_reference: {
960
+ table_id: 'foo',
961
+ },
962
+ schema: {
963
+ fields: JSON.parse(File.read(File.join(File.dirname(__FILE__), "testdata", "apache.schema"))).map(&:deep_symbolize_keys),
964
+ }
965
+ }, {}) {
966
+ stub!
967
+ }
968
+ end
969
+ chunk = Fluent::MemoryBufferChunk.new("my.tag")
970
+ chunk << message.to_msgpack
971
+
972
+ driver.instance.start
973
+
974
+ assert_raise(RuntimeError) {
975
+ driver.instance.write(chunk)
976
+ }
977
+ driver.instance.shutdown
978
+ end
979
+
980
+ private
981
+
982
+ def sudo_schema_response
983
+ {
984
+ schema: {
985
+ fields: [
986
+ {
987
+ name: "time",
988
+ type: "TIMESTAMP",
989
+ mode: "REQUIRED"
990
+ },
991
+ {
992
+ name: "tty",
993
+ type: "STRING",
994
+ mode: "NULLABLE"
995
+ },
996
+ {
997
+ name: "pwd",
998
+ type: "STRING",
999
+ mode: "REQUIRED"
1000
+ },
1001
+ {
1002
+ name: "user",
1003
+ type: "STRING",
1004
+ mode: "REQUIRED"
1005
+ },
1006
+ {
1007
+ name: "argv",
1008
+ type: "STRING",
1009
+ mode: "REPEATED"
1010
+ }
1011
+ ]
1012
+ }
1013
+ }
1014
+ end
1015
+ end