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.
@@ -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