fluent-plugin-google-cloud 0.6.25.1 → 0.7.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.rdoc +15 -24
- data/Rakefile +2 -1
- data/fluent-plugin-google-cloud.gemspec +8 -8
- data/lib/fluent/plugin/out_google_cloud.rb +33 -36
- data/test/plugin/base_test.rb +115 -223
- data/test/plugin/constants.rb +33 -33
- metadata +14 -16
- data/lib/fluent/plugin/filter_add_insert_ids.rb +0 -99
- data/test/plugin/test_filter_add_insert_ids.rb +0 -135
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5fa418888cc895f5387d6d03b60154d4940a07d7
|
4
|
+
data.tar.gz: 06ecdf13e10c1262dabf80aedff34af14d132656
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8c1e36fdb141fc017340abcc27c39dd9989e1bda2a9dd16fdd77d8596f3b68203a022b4c221de35240546117f9ad78e67c8ed3dcf570804c62bb9459cd6676d
|
7
|
+
data.tar.gz: 1b4cc062cfbfe10db494c3ecb52499df5f5a52f204e863723f1212b72e61ac89d1a137039af4f7ea3d293a50c9f9eb14d043b22dfe1ef293fb888c7b4fc0da48
|
data/README.rdoc
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
= Google Cloud Logging plugin for {fluentd}[http://github.com/fluent/fluentd]
|
2
2
|
|
3
|
-
fluent-plugin-google-cloud
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
which sends logs to the {Stackdriver Logging API}[https://cloud.google.com/logging/docs/api/].
|
3
|
+
fluent-plugin-google-cloud is an
|
4
|
+
{output plugin for fluentd}[http://docs.fluentd.org/articles/output-plugin-overview]
|
5
|
+
which sends logs to the
|
6
|
+
{Stackdriver Logging API}[https://cloud.google.com/logging/docs/api/].
|
8
7
|
|
9
8
|
This is an official Google Ruby gem.
|
10
9
|
|
@@ -13,7 +12,8 @@ This is an official Google Ruby gem.
|
|
13
12
|
|
14
13
|
== Installation
|
15
14
|
|
16
|
-
This gem is hosted at
|
15
|
+
This gem is hosted at
|
16
|
+
{RubyGems.org}[https://rubygems.org/gems/fluent-plugin-google-cloud]
|
17
17
|
and can be installed using:
|
18
18
|
|
19
19
|
$ gem install fluent-plugin-google-cloud
|
@@ -23,31 +23,22 @@ will also install and configure the gem.
|
|
23
23
|
|
24
24
|
== Configuration
|
25
25
|
|
26
|
-
To
|
27
|
-
in a
|
28
|
-
|
29
|
-
|
30
|
-
<filter **>
|
31
|
-
@type add_insert_ids
|
32
|
-
insert_id_key my_insert_id_field_name # Optional.
|
33
|
-
</filter>
|
34
|
-
|
35
|
-
insert_id_key can be used to customize the insertId field name.
|
36
|
-
|
37
|
-
To send logs to Google Cloud Logging, specify <code>@type google_cloud</code>
|
38
|
-
in a {match clause}[http://docs.fluentd.org/articles/config-file#2-ldquomatchrdquo-tell-fluentd-what-to-do]
|
39
|
-
of your Fluentd configuration file, for example:
|
26
|
+
To send logs to Google Cloud Logging, specify <code>type google_cloud</code>
|
27
|
+
in a
|
28
|
+
{match clause}[http://docs.fluentd.org/articles/config-file#2-ldquomatchrdquo-tell-fluentd-what-to-do]
|
29
|
+
of your fluentd configuration file, for example:
|
40
30
|
|
41
31
|
<match **>
|
42
|
-
|
32
|
+
type google_cloud
|
43
33
|
</match>
|
44
34
|
|
45
|
-
|
46
|
-
The plugin uses
|
35
|
+
No further configuration is required. The plugin uses
|
47
36
|
{Google Application Default Credentials}[https://developers.google.com/identity/protocols/application-default-credentials]
|
48
|
-
for authorization - for additional information see
|
37
|
+
for authorization - for additional information see
|
49
38
|
{here}[https://cloud.google.com/logging/docs/agent/authorization].
|
50
39
|
|
40
|
+
<em>The previously documented parameters auth_method, private_key_email,
|
41
|
+
and private_key_path are removed, and can no longer be used.</em>
|
51
42
|
|
52
43
|
== Copyright
|
53
44
|
|
data/Rakefile
CHANGED
@@ -21,7 +21,8 @@ end
|
|
21
21
|
desc 'Fix file permissions'
|
22
22
|
task :fix_perms do
|
23
23
|
files = [
|
24
|
-
'lib/fluent/plugin
|
24
|
+
'lib/fluent/plugin/out_google_cloud.rb',
|
25
|
+
'lib/fluent/plugin/monitoring.rb',
|
25
26
|
'lib/google/**/*.rb'
|
26
27
|
].flat_map do |file|
|
27
28
|
file.include?('*') ? Dir.glob(file) : [file]
|
@@ -1,17 +1,17 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
2
|
gem.name = 'fluent-plugin-google-cloud'
|
3
3
|
gem.description = <<-eos
|
4
|
-
Fluentd
|
5
|
-
viewable in the
|
6
|
-
in Google Cloud Storage and/or BigQuery.
|
4
|
+
Fluentd output plugin for the Stackdriver Logging API, which will make
|
5
|
+
logs viewable in the Developer Console's log viewer and can optionally
|
6
|
+
store them in Google Cloud Storage and/or BigQuery.
|
7
7
|
This is an official Google Ruby gem.
|
8
8
|
eos
|
9
|
-
gem.summary = 'fluentd
|
9
|
+
gem.summary = 'fluentd output plugin for the Stackdriver Logging API'
|
10
10
|
gem.homepage =
|
11
11
|
'https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud'
|
12
12
|
gem.license = 'Apache-2.0'
|
13
|
-
gem.version = '0.
|
14
|
-
gem.authors = ['
|
13
|
+
gem.version = '0.7.0.pre.1'
|
14
|
+
gem.authors = ['Ling Huang', 'Igor Peshansky']
|
15
15
|
gem.email = ['stackdriver-agents@google.com']
|
16
16
|
gem.required_ruby_version = Gem::Requirement.new('>= 2.2')
|
17
17
|
|
@@ -19,13 +19,13 @@ eos
|
|
19
19
|
gem.test_files = gem.files.grep(/^(test)/)
|
20
20
|
gem.require_paths = ['lib']
|
21
21
|
|
22
|
-
gem.add_runtime_dependency 'fluentd', '~>
|
22
|
+
gem.add_runtime_dependency 'fluentd', '~> 1.2.5'
|
23
23
|
gem.add_runtime_dependency 'googleapis-common-protos', '~> 1.3'
|
24
24
|
gem.add_runtime_dependency 'google-api-client', '~> 0.17'
|
25
25
|
gem.add_runtime_dependency 'google-cloud-logging', '~> 1.3', '>= 1.3.2'
|
26
26
|
gem.add_runtime_dependency 'googleauth', '~> 0.6'
|
27
27
|
gem.add_runtime_dependency 'grpc', '~> 1.0'
|
28
|
-
gem.add_runtime_dependency 'json', '~>
|
28
|
+
gem.add_runtime_dependency 'json', '~> 1.8'
|
29
29
|
gem.add_runtime_dependency 'google-protobuf', '~> 3.6', '>= 3.6.1'
|
30
30
|
|
31
31
|
gem.add_development_dependency 'mocha', '~> 1.1'
|
@@ -86,7 +86,7 @@ module Fluent
|
|
86
86
|
resource_type: 'container',
|
87
87
|
extra_resource_labels: %w(namespace_id pod_id container_name),
|
88
88
|
extra_common_labels: %w(namespace_name pod_name),
|
89
|
-
metadata_attributes: %w(
|
89
|
+
metadata_attributes: %w(kube-env),
|
90
90
|
stream_severity_map: {
|
91
91
|
'stdout' => 'INFO',
|
92
92
|
'stderr' => 'ERROR'
|
@@ -228,16 +228,7 @@ module Fluent
|
|
228
228
|
Fluent::Plugin.register_output('google_cloud', self)
|
229
229
|
|
230
230
|
PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'.freeze
|
231
|
-
|
232
|
-
PLUGIN_VERSION = begin
|
233
|
-
dependency = Gem::Dependency.new('fluent-plugin-google-cloud')
|
234
|
-
all_specs, = Gem::SpecFetcher.fetcher.spec_for_dependency(dependency)
|
235
|
-
matching_spec, = all_specs.grep(
|
236
|
-
proc { |spec,| __FILE__.include?(spec.full_gem_path) }) do |spec,|
|
237
|
-
spec.version.to_s
|
238
|
-
end
|
239
|
-
matching_spec
|
240
|
-
end.freeze
|
231
|
+
PLUGIN_VERSION = '0.7.0.pre.1'.freeze
|
241
232
|
|
242
233
|
# Name of the the Google cloud logging write scope.
|
243
234
|
LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'.freeze
|
@@ -550,12 +541,10 @@ module Fluent
|
|
550
541
|
@write_request = method(:write_request_via_rest)
|
551
542
|
end
|
552
543
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
"instance_id/#{@vm_id}"
|
558
|
-
end
|
544
|
+
# Log an informational message containing the Logs viewer URL
|
545
|
+
@log.info 'Logs viewer address: https://console.cloud.google.com/logs/',
|
546
|
+
"viewer?project=#{@project_id}&resource=#{@resource.type}/",
|
547
|
+
"instance_id/#{@vm_id}"
|
559
548
|
end
|
560
549
|
|
561
550
|
def start
|
@@ -586,17 +575,16 @@ module Fluent
|
|
586
575
|
|
587
576
|
is_json = false
|
588
577
|
if @detect_json
|
589
|
-
# Save the
|
578
|
+
# Save the timestamp and severity if available, then clear it out to
|
590
579
|
# allow for determining whether we should parse the log or message
|
591
580
|
# field.
|
592
|
-
|
593
|
-
|
594
|
-
]
|
581
|
+
timestamp = record.delete('time')
|
582
|
+
severity = record.delete('severity')
|
595
583
|
|
596
584
|
# If the log is json, we want to export it as a structured log
|
597
585
|
# unless there is additional metadata that would be lost.
|
598
586
|
record_json = nil
|
599
|
-
if
|
587
|
+
if record.length == 1
|
600
588
|
%w(log message msg).each do |field|
|
601
589
|
if record.key?(field)
|
602
590
|
record_json = parse_json_or_nil(record[field])
|
@@ -604,15 +592,13 @@ module Fluent
|
|
604
592
|
end
|
605
593
|
end
|
606
594
|
unless record_json.nil?
|
607
|
-
# Propagate these if necessary. Note that we don't want to
|
608
|
-
# override these keys in the JSON we've just parsed.
|
609
|
-
preserved_keys.each do |key|
|
610
|
-
record_json[key] ||= record[key] if record.key?(key)
|
611
|
-
end
|
612
|
-
|
613
595
|
record = record_json
|
614
596
|
is_json = true
|
615
597
|
end
|
598
|
+
# Restore timestamp and severity if necessary. Note that we don't
|
599
|
+
# want to override these keys in the JSON we've just parsed.
|
600
|
+
record['time'] ||= timestamp if timestamp
|
601
|
+
record['severity'] ||= severity if severity
|
616
602
|
end
|
617
603
|
|
618
604
|
ts_secs, ts_nanos = compute_timestamp(
|
@@ -626,10 +612,13 @@ module Fluent
|
|
626
612
|
ts_secs,
|
627
613
|
ts_nanos)
|
628
614
|
|
629
|
-
trace
|
630
|
-
|
615
|
+
# Get fully-qualified trace id for LogEntry "trace" field.
|
616
|
+
fq_trace_id = record.delete(@trace_key)
|
617
|
+
entry.trace = fq_trace_id if fq_trace_id
|
618
|
+
|
631
619
|
span_id = record.delete(@span_id_key)
|
632
620
|
entry.span_id = span_id if span_id
|
621
|
+
|
633
622
|
insert_id = record.delete(@insert_id_key)
|
634
623
|
entry.insert_id = insert_id if insert_id
|
635
624
|
|
@@ -1029,10 +1018,8 @@ module Fluent
|
|
1029
1018
|
# All metadata parameters must now be set.
|
1030
1019
|
missing = []
|
1031
1020
|
missing << 'project_id' unless @project_id
|
1032
|
-
|
1033
|
-
|
1034
|
-
missing << 'vm_id' unless @vm_id
|
1035
|
-
end
|
1021
|
+
missing << 'zone' unless @zone
|
1022
|
+
missing << 'vm_id' unless @vm_id
|
1036
1023
|
return if missing.empty?
|
1037
1024
|
raise Fluent::ConfigError,
|
1038
1025
|
"Unable to obtain metadata parameters: #{missing.join(' ')}"
|
@@ -1148,11 +1135,12 @@ module Fluent
|
|
1148
1135
|
|
1149
1136
|
# GKE container.
|
1150
1137
|
when GKE_CONSTANTS[:resource_type]
|
1138
|
+
raw_kube_env = fetch_gce_metadata('instance/attributes/kube-env')
|
1139
|
+
kube_env = YAML.load(raw_kube_env)
|
1151
1140
|
return {
|
1152
1141
|
'instance_id' => @vm_id,
|
1153
1142
|
'zone' => @zone,
|
1154
|
-
'cluster_name' =>
|
1155
|
-
fetch_gce_metadata('instance/attributes/cluster-name')
|
1143
|
+
'cluster_name' => cluster_name_from_kube_env(kube_env)
|
1156
1144
|
}
|
1157
1145
|
|
1158
1146
|
# Cloud Dataproc.
|
@@ -1534,6 +1522,15 @@ module Fluent
|
|
1534
1522
|
end
|
1535
1523
|
end
|
1536
1524
|
|
1525
|
+
def cluster_name_from_kube_env(kube_env)
|
1526
|
+
return kube_env['CLUSTER_NAME'] if kube_env.key?('CLUSTER_NAME')
|
1527
|
+
instance_prefix = kube_env['INSTANCE_PREFIX']
|
1528
|
+
gke_name_match = /^gke-(.+)-[0-9a-f]{8}$/.match(instance_prefix)
|
1529
|
+
return gke_name_match.captures[0] if gke_name_match &&
|
1530
|
+
!gke_name_match.captures.empty?
|
1531
|
+
instance_prefix
|
1532
|
+
end
|
1533
|
+
|
1537
1534
|
def time_or_nil(ts_secs, ts_nanos)
|
1538
1535
|
Time.at((Integer ts_secs), (Integer ts_nanos) / 1_000.0)
|
1539
1536
|
rescue ArgumentError, TypeError
|
data/test/plugin/base_test.rb
CHANGED
@@ -81,27 +81,26 @@ module BaseTest
|
|
81
81
|
assert_equal CUSTOM_VM_ID, d.instance.vm_id
|
82
82
|
end
|
83
83
|
|
84
|
-
def
|
84
|
+
def test_configure_invalid_metadata_missing_parts
|
85
85
|
setup_no_metadata_service_stubs
|
86
86
|
Fluent::GoogleCloudOutput::CredentialsInfo.stubs(:project_id).returns(nil)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
87
|
+
{ CONFIG_MISSING_METADATA_PROJECT_ID => ['project_id'],
|
88
|
+
CONFIG_MISSING_METADATA_ZONE => ['zone'],
|
89
|
+
CONFIG_MISSING_METADATA_VM_ID => ['vm_id'],
|
90
|
+
CONFIG_MISSING_METADATA_ALL => %w(project_id zone vm_id)
|
91
|
+
}.each_with_index do |(config, parts), index|
|
92
|
+
exception_count = 0
|
92
93
|
begin
|
93
94
|
create_driver(config)
|
94
|
-
assert_true is_valid_config, "Invalid config at index #{index} should "\
|
95
|
-
'have raised an error.'
|
96
95
|
rescue Fluent::ConfigError => error
|
97
|
-
assert_false is_valid_config, "Valid config at index #{index} should "\
|
98
|
-
"not have raised an error #{error}."
|
99
96
|
assert error.message.include?('Unable to obtain metadata parameters:'),
|
100
97
|
"Index #{index} failed."
|
101
|
-
|
98
|
+
parts.each do |part|
|
102
99
|
assert error.message.include?(part), "Index #{index} failed."
|
103
100
|
end
|
101
|
+
exception_count += 1
|
104
102
|
end
|
103
|
+
assert_equal 1, exception_count, "Index #{index} failed."
|
105
104
|
end
|
106
105
|
end
|
107
106
|
|
@@ -180,7 +179,7 @@ module BaseTest
|
|
180
179
|
setup_gce_metadata_stubs
|
181
180
|
# This would cause the resource type to be container.googleapis.com if not
|
182
181
|
# for the detect_subservice=false config.
|
183
|
-
|
182
|
+
setup_container_metadata_stubs
|
184
183
|
d = create_driver(NO_DETECT_SUBSERVICE_CONFIG)
|
185
184
|
d.run
|
186
185
|
assert_equal COMPUTE_CONSTANTS[:resource_type], d.instance.resource.type
|
@@ -468,7 +467,7 @@ module BaseTest
|
|
468
467
|
|
469
468
|
def test_structured_payload_json_log_default_container_not_parsed
|
470
469
|
setup_gce_metadata_stubs
|
471
|
-
|
470
|
+
setup_container_metadata_stubs
|
472
471
|
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
|
473
472
|
'"data": 5000, "some_null_field": null}'
|
474
473
|
setup_logging_stubs do
|
@@ -485,7 +484,7 @@ module BaseTest
|
|
485
484
|
|
486
485
|
def test_structured_payload_json_log_detect_json_container_not_parsed
|
487
486
|
setup_gce_metadata_stubs
|
488
|
-
|
487
|
+
setup_container_metadata_stubs
|
489
488
|
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
|
490
489
|
'"data": 5000, "some_null_field": null}'
|
491
490
|
setup_logging_stubs do
|
@@ -500,7 +499,7 @@ module BaseTest
|
|
500
499
|
|
501
500
|
def test_structured_payload_json_log_detect_json_container_parsed
|
502
501
|
setup_gce_metadata_stubs
|
503
|
-
|
502
|
+
setup_container_metadata_stubs
|
504
503
|
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
|
505
504
|
'"data": 5000, "some_null_field": null}'
|
506
505
|
setup_logging_stubs do
|
@@ -539,7 +538,7 @@ module BaseTest
|
|
539
538
|
# match, thus the original tag is used as the log name.
|
540
539
|
def test_handle_empty_container_name
|
541
540
|
setup_gce_metadata_stubs
|
542
|
-
|
541
|
+
setup_container_metadata_stubs
|
543
542
|
container_name = ''
|
544
543
|
# This tag will not match the kubernetes regex because it requires a
|
545
544
|
# non-empty container name.
|
@@ -561,7 +560,7 @@ module BaseTest
|
|
561
560
|
# 'require_valid_tags' is true.
|
562
561
|
def test_reject_non_utf8_container_name_with_require_valid_tags_true
|
563
562
|
setup_gce_metadata_stubs
|
564
|
-
|
563
|
+
setup_container_metadata_stubs
|
565
564
|
non_utf8_tags = INVALID_TAGS.select do |tag, _|
|
566
565
|
tag.is_a?(String) && !tag.empty?
|
567
566
|
end
|
@@ -601,7 +600,7 @@ module BaseTest
|
|
601
600
|
# Verify that tags extracted from container names are properly encoded.
|
602
601
|
def test_encode_tags_from_container_name_with_require_valid_tags_true
|
603
602
|
setup_gce_metadata_stubs
|
604
|
-
|
603
|
+
setup_container_metadata_stubs
|
605
604
|
VALID_TAGS.each do |tag, encoded_tag|
|
606
605
|
setup_logging_stubs do
|
607
606
|
@logs_sent = []
|
@@ -640,7 +639,7 @@ module BaseTest
|
|
640
639
|
# sanitized.
|
641
640
|
def test_sanitize_tags_from_container_name_with_require_valid_tags_false
|
642
641
|
setup_gce_metadata_stubs
|
643
|
-
|
642
|
+
setup_container_metadata_stubs
|
644
643
|
# Log names are derived from container names for containers. And container
|
645
644
|
# names are extracted from the tag based on a regex match pattern. As a
|
646
645
|
# prerequisite, the tag should already be a string, thus we only test
|
@@ -969,7 +968,7 @@ module BaseTest
|
|
969
968
|
|
970
969
|
def test_one_container_log_from_tag_stderr
|
971
970
|
setup_gce_metadata_stubs
|
972
|
-
|
971
|
+
setup_container_metadata_stubs
|
973
972
|
setup_logging_stubs do
|
974
973
|
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
975
974
|
d.emit(container_log_entry(log_entry(0), 'stderr'))
|
@@ -980,15 +979,15 @@ module BaseTest
|
|
980
979
|
) { |_, oldval, newval| oldval.merge(newval) }
|
981
980
|
verify_log_entries(1, expected_params) do |entry, i|
|
982
981
|
verify_default_log_entry_text(entry['textPayload'], i, entry)
|
983
|
-
assert_equal
|
984
|
-
assert_equal
|
982
|
+
assert_equal CONTAINER_SECONDS_EPOCH, entry['timestamp']['seconds'], entry
|
983
|
+
assert_equal CONTAINER_NANOS, entry['timestamp']['nanos'], entry
|
985
984
|
assert_equal 'ERROR', entry['severity'], entry
|
986
985
|
end
|
987
986
|
end
|
988
987
|
|
989
988
|
def test_json_container_log_metadata_from_plugin
|
990
989
|
setup_gce_metadata_stubs
|
991
|
-
|
990
|
+
setup_container_metadata_stubs
|
992
991
|
setup_logging_stubs do
|
993
992
|
d = create_driver(DETECT_JSON_CONFIG, CONTAINER_TAG)
|
994
993
|
d.emit(container_log_entry_with_metadata('{"msg": "test log entry 0", ' \
|
@@ -1003,15 +1002,15 @@ module BaseTest
|
|
1003
1002
|
assert_equal 'test log entry 0', get_string(fields['msg']), entry
|
1004
1003
|
assert_equal 'test', get_string(fields['tag2']), entry
|
1005
1004
|
assert_equal 5000, get_number(fields['data']), entry
|
1006
|
-
assert_equal
|
1007
|
-
assert_equal
|
1005
|
+
assert_equal CONTAINER_SECONDS_EPOCH, entry['timestamp']['seconds'], entry
|
1006
|
+
assert_equal CONTAINER_NANOS, entry['timestamp']['nanos'], entry
|
1008
1007
|
assert_equal 'WARNING', entry['severity'], entry
|
1009
1008
|
end
|
1010
1009
|
end
|
1011
1010
|
|
1012
1011
|
def test_json_container_log_metadata_from_tag
|
1013
1012
|
setup_gce_metadata_stubs
|
1014
|
-
|
1013
|
+
setup_container_metadata_stubs
|
1015
1014
|
setup_logging_stubs do
|
1016
1015
|
d = create_driver(DETECT_JSON_CONFIG, CONTAINER_TAG)
|
1017
1016
|
d.emit(container_log_entry('{"msg": "test log entry 0", ' \
|
@@ -1026,8 +1025,8 @@ module BaseTest
|
|
1026
1025
|
assert_equal 'test log entry 0', get_string(fields['msg']), entry
|
1027
1026
|
assert_equal 'test', get_string(fields['tag2']), entry
|
1028
1027
|
assert_equal 5000, get_number(fields['data']), entry
|
1029
|
-
assert_equal
|
1030
|
-
assert_equal
|
1028
|
+
assert_equal CONTAINER_SECONDS_EPOCH, entry['timestamp']['seconds'], entry
|
1029
|
+
assert_equal CONTAINER_NANOS, entry['timestamp']['nanos'], entry
|
1031
1030
|
assert_equal 'WARNING', entry['severity'], entry
|
1032
1031
|
end
|
1033
1032
|
end
|
@@ -1222,32 +1221,18 @@ module BaseTest
|
|
1222
1221
|
|
1223
1222
|
def test_log_entry_trace_field
|
1224
1223
|
verify_field_key('trace', DEFAULT_TRACE_KEY, 'custom_trace_key',
|
1225
|
-
CONFIG_CUSTOM_TRACE_KEY_SPECIFIED,
|
1224
|
+
CONFIG_CUSTOM_TRACE_KEY_SPECIFIED,
|
1225
|
+
'projects/proj1/traces/1234567890abcdef1234567890abcdef')
|
1226
1226
|
end
|
1227
1227
|
|
1228
1228
|
def test_log_entry_span_id_field
|
1229
1229
|
verify_field_key('spanId', DEFAULT_SPAN_ID_KEY, 'custom_span_id_key',
|
1230
|
-
CONFIG_CUSTOM_SPAN_ID_KEY_SPECIFIED,
|
1230
|
+
CONFIG_CUSTOM_SPAN_ID_KEY_SPECIFIED, '000000000000004a')
|
1231
1231
|
end
|
1232
1232
|
|
1233
1233
|
def test_log_entry_insert_id_field
|
1234
1234
|
verify_field_key('insertId', DEFAULT_INSERT_ID_KEY, 'custom_insert_id_key',
|
1235
|
-
CONFIG_CUSTOM_INSERT_ID_KEY_SPECIFIED,
|
1236
|
-
end
|
1237
|
-
|
1238
|
-
def test_cascading_json_detection_with_log_entry_trace_field
|
1239
|
-
verify_cascading_json_detection_with_log_entry_fields(
|
1240
|
-
'trace', DEFAULT_TRACE_KEY, TRACE, TRACE2)
|
1241
|
-
end
|
1242
|
-
|
1243
|
-
def test_cascading_json_detection_with_log_entry_span_id_field
|
1244
|
-
verify_cascading_json_detection_with_log_entry_fields(
|
1245
|
-
'spanId', DEFAULT_SPAN_ID_KEY, SPAN_ID, SPAN_ID2)
|
1246
|
-
end
|
1247
|
-
|
1248
|
-
def test_cascading_json_detection_with_log_entry_insert_id_field
|
1249
|
-
verify_cascading_json_detection_with_log_entry_fields(
|
1250
|
-
'insertId', DEFAULT_INSERT_ID_KEY, INSERT_ID, INSERT_ID2)
|
1235
|
+
CONFIG_CUSTOM_INSERT_ID_KEY_SPECIFIED, 'fah7yr7iw64tg857y')
|
1251
1236
|
end
|
1252
1237
|
|
1253
1238
|
# Metadata Agent related tests.
|
@@ -1340,24 +1325,25 @@ module BaseTest
|
|
1340
1325
|
end
|
1341
1326
|
end
|
1342
1327
|
|
1343
|
-
# Test
|
1344
|
-
#
|
1345
|
-
def
|
1328
|
+
# Test k8s monitored resource including the fallback when Metadata Agent
|
1329
|
+
# restarts.
|
1330
|
+
def test_k8s_monitored_resource_fallback
|
1346
1331
|
[
|
1332
|
+
# k8s_container.
|
1347
1333
|
# When enable_metadata_agent is false.
|
1348
1334
|
{
|
1349
1335
|
config: APPLICATION_DEFAULT_CONFIG,
|
1350
1336
|
setup_metadata_agent_stub: false,
|
1351
1337
|
setup_k8s_stub: false,
|
1352
1338
|
log_entry: k8s_container_log_entry(log_entry(0)),
|
1353
|
-
expected_params:
|
1339
|
+
expected_params: COMPUTE_PARAMS
|
1354
1340
|
},
|
1355
1341
|
{
|
1356
1342
|
config: APPLICATION_DEFAULT_CONFIG,
|
1357
1343
|
setup_metadata_agent_stub: true,
|
1358
1344
|
setup_k8s_stub: false,
|
1359
1345
|
log_entry: k8s_container_log_entry(log_entry(0)),
|
1360
|
-
expected_params:
|
1346
|
+
expected_params: COMPUTE_PARAMS
|
1361
1347
|
},
|
1362
1348
|
{
|
1363
1349
|
config: APPLICATION_DEFAULT_CONFIG,
|
@@ -1379,7 +1365,7 @@ module BaseTest
|
|
1379
1365
|
setup_metadata_agent_stub: false,
|
1380
1366
|
setup_k8s_stub: false,
|
1381
1367
|
log_entry: k8s_container_log_entry(log_entry(0)),
|
1382
|
-
expected_params:
|
1368
|
+
expected_params: COMPUTE_PARAMS
|
1383
1369
|
},
|
1384
1370
|
{
|
1385
1371
|
config: ENABLE_METADATA_AGENT_CONFIG,
|
@@ -1415,30 +1401,7 @@ module BaseTest
|
|
1415
1401
|
setup_k8s_stub: true,
|
1416
1402
|
log_entry: k8s_container_log_entry(log_entry(0)),
|
1417
1403
|
expected_params: K8S_CONTAINER_PARAMS
|
1418
|
-
}
|
1419
|
-
].each do |test_params|
|
1420
|
-
new_stub_context do
|
1421
|
-
setup_gce_metadata_stubs
|
1422
|
-
setup_metadata_agent_stubs(test_params[:setup_metadata_agent_stub])
|
1423
|
-
setup_k8s_metadata_stubs(test_params[:setup_k8s_stub])
|
1424
|
-
setup_logging_stubs do
|
1425
|
-
d = create_driver(test_params[:config], CONTAINER_TAG)
|
1426
|
-
d.emit(test_params[:log_entry])
|
1427
|
-
d.run
|
1428
|
-
end
|
1429
|
-
verify_log_entries(1, test_params[:expected_params],
|
1430
|
-
'jsonPayload') do |entry|
|
1431
|
-
fields = get_fields(entry['jsonPayload'])
|
1432
|
-
assert_equal 2, fields.size, entry
|
1433
|
-
assert_equal 'test log entry 0', get_string(fields['log']), entry
|
1434
|
-
assert_equal K8S_STREAM, get_string(fields['stream']), entry
|
1435
|
-
end
|
1436
|
-
end
|
1437
|
-
end
|
1438
|
-
end
|
1439
|
-
|
1440
|
-
def test_k8s_container_monitored_resource_invalid_local_resource_id
|
1441
|
-
[
|
1404
|
+
},
|
1442
1405
|
# When local_resource_id is not present or does not match k8s regexes.
|
1443
1406
|
{
|
1444
1407
|
config: ENABLE_METADATA_AGENT_CONFIG,
|
@@ -1446,7 +1409,7 @@ module BaseTest
|
|
1446
1409
|
setup_k8s_stub: true,
|
1447
1410
|
log_entry: k8s_container_log_entry(
|
1448
1411
|
log_entry(0)).reject { |k, _| k == LOCAL_RESOURCE_ID_KEY },
|
1449
|
-
expected_params:
|
1412
|
+
expected_params: COMPUTE_PARAMS
|
1450
1413
|
},
|
1451
1414
|
{
|
1452
1415
|
config: ENABLE_METADATA_AGENT_CONFIG,
|
@@ -1455,29 +1418,11 @@ module BaseTest
|
|
1455
1418
|
log_entry: k8s_container_log_entry(
|
1456
1419
|
log_entry(0),
|
1457
1420
|
local_resource_id: RANDOM_LOCAL_RESOURCE_ID),
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
setup_metadata_agent_stubs(test_params[:setup_metadata_agent_stub])
|
1464
|
-
setup_k8s_metadata_stubs(test_params[:setup_k8s_stub])
|
1465
|
-
setup_logging_stubs do
|
1466
|
-
d = create_driver(test_params[:config], CONTAINER_TAG)
|
1467
|
-
d.emit(test_params[:log_entry])
|
1468
|
-
d.run
|
1469
|
-
end
|
1470
|
-
verify_log_entries(1, test_params[:expected_params]) do |entry|
|
1471
|
-
assert_equal 'test log entry 0', entry['textPayload'], entry
|
1472
|
-
end
|
1473
|
-
end
|
1474
|
-
end
|
1475
|
-
end
|
1476
|
-
|
1477
|
-
# Test k8s_node monitored resource including the fallback when Metadata Agent
|
1478
|
-
# restarts.
|
1479
|
-
def test_k8s_node_monitored_resource_fallback
|
1480
|
-
[
|
1421
|
+
# When 'kube-env' is present, "compute.googleapis.com/resource_name" is
|
1422
|
+
# not added.
|
1423
|
+
expected_params: COMPUTE_PARAMS
|
1424
|
+
},
|
1425
|
+
# Specific cases for k8s_node.
|
1481
1426
|
{
|
1482
1427
|
config: APPLICATION_DEFAULT_CONFIG,
|
1483
1428
|
setup_metadata_agent_stub: true,
|
@@ -1516,8 +1461,16 @@ module BaseTest
|
|
1516
1461
|
].each do |test_params|
|
1517
1462
|
new_stub_context do
|
1518
1463
|
setup_gce_metadata_stubs
|
1519
|
-
|
1520
|
-
|
1464
|
+
if test_params[:setup_metadata_agent_stub]
|
1465
|
+
setup_metadata_agent_stubs
|
1466
|
+
else
|
1467
|
+
setup_no_metadata_agent_stubs
|
1468
|
+
end
|
1469
|
+
if test_params[:setup_k8s_stub]
|
1470
|
+
setup_k8s_metadata_stubs
|
1471
|
+
else
|
1472
|
+
setup_no_k8s_metadata_stubs
|
1473
|
+
end
|
1521
1474
|
setup_logging_stubs do
|
1522
1475
|
d = create_driver(test_params[:config])
|
1523
1476
|
d.emit(test_params[:log_entry])
|
@@ -1594,7 +1547,7 @@ module BaseTest
|
|
1594
1547
|
[1, 2, 3, 5, 11, 50].each do |n|
|
1595
1548
|
new_stub_context do
|
1596
1549
|
setup_gce_metadata_stubs
|
1597
|
-
|
1550
|
+
setup_container_metadata_stubs
|
1598
1551
|
setup_metadata_agent_stubs
|
1599
1552
|
setup_logging_stubs do
|
1600
1553
|
d = create_driver(ENABLE_METADATA_AGENT_CONFIG)
|
@@ -1606,7 +1559,7 @@ module BaseTest
|
|
1606
1559
|
verify_log_entries(n, CONTAINER_FROM_APPLICATION_PARAMS)
|
1607
1560
|
assert_requested_metadata_agent_stub(
|
1608
1561
|
"#{CONTAINER_LOCAL_RESOURCE_ID_PREFIX}.#{CONTAINER_NAMESPACE_ID}" \
|
1609
|
-
".#{
|
1562
|
+
".#{CONTAINER_POD_NAME}.#{CONTAINER_CONTAINER_NAME}")
|
1610
1563
|
end
|
1611
1564
|
end
|
1612
1565
|
end
|
@@ -1686,34 +1639,44 @@ module BaseTest
|
|
1686
1639
|
MANAGED_VM_BACKEND_VERSION)
|
1687
1640
|
end
|
1688
1641
|
|
1689
|
-
def
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1642
|
+
def setup_container_metadata_stubs
|
1643
|
+
stub_metadata_request(
|
1644
|
+
'instance/attributes/',
|
1645
|
+
"attribute1\nkube-env\nlast_attribute")
|
1646
|
+
stub_metadata_request('instance/attributes/kube-env',
|
1647
|
+
"ENABLE_NODE_LOGGING: \"true\"\n"\
|
1648
|
+
'INSTANCE_PREFIX: '\
|
1649
|
+
"gke-#{CONTAINER_CLUSTER_NAME}-740fdafa\n"\
|
1650
|
+
'KUBE_BEARER_TOKEN: AoQiMuwkNP2BMT0S')
|
1651
|
+
end
|
1652
|
+
|
1653
|
+
def setup_k8s_metadata_stubs
|
1654
|
+
stub_metadata_request(
|
1655
|
+
'instance/attributes/',
|
1656
|
+
"attribute1\ncluster-name\ncluster-location\nlast_attribute")
|
1657
|
+
stub_metadata_request('instance/attributes/cluster-location', K8S_LOCATION2)
|
1658
|
+
stub_metadata_request('instance/attributes/cluster-name', K8S_CLUSTER_NAME)
|
1659
|
+
end
|
1660
|
+
|
1661
|
+
def setup_no_k8s_metadata_stubs
|
1662
|
+
['cluster-location', 'cluster-name'].each do |metadata_name|
|
1663
|
+
stub_request(:get, %r{.*instance/attributes/#{metadata_name}.*})
|
1664
|
+
.to_return(status: 404,
|
1665
|
+
body: 'The requested URL /computeMetadata/v1/instance/' \
|
1666
|
+
"attributes/#{metadata_name} was not found on this" \
|
1667
|
+
' server.')
|
1706
1668
|
end
|
1707
1669
|
end
|
1708
1670
|
|
1709
1671
|
def setup_cloudfunctions_metadata_stubs
|
1710
1672
|
stub_metadata_request(
|
1711
1673
|
'instance/attributes/',
|
1712
|
-
"attribute1\
|
1713
|
-
stub_metadata_request('instance/attributes/
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1674
|
+
"attribute1\nkube-env\ngcf_region\nlast_attribute")
|
1675
|
+
stub_metadata_request('instance/attributes/kube-env',
|
1676
|
+
"ENABLE_NODE_LOGGING: \"true\"\n"\
|
1677
|
+
'INSTANCE_PREFIX: '\
|
1678
|
+
"gke-#{CLOUDFUNCTIONS_CLUSTER_NAME}-740fdafa\n"\
|
1679
|
+
'KUBE_BEARER_TOKEN: AoQiMuwkNP2BMT0S')
|
1717
1680
|
stub_metadata_request('instance/attributes/gcf_region',
|
1718
1681
|
CLOUDFUNCTIONS_REGION)
|
1719
1682
|
end
|
@@ -1748,20 +1711,19 @@ module BaseTest
|
|
1748
1711
|
WebMock.reset!
|
1749
1712
|
end
|
1750
1713
|
|
1751
|
-
def setup_metadata_agent_stubs
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
.to_return(status: 200, body: resource)
|
1756
|
-
end
|
1757
|
-
stub_request(:get, metadata_request_url(RANDOM_LOCAL_RESOURCE_ID))
|
1758
|
-
.to_return(status: 404, body: '')
|
1759
|
-
else
|
1760
|
-
# Simulate an environment with no metadata agent endpoint present.
|
1761
|
-
stub_request(:get,
|
1762
|
-
%r{#{DEFAULT_METADATA_AGENT_URL}\/monitoredResource/.*})
|
1763
|
-
.to_raise(Errno::EHOSTUNREACH)
|
1714
|
+
def setup_metadata_agent_stubs
|
1715
|
+
MONITORED_RESOURCE_STUBS.each do |local_resource_id, resource|
|
1716
|
+
stub_request(:get, metadata_request_url(local_resource_id))
|
1717
|
+
.to_return(status: 200, body: resource)
|
1764
1718
|
end
|
1719
|
+
stub_request(:get, metadata_request_url(RANDOM_LOCAL_RESOURCE_ID))
|
1720
|
+
.to_return(status: 404, body: '')
|
1721
|
+
end
|
1722
|
+
|
1723
|
+
def setup_no_metadata_agent_stubs
|
1724
|
+
# Simulate an environment with no metadata agent endpoint present.
|
1725
|
+
stub_request(:get, %r{#{DEFAULT_METADATA_AGENT_URL}\/monitoredResource/.*})
|
1726
|
+
.to_raise(Errno::EHOSTUNREACH)
|
1765
1727
|
end
|
1766
1728
|
|
1767
1729
|
def assert_requested_metadata_agent_stub(local_resource_id)
|
@@ -1771,20 +1733,21 @@ module BaseTest
|
|
1771
1733
|
# GKE Container.
|
1772
1734
|
|
1773
1735
|
def container_tag_with_container_name(container_name)
|
1774
|
-
"kubernetes.#{
|
1736
|
+
"kubernetes.#{CONTAINER_POD_NAME}_#{CONTAINER_NAMESPACE_NAME}_" \
|
1737
|
+
"#{container_name}"
|
1775
1738
|
end
|
1776
1739
|
|
1777
1740
|
def container_log_entry_with_metadata(
|
1778
|
-
log, container_name =
|
1741
|
+
log, container_name = CONTAINER_CONTAINER_NAME)
|
1779
1742
|
{
|
1780
1743
|
log: log,
|
1781
|
-
stream:
|
1782
|
-
time:
|
1744
|
+
stream: CONTAINER_STREAM,
|
1745
|
+
time: CONTAINER_TIMESTAMP,
|
1783
1746
|
kubernetes: {
|
1784
1747
|
namespace_id: CONTAINER_NAMESPACE_ID,
|
1785
|
-
namespace_name:
|
1748
|
+
namespace_name: CONTAINER_NAMESPACE_NAME,
|
1786
1749
|
pod_id: CONTAINER_POD_ID,
|
1787
|
-
pod_name:
|
1750
|
+
pod_name: CONTAINER_POD_NAME,
|
1788
1751
|
container_name: container_name,
|
1789
1752
|
labels: {
|
1790
1753
|
CONTAINER_LABEL_KEY => CONTAINER_LABEL_VALUE
|
@@ -1793,11 +1756,11 @@ module BaseTest
|
|
1793
1756
|
}
|
1794
1757
|
end
|
1795
1758
|
|
1796
|
-
def container_log_entry(log, stream =
|
1759
|
+
def container_log_entry(log, stream = CONTAINER_STREAM)
|
1797
1760
|
{
|
1798
1761
|
log: log,
|
1799
1762
|
stream: stream,
|
1800
|
-
time:
|
1763
|
+
time: CONTAINER_TIMESTAMP
|
1801
1764
|
}
|
1802
1765
|
end
|
1803
1766
|
|
@@ -1806,7 +1769,7 @@ module BaseTest
|
|
1806
1769
|
log: log,
|
1807
1770
|
LOCAL_RESOURCE_ID_KEY =>
|
1808
1771
|
"#{CONTAINER_LOCAL_RESOURCE_ID_PREFIX}.#{CONTAINER_NAMESPACE_ID}" \
|
1809
|
-
".#{
|
1772
|
+
".#{CONTAINER_POD_NAME}.#{CONTAINER_CONTAINER_NAME}"
|
1810
1773
|
}
|
1811
1774
|
end
|
1812
1775
|
|
@@ -1897,13 +1860,6 @@ module BaseTest
|
|
1897
1860
|
}
|
1898
1861
|
end
|
1899
1862
|
|
1900
|
-
def structured_log_entry
|
1901
|
-
{
|
1902
|
-
'name' => 'test name',
|
1903
|
-
'code' => 'test code'
|
1904
|
-
}
|
1905
|
-
end
|
1906
|
-
|
1907
1863
|
def log_entry(i)
|
1908
1864
|
"test log entry #{i}"
|
1909
1865
|
end
|
@@ -1972,7 +1928,7 @@ module BaseTest
|
|
1972
1928
|
|
1973
1929
|
def verify_container_logs(log_entry_factory, expected_params)
|
1974
1930
|
setup_gce_metadata_stubs
|
1975
|
-
|
1931
|
+
setup_container_metadata_stubs
|
1976
1932
|
[1, 2, 3, 5, 11, 50].each do |n|
|
1977
1933
|
@logs_sent = []
|
1978
1934
|
setup_logging_stubs do
|
@@ -1982,8 +1938,9 @@ module BaseTest
|
|
1982
1938
|
end
|
1983
1939
|
verify_log_entries(n, expected_params) do |entry, i|
|
1984
1940
|
verify_default_log_entry_text(entry['textPayload'], i, entry)
|
1985
|
-
assert_equal
|
1986
|
-
|
1941
|
+
assert_equal CONTAINER_SECONDS_EPOCH, entry['timestamp']['seconds'],
|
1942
|
+
entry
|
1943
|
+
assert_equal CONTAINER_NANOS, entry['timestamp']['nanos'], entry
|
1987
1944
|
assert_equal CONTAINER_SEVERITY, entry['severity'], entry
|
1988
1945
|
end
|
1989
1946
|
end
|
@@ -2052,71 +2009,6 @@ module BaseTest
|
|
2052
2009
|
end
|
2053
2010
|
end
|
2054
2011
|
|
2055
|
-
# Cascading JSON detection is only triggered when the record has one field
|
2056
|
-
# left with name "log", "message" or "msg". This test verifies additional
|
2057
|
-
# LogEntry fields like spanId and traceId do not disable that by accident.
|
2058
|
-
def verify_cascading_json_detection_with_log_entry_fields(
|
2059
|
-
log_entry_field, default_key, root_level_value, nested_level_value)
|
2060
|
-
setup_gce_metadata_stubs
|
2061
|
-
|
2062
|
-
# {
|
2063
|
-
# "logging.googleapis.com/XXX' => 'sample value'
|
2064
|
-
# "msg": {
|
2065
|
-
# "name": "test name",
|
2066
|
-
# "code": "test code"
|
2067
|
-
# }
|
2068
|
-
# }
|
2069
|
-
log_entry_with_root_level_field = {
|
2070
|
-
default_key => root_level_value,
|
2071
|
-
'msg' => structured_log_entry.to_json
|
2072
|
-
}
|
2073
|
-
# {
|
2074
|
-
# "msg": {
|
2075
|
-
# "logging.googleapis.com/XXX' => 'another value',
|
2076
|
-
# "name": "test name",
|
2077
|
-
# "code": "test code"
|
2078
|
-
# }
|
2079
|
-
# }
|
2080
|
-
log_entry_with_nested_level_field = {
|
2081
|
-
'msg' => {
|
2082
|
-
default_key => nested_level_value
|
2083
|
-
}.merge(structured_log_entry).to_json
|
2084
|
-
}
|
2085
|
-
# {
|
2086
|
-
# "logging.googleapis.com/XXX' => 'sample value'
|
2087
|
-
# "msg": {
|
2088
|
-
# "logging.googleapis.com/XXX' => 'another value',
|
2089
|
-
# "name": "test name",
|
2090
|
-
# "code": "test code"
|
2091
|
-
# }
|
2092
|
-
# }
|
2093
|
-
log_entry_with_both_level_fields = log_entry_with_nested_level_field.merge(
|
2094
|
-
default_key => root_level_value)
|
2095
|
-
|
2096
|
-
{
|
2097
|
-
log_entry_with_root_level_field => root_level_value,
|
2098
|
-
log_entry_with_nested_level_field => nested_level_value,
|
2099
|
-
log_entry_with_both_level_fields => nested_level_value
|
2100
|
-
}.each_with_index do |(input_log_entry, expected_value), index|
|
2101
|
-
setup_logging_stubs do
|
2102
|
-
@logs_sent = []
|
2103
|
-
d = create_driver(DETECT_JSON_CONFIG)
|
2104
|
-
d.emit(input_log_entry)
|
2105
|
-
d.run
|
2106
|
-
end
|
2107
|
-
verify_log_entries(1, COMPUTE_PARAMS, 'jsonPayload') do |entry|
|
2108
|
-
assert_equal expected_value, entry[log_entry_field],
|
2109
|
-
"Index #{index} failed. #{expected_value} is expected" \
|
2110
|
-
" for #{log_entry_field} field."
|
2111
|
-
payload_fields = get_fields(entry['jsonPayload'])
|
2112
|
-
assert_equal structured_log_entry.size, payload_fields.size
|
2113
|
-
payload_fields.each do |key, value|
|
2114
|
-
assert_equal structured_log_entry[key], get_string(value)
|
2115
|
-
end
|
2116
|
-
end
|
2117
|
-
end
|
2118
|
-
end
|
2119
|
-
|
2120
2012
|
def verify_field_key(log_entry_field, default_key, custom_key,
|
2121
2013
|
custom_key_config, sample_value)
|
2122
2014
|
setup_gce_metadata_stubs
|