fluent-plugin-vadimberezniker-gcp 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTING +24 -0
- data/Gemfile +3 -0
- data/LICENSE +201 -0
- data/README.rdoc +53 -0
- data/Rakefile +43 -0
- data/fluent-plugin-google-cloud.gemspec +43 -0
- data/fluent-plugin-vadimberezniker-gcp-0.13.2.gem +0 -0
- data/lib/fluent/plugin/common.rb +399 -0
- data/lib/fluent/plugin/filter_add_insert_ids.rb +86 -0
- data/lib/fluent/plugin/filter_analyze_config.rb +410 -0
- data/lib/fluent/plugin/in_object_space_dump.rb +62 -0
- data/lib/fluent/plugin/monitoring.rb +265 -0
- data/lib/fluent/plugin/out_google_cloud.rb +2209 -0
- data/lib/fluent/plugin/statusz.rb +124 -0
- data/test/helper.rb +46 -0
- data/test/plugin/asserts.rb +87 -0
- data/test/plugin/base_test.rb +2680 -0
- data/test/plugin/constants.rb +1114 -0
- data/test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12 +0 -0
- data/test/plugin/data/credentials.json +7 -0
- data/test/plugin/data/google-fluentd-baseline.conf +24 -0
- data/test/plugin/data/google-fluentd-custom.conf +40 -0
- data/test/plugin/data/iam-credentials.json +11 -0
- data/test/plugin/data/invalid_credentials.json +8 -0
- data/test/plugin/data/new-style-credentials.json +12 -0
- data/test/plugin/test_driver.rb +56 -0
- data/test/plugin/test_filter_add_insert_ids.rb +137 -0
- data/test/plugin/test_filter_analyze_config.rb +257 -0
- data/test/plugin/test_out_google_cloud.rb +465 -0
- data/test/plugin/test_out_google_cloud_grpc.rb +478 -0
- data/test/plugin/utils.rb +148 -0
- metadata +347 -0
@@ -0,0 +1,478 @@
|
|
1
|
+
# Copyright 2016 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 'grpc'
|
16
|
+
|
17
|
+
require_relative 'base_test'
|
18
|
+
require_relative 'test_driver'
|
19
|
+
|
20
|
+
# Unit tests for Google Cloud Logging plugin
|
21
|
+
class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
|
22
|
+
include BaseTest
|
23
|
+
|
24
|
+
def test_configure_use_grpc
|
25
|
+
setup_gce_metadata_stubs
|
26
|
+
d = create_driver
|
27
|
+
assert_true d.instance.instance_variable_get(:@use_grpc)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_user_agent
|
31
|
+
setup_gce_metadata_stubs
|
32
|
+
|
33
|
+
user_agent = nil
|
34
|
+
# Record user agent when creating a GRPC::Core::Channel.
|
35
|
+
GRPC::Core::Channel.class_eval do
|
36
|
+
old_initialize = instance_method(:initialize)
|
37
|
+
# Suppress redefine warning (https://bugs.ruby-lang.org/issues/17055).
|
38
|
+
alias_method :initialize, :initialize
|
39
|
+
define_method(:initialize) do |url, args, creds|
|
40
|
+
user_agent = args['grpc.primary_user_agent']
|
41
|
+
old_initialize.bind(self).call(url, args, creds)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
d = create_driver
|
46
|
+
d.instance.send :init_api_client
|
47
|
+
assert_match Regexp.new("#{Fluent::GoogleCloudOutput::PLUGIN_NAME}/" \
|
48
|
+
"#{Fluent::GoogleCloudOutput::PLUGIN_VERSION}"), \
|
49
|
+
user_agent
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_client_error
|
53
|
+
setup_gce_metadata_stubs
|
54
|
+
{
|
55
|
+
GRPC::Core::StatusCodes::CANCELLED => 'Cancelled',
|
56
|
+
GRPC::Core::StatusCodes::UNKNOWN => 'Unknown',
|
57
|
+
GRPC::Core::StatusCodes::INVALID_ARGUMENT => 'InvalidArgument',
|
58
|
+
GRPC::Core::StatusCodes::NOT_FOUND => 'NotFound',
|
59
|
+
GRPC::Core::StatusCodes::PERMISSION_DENIED => 'PermissionDenied',
|
60
|
+
GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED => 'ResourceExhausted',
|
61
|
+
GRPC::Core::StatusCodes::FAILED_PRECONDITION => 'FailedPrecondition',
|
62
|
+
GRPC::Core::StatusCodes::ABORTED => 'Aborted',
|
63
|
+
GRPC::Core::StatusCodes::UNAUTHENTICATED => 'Unauthenticated'
|
64
|
+
}.each_with_index do |(code, message), index|
|
65
|
+
setup_logging_stubs(nil, code, message) do
|
66
|
+
d = create_driver(USE_GRPC_CONFIG, 'test')
|
67
|
+
# The API Client should not retry this and the plugin should consume the
|
68
|
+
# exception.
|
69
|
+
d.emit('message' => log_entry(0))
|
70
|
+
d.run
|
71
|
+
end
|
72
|
+
assert_equal 1, @failed_attempts.size, "Index #{index} failed."
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_invalid_error
|
77
|
+
setup_gce_metadata_stubs
|
78
|
+
setup_logging_stubs(RuntimeError.new('Some non-gRPC error')) do
|
79
|
+
d = create_driver(USE_GRPC_CONFIG, 'test')
|
80
|
+
# The API Client should not retry this and the plugin should consume the
|
81
|
+
# exception.
|
82
|
+
d.emit('message' => log_entry(0))
|
83
|
+
d.run
|
84
|
+
end
|
85
|
+
assert_equal 1, @failed_attempts.size
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_partial_success
|
89
|
+
setup_gce_metadata_stubs
|
90
|
+
clear_metrics
|
91
|
+
setup_logging_stubs(
|
92
|
+
GRPC::PermissionDenied.new('User not authorized.',
|
93
|
+
PARTIAL_SUCCESS_GRPC_METADATA)
|
94
|
+
) do
|
95
|
+
# The API Client should not retry this and the plugin should consume
|
96
|
+
# the exception.
|
97
|
+
d = create_driver(ENABLE_PROMETHEUS_CONFIG)
|
98
|
+
4.times do |i|
|
99
|
+
d.emit('message' => log_entry(i.to_s))
|
100
|
+
end
|
101
|
+
d.run
|
102
|
+
assert_prometheus_metric_value(
|
103
|
+
:stackdriver_successful_requests_count, 1,
|
104
|
+
'agent.googleapis.com/agent', OpenCensus::Stats::Aggregation::Sum, d,
|
105
|
+
grpc: use_grpc, code: GRPC::Core::StatusCodes::OK
|
106
|
+
)
|
107
|
+
assert_prometheus_metric_value(
|
108
|
+
:stackdriver_failed_requests_count, 0,
|
109
|
+
'agent.googleapis.com/agent', OpenCensus::Stats::Aggregation::Sum, d,
|
110
|
+
grpc: use_grpc, code: GRPC::Core::StatusCodes::PERMISSION_DENIED
|
111
|
+
)
|
112
|
+
assert_prometheus_metric_value(
|
113
|
+
:stackdriver_ingested_entries_count, 1,
|
114
|
+
'agent.googleapis.com/agent', OpenCensus::Stats::Aggregation::Sum, d,
|
115
|
+
grpc: use_grpc, code: GRPC::Core::StatusCodes::OK
|
116
|
+
)
|
117
|
+
assert_prometheus_metric_value(
|
118
|
+
:stackdriver_dropped_entries_count, 2,
|
119
|
+
'agent.googleapis.com/agent', OpenCensus::Stats::Aggregation::Sum, d,
|
120
|
+
grpc: use_grpc, code: GRPC::Core::StatusCodes::INVALID_ARGUMENT
|
121
|
+
)
|
122
|
+
assert_prometheus_metric_value(
|
123
|
+
:stackdriver_dropped_entries_count, 1,
|
124
|
+
'agent.googleapis.com/agent', OpenCensus::Stats::Aggregation::Sum, d,
|
125
|
+
grpc: use_grpc, code: GRPC::Core::StatusCodes::PERMISSION_DENIED
|
126
|
+
)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_non_api_error
|
131
|
+
setup_gce_metadata_stubs
|
132
|
+
clear_metrics
|
133
|
+
setup_logging_stubs(
|
134
|
+
GRPC::InvalidArgument.new('internal client error',
|
135
|
+
PARSE_ERROR_GRPC_METADATA)
|
136
|
+
) do
|
137
|
+
# The API Client should not retry this and the plugin should consume
|
138
|
+
# the exception.
|
139
|
+
d = create_driver(ENABLE_PROMETHEUS_CONFIG)
|
140
|
+
d.emit('message' => log_entry(0))
|
141
|
+
d.run
|
142
|
+
assert_prometheus_metric_value(
|
143
|
+
:stackdriver_successful_requests_count, 0,
|
144
|
+
'agent.googleapis.com/agent', OpenCensus::Stats::Aggregation::Sum, d,
|
145
|
+
grpc: use_grpc, code: GRPC::Core::StatusCodes::OK
|
146
|
+
)
|
147
|
+
assert_prometheus_metric_value(
|
148
|
+
:stackdriver_failed_requests_count, 1,
|
149
|
+
'agent.googleapis.com/agent', OpenCensus::Stats::Aggregation::Sum, d,
|
150
|
+
grpc: use_grpc, code: GRPC::Core::StatusCodes::INVALID_ARGUMENT
|
151
|
+
)
|
152
|
+
assert_prometheus_metric_value(
|
153
|
+
:stackdriver_ingested_entries_count, 0,
|
154
|
+
'agent.googleapis.com/agent', OpenCensus::Stats::Aggregation::Sum, d,
|
155
|
+
grpc: use_grpc, code: GRPC::Core::StatusCodes::OK
|
156
|
+
)
|
157
|
+
assert_prometheus_metric_value(
|
158
|
+
:stackdriver_dropped_entries_count, 1,
|
159
|
+
'agent.googleapis.com/agent', OpenCensus::Stats::Aggregation::Sum, d,
|
160
|
+
grpc: use_grpc, code: GRPC::Core::StatusCodes::INVALID_ARGUMENT
|
161
|
+
)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_server_error
|
166
|
+
setup_gce_metadata_stubs
|
167
|
+
{
|
168
|
+
GRPC::Core::StatusCodes::DEADLINE_EXCEEDED => 'DeadlineExceeded',
|
169
|
+
GRPC::Core::StatusCodes::UNIMPLEMENTED => 'Unimplemented',
|
170
|
+
GRPC::Core::StatusCodes::INTERNAL => 'Internal',
|
171
|
+
GRPC::Core::StatusCodes::UNAVAILABLE => 'Unavailable'
|
172
|
+
}.each_with_index do |(code, message), index|
|
173
|
+
exception_count = 0
|
174
|
+
setup_logging_stubs(nil, code, message) do
|
175
|
+
d = create_driver(USE_GRPC_CONFIG, 'test')
|
176
|
+
# The API client should retry this once, then throw an exception which
|
177
|
+
# gets propagated through the plugin
|
178
|
+
d.emit('message' => log_entry(0))
|
179
|
+
begin
|
180
|
+
d.run
|
181
|
+
rescue GRPC::BadStatus => e
|
182
|
+
assert_equal "#{code}:#{message}", e.message
|
183
|
+
exception_count += 1
|
184
|
+
end
|
185
|
+
end
|
186
|
+
assert_equal 1, @failed_attempts.size, "Index #{index} failed."
|
187
|
+
assert_equal 1, exception_count, "Index #{index} failed."
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# This test looks similar between the grpc and non-grpc paths except that when
|
192
|
+
# parsing "105", the grpc path responds with "DEBUG", while the non-grpc path
|
193
|
+
# responds with "100".
|
194
|
+
#
|
195
|
+
# TODO(lingshi) consolidate the tests between the grpc path and the non-grpc
|
196
|
+
# path, or at least split into two tests, one with string severities and one
|
197
|
+
# with numeric severities.
|
198
|
+
def test_severities
|
199
|
+
setup_gce_metadata_stubs
|
200
|
+
expected_severity = []
|
201
|
+
emit_index = 0
|
202
|
+
setup_logging_stubs do
|
203
|
+
d = create_driver
|
204
|
+
# Array of pairs of [parsed_severity, expected_severity]
|
205
|
+
[%w[INFO INFO], %w[warn WARNING], %w[E ERROR], %w[BLAH DEFAULT],
|
206
|
+
%w[105 DEBUG], ['', 'DEFAULT']].each do |sev|
|
207
|
+
d.emit('message' => log_entry(emit_index), 'severity' => sev[0])
|
208
|
+
expected_severity.push(sev[1])
|
209
|
+
emit_index += 1
|
210
|
+
end
|
211
|
+
d.run
|
212
|
+
end
|
213
|
+
verify_index = 0
|
214
|
+
verify_log_entries(emit_index, COMPUTE_PARAMS) do |entry|
|
215
|
+
assert_equal_with_default(entry['severity'],
|
216
|
+
expected_severity[verify_index],
|
217
|
+
'DEFAULT', entry)
|
218
|
+
verify_index += 1
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# TODO(qingling128): Verify if we need this on the REST side and add it if
|
223
|
+
# needed.
|
224
|
+
def test_struct_payload_non_utf8_log
|
225
|
+
setup_gce_metadata_stubs
|
226
|
+
setup_logging_stubs do
|
227
|
+
d = create_driver
|
228
|
+
d.emit('msg' => log_entry(0),
|
229
|
+
'normal_key' => "test#{non_utf8_character}non utf8",
|
230
|
+
"non_utf8#{non_utf8_character}key" => 5000,
|
231
|
+
'nested_struct' => { "non_utf8#{non_utf8_character}key" => \
|
232
|
+
"test#{non_utf8_character}non utf8" },
|
233
|
+
'null_field' => nil)
|
234
|
+
d.run
|
235
|
+
end
|
236
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
|
237
|
+
fields = entry['jsonPayload']
|
238
|
+
assert_equal 5, fields.size, entry
|
239
|
+
assert_equal 'test log entry 0', fields['msg'], entry
|
240
|
+
assert_equal 'test non utf8', fields['normal_key'], entry
|
241
|
+
assert_equal 5000, fields['non_utf8 key'], entry
|
242
|
+
assert_equal 'test non utf8', fields['nested_struct']['non_utf8 key'],
|
243
|
+
entry
|
244
|
+
assert_nil fields['null_field'], entry
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def test_non_integer_timestamp
|
249
|
+
setup_gce_metadata_stubs
|
250
|
+
time = Time.now
|
251
|
+
{
|
252
|
+
{ 'seconds' => nil, 'nanos' => nil } => nil,
|
253
|
+
{ 'seconds' => nil, 'nanos' => time.tv_nsec } => nil,
|
254
|
+
{ 'seconds' => 'seconds', 'nanos' => time.tv_nsec } => nil,
|
255
|
+
{ 'seconds' => time.tv_sec, 'nanos' => 'nanos' } => \
|
256
|
+
time.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
257
|
+
{ 'seconds' => time.tv_sec, 'nanos' => nil } => \
|
258
|
+
time.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
259
|
+
}.each do |input, expected|
|
260
|
+
setup_logging_stubs do
|
261
|
+
d = create_driver
|
262
|
+
@logs_sent = []
|
263
|
+
d.emit('message' => log_entry(0), 'timestamp' => input)
|
264
|
+
d.run
|
265
|
+
end
|
266
|
+
verify_log_entries(1, COMPUTE_PARAMS) do |entry|
|
267
|
+
assert_equal expected, entry['timestamp'], 'Test with timestamp ' \
|
268
|
+
"'#{input}' failed for entry: '#{entry}'."
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
|
275
|
+
WriteLogEntriesRequest = Google::Cloud::Logging::V2::WriteLogEntriesRequest
|
276
|
+
WriteLogEntriesResponse = Google::Cloud::Logging::V2::WriteLogEntriesResponse
|
277
|
+
|
278
|
+
USE_GRPC_CONFIG = %(
|
279
|
+
use_grpc true
|
280
|
+
).freeze
|
281
|
+
|
282
|
+
# The conversions from user input to output.
|
283
|
+
def latency_conversion
|
284
|
+
{
|
285
|
+
'32 s' => '32s',
|
286
|
+
'32s' => '32s',
|
287
|
+
'0.32s' => '0.320s',
|
288
|
+
' 123 s ' => '123s',
|
289
|
+
'1.3442 s' => '1.344200s',
|
290
|
+
|
291
|
+
# Test whitespace.
|
292
|
+
# \t: tab. \r: carriage return. \n: line break.
|
293
|
+
# \v: vertical whitespace. \f: form feed.
|
294
|
+
"\t123.5\ts\t" => '123.500s',
|
295
|
+
"\r123.5\rs\r" => '123.500s',
|
296
|
+
"\n123.5\ns\n" => '123.500s',
|
297
|
+
"\v123.5\vs\v" => '123.500s',
|
298
|
+
"\f123.5\fs\f" => '123.500s',
|
299
|
+
"\r123.5\ts\f" => '123.500s'
|
300
|
+
}
|
301
|
+
end
|
302
|
+
|
303
|
+
# Create a Fluentd output test driver with the Google Cloud Output plugin with
|
304
|
+
# grpc enabled. The signature of this method is different between the grpc
|
305
|
+
# path and the non-grpc path. For grpc, an additional grpc stub class can be
|
306
|
+
# passed in to construct the mock used by the test driver.
|
307
|
+
def create_driver(conf = APPLICATION_DEFAULT_CONFIG,
|
308
|
+
tag = 'test',
|
309
|
+
multi_tags = false)
|
310
|
+
conf += USE_GRPC_CONFIG
|
311
|
+
driver = if multi_tags
|
312
|
+
Fluent::Test::MultiTagBufferedOutputTestDriver.new(
|
313
|
+
GoogleCloudOutputWithGRPCMock.new(@grpc_stub)
|
314
|
+
)
|
315
|
+
else
|
316
|
+
Fluent::Test::BufferedOutputTestDriver.new(
|
317
|
+
GoogleCloudOutputWithGRPCMock.new(@grpc_stub), tag
|
318
|
+
)
|
319
|
+
end
|
320
|
+
driver.configure(conf, true)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Google Cloud Fluent output stub with grpc mock.
|
324
|
+
class GoogleCloudOutputWithGRPCMock < Fluent::GoogleCloudOutput
|
325
|
+
def initialize(grpc_stub)
|
326
|
+
super()
|
327
|
+
@grpc_stub = grpc_stub
|
328
|
+
end
|
329
|
+
|
330
|
+
def api_client
|
331
|
+
@grpc_stub
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# GRPC logging mock that successfully logs the records.
|
336
|
+
class GRPCLoggingMockService <
|
337
|
+
Google::Cloud::Logging::V2::LoggingService::Client
|
338
|
+
def initialize(requests_received)
|
339
|
+
super()
|
340
|
+
@requests_received = requests_received
|
341
|
+
end
|
342
|
+
|
343
|
+
def write_log_entries(entries:,
|
344
|
+
log_name: nil,
|
345
|
+
resource: nil,
|
346
|
+
labels: nil,
|
347
|
+
partial_success: nil)
|
348
|
+
request = Google::Apis::LoggingV2::WriteLogEntriesRequest.new(
|
349
|
+
log_name: log_name,
|
350
|
+
resource: resource,
|
351
|
+
labels: labels,
|
352
|
+
entries: entries,
|
353
|
+
partial_success: partial_success
|
354
|
+
)
|
355
|
+
@requests_received << request
|
356
|
+
WriteLogEntriesResponse.new
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
# GRPC logging mock that fails and returns server side or client side errors.
|
361
|
+
class GRPCLoggingMockFailingService <
|
362
|
+
Google::Cloud::Logging::V2::LoggingService::Client
|
363
|
+
def initialize(error, failed_attempts)
|
364
|
+
super()
|
365
|
+
@error = error
|
366
|
+
@failed_attempts = failed_attempts
|
367
|
+
end
|
368
|
+
|
369
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
370
|
+
def write_log_entries(entries:,
|
371
|
+
log_name: nil,
|
372
|
+
resource: nil,
|
373
|
+
labels: nil,
|
374
|
+
partial_success: nil)
|
375
|
+
@failed_attempts << 1
|
376
|
+
begin
|
377
|
+
raise @error
|
378
|
+
rescue StandardError
|
379
|
+
# Google::Cloud::Error will wrap the latest thrown exception as @cause.
|
380
|
+
raise Google::Cloud::Error, 'This test message does not matter.'
|
381
|
+
end
|
382
|
+
end
|
383
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
384
|
+
end
|
385
|
+
|
386
|
+
# Set up grpc stubs to mock the external calls.
|
387
|
+
def setup_logging_stubs(error = nil, code = nil, message = 'some message')
|
388
|
+
if error.nil? && (code.nil? || code.zero?)
|
389
|
+
@requests_sent = []
|
390
|
+
@grpc_stub = GRPCLoggingMockService.new(@requests_sent)
|
391
|
+
else
|
392
|
+
@failed_attempts = []
|
393
|
+
# Only fall back to constructing an error with code and message if no
|
394
|
+
# error is passed in.
|
395
|
+
error ||= GRPC::BadStatus.new_status_exception(code, message)
|
396
|
+
@grpc_stub = GRPCLoggingMockFailingService.new(error, @failed_attempts)
|
397
|
+
end
|
398
|
+
yield
|
399
|
+
end
|
400
|
+
|
401
|
+
# Whether this is the grpc path
|
402
|
+
def use_grpc
|
403
|
+
true
|
404
|
+
end
|
405
|
+
|
406
|
+
# The OK status code for the grpc path.
|
407
|
+
def ok_status_code
|
408
|
+
0
|
409
|
+
end
|
410
|
+
|
411
|
+
# A client side error status code for the grpc path.
|
412
|
+
def client_error_status_code
|
413
|
+
16
|
414
|
+
end
|
415
|
+
|
416
|
+
# A server side error status code for the grpc path.
|
417
|
+
def server_error_status_code
|
418
|
+
13
|
419
|
+
end
|
420
|
+
|
421
|
+
# The parent error type to expect in the mock
|
422
|
+
def mock_error_type
|
423
|
+
GRPC::BadStatus
|
424
|
+
end
|
425
|
+
|
426
|
+
# Verify the number and the content of the log entries match the expectation.
|
427
|
+
# The caller can optionally provide a block which is called for each entry.
|
428
|
+
def verify_log_entries(expected_count, params, payload_type = 'textPayload',
|
429
|
+
check_exact_entry_labels = true, &block)
|
430
|
+
@requests_sent.each do |request|
|
431
|
+
@logs_sent << {
|
432
|
+
'entries' => request.entries.map { |entry| JSON.parse(entry.to_json) },
|
433
|
+
'labels' => request.labels,
|
434
|
+
'resource' => request.resource,
|
435
|
+
'logName' => request.log_name
|
436
|
+
}
|
437
|
+
end
|
438
|
+
verify_json_log_entries(expected_count, params, payload_type,
|
439
|
+
check_exact_entry_labels, &block)
|
440
|
+
end
|
441
|
+
|
442
|
+
# Use the right single quotation mark as the sample non-utf8 character.
|
443
|
+
def non_utf8_character
|
444
|
+
[0x92].pack('C*')
|
445
|
+
end
|
446
|
+
|
447
|
+
# For an optional field with default values, Protobuf omits the field when it
|
448
|
+
# is deserialized to json. So we need to add an extra check for gRPC which
|
449
|
+
# uses Protobuf.
|
450
|
+
#
|
451
|
+
# An optional block can be passed in if we need to assert something other than
|
452
|
+
# a plain equal. e.g. assert_in_delta.
|
453
|
+
def assert_equal_with_default(field, expected_value, default_value, entry)
|
454
|
+
if expected_value == default_value
|
455
|
+
assert_nil field
|
456
|
+
elsif block_given?
|
457
|
+
yield
|
458
|
+
else
|
459
|
+
assert_equal expected_value, field, entry
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
def expected_operation_message2
|
464
|
+
# 'last' is a boolean field with false as default value. Protobuf omit
|
465
|
+
# fields with default values during deserialization.
|
466
|
+
OPERATION_MESSAGE2.reject { |k, _| k == 'last' }
|
467
|
+
end
|
468
|
+
|
469
|
+
# Parse timestamp and convert it to a hash with two keys:
|
470
|
+
# "seconds" and "nanos".
|
471
|
+
def timestamp_parse(timestamp)
|
472
|
+
parsed = Time.parse(timestamp)
|
473
|
+
{
|
474
|
+
'seconds' => parsed.tv_sec,
|
475
|
+
'nanos' => parsed.tv_nsec
|
476
|
+
}
|
477
|
+
end
|
478
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# Copyright 2020 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 'constants'
|
16
|
+
|
17
|
+
require 'prometheus/client'
|
18
|
+
require 'webmock/test_unit'
|
19
|
+
|
20
|
+
module Utils
|
21
|
+
include Constants
|
22
|
+
|
23
|
+
def delete_env_vars
|
24
|
+
# delete environment variables that googleauth uses to find credentials.
|
25
|
+
ENV.delete(CREDENTIALS_PATH_ENV_VAR)
|
26
|
+
# service account env.
|
27
|
+
ENV.delete(PRIVATE_KEY_VAR)
|
28
|
+
ENV.delete(CLIENT_EMAIL_VAR)
|
29
|
+
ENV.delete(PROJECT_ID_VAR)
|
30
|
+
# authorized_user env.
|
31
|
+
ENV.delete(CLIENT_ID_VAR)
|
32
|
+
ENV.delete(CLIENT_SECRET_VAR)
|
33
|
+
ENV.delete(REFRESH_TOKEN_VAR)
|
34
|
+
# home var, which is used to find $HOME/.gcloud/...
|
35
|
+
ENV.delete('HOME')
|
36
|
+
end
|
37
|
+
|
38
|
+
def stub_metadata_request(metadata_path, response_body)
|
39
|
+
stub_request(:get, "http://169.254.169.254/computeMetadata/v1/#{metadata_path}")
|
40
|
+
.to_return(body: response_body, status: 200,
|
41
|
+
headers: { 'Content-Length' => response_body.length })
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup_no_metadata_service_stubs
|
45
|
+
# Simulate a machine with no metadata service present
|
46
|
+
stub_request(:any, %r{http://169.254.169.254/.*})
|
47
|
+
.to_raise(Errno::EHOSTUNREACH)
|
48
|
+
end
|
49
|
+
|
50
|
+
def setup_gce_metadata_stubs
|
51
|
+
# Stub the root, used for platform detection by the plugin and 'googleauth'.
|
52
|
+
stub_request(:get, 'http://169.254.169.254')
|
53
|
+
.to_return(status: 200, headers: { 'Metadata-Flavor' => 'Google' })
|
54
|
+
|
55
|
+
# Create stubs for all the GCE metadata lookups the agent needs to make.
|
56
|
+
stub_metadata_request('project/project-id', PROJECT_ID)
|
57
|
+
stub_metadata_request('instance/zone', FULLY_QUALIFIED_ZONE)
|
58
|
+
stub_metadata_request('instance/id', VM_ID)
|
59
|
+
stub_metadata_request('instance/attributes/',
|
60
|
+
"attribute1\nattribute2\nattribute3")
|
61
|
+
|
62
|
+
# Used by 'googleauth' to fetch the default service account credentials.
|
63
|
+
stub_request(:get, %r{http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token(?:\?.*)})
|
64
|
+
.to_return(body: %({"access_token": "#{FAKE_AUTH_TOKEN}"}),
|
65
|
+
status: 200,
|
66
|
+
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
67
|
+
'Content-Type' => 'application/json' })
|
68
|
+
end
|
69
|
+
|
70
|
+
def setup_ec2_metadata_stubs
|
71
|
+
# Stub the root, used for platform detection.
|
72
|
+
stub_request(:get, 'http://169.254.169.254')
|
73
|
+
.to_return(status: 200, headers: { 'Server' => 'EC2ws' })
|
74
|
+
|
75
|
+
# Stub the identity document lookup made by the agent.
|
76
|
+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/' \
|
77
|
+
'instance-identity/document')
|
78
|
+
.to_return(body: EC2_IDENTITY_DOCUMENT, status: 200,
|
79
|
+
headers: { 'Content-Length' => EC2_IDENTITY_DOCUMENT.length })
|
80
|
+
end
|
81
|
+
|
82
|
+
def setup_auth_stubs(base_url)
|
83
|
+
# Used when loading credentials from a JSON file.
|
84
|
+
stub_request(:post, base_url)
|
85
|
+
.with(body: hash_including(grant_type: AUTH_GRANT_TYPE))
|
86
|
+
.to_return(body: %({"access_token": "#{FAKE_AUTH_TOKEN}"}),
|
87
|
+
status: 200,
|
88
|
+
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
89
|
+
'Content-Type' => 'application/json' })
|
90
|
+
|
91
|
+
stub_request(:post, base_url)
|
92
|
+
.with(body: hash_including(grant_type: 'refresh_token'))
|
93
|
+
.to_return(body: %({"access_token": "#{FAKE_AUTH_TOKEN}"}),
|
94
|
+
status: 200,
|
95
|
+
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
96
|
+
'Content-Type' => 'application/json' })
|
97
|
+
end
|
98
|
+
|
99
|
+
def setup_managed_vm_metadata_stubs
|
100
|
+
stub_metadata_request(
|
101
|
+
'instance/attributes/',
|
102
|
+
"attribute1\ngae_backend_name\ngae_backend_version\nlast_attribute"
|
103
|
+
)
|
104
|
+
stub_metadata_request('instance/attributes/gae_backend_name',
|
105
|
+
MANAGED_VM_BACKEND_NAME)
|
106
|
+
stub_metadata_request('instance/attributes/gae_backend_version',
|
107
|
+
MANAGED_VM_BACKEND_VERSION)
|
108
|
+
end
|
109
|
+
|
110
|
+
def setup_k8s_metadata_stubs(should_respond = true)
|
111
|
+
if should_respond
|
112
|
+
stub_metadata_request(
|
113
|
+
'instance/attributes/',
|
114
|
+
"attribute1\ncluster-location\ncluster-name\nlast_attribute"
|
115
|
+
)
|
116
|
+
stub_metadata_request('instance/attributes/cluster-location',
|
117
|
+
K8S_LOCATION2)
|
118
|
+
stub_metadata_request('instance/attributes/cluster-name',
|
119
|
+
K8S_CLUSTER_NAME)
|
120
|
+
else
|
121
|
+
%w[cluster-location cluster-name].each do |metadata_name|
|
122
|
+
stub_request(:get, %r{.*instance/attributes/#{metadata_name}.*})
|
123
|
+
.to_return(status: 404,
|
124
|
+
body: 'The requested URL /computeMetadata/v1/instance/' \
|
125
|
+
"attributes/#{metadata_name} was not found on this" \
|
126
|
+
' server.')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def setup_dataproc_metadata_stubs
|
132
|
+
stub_metadata_request(
|
133
|
+
'instance/attributes/',
|
134
|
+
"attribute1\ndataproc-cluster-uuid\ndataproc-cluster-name"
|
135
|
+
)
|
136
|
+
stub_metadata_request('instance/attributes/dataproc-cluster-name',
|
137
|
+
DATAPROC_CLUSTER_NAME)
|
138
|
+
stub_metadata_request('instance/attributes/dataproc-cluster-uuid',
|
139
|
+
DATAPROC_CLUSTER_UUID)
|
140
|
+
stub_metadata_request('instance/attributes/dataproc-region',
|
141
|
+
DATAPROC_REGION)
|
142
|
+
end
|
143
|
+
|
144
|
+
def clear_metrics
|
145
|
+
Prometheus::Client.registry.instance_variable_set('@metrics', {})
|
146
|
+
OpenCensus::Stats.ensure_recorder.clear_stats
|
147
|
+
end
|
148
|
+
end
|