fluent-plugin-vadimberezniker-gcp 0.1.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/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
|