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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +424 -0
- data/Rakefile +11 -0
- data/fluent-plugin-bigquery-custom.gemspec +34 -0
- data/lib/fluent/plugin/bigquery/version.rb +6 -0
- data/lib/fluent/plugin/out_bigquery.rb +727 -0
- data/test/helper.rb +34 -0
- data/test/plugin/test_out_bigquery.rb +1015 -0
- data/test/plugin/testdata/apache.schema +98 -0
- data/test/plugin/testdata/json_key.json +7 -0
- data/test/plugin/testdata/sudo.schema +27 -0
- metadata +218 -0
data/test/helper.rb
ADDED
@@ -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
|