fluent-plugin-bigquery 0.2.16 → 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.
@@ -29,6 +29,7 @@ require 'fluent/plugin/buf_file'
29
29
  require 'fluent/plugin/out_bigquery'
30
30
 
31
31
  require 'rr'
32
+ require 'test/unit/rr'
32
33
 
33
34
  class Test::Unit::TestCase
34
35
  end
@@ -34,20 +34,10 @@ class BigQueryOutputTest < Test::Unit::TestCase
34
34
  Fluent::Test::TimeSlicedOutputTestDriver.new(Fluent::BigQueryOutput).configure(conf)
35
35
  end
36
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
37
+ def stub_writer(driver)
38
+ writer = driver.instance.writer
39
+ stub(writer).get_auth { nil }
40
+ writer
51
41
  end
52
42
 
53
43
  def test_configure_table
@@ -76,21 +66,25 @@ class BigQueryOutputTest < Test::Unit::TestCase
76
66
  issuer: 'foo@bar.example',
77
67
  signing_key: key) { authorization }
78
68
 
79
- mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args {
80
- mock!.__send__(:authorization=, authorization) {}
81
- }
69
+ mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args do |cl|
70
+ mock(cl).__send__(:authorization=, authorization) {}
71
+ cl
72
+ end
82
73
 
83
- driver = create_driver(CONFIG)
84
- driver.instance.client()
74
+ driver = create_driver
75
+ mock.proxy(Fluent::BigQuery::Writer).new(duck_type(:info, :error, :warn), driver.instance.auth_method, is_a(Hash))
76
+ driver.instance.writer
77
+ assert driver.instance.writer.client.is_a?(Google::Apis::BigqueryV2::BigqueryService)
85
78
  end
86
79
 
87
80
  def test_configure_auth_compute_engine
88
81
  authorization = Object.new
89
82
  mock(Google::Auth::GCECredentials).new { authorization }
90
83
 
91
- mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args {
92
- mock!.__send__(:authorization=, authorization) {}
93
- }
84
+ mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args do |cl|
85
+ mock(cl).__send__(:authorization=, authorization) {}
86
+ cl
87
+ end
94
88
 
95
89
  driver = create_driver(%[
96
90
  table foo
@@ -99,7 +93,9 @@ class BigQueryOutputTest < Test::Unit::TestCase
99
93
  dataset yourdataset_id
100
94
  field_integer time,status,bytes
101
95
  ])
102
- driver.instance.client()
96
+ mock.proxy(Fluent::BigQuery::Writer).new(duck_type(:info, :error, :warn), driver.instance.auth_method, is_a(Hash))
97
+ driver.instance.writer
98
+ assert driver.instance.writer.client.is_a?(Google::Apis::BigqueryV2::BigqueryService)
103
99
  end
104
100
 
105
101
  def test_configure_auth_json_key_as_file
@@ -107,9 +103,10 @@ class BigQueryOutputTest < Test::Unit::TestCase
107
103
  authorization = Object.new
108
104
  mock(Google::Auth::ServiceAccountCredentials).make_creds(json_key_io: File.open(json_key_path), scope: API_SCOPE) { authorization }
109
105
 
110
- mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args {
111
- mock!.__send__(:authorization=, authorization) {}
112
- }
106
+ mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args do |cl|
107
+ mock(cl).__send__(:authorization=, authorization) {}
108
+ cl
109
+ end
113
110
 
114
111
  driver = create_driver(%[
115
112
  table foo
@@ -119,7 +116,32 @@ class BigQueryOutputTest < Test::Unit::TestCase
119
116
  dataset yourdataset_id
120
117
  field_integer time,status,bytes
121
118
  ])
122
- driver.instance.client()
119
+ mock.proxy(Fluent::BigQuery::Writer).new(duck_type(:info, :error, :warn), driver.instance.auth_method, is_a(Hash))
120
+ driver.instance.writer
121
+ assert driver.instance.writer.client.is_a?(Google::Apis::BigqueryV2::BigqueryService)
122
+ end
123
+
124
+ def test_configure_auth_json_key_as_file_raise_permission_error
125
+ json_key_path = 'test/plugin/testdata/json_key.json'
126
+ json_key_path_dir = File.dirname(json_key_path)
127
+
128
+ begin
129
+ File.chmod(0000, json_key_path_dir)
130
+
131
+ driver = create_driver(%[
132
+ table foo
133
+ auth_method json_key
134
+ json_key #{json_key_path}
135
+ project yourproject_id
136
+ dataset yourdataset_id
137
+ field_integer time,status,bytes
138
+ ])
139
+ assert_raises(Errno::EACCES) do
140
+ driver.instance.writer.client
141
+ end
142
+ ensure
143
+ File.chmod(0755, json_key_path_dir)
144
+ end
123
145
  end
124
146
 
125
147
  def test_configure_auth_json_key_as_string
@@ -129,9 +151,10 @@ class BigQueryOutputTest < Test::Unit::TestCase
129
151
  authorization = Object.new
130
152
  mock(Google::Auth::ServiceAccountCredentials).make_creds(json_key_io: json_key_io, scope: API_SCOPE) { authorization }
131
153
 
132
- mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args {
133
- mock!.__send__(:authorization=, authorization) {}
134
- }
154
+ mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args do |cl|
155
+ mock(cl).__send__(:authorization=, authorization) {}
156
+ cl
157
+ end
135
158
 
136
159
  driver = create_driver(%[
137
160
  table foo
@@ -141,16 +164,19 @@ class BigQueryOutputTest < Test::Unit::TestCase
141
164
  dataset yourdataset_id
142
165
  field_integer time,status,bytes
143
166
  ])
144
- driver.instance.client()
167
+ mock.proxy(Fluent::BigQuery::Writer).new(duck_type(:info, :error, :warn), driver.instance.auth_method, is_a(Hash))
168
+ driver.instance.writer
169
+ assert driver.instance.writer.client.is_a?(Google::Apis::BigqueryV2::BigqueryService)
145
170
  end
146
171
 
147
172
  def test_configure_auth_application_default
148
173
  authorization = Object.new
149
174
  mock(Google::Auth).get_application_default([API_SCOPE]) { authorization }
150
175
 
151
- mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args {
152
- mock!.__send__(:authorization=, authorization) {}
153
- }
176
+ mock.proxy(Google::Apis::BigqueryV2::BigqueryService).new.with_any_args do |cl|
177
+ mock(cl).__send__(:authorization=, authorization) {}
178
+ cl
179
+ end
154
180
 
155
181
  driver = create_driver(%[
156
182
  table foo
@@ -159,7 +185,10 @@ class BigQueryOutputTest < Test::Unit::TestCase
159
185
  dataset yourdataset_id
160
186
  field_integer time,status,bytes
161
187
  ])
162
- driver.instance.client()
188
+
189
+ mock.proxy(Fluent::BigQuery::Writer).new(duck_type(:info, :error, :warn), driver.instance.auth_method, is_a(Hash))
190
+ driver.instance.writer
191
+ assert driver.instance.writer.client.is_a?(Google::Apis::BigqueryV2::BigqueryService)
163
192
  end
164
193
 
165
194
  def test_configure_fieldname_stripped
@@ -275,9 +304,15 @@ class BigQueryOutputTest < Test::Unit::TestCase
275
304
  "requesttime" => (now - 1).to_f.to_s.to_f,
276
305
  "bot_access" => true,
277
306
  "loginsession" => false,
307
+ "something-else" => "would be ignored",
308
+ "yet-another" => {
309
+ "foo" => "bar",
310
+ "baz" => 1,
311
+ },
278
312
  "remote" => {
279
313
  "host" => "remote.example",
280
314
  "ip" => "192.0.2.1",
315
+ "port" => 12345,
281
316
  "user" => "tagomoris",
282
317
  }
283
318
  }
@@ -327,7 +362,6 @@ class BigQueryOutputTest < Test::Unit::TestCase
327
362
  time_field time
328
363
  #{type} time
329
364
  CONFIG
330
- stub_client(driver)
331
365
 
332
366
  driver.instance.start
333
367
  buf = driver.instance.format_stream("my.tag", [input])
@@ -371,7 +405,7 @@ class BigQueryOutputTest < Test::Unit::TestCase
371
405
  field_integer metadata.time
372
406
  field_string metadata.node,log
373
407
  CONFIG
374
- stub_client(driver)
408
+
375
409
  driver.instance.start
376
410
  buf = driver.instance.format_stream("my.tag", [input])
377
411
  driver.instance.shutdown
@@ -429,12 +463,18 @@ class BigQueryOutputTest < Test::Unit::TestCase
429
463
  "remote" => {
430
464
  "host" => "remote.example",
431
465
  "ip" => "192.0.2.1",
466
+ "port" => 12345,
432
467
  "user" => "tagomoris",
433
468
  },
434
469
  "response" => {
435
470
  "status" => 1,
436
471
  "bytes" => 3,
437
472
  },
473
+ "something-else" => "would be ignored",
474
+ "yet-another" => {
475
+ "foo" => "bar",
476
+ "baz" => 1,
477
+ },
438
478
  }
439
479
  }
440
480
 
@@ -531,16 +571,10 @@ class BigQueryOutputTest < Test::Unit::TestCase
531
571
  fetch_schema true
532
572
  field_integer time
533
573
  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
- }
574
+
575
+ writer = stub_writer(driver)
576
+ mock(writer).fetch_schema('yourproject_id', 'yourdataset_id', 'foo') do
577
+ sudo_schema_response.deep_stringify_keys["schema"]["fields"]
544
578
  end
545
579
  driver.instance.start
546
580
  buf = driver.instance.format_stream("my.tag", [input])
@@ -603,16 +637,10 @@ class BigQueryOutputTest < Test::Unit::TestCase
603
637
  fetch_schema true
604
638
  field_integer time
605
639
  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
- }
640
+
641
+ writer = stub_writer(driver)
642
+ mock(writer).fetch_schema('yourproject_id', 'yourdataset_id', now.strftime('foo_%Y_%m_%d')) do
643
+ sudo_schema_response.deep_stringify_keys["schema"]["fields"]
616
644
  end
617
645
  driver.instance.start
618
646
  buf = driver.instance.format_stream("my.tag", [input])
@@ -731,6 +759,8 @@ class BigQueryOutputTest < Test::Unit::TestCase
731
759
  dataset yourdataset_id
732
760
 
733
761
  field_string uuid
762
+
763
+ buffer_type memory
734
764
  CONFIG
735
765
  driver.instance.start
736
766
  buf = driver.instance.format_stream("my.tag", [input])
@@ -739,38 +769,6 @@ class BigQueryOutputTest < Test::Unit::TestCase
739
769
  assert_equal expected, buf
740
770
  end
741
771
 
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
772
  def test_replace_record_key
775
773
  now = Time.now
776
774
  input = [
@@ -869,13 +867,119 @@ class BigQueryOutputTest < Test::Unit::TestCase
869
867
 
870
868
  def test_write
871
869
  entry = {json: {a: "b"}}, {json: {b: "c"}}
872
- driver = create_driver(CONFIG)
873
- mock_client(driver) do |expect|
874
- expect.insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo', {
875
- rows: entry
876
- }, {}) { stub! }
870
+ driver = create_driver
871
+
872
+ writer = stub_writer(driver)
873
+ mock.proxy(writer).insert_rows('yourproject_id', 'yourdataset_id', 'foo', entry, hash_including(
874
+ skip_invalid_rows: false,
875
+ ignore_unknown_values: false
876
+ ))
877
+ mock(writer.client).insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo', {
878
+ rows: entry,
879
+ skip_invalid_rows: false,
880
+ ignore_unknown_values: false
881
+ }, {options: {timeout_sec: nil, open_timeout_sec: 60}}) do
882
+ s = stub!
883
+ s.insert_errors { nil }
884
+ s
885
+ end
886
+
887
+ chunk = Fluent::MemoryBufferChunk.new("my.tag")
888
+ entry.each do |e|
889
+ chunk << e.to_msgpack
890
+ end
891
+
892
+ driver.instance.start
893
+ driver.instance.write(chunk)
894
+ driver.instance.shutdown
895
+ end
896
+
897
+ def test_write_with_retryable_error
898
+ entry = {json: {a: "b"}}, {json: {b: "c"}}
899
+ driver = create_driver(<<-CONFIG)
900
+ table foo
901
+ email foo@bar.example
902
+ private_key_path /path/to/key
903
+ project yourproject_id
904
+ dataset yourdataset_id
905
+
906
+ time_format %s
907
+ time_field time
908
+
909
+ field_integer time,status,bytes
910
+ field_string vhost,path,method,protocol,agent,referer,remote.host,remote.ip,remote.user
911
+ field_float requesttime
912
+ field_boolean bot_access,loginsession
913
+ <secondary>
914
+ type file
915
+ path error
916
+ utc
917
+ </secondary>
918
+ CONFIG
919
+
920
+ writer = stub_writer(driver)
921
+ mock(writer.client).insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo', {
922
+ rows: entry,
923
+ skip_invalid_rows: false,
924
+ ignore_unknown_values: false
925
+ }, {options: {timeout_sec: nil, open_timeout_sec: 60}}) do
926
+ ex = Google::Apis::ServerError.new("error")
927
+ def ex.reason
928
+ "backendError"
929
+ end
930
+ raise ex
931
+ end
932
+
933
+ chunk = Fluent::MemoryBufferChunk.new("my.tag")
934
+ entry.each do |e|
935
+ chunk << e.to_msgpack
936
+ end
937
+
938
+ driver.instance.start
939
+ assert_raise Fluent::BigQuery::Writer::RetryableError do
940
+ driver.instance.write(chunk)
941
+ end
942
+ driver.instance.shutdown
943
+ end
944
+
945
+ def test_write_with_not_retryable_error
946
+ entry = {json: {a: "b"}}, {json: {b: "c"}}
947
+ driver = create_driver(<<-CONFIG)
948
+ table foo
949
+ email foo@bar.example
950
+ private_key_path /path/to/key
951
+ project yourproject_id
952
+ dataset yourdataset_id
953
+
954
+ time_format %s
955
+ time_field time
956
+
957
+ field_integer time,status,bytes
958
+ field_string vhost,path,method,protocol,agent,referer,remote.host,remote.ip,remote.user
959
+ field_float requesttime
960
+ field_boolean bot_access,loginsession
961
+ <secondary>
962
+ type file
963
+ path error
964
+ utc
965
+ </secondary>
966
+ CONFIG
967
+
968
+ writer = stub_writer(driver)
969
+ mock(writer.client).insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo', {
970
+ rows: entry,
971
+ skip_invalid_rows: false,
972
+ ignore_unknown_values: false
973
+ }, {options: {timeout_sec: nil, open_timeout_sec: 60}}) do
974
+ ex = Google::Apis::ServerError.new("error")
975
+ def ex.reason
976
+ "invalid"
977
+ end
978
+ raise ex
877
979
  end
878
980
 
981
+ mock(driver.instance).flush_secondary(is_a(Fluent::Output))
982
+
879
983
  chunk = Fluent::MemoryBufferChunk.new("my.tag")
880
984
  entry.each do |e|
881
985
  chunk << e.to_msgpack
@@ -902,41 +1006,271 @@ class BigQueryOutputTest < Test::Unit::TestCase
902
1006
 
903
1007
  schema_path #{schema_path}
904
1008
  field_integer time
1009
+
1010
+ buffer_type memory
905
1011
  CONFIG
906
1012
  schema_fields = MultiJson.load(File.read(schema_path)).map(&:deep_symbolize_keys).tap do |h|
907
1013
  h[0][:type] = "INTEGER"
908
1014
  h[0][:mode] = "NULLABLE"
909
1015
  end
910
1016
 
1017
+ writer = stub_writer(driver)
911
1018
  chunk = Fluent::MemoryBufferChunk.new("my.tag")
912
1019
  io = StringIO.new("hello")
913
1020
  mock(driver.instance).create_upload_source(chunk).yields(io)
914
- mock_client(driver) do |expect|
915
- expect.insert_job('yourproject_id', {
916
- configuration: {
917
- load: {
918
- destination_table: {
919
- project_id: 'yourproject_id',
920
- dataset_id: 'yourdataset_id',
921
- table_id: 'foo',
922
- },
923
- schema: {
924
- fields: schema_fields,
925
- },
926
- write_disposition: "WRITE_APPEND",
927
- source_format: "NEWLINE_DELIMITED_JSON"
928
- }
1021
+ mock(writer).wait_load_job("yourproject_id", "yourdataset_id", "dummy_job_id", "foo") { nil }
1022
+ mock(writer.client).insert_job('yourproject_id', {
1023
+ configuration: {
1024
+ load: {
1025
+ destination_table: {
1026
+ project_id: 'yourproject_id',
1027
+ dataset_id: 'yourdataset_id',
1028
+ table_id: 'foo',
1029
+ },
1030
+ schema: {
1031
+ fields: schema_fields,
1032
+ },
1033
+ write_disposition: "WRITE_APPEND",
1034
+ source_format: "NEWLINE_DELIMITED_JSON",
1035
+ ignore_unknown_values: false,
1036
+ max_bad_records: 0,
1037
+ }
1038
+ }
1039
+ }, {upload_source: io, content_type: "application/octet-stream", options: {timeout_sec: nil, open_timeout_sec: 60}}) do
1040
+ s = stub!
1041
+ job_reference_stub = stub!
1042
+ s.job_reference { job_reference_stub }
1043
+ job_reference_stub.job_id { "dummy_job_id" }
1044
+ s
1045
+ end
1046
+
1047
+ entry.each do |e|
1048
+ chunk << MultiJson.dump(e) + "\n"
1049
+ end
1050
+
1051
+ driver.instance.start
1052
+ driver.instance.write(chunk)
1053
+ driver.instance.shutdown
1054
+ end
1055
+
1056
+ def test_write_for_load_with_prevent_duplicate_load
1057
+ schema_path = File.join(File.dirname(__FILE__), "testdata", "sudo.schema")
1058
+ entry = {a: "b"}, {b: "c"}
1059
+ driver = create_driver(<<-CONFIG)
1060
+ method load
1061
+ table foo
1062
+ email foo@bar.example
1063
+ private_key_path /path/to/key
1064
+ project yourproject_id
1065
+ dataset yourdataset_id
1066
+
1067
+ time_format %s
1068
+ time_field time
1069
+
1070
+ schema_path #{schema_path}
1071
+ field_integer time
1072
+ prevent_duplicate_load true
1073
+
1074
+ buffer_type memory
1075
+ CONFIG
1076
+ schema_fields = MultiJson.load(File.read(schema_path)).map(&:deep_symbolize_keys).tap do |h|
1077
+ h[0][:type] = "INTEGER"
1078
+ h[0][:mode] = "NULLABLE"
1079
+ end
1080
+
1081
+ chunk = Fluent::MemoryBufferChunk.new("my.tag")
1082
+ io = StringIO.new("hello")
1083
+ mock(driver.instance).create_upload_source(chunk).yields(io)
1084
+ mock.proxy(driver.instance).create_job_id(duck_type(:unique_id), "yourdataset_id", "foo", driver.instance.instance_variable_get(:@fields).to_a, 0, false)
1085
+ writer = stub_writer(driver)
1086
+ mock(writer).wait_load_job("yourproject_id", "yourdataset_id", "dummy_job_id", "foo") { nil }
1087
+ mock(writer.client).insert_job('yourproject_id', {
1088
+ configuration: {
1089
+ load: {
1090
+ destination_table: {
1091
+ project_id: 'yourproject_id',
1092
+ dataset_id: 'yourdataset_id',
1093
+ table_id: 'foo',
1094
+ },
1095
+ schema: {
1096
+ fields: schema_fields,
1097
+ },
1098
+ write_disposition: "WRITE_APPEND",
1099
+ source_format: "NEWLINE_DELIMITED_JSON",
1100
+ ignore_unknown_values: false,
1101
+ max_bad_records: 0,
1102
+ },
1103
+ },
1104
+ job_reference: {project_id: 'yourproject_id', job_id: satisfy { |x| x =~ /fluentd_job_.*/}} ,
1105
+ }, {upload_source: io, content_type: "application/octet-stream", options: {timeout_sec: nil, open_timeout_sec: 60}}) do
1106
+ s = stub!
1107
+ job_reference_stub = stub!
1108
+ s.job_reference { job_reference_stub }
1109
+ job_reference_stub.job_id { "dummy_job_id" }
1110
+ s
1111
+ end
1112
+
1113
+ entry.each do |e|
1114
+ chunk << MultiJson.dump(e) + "\n"
1115
+ end
1116
+
1117
+ driver.instance.start
1118
+ driver.instance.write(chunk)
1119
+ driver.instance.shutdown
1120
+ end
1121
+
1122
+ def test_write_for_load_with_retryable_error
1123
+ schema_path = File.join(File.dirname(__FILE__), "testdata", "sudo.schema")
1124
+ entry = {a: "b"}, {b: "c"}
1125
+ driver = create_driver(<<-CONFIG)
1126
+ method load
1127
+ table foo
1128
+ email foo@bar.example
1129
+ private_key_path /path/to/key
1130
+ project yourproject_id
1131
+ dataset yourdataset_id
1132
+
1133
+ time_format %s
1134
+ time_field time
1135
+
1136
+ schema_path #{schema_path}
1137
+ field_integer time
1138
+
1139
+ buffer_type memory
1140
+ CONFIG
1141
+ schema_fields = MultiJson.load(File.read(schema_path)).map(&:deep_symbolize_keys).tap do |h|
1142
+ h[0][:type] = "INTEGER"
1143
+ h[0][:mode] = "NULLABLE"
1144
+ end
1145
+
1146
+ chunk = Fluent::MemoryBufferChunk.new("my.tag")
1147
+ io = StringIO.new("hello")
1148
+ mock(driver.instance).create_upload_source(chunk).yields(io)
1149
+ writer = stub_writer(driver)
1150
+ mock(writer.client).insert_job('yourproject_id', {
1151
+ configuration: {
1152
+ load: {
1153
+ destination_table: {
1154
+ project_id: 'yourproject_id',
1155
+ dataset_id: 'yourdataset_id',
1156
+ table_id: 'foo',
1157
+ },
1158
+ schema: {
1159
+ fields: schema_fields,
1160
+ },
1161
+ write_disposition: "WRITE_APPEND",
1162
+ source_format: "NEWLINE_DELIMITED_JSON",
1163
+ ignore_unknown_values: false,
1164
+ max_bad_records: 0,
1165
+ }
1166
+ }
1167
+ }, {upload_source: io, content_type: "application/octet-stream", options: {timeout_sec: nil, open_timeout_sec: 60}}) do
1168
+ s = stub!
1169
+ job_reference_stub = stub!
1170
+ s.job_reference { job_reference_stub }
1171
+ job_reference_stub.job_id { "dummy_job_id" }
1172
+ s
1173
+ end
1174
+
1175
+ mock(writer.client).get_job('yourproject_id', 'dummy_job_id') do
1176
+ s = stub!
1177
+ status_stub = stub!
1178
+ error_result = stub!
1179
+
1180
+ s.status { status_stub }
1181
+ status_stub.state { "DONE" }
1182
+ status_stub.error_result { error_result }
1183
+ status_stub.errors { nil }
1184
+ error_result.message { "error" }
1185
+ error_result.reason { "backendError" }
1186
+ s
1187
+ end
1188
+
1189
+ entry.each do |e|
1190
+ chunk << MultiJson.dump(e) + "\n"
1191
+ end
1192
+
1193
+ driver.instance.start
1194
+ assert_raise Fluent::BigQuery::Writer::RetryableError do
1195
+ driver.instance.write(chunk)
1196
+ end
1197
+ driver.instance.shutdown
1198
+ end
1199
+
1200
+ def test_write_for_load_with_not_retryable_error
1201
+ schema_path = File.join(File.dirname(__FILE__), "testdata", "sudo.schema")
1202
+ entry = {a: "b"}, {b: "c"}
1203
+ driver = create_driver(<<-CONFIG)
1204
+ method load
1205
+ table foo
1206
+ email foo@bar.example
1207
+ private_key_path /path/to/key
1208
+ project yourproject_id
1209
+ dataset yourdataset_id
1210
+
1211
+ time_format %s
1212
+ time_field time
1213
+
1214
+ schema_path #{schema_path}
1215
+ field_integer time
1216
+
1217
+ buffer_type memory
1218
+ <secondary>
1219
+ type file
1220
+ path error
1221
+ utc
1222
+ </secondary>
1223
+ CONFIG
1224
+ schema_fields = MultiJson.load(File.read(schema_path)).map(&:deep_symbolize_keys).tap do |h|
1225
+ h[0][:type] = "INTEGER"
1226
+ h[0][:mode] = "NULLABLE"
1227
+ end
1228
+
1229
+ chunk = Fluent::MemoryBufferChunk.new("my.tag")
1230
+ io = StringIO.new("hello")
1231
+ mock(driver.instance).create_upload_source(chunk).yields(io)
1232
+ writer = stub_writer(driver)
1233
+ mock(writer.client).insert_job('yourproject_id', {
1234
+ configuration: {
1235
+ load: {
1236
+ destination_table: {
1237
+ project_id: 'yourproject_id',
1238
+ dataset_id: 'yourdataset_id',
1239
+ table_id: 'foo',
1240
+ },
1241
+ schema: {
1242
+ fields: schema_fields,
1243
+ },
1244
+ write_disposition: "WRITE_APPEND",
1245
+ source_format: "NEWLINE_DELIMITED_JSON",
1246
+ ignore_unknown_values: false,
1247
+ max_bad_records: 0,
929
1248
  }
930
- }, {upload_source: io, content_type: "application/octet-stream"}) {
931
- s = stub!
932
- status_stub = stub!
933
- s.status { status_stub }
934
- status_stub.state { "DONE" }
935
- status_stub.error_result { nil }
936
- s
937
1249
  }
1250
+ }, {upload_source: io, content_type: "application/octet-stream", options: {timeout_sec: nil, open_timeout_sec: 60}}) do
1251
+ s = stub!
1252
+ job_reference_stub = stub!
1253
+ s.job_reference { job_reference_stub }
1254
+ job_reference_stub.job_id { "dummy_job_id" }
1255
+ s
938
1256
  end
939
1257
 
1258
+ mock(writer.client).get_job('yourproject_id', 'dummy_job_id') do
1259
+ s = stub!
1260
+ status_stub = stub!
1261
+ error_result = stub!
1262
+
1263
+ s.status { status_stub }
1264
+ status_stub.state { "DONE" }
1265
+ status_stub.error_result { error_result }
1266
+ status_stub.errors { nil }
1267
+ error_result.message { "error" }
1268
+ error_result.reason { "invalid" }
1269
+ s
1270
+ end
1271
+
1272
+ mock(driver.instance).flush_secondary(is_a(Fluent::Output))
1273
+
940
1274
  entry.each do |e|
941
1275
  chunk << MultiJson.dump(e) + "\n"
942
1276
  end
@@ -966,15 +1300,19 @@ class BigQueryOutputTest < Test::Unit::TestCase
966
1300
  field_float requesttime
967
1301
  field_boolean bot_access,loginsession
968
1302
  CONFIG
969
- mock_client(driver) do |expect|
970
- expect.insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo_2014_08_20', {
971
- rows: [entry[0]]
972
- }, {})
973
-
974
- expect.insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo_2014_08_21', {
975
- rows: [entry[1]]
976
- }, {})
977
- end
1303
+
1304
+ writer = stub_writer(driver)
1305
+ mock(writer.client).insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo_2014_08_20', {
1306
+ rows: [entry[0]],
1307
+ skip_invalid_rows: false,
1308
+ ignore_unknown_values: false
1309
+ }, {options: {timeout_sec: nil, open_timeout_sec: 60}}) { stub!.insert_errors { nil } }
1310
+
1311
+ mock(writer.client).insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo_2014_08_21', {
1312
+ rows: [entry[1]],
1313
+ skip_invalid_rows: false,
1314
+ ignore_unknown_values: false
1315
+ }, {options: {timeout_sec: nil, open_timeout_sec: 60}}) { stub!.insert_errors { nil } }
978
1316
 
979
1317
  chunk = Fluent::MemoryBufferChunk.new("my.tag")
980
1318
  entry.each do |object|
@@ -1085,23 +1423,13 @@ class BigQueryOutputTest < Test::Unit::TestCase
1085
1423
  auto_create_table true
1086
1424
  schema_path #{File.join(File.dirname(__FILE__), "testdata", "apache.schema")}
1087
1425
  CONFIG
1088
- mock_client(driver) do |expect|
1089
- expect.insert_all_table_data('yourproject_id', 'yourdataset_id', 'foo', {
1090
- rows: [message]
1091
- }, {}) {
1092
- 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")
1093
- }
1094
- expect.insert_table('yourproject_id', 'yourdataset_id', {
1095
- table_reference: {
1096
- table_id: 'foo',
1097
- },
1098
- schema: {
1099
- fields: JSON.parse(File.read(File.join(File.dirname(__FILE__), "testdata", "apache.schema"))).map(&:deep_symbolize_keys),
1100
- }
1101
- }, {}) {
1102
- stub!
1103
- }
1104
- end
1426
+ writer = stub_writer(driver)
1427
+ mock(writer).insert_rows('yourproject_id', 'yourdataset_id', 'foo', [message], hash_including(
1428
+ skip_invalid_rows: false,
1429
+ ignore_unknown_values: false,
1430
+ )) { raise Fluent::BigQuery::Writer::RetryableError.new(nil, Google::Apis::ServerError.new("Not found: Table yourproject_id:yourdataset_id.foo", status_code: 404, body: "Not found: Table yourproject_id:yourdataset_id.foo")) }
1431
+ mock(writer).create_table('yourproject_id', 'yourdataset_id', 'foo', driver.instance.instance_variable_get(:@fields))
1432
+
1105
1433
  chunk = Fluent::MemoryBufferChunk.new("my.tag")
1106
1434
  chunk << message.to_msgpack
1107
1435