vmik-fluent-plugin-google-cloud 0.5.5.alpha1
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.
- data/CONTRIBUTING +24 -0
- data/Gemfile +3 -0
- data/LICENSE +201 -0
- data/README.rdoc +46 -0
- data/Rakefile +42 -0
- data/fluent-plugin-google-cloud.gemspec +34 -0
- data/lib/fluent/plugin/out_google_cloud.rb +1184 -0
- data/lib/google/logging/type/http_request_pb.rb +30 -0
- data/lib/google/logging/type/log_severity_pb.rb +26 -0
- data/lib/google/logging/v1/log_entry_pb.rb +52 -0
- data/lib/google/logging/v1/logging_pb.rb +84 -0
- data/lib/google/logging/v1/logging_services_pb.rb +150 -0
- data/test/helper.rb +40 -0
- data/test/plugin/base_test.rb +1534 -0
- data/test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12 +0 -0
- data/test/plugin/data/credentials.json +7 -0
- data/test/plugin/data/iam-credentials.json +11 -0
- data/test/plugin/data/invalid_credentials.json +8 -0
- data/test/plugin/test_out_google_cloud.rb +297 -0
- data/test/plugin/test_out_google_cloud_grpc.rb +381 -0
- data/vmik-fluent-plugin-google-cloud-0.5.5.gem +0 -0
- metadata +291 -0
@@ -0,0 +1,7 @@
|
|
1
|
+
{
|
2
|
+
"private_key_id": "cbedb7568906086cab57859bbfc1748749cc46c4",
|
3
|
+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIICdwIBADANBgkqhkiG9w0BAQEcAASCAmEwggJdAgEAAoGBAKizy6B+aJ0Wua0e\njZ3pkHV0a2Ce1prJGhzGL5NpkbUjk6J11Kwp1yvPikTwALyy4PtUIZ+23D/unVRM\nHlKa2MkHIGjJg+mykX5Bd7eRJOxdJ0iu+eRWh7HiH+mdDntHwaz4xXihJBog71qS\n+9N+r2hy1hicybechchMiXHhmWPbAgMBAAECgYEAnSzeI4qCZxEcLtnPcXeBWpz7\nycpTAWUpycMvsjTiRxR9YRhM65YT3cJ//VhqJ2S1ThOcPCt/KqViuX4tpiKUo7qA\nH1AI9APbTo66wiGpgy+qG0wPJkKIQC8PpITNNcHqcbbAsIr3/XQduihsqxP2W2mT\na0nk5XJghs1Wa0xt28ECQQDgMqZjVDcDQyqM+bcBKJUUc/247KusjpdK70r6sx2o\nkZJGy/w9exlM5QrB6DLpw34/p5x4MoecZ7lS3yHdmaEhAkEAwKHsV4k5SXTUp4+J\nWK6GlQVvnwc+PQdX5gzt4/gWSY0Op5EQ+YD6cC7Lkz+GzXUzvmdp35c0ahS93D1/\nZLTZewJBAIjOc3cHMNadyr5BtulPEUE0ro+EY/GlBS8lu/QlDmkJg2AOI3qEvliM\nvza58S9yKny/U5yJAPVw2cZ3ABxQHeECQDyBX8PrBURuXvE2o5RoVTtvlqziAi3X\nJaPLwdkOLqnxlX3KkgNcoM0l1amtlYDpZcRVcSs0+9TqKOyJoH8YUwsCQA4cJmv3\n119xcijXPM2HZOB5cCxTHj59MRtQlLboNZ2witDCJ20eG9AC3ZcH7csS0H9dz8Jr\nXGEoQMPD2ck4T0U\u003d\n-----END PRIVATE KEY-----\n",
|
4
|
+
"client_email": "847859579879-q8ancssppuvtv8dac0i742pslde81jgl@developer.gserviceaccount.com",
|
5
|
+
"client_id": "847859579879-q8ancssppuvtv8dac0i742pslde81jgl.apps.googleusercontent.com",
|
6
|
+
"type": "service_account"
|
7
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
{
|
2
|
+
"type": "service_account",
|
3
|
+
"private_key_id": "5985985bcdfe958895bd8d76456fe90d8484789d",
|
4
|
+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIICdwIBADANBgkqhkiG9w0BAQEcAASCAmEwggJdAgEAAoGBAKizy6B+aJ0Wua0e\njZ3pkHV0a2Ce1prJGhzGL5NpkbUjk6J11Kwp1yvPikTwALyy4PtUIZ+23D/unVRM\nHlKa2MkHIGjJg+mykX5Bd7eRJOxdJ0iu+eRWh7HiH+mdDntHwaz4xXihJBog71qS\n+9N+r2hy1hicybechchMiXHhmWPbAgMBAAECgYEAnSzeI4qCZxEcLtnPcXeBWpz7\nycpTAWUpycMvsjTiRxR9YRhM65YT3cJ//VhqJ2S1ThOcPCt/KqViuX4tpiKUo7qA\nH1AI9APbTo66wiGpgy+qG0wPJkKIQC8PpITNNcHqcbbAsIr3/XQduihsqxP2W2mT\na0nk5XJghs1Wa0xt28ECQQDgMqZjVDcDQyqM+bcBKJUUc/247KusjpdK70r6sx2o\nkZJGy/w9exlM5QrB6DLpw34/p5x4MoecZ7lS3yHdmaEhAkEAwKHsV4k5SXTUp4+J\nWK6GlQVvnwc+PQdX5gzt4/gWSY0Op5EQ+YD6cC7Lkz+GzXUzvmdp35c0ahS93D1/\nZLTZewJBAIjOc3cHMNadyr5BtulPEUE0ro+EY/GlBS8lu/QlDmkJg2AOI3qEvliM\nvza58S9yKny/U5yJAPVw2cZ3ABxQHeECQDyBX8PrBURuXvE2o5RoVTtvlqziAi3X\nJaPLwdkOLqnxlX3KkgNcoM0l1amtlYDpZcRVcSs0+9TqKOyJoH8YUwsCQA4cJmv3\n119xcijXPM2HZOB5cCxTHj59MRtQlLboNZ2witDCJ20eG9AC3ZcH7csS0H9dz8Jr\nXGEoQMPD2ck4T0U\u003d\n-----END PRIVATE KEY-----\n",
|
5
|
+
"client_email": "account-name@fluent-test-project.iam.gserviceaccount.com",
|
6
|
+
"client_id": "275859789789367827863",
|
7
|
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
8
|
+
"token_uri": "https://accounts.google.com/o/oauth2/token",
|
9
|
+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
10
|
+
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/account-name%40fluent-test-project.iam.gserviceaccount.com"
|
11
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
{
|
2
|
+
"private_key_id": "cbedb7568906086cab57859bbfc1748749cc46c4",
|
3
|
+
"private_key": "-----BEGIN PRIVATE KEY-----\nCeci n'est pas une cle\n-----END PRIVATE KEY-----\n",
|
4
|
+
"client_email": "847859579879-q8ancssppuvtv8dac0i742pslde81jgl@developer.gserviceaccount.com",
|
5
|
+
"client_id": "847859579879-q8ancssppuvtv8dac0i742pslde81jgl.apps.googleusercontent.com",
|
6
|
+
"type": "service_account"
|
7
|
+
}
|
8
|
+
|
@@ -0,0 +1,297 @@
|
|
1
|
+
# Copyright 2014 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative 'base_test'
|
16
|
+
|
17
|
+
# Unit tests for Google Cloud Logging plugin
|
18
|
+
class GoogleCloudOutputTest < Test::Unit::TestCase
|
19
|
+
include BaseTest
|
20
|
+
|
21
|
+
def test_configure_use_grpc
|
22
|
+
setup_gce_metadata_stubs
|
23
|
+
d = create_driver
|
24
|
+
assert_false d.instance.instance_variable_get(:@use_grpc)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_client_400
|
28
|
+
setup_gce_metadata_stubs
|
29
|
+
# The API Client should not retry this and the plugin should consume
|
30
|
+
# the exception.
|
31
|
+
stub_request(:post, uri_for_log(COMPUTE_PARAMS))
|
32
|
+
.to_return(status: 400, body: 'Bad Request')
|
33
|
+
d = create_driver
|
34
|
+
d.emit('message' => log_entry(0))
|
35
|
+
d.run
|
36
|
+
assert_requested(:post, uri_for_log(COMPUTE_PARAMS), times: 1)
|
37
|
+
end
|
38
|
+
|
39
|
+
# All credentials errors resolve to a 401.
|
40
|
+
def test_client_401
|
41
|
+
setup_gce_metadata_stubs
|
42
|
+
stub_request(:post, uri_for_log(COMPUTE_PARAMS))
|
43
|
+
.to_return(status: 401, body: 'Unauthorized')
|
44
|
+
d = create_driver
|
45
|
+
d.emit('message' => log_entry(0))
|
46
|
+
begin
|
47
|
+
d.run
|
48
|
+
rescue Google::Apis::AuthorizationError => error
|
49
|
+
assert_equal 'Unauthorized', error.message
|
50
|
+
end
|
51
|
+
assert_requested(:post, uri_for_log(COMPUTE_PARAMS), times: 2)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_server_error
|
55
|
+
setup_gce_metadata_stubs
|
56
|
+
# The API client should retry this once, then throw an exception which
|
57
|
+
# gets propagated through the plugin.
|
58
|
+
stub_request(:post, uri_for_log(COMPUTE_PARAMS))
|
59
|
+
.to_return(status: 500, body: 'Server Error')
|
60
|
+
d = create_driver
|
61
|
+
d.emit('message' => log_entry(0))
|
62
|
+
exception_count = 0
|
63
|
+
begin
|
64
|
+
d.run
|
65
|
+
rescue Google::Apis::ServerError => error
|
66
|
+
assert_equal 'Server error', error.message
|
67
|
+
exception_count += 1
|
68
|
+
end
|
69
|
+
assert_requested(:post, uri_for_log(COMPUTE_PARAMS), times: 1)
|
70
|
+
assert_equal 1, exception_count
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_http_request_from_record_with_referer_nil
|
74
|
+
setup_gce_metadata_stubs
|
75
|
+
setup_logging_stubs do
|
76
|
+
d = create_driver
|
77
|
+
d.emit('httpRequest' => http_request_message_with_nil_referer)
|
78
|
+
d.run
|
79
|
+
end
|
80
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'httpRequest') do |entry|
|
81
|
+
# The request we send to Logging API has json like:
|
82
|
+
# "httpRequest": { "referer": null }, but eventually the stored LogEntry
|
83
|
+
# would be "httpRequest": {}, since 'referer' is defined as a string in
|
84
|
+
# the proto.
|
85
|
+
assert_equal http_request_message_with_nil_referer,
|
86
|
+
entry['httpRequest'], entry
|
87
|
+
assert_nil get_fields(entry['structPayload'])['httpRequest'], entry
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# This test looks similar between the grpc and non-grpc paths except that when
|
92
|
+
# parsing "105", the grpc path responds with "DEBUG", while the non-grpc path
|
93
|
+
# responds with "100".
|
94
|
+
#
|
95
|
+
# TODO(lingshi) consolidate the tests between the grpc path and the non-grpc
|
96
|
+
# path, or at least split into two tests, one with string severities and one
|
97
|
+
# with numeric severities.
|
98
|
+
def test_severities
|
99
|
+
setup_gce_metadata_stubs
|
100
|
+
expected_severity = []
|
101
|
+
emit_index = 0
|
102
|
+
setup_logging_stubs do
|
103
|
+
d = create_driver
|
104
|
+
# Array of pairs of [parsed_severity, expected_severity]
|
105
|
+
[%w(INFO INFO), %w(warn WARNING), %w(E ERROR), %w(BLAH DEFAULT),
|
106
|
+
['105', 100], ['', 'DEFAULT']].each do |sev|
|
107
|
+
d.emit('message' => log_entry(emit_index), 'severity' => sev[0])
|
108
|
+
expected_severity.push(sev[1])
|
109
|
+
emit_index += 1
|
110
|
+
end
|
111
|
+
d.run
|
112
|
+
end
|
113
|
+
verify_index = 0
|
114
|
+
verify_log_entries(emit_index, COMPUTE_PARAMS) do |entry|
|
115
|
+
assert_equal expected_severity[verify_index],
|
116
|
+
entry['metadata']['severity'], entry
|
117
|
+
verify_index += 1
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_parse_severity
|
122
|
+
test_obj = Fluent::GoogleCloudOutput.new
|
123
|
+
|
124
|
+
# known severities should translate to themselves, regardless of case
|
125
|
+
%w(DEFAULT DEBUG INFO NOTICE WARNING ERROR CRITICAL ALERT EMERGENCY).each \
|
126
|
+
do |severity|
|
127
|
+
assert_equal(severity, test_obj.parse_severity(severity))
|
128
|
+
assert_equal(severity, test_obj.parse_severity(severity.downcase))
|
129
|
+
assert_equal(severity, test_obj.parse_severity(severity.capitalize))
|
130
|
+
end
|
131
|
+
|
132
|
+
# numeric levels
|
133
|
+
assert_equal(0, test_obj.parse_severity('0'))
|
134
|
+
assert_equal(100, test_obj.parse_severity('100'))
|
135
|
+
assert_equal(200, test_obj.parse_severity('200'))
|
136
|
+
assert_equal(300, test_obj.parse_severity('300'))
|
137
|
+
assert_equal(400, test_obj.parse_severity('400'))
|
138
|
+
assert_equal(500, test_obj.parse_severity('500'))
|
139
|
+
assert_equal(600, test_obj.parse_severity('600'))
|
140
|
+
assert_equal(700, test_obj.parse_severity('700'))
|
141
|
+
assert_equal(800, test_obj.parse_severity('800'))
|
142
|
+
|
143
|
+
assert_equal(800, test_obj.parse_severity('900'))
|
144
|
+
assert_equal(0, test_obj.parse_severity('1'))
|
145
|
+
assert_equal(100, test_obj.parse_severity('105'))
|
146
|
+
assert_equal(400, test_obj.parse_severity('420'))
|
147
|
+
assert_equal(700, test_obj.parse_severity('799'))
|
148
|
+
|
149
|
+
assert_equal(100, test_obj.parse_severity('105 '))
|
150
|
+
assert_equal(100, test_obj.parse_severity(' 105'))
|
151
|
+
assert_equal(100, test_obj.parse_severity(' 105 '))
|
152
|
+
|
153
|
+
assert_equal('DEFAULT', test_obj.parse_severity('-100'))
|
154
|
+
assert_equal('DEFAULT', test_obj.parse_severity('105 100'))
|
155
|
+
|
156
|
+
# synonyms for existing log levels
|
157
|
+
assert_equal('ERROR', test_obj.parse_severity('ERR'))
|
158
|
+
assert_equal('WARNING', test_obj.parse_severity('WARN'))
|
159
|
+
assert_equal('CRITICAL', test_obj.parse_severity('FATAL'))
|
160
|
+
assert_equal('DEBUG', test_obj.parse_severity('TRACE'))
|
161
|
+
assert_equal('DEBUG', test_obj.parse_severity('TRACE_INT'))
|
162
|
+
assert_equal('DEBUG', test_obj.parse_severity('FINE'))
|
163
|
+
assert_equal('DEBUG', test_obj.parse_severity('FINER'))
|
164
|
+
assert_equal('DEBUG', test_obj.parse_severity('FINEST'))
|
165
|
+
|
166
|
+
# single letters.
|
167
|
+
assert_equal('DEBUG', test_obj.parse_severity('D'))
|
168
|
+
assert_equal('INFO', test_obj.parse_severity('I'))
|
169
|
+
assert_equal('NOTICE', test_obj.parse_severity('N'))
|
170
|
+
assert_equal('WARNING', test_obj.parse_severity('W'))
|
171
|
+
assert_equal('ERROR', test_obj.parse_severity('E'))
|
172
|
+
assert_equal('CRITICAL', test_obj.parse_severity('C'))
|
173
|
+
assert_equal('ALERT', test_obj.parse_severity('A'))
|
174
|
+
assert_equal('ERROR', test_obj.parse_severity('e'))
|
175
|
+
|
176
|
+
assert_equal('DEFAULT', test_obj.parse_severity('x'))
|
177
|
+
assert_equal('DEFAULT', test_obj.parse_severity('-'))
|
178
|
+
|
179
|
+
# leading/trailing whitespace should be stripped
|
180
|
+
assert_equal('ERROR', test_obj.parse_severity(' ERROR'))
|
181
|
+
assert_equal('ERROR', test_obj.parse_severity('ERROR '))
|
182
|
+
assert_equal('ERROR', test_obj.parse_severity(' ERROR '))
|
183
|
+
assert_equal('ERROR', test_obj.parse_severity("\t ERROR "))
|
184
|
+
|
185
|
+
# space in the middle should not be stripped.
|
186
|
+
assert_equal('DEFAULT', test_obj.parse_severity('ER ROR'))
|
187
|
+
|
188
|
+
# anything else should translate to 'DEFAULT'
|
189
|
+
assert_equal('DEFAULT', test_obj.parse_severity(''))
|
190
|
+
assert_equal('DEFAULT', test_obj.parse_severity('garbage'))
|
191
|
+
assert_equal('DEFAULT', test_obj.parse_severity('er'))
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_non_integer_timestamp
|
195
|
+
setup_gce_metadata_stubs
|
196
|
+
time = Time.now
|
197
|
+
[
|
198
|
+
{ 'seconds' => nil, 'nanos' => nil },
|
199
|
+
{ 'seconds' => nil, 'nanos' => time.tv_nsec },
|
200
|
+
{ 'seconds' => 'seconds', 'nanos' => time.tv_nsec },
|
201
|
+
{ 'seconds' => time.tv_sec, 'nanos' => 'nanos' },
|
202
|
+
{ 'seconds' => time.tv_sec, 'nanos' => nil }
|
203
|
+
].each do |timestamp|
|
204
|
+
setup_logging_stubs do
|
205
|
+
d = create_driver
|
206
|
+
@logs_sent = []
|
207
|
+
d.emit('message' => log_entry(0), 'timestamp' => timestamp)
|
208
|
+
d.run
|
209
|
+
end
|
210
|
+
verify_log_entries(1, COMPUTE_PARAMS) do |entry|
|
211
|
+
assert_equal timestamp, entry['metadata']['timestamp'],
|
212
|
+
"Test with timestamp '#{timestamp}' failed for " \
|
213
|
+
"entry: '#{entry}'."
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
def rename_key(hash, old_key, new_key)
|
221
|
+
hash.merge(new_key => hash[old_key]).reject { |k, _| k == old_key }
|
222
|
+
end
|
223
|
+
|
224
|
+
# The REST path uses old bindings that were generated prior to the field
|
225
|
+
# rename, and has to use the old name, which is 'validatedWithOriginServer'.
|
226
|
+
def http_request_message
|
227
|
+
rename_key(super, 'cacheValidatedWithOriginServer',
|
228
|
+
'validatedWithOriginServer')
|
229
|
+
end
|
230
|
+
|
231
|
+
# Set up http stubs to mock the external calls.
|
232
|
+
def setup_logging_stubs(override_stub_params = nil)
|
233
|
+
stub_params = override_stub_params || \
|
234
|
+
[COMPUTE_PARAMS, VMENGINE_PARAMS, CONTAINER_FROM_TAG_PARAMS,
|
235
|
+
CONTAINER_FROM_METADATA_PARAMS, CLOUDFUNCTIONS_PARAMS,
|
236
|
+
CUSTOM_PARAMS, EC2_PARAMS]
|
237
|
+
stub_params.each do |params|
|
238
|
+
stub_request(:post, uri_for_log(params)).to_return do |request|
|
239
|
+
log_name = "projects/#{params[:project_id]}/logs/#{params[:log_name]}"
|
240
|
+
@logs_sent << JSON.parse(request.body).merge('logName' => log_name)
|
241
|
+
{ body: '' }
|
242
|
+
end
|
243
|
+
end
|
244
|
+
yield
|
245
|
+
end
|
246
|
+
|
247
|
+
# Create a Fluentd output test driver with the Google Cloud Output plugin.
|
248
|
+
def create_driver(conf = APPLICATION_DEFAULT_CONFIG, tag = 'test')
|
249
|
+
Fluent::Test::BufferedOutputTestDriver.new(
|
250
|
+
Fluent::GoogleCloudOutput, tag).configure(conf, true)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Verify the number and the content of the log entries match the expectation.
|
254
|
+
# The caller can optionally provide a block which is called for each entry.
|
255
|
+
def verify_log_entries(n, params, payload_type = 'textPayload', &block)
|
256
|
+
verify_json_log_entries(n, params, payload_type, &block)
|
257
|
+
end
|
258
|
+
|
259
|
+
# For an optional field with default values, Protobuf omits the field when it
|
260
|
+
# is deserialized to json. So we need to add an extra check for gRPC which
|
261
|
+
# uses Protobuf.
|
262
|
+
#
|
263
|
+
# An optional block can be passed in if we need to assert something other than
|
264
|
+
# a plain equal. e.g. assert_in_delta.
|
265
|
+
def assert_equal_with_default(field, expected_value, _default_value, entry)
|
266
|
+
if block_given?
|
267
|
+
yield
|
268
|
+
else
|
269
|
+
assert_equal expected_value, field, entry
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# Get the fields of the struct payload.
|
274
|
+
def get_fields(struct_payload)
|
275
|
+
struct_payload
|
276
|
+
end
|
277
|
+
|
278
|
+
# Get the value of a struct field.
|
279
|
+
def get_struct(field)
|
280
|
+
field
|
281
|
+
end
|
282
|
+
|
283
|
+
# Get the value of a string field.
|
284
|
+
def get_string(field)
|
285
|
+
field
|
286
|
+
end
|
287
|
+
|
288
|
+
# Get the value of a number field.
|
289
|
+
def get_number(field)
|
290
|
+
field
|
291
|
+
end
|
292
|
+
|
293
|
+
# The null value.
|
294
|
+
def null_value
|
295
|
+
nil
|
296
|
+
end
|
297
|
+
end
|