fluent-plugin-google-cloud 0.5.3.grpc.alpha.3 → 0.5.3.grpc.alpha.4
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 +4 -4
- data/README.rdoc +1 -1
- data/fluent-plugin-google-cloud.gemspec +5 -5
- data/lib/fluent/plugin/out_google_cloud.rb +41 -19
- data/test/plugin/base_test.rb +1308 -0
- data/test/plugin/test_out_google_cloud.rb +87 -1253
- data/test/plugin/test_out_google_cloud_grpc.rb +309 -0
- metadata +32 -35
- data/Gemfile.lock +0 -131
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29718f443dea67bcf817ee44390e61373a307db7
|
4
|
+
data.tar.gz: 54b6472916c8657230db649ff339ab1d31b5362d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d5695d8834eafc6f059c5a591ff9d3188739814fd7dcc4e2dc976324c8e45f3b5f0b7d914062c9581b2242f76dfd75d0356178c5a6b7f2cbadd3d49b064ec61
|
7
|
+
data.tar.gz: 46c8d492364314a4220f3ea61a81814fbd9db64c3af06331e025853582404d33806a5bcacc7be64379aa2a646f415d5e6507e6fcae6d95085fafe838b4f5e19a
|
data/README.rdoc
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
fluent-plugin-google-cloud is an
|
4
4
|
{output plugin for fluentd}[http://docs.fluentd.org/articles/output-plugin-overview]
|
5
5
|
which sends logs to the
|
6
|
-
{
|
6
|
+
{Stackdriver Logging API}[https://cloud.google.com/logging/docs/api/].
|
7
7
|
|
8
8
|
This is an official Google Ruby gem.
|
9
9
|
|
@@ -1,16 +1,16 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
2
|
gem.name = 'fluent-plugin-google-cloud'
|
3
3
|
gem.description = <<-eos
|
4
|
-
Fluentd output plugin for the
|
4
|
+
Fluentd output plugin for the Stackdriver Logging API, which will make
|
5
5
|
logs viewable in the Developer Console's log viewer and can optionally
|
6
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 output plugin for the
|
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.5.3.grpc.alpha.
|
13
|
+
gem.version = '0.5.3.grpc.alpha.4'
|
14
14
|
gem.authors = ['Todd Derr', 'Alex Robinson']
|
15
15
|
gem.email = ['salty@google.com']
|
16
16
|
gem.required_ruby_version = Gem::Requirement.new('>= 2.0')
|
@@ -19,7 +19,7 @@ eos
|
|
19
19
|
gem.test_files = gem.files.grep(/^(test)/)
|
20
20
|
gem.require_paths = ['lib']
|
21
21
|
|
22
|
-
gem.add_runtime_dependency 'fluentd', '~> 0.10'
|
22
|
+
gem.add_runtime_dependency 'fluentd', '~> 0.10'
|
23
23
|
gem.add_runtime_dependency 'googleapis-common-protos', '~> 1.3'
|
24
24
|
gem.add_runtime_dependency 'google-api-client', '> 0.9'
|
25
25
|
gem.add_runtime_dependency 'googleauth', '~> 0.4'
|
@@ -28,7 +28,7 @@ eos
|
|
28
28
|
|
29
29
|
gem.add_development_dependency 'mocha', '~> 1.1'
|
30
30
|
gem.add_development_dependency 'rake', '~> 10.3'
|
31
|
-
gem.add_development_dependency 'rubocop', '
|
31
|
+
gem.add_development_dependency 'rubocop', '~> 0.35.0'
|
32
32
|
gem.add_development_dependency 'webmock', '~> 1.17'
|
33
33
|
gem.add_development_dependency 'test-unit', '~> 3.0'
|
34
34
|
end
|
@@ -24,13 +24,22 @@ require 'google/logging/v1/logging_services_pb'
|
|
24
24
|
require 'google/logging/v1/log_entry_pb'
|
25
25
|
require 'googleauth'
|
26
26
|
|
27
|
+
module Google
|
28
|
+
module Protobuf
|
29
|
+
# Alias the has_key? method to have the same interface as a regular map.
|
30
|
+
class Map
|
31
|
+
alias_method :key?, :has_key?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
27
36
|
module Fluent
|
28
|
-
# fluentd output plugin for the
|
37
|
+
# fluentd output plugin for the Stackdriver Logging API
|
29
38
|
class GoogleCloudOutput < BufferedOutput
|
30
39
|
Fluent::Plugin.register_output('google_cloud', self)
|
31
40
|
|
32
41
|
PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'
|
33
|
-
PLUGIN_VERSION = '0.5.3.grpc.alpha.
|
42
|
+
PLUGIN_VERSION = '0.5.3.grpc.alpha.4'
|
34
43
|
|
35
44
|
# Constants for service names.
|
36
45
|
APPENGINE_SERVICE = 'appengine.googleapis.com'
|
@@ -502,7 +511,7 @@ module Fluent
|
|
502
511
|
# to aid with verification and troubleshooting.
|
503
512
|
unless @successful_call
|
504
513
|
@successful_call = true
|
505
|
-
@log.info 'Successfully sent gRPC to
|
514
|
+
@log.info 'Successfully sent gRPC to Stackdriver Logging API.'
|
506
515
|
end
|
507
516
|
|
508
517
|
rescue GRPC::Cancelled => error
|
@@ -564,7 +573,7 @@ module Fluent
|
|
564
573
|
# to aid with verification and troubleshooting.
|
565
574
|
unless @successful_call
|
566
575
|
@successful_call = true
|
567
|
-
@log.info 'Successfully sent to
|
576
|
+
@log.info 'Successfully sent to Stackdriver Logging API.'
|
568
577
|
end
|
569
578
|
|
570
579
|
rescue Google::Apis::ServerError => error
|
@@ -746,7 +755,7 @@ module Fluent
|
|
746
755
|
# # to aid with verification and troubleshooting.
|
747
756
|
# unless @successful_call
|
748
757
|
# @successful_call = true
|
749
|
-
# @log.info 'Successfully sent gRPC to
|
758
|
+
# @log.info 'Successfully sent gRPC to Stackdriver Logging API.'
|
750
759
|
# end
|
751
760
|
#
|
752
761
|
# rescue GRPC::Cancelled => error
|
@@ -895,7 +904,7 @@ module Fluent
|
|
895
904
|
# # to aid with verification and troubleshooting.
|
896
905
|
# unless @successful_call
|
897
906
|
# @successful_call = true
|
898
|
-
# @log.info 'Successfully sent to
|
907
|
+
# @log.info 'Successfully sent to Stackdriver Logging API.'
|
899
908
|
# end
|
900
909
|
#
|
901
910
|
# rescue Google::Apis::ServerError => error
|
@@ -1078,8 +1087,8 @@ module Fluent
|
|
1078
1087
|
elsif @service_name == CLOUDFUNCTIONS_SERVICE &&
|
1079
1088
|
@cloudfunctions_log_match
|
1080
1089
|
timestamp = DateTime.parse(@cloudfunctions_log_match['timestamp'])
|
1081
|
-
ts_secs = timestamp.strftime('%s')
|
1082
|
-
ts_nanos = timestamp.strftime('%N')
|
1090
|
+
ts_secs = timestamp.strftime('%s').to_i
|
1091
|
+
ts_nanos = timestamp.strftime('%N').to_i
|
1083
1092
|
elsif record.key?('time')
|
1084
1093
|
# k8s ISO8601 timestamp
|
1085
1094
|
begin
|
@@ -1150,17 +1159,27 @@ module Fluent
|
|
1150
1159
|
return nil unless record['httpRequest'].is_a?(Hash)
|
1151
1160
|
input = record['httpRequest']
|
1152
1161
|
output = Google::Logging::Type::HttpRequest.new
|
1153
|
-
output.request_method = input.delete('requestMethod')
|
1154
|
-
|
1155
|
-
output.
|
1156
|
-
|
1157
|
-
output.
|
1158
|
-
|
1159
|
-
output.
|
1160
|
-
|
1161
|
-
output.
|
1162
|
-
|
1163
|
-
|
1162
|
+
output.request_method = input.delete('requestMethod') if
|
1163
|
+
input.key?('requestMethod')
|
1164
|
+
output.request_url = input.delete('requestUrl') if
|
1165
|
+
input.key?('requestUrl')
|
1166
|
+
output.request_size = input.delete('requestSize').to_i if
|
1167
|
+
input.key?('requestSize')
|
1168
|
+
output.status = input.delete('status').to_i if
|
1169
|
+
input.key?('status')
|
1170
|
+
output.response_size = input.delete('responseSize').to_i if
|
1171
|
+
input.key?('responseSize')
|
1172
|
+
output.user_agent = input.delete('userAgent') if
|
1173
|
+
input.key?('userAgent')
|
1174
|
+
output.remote_ip = input.delete('remoteIp') if
|
1175
|
+
input.key?('remoteIp')
|
1176
|
+
output.referer = input.delete('referer') if
|
1177
|
+
input.key?('referer')
|
1178
|
+
output.cache_hit = input.delete('cacheHit') if
|
1179
|
+
input.key?('cacheHit')
|
1180
|
+
output.cache_validated_with_origin_server = \
|
1181
|
+
input.delete('cacheValidatedWithOriginServer') if
|
1182
|
+
input.key?('cacheValidatedWithOriginServer')
|
1164
1183
|
record.delete('httpRequest') if input.empty?
|
1165
1184
|
entry.http_request = output
|
1166
1185
|
end
|
@@ -1179,6 +1198,9 @@ module Fluent
|
|
1179
1198
|
'FINE' => 'DEBUG',
|
1180
1199
|
'FINER' => 'DEBUG',
|
1181
1200
|
'FINEST' => 'DEBUG',
|
1201
|
+
# nginx levels (only missing ones from above listed).
|
1202
|
+
'CRIT' => 'CRITICAL',
|
1203
|
+
'EMERG' => 'EMERGENCY',
|
1182
1204
|
# single-letter levels. Note E->ERROR and D->DEBUG.
|
1183
1205
|
'D' => 'DEBUG',
|
1184
1206
|
'I' => 'INFO',
|
@@ -0,0 +1,1308 @@
|
|
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 'google/apis'
|
16
|
+
require 'helper'
|
17
|
+
require 'mocha/test_unit'
|
18
|
+
require 'webmock/test_unit'
|
19
|
+
|
20
|
+
# Unit tests for Google Cloud Logging plugin
|
21
|
+
module BaseTest
|
22
|
+
def setup
|
23
|
+
Fluent::Test.setup
|
24
|
+
# delete environment variables that googleauth uses to find credentials.
|
25
|
+
ENV.delete('GOOGLE_APPLICATION_CREDENTIALS')
|
26
|
+
# service account env.
|
27
|
+
ENV.delete('PRIVATE_KEY_VAR')
|
28
|
+
ENV.delete('CLIENT_EMAIL_VAR')
|
29
|
+
# authorized_user env.
|
30
|
+
ENV.delete('CLIENT_ID_VAR')
|
31
|
+
ENV.delete('CLIENT_SECRET_VAR')
|
32
|
+
ENV.delete('REFRESH_TOKEN_VAR')
|
33
|
+
# home var, which is used to find $HOME/.gcloud/...
|
34
|
+
ENV.delete('HOME')
|
35
|
+
|
36
|
+
setup_auth_stubs
|
37
|
+
@logs_sent = []
|
38
|
+
end
|
39
|
+
|
40
|
+
# generic attributes
|
41
|
+
HOSTNAME = Socket.gethostname
|
42
|
+
|
43
|
+
# attributes used for the GCE metadata service
|
44
|
+
PROJECT_ID = 'test-project-id'
|
45
|
+
ZONE = 'us-central1-b'
|
46
|
+
FULLY_QUALIFIED_ZONE = 'projects/' + PROJECT_ID + '/zones/' + ZONE
|
47
|
+
VM_ID = '9876543210'
|
48
|
+
|
49
|
+
# attributes used for custom (overridden) configs
|
50
|
+
CUSTOM_PROJECT_ID = 'test-custom-project-id'
|
51
|
+
CUSTOM_ZONE = 'us-custom-central1-b'
|
52
|
+
CUSTOM_FULLY_QUALIFIED_ZONE = 'projects/' + PROJECT_ID + '/zones/' + ZONE
|
53
|
+
CUSTOM_VM_ID = 'C9876543210'
|
54
|
+
CUSTOM_HOSTNAME = 'custom.hostname.org'
|
55
|
+
|
56
|
+
# attributes used for the EC2 metadata service
|
57
|
+
EC2_PROJECT_ID = 'test-ec2-project-id'
|
58
|
+
EC2_ZONE = 'us-west-2b'
|
59
|
+
EC2_PREFIXED_ZONE = 'aws:' + EC2_ZONE
|
60
|
+
EC2_VM_ID = 'i-81c16767'
|
61
|
+
EC2_ACCOUNT_ID = '123456789012'
|
62
|
+
|
63
|
+
# The formatting here matches the format used on the VM.
|
64
|
+
EC2_IDENTITY_DOCUMENT = %({
|
65
|
+
"accountId" : "#{EC2_ACCOUNT_ID}",
|
66
|
+
"availabilityZone" : "#{EC2_ZONE}",
|
67
|
+
"instanceId" : "#{EC2_VM_ID}"
|
68
|
+
})
|
69
|
+
|
70
|
+
# Managed VMs specific labels
|
71
|
+
MANAGED_VM_BACKEND_NAME = 'default'
|
72
|
+
MANAGED_VM_BACKEND_VERSION = 'guestbook2.0'
|
73
|
+
|
74
|
+
# Container Engine / Kubernetes specific labels
|
75
|
+
CONTAINER_CLUSTER_NAME = 'cluster-1'
|
76
|
+
CONTAINER_NAMESPACE_ID = '898268c8-4a36-11e5-9d81-42010af0194c'
|
77
|
+
CONTAINER_NAMESPACE_NAME = 'kube-system'
|
78
|
+
CONTAINER_POD_ID = 'cad3c3c4-4b9c-11e5-9d81-42010af0194c'
|
79
|
+
CONTAINER_POD_NAME = 'redis-master-c0l82.foo.bar'
|
80
|
+
CONTAINER_CONTAINER_NAME = 'redis'
|
81
|
+
CONTAINER_LABEL_KEY = 'component'
|
82
|
+
CONTAINER_LABEL_VALUE = 'redis-component'
|
83
|
+
CONTAINER_STREAM = 'stdout'
|
84
|
+
CONTAINER_SEVERITY = 'INFO'
|
85
|
+
# Timestamp for 1234567890 seconds and 987654321 nanoseconds since epoch
|
86
|
+
CONTAINER_TIMESTAMP = '2009-02-13T23:31:30.987654321Z'
|
87
|
+
CONTAINER_SECONDS_EPOCH = 1_234_567_890
|
88
|
+
CONTAINER_NANOS = 987_654_321
|
89
|
+
|
90
|
+
# Cloud Functions specific labels
|
91
|
+
CLOUDFUNCTIONS_FUNCTION_NAME = '$My_Function.Name-@1'
|
92
|
+
CLOUDFUNCTIONS_REGION = 'us-central1'
|
93
|
+
CLOUDFUNCTIONS_EXECUTION_ID = '123-0'
|
94
|
+
CLOUDFUNCTIONS_CLUSTER_NAME = 'cluster-1'
|
95
|
+
CLOUDFUNCTIONS_NAMESPACE_NAME = 'default'
|
96
|
+
CLOUDFUNCTIONS_POD_NAME = 'd.dc.myu.uc.functionp.pc.name-a.a1.987-c0l82'
|
97
|
+
CLOUDFUNCTIONS_CONTAINER_NAME = 'worker'
|
98
|
+
|
99
|
+
# Parameters used for authentication
|
100
|
+
AUTH_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
101
|
+
FAKE_AUTH_TOKEN = 'abc123'
|
102
|
+
|
103
|
+
# Information about test credentials files.
|
104
|
+
# path: Path to the credentials file.
|
105
|
+
# project_id: ID of the project, which must correspond to the file contents.
|
106
|
+
IAM_CREDENTIALS = {
|
107
|
+
path: 'test/plugin/data/iam-credentials.json',
|
108
|
+
project_id: 'fluent-test-project'
|
109
|
+
}
|
110
|
+
LEGACY_CREDENTIALS = {
|
111
|
+
path: 'test/plugin/data/credentials.json',
|
112
|
+
project_id: '847859579879'
|
113
|
+
}
|
114
|
+
INVALID_CREDENTIALS = {
|
115
|
+
path: 'test/plugin/data/invalid_credentials.json',
|
116
|
+
project_id: ''
|
117
|
+
}
|
118
|
+
|
119
|
+
# Configuration files for various test scenarios
|
120
|
+
APPLICATION_DEFAULT_CONFIG = %(
|
121
|
+
)
|
122
|
+
|
123
|
+
# rubocop:disable Metrics/LineLength
|
124
|
+
PRIVATE_KEY_CONFIG = %(
|
125
|
+
auth_method private_key
|
126
|
+
private_key_email 271661262351-ft99kc9kjro9rrihq3k2n3s2inbplu0q@developer.gserviceaccount.com
|
127
|
+
private_key_path test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12
|
128
|
+
)
|
129
|
+
# rubocop:enable Metrics/LineLength
|
130
|
+
|
131
|
+
NO_METADATA_SERVICE_CONFIG = %(
|
132
|
+
use_metadata_service false
|
133
|
+
)
|
134
|
+
|
135
|
+
NO_DETECT_SUBSERVICE_CONFIG = %(
|
136
|
+
detect_subservice false
|
137
|
+
)
|
138
|
+
|
139
|
+
CUSTOM_METADATA_CONFIG = %(
|
140
|
+
project_id #{CUSTOM_PROJECT_ID}
|
141
|
+
zone #{CUSTOM_ZONE}
|
142
|
+
vm_id #{CUSTOM_VM_ID}
|
143
|
+
vm_name #{CUSTOM_HOSTNAME}
|
144
|
+
)
|
145
|
+
|
146
|
+
CONFIG_MISSING_METADATA_PROJECT_ID = %(
|
147
|
+
zone #{CUSTOM_ZONE}
|
148
|
+
vm_id #{CUSTOM_VM_ID}
|
149
|
+
)
|
150
|
+
CONFIG_MISSING_METADATA_ZONE = %(
|
151
|
+
project_id #{CUSTOM_PROJECT_ID}
|
152
|
+
vm_id #{CUSTOM_VM_ID}
|
153
|
+
)
|
154
|
+
CONFIG_MISSING_METADATA_VM_ID = %(
|
155
|
+
project_id #{CUSTOM_PROJECT_ID}
|
156
|
+
zone #{CUSTOM_ZONE}
|
157
|
+
)
|
158
|
+
CONFIG_MISSING_METADATA_ALL = %(
|
159
|
+
)
|
160
|
+
|
161
|
+
CONFIG_EC2_PROJECT_ID = %(
|
162
|
+
project_id #{EC2_PROJECT_ID}
|
163
|
+
)
|
164
|
+
|
165
|
+
CONFIG_EC2_PROJECT_ID_AND_CUSTOM_VM_ID = %(
|
166
|
+
project_id #{EC2_PROJECT_ID}
|
167
|
+
vm_id #{CUSTOM_VM_ID}
|
168
|
+
)
|
169
|
+
|
170
|
+
# Service configurations for various services
|
171
|
+
COMPUTE_SERVICE_NAME = 'compute.googleapis.com'
|
172
|
+
APPENGINE_SERVICE_NAME = 'appengine.googleapis.com'
|
173
|
+
CONTAINER_SERVICE_NAME = 'container.googleapis.com'
|
174
|
+
CLOUDFUNCTIONS_SERVICE_NAME = 'cloudfunctions.googleapis.com'
|
175
|
+
EC2_SERVICE_NAME = 'ec2.amazonaws.com'
|
176
|
+
|
177
|
+
COMPUTE_PARAMS = {
|
178
|
+
service_name: COMPUTE_SERVICE_NAME,
|
179
|
+
log_name: 'test',
|
180
|
+
project_id: PROJECT_ID,
|
181
|
+
zone: ZONE,
|
182
|
+
labels: {
|
183
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
184
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
185
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
VMENGINE_PARAMS = {
|
190
|
+
service_name: APPENGINE_SERVICE_NAME,
|
191
|
+
log_name: "#{APPENGINE_SERVICE_NAME}%2Ftest",
|
192
|
+
project_id: PROJECT_ID,
|
193
|
+
zone: ZONE,
|
194
|
+
labels: {
|
195
|
+
"#{APPENGINE_SERVICE_NAME}/module_id" => MANAGED_VM_BACKEND_NAME,
|
196
|
+
"#{APPENGINE_SERVICE_NAME}/version_id" => MANAGED_VM_BACKEND_VERSION,
|
197
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
198
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
199
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
CONTAINER_TAG = "kubernetes.#{CONTAINER_POD_NAME}_" \
|
204
|
+
"#{CONTAINER_NAMESPACE_NAME}_#{CONTAINER_CONTAINER_NAME}"
|
205
|
+
|
206
|
+
CONTAINER_FROM_METADATA_PARAMS = {
|
207
|
+
service_name: CONTAINER_SERVICE_NAME,
|
208
|
+
log_name: CONTAINER_CONTAINER_NAME,
|
209
|
+
project_id: PROJECT_ID,
|
210
|
+
zone: ZONE,
|
211
|
+
labels: {
|
212
|
+
"#{CONTAINER_SERVICE_NAME}/instance_id" => VM_ID,
|
213
|
+
"#{CONTAINER_SERVICE_NAME}/cluster_name" => CONTAINER_CLUSTER_NAME,
|
214
|
+
"#{CONTAINER_SERVICE_NAME}/namespace_name" => CONTAINER_NAMESPACE_NAME,
|
215
|
+
"#{CONTAINER_SERVICE_NAME}/namespace_id" => CONTAINER_NAMESPACE_ID,
|
216
|
+
"#{CONTAINER_SERVICE_NAME}/pod_name" => CONTAINER_POD_NAME,
|
217
|
+
"#{CONTAINER_SERVICE_NAME}/pod_id" => CONTAINER_POD_ID,
|
218
|
+
"#{CONTAINER_SERVICE_NAME}/container_name" => CONTAINER_CONTAINER_NAME,
|
219
|
+
"#{CONTAINER_SERVICE_NAME}/stream" => CONTAINER_STREAM,
|
220
|
+
"label/#{CONTAINER_LABEL_KEY}" => CONTAINER_LABEL_VALUE,
|
221
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
222
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
223
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
# Almost the same as from metadata, but missing namespace_id and pod_id.
|
228
|
+
CONTAINER_FROM_TAG_PARAMS = {
|
229
|
+
service_name: CONTAINER_SERVICE_NAME,
|
230
|
+
log_name: CONTAINER_CONTAINER_NAME,
|
231
|
+
project_id: PROJECT_ID,
|
232
|
+
zone: ZONE,
|
233
|
+
labels: {
|
234
|
+
"#{CONTAINER_SERVICE_NAME}/instance_id" => VM_ID,
|
235
|
+
"#{CONTAINER_SERVICE_NAME}/cluster_name" => CONTAINER_CLUSTER_NAME,
|
236
|
+
"#{CONTAINER_SERVICE_NAME}/namespace_name" => CONTAINER_NAMESPACE_NAME,
|
237
|
+
"#{CONTAINER_SERVICE_NAME}/pod_name" => CONTAINER_POD_NAME,
|
238
|
+
"#{CONTAINER_SERVICE_NAME}/container_name" => CONTAINER_CONTAINER_NAME,
|
239
|
+
"#{CONTAINER_SERVICE_NAME}/stream" => CONTAINER_STREAM,
|
240
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
241
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
242
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
CLOUDFUNCTIONS_TAG = "kubernetes.#{CLOUDFUNCTIONS_POD_NAME}_" \
|
247
|
+
"#{CLOUDFUNCTIONS_NAMESPACE_NAME}_" \
|
248
|
+
"#{CLOUDFUNCTIONS_CONTAINER_NAME}"
|
249
|
+
|
250
|
+
CLOUDFUNCTIONS_PARAMS = {
|
251
|
+
service_name: CLOUDFUNCTIONS_SERVICE_NAME,
|
252
|
+
log_name: 'cloud-functions',
|
253
|
+
project_id: PROJECT_ID,
|
254
|
+
zone: ZONE,
|
255
|
+
labels: {
|
256
|
+
'execution_id' => CLOUDFUNCTIONS_EXECUTION_ID,
|
257
|
+
"#{CLOUDFUNCTIONS_SERVICE_NAME}/function_name" =>
|
258
|
+
CLOUDFUNCTIONS_FUNCTION_NAME,
|
259
|
+
"#{CLOUDFUNCTIONS_SERVICE_NAME}/region" => CLOUDFUNCTIONS_REGION,
|
260
|
+
"#{CONTAINER_SERVICE_NAME}/instance_id" => VM_ID,
|
261
|
+
"#{CONTAINER_SERVICE_NAME}/cluster_name" => CLOUDFUNCTIONS_CLUSTER_NAME,
|
262
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
263
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
264
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
265
|
+
}
|
266
|
+
}
|
267
|
+
|
268
|
+
CLOUDFUNCTIONS_TEXT_NOT_MATCHED_PARAMS = {
|
269
|
+
service_name: CLOUDFUNCTIONS_SERVICE_NAME,
|
270
|
+
log_name: 'cloud-functions',
|
271
|
+
project_id: PROJECT_ID,
|
272
|
+
zone: ZONE,
|
273
|
+
labels: {
|
274
|
+
"#{CLOUDFUNCTIONS_SERVICE_NAME}/function_name" =>
|
275
|
+
CLOUDFUNCTIONS_FUNCTION_NAME,
|
276
|
+
"#{CLOUDFUNCTIONS_SERVICE_NAME}/region" => CLOUDFUNCTIONS_REGION,
|
277
|
+
"#{CONTAINER_SERVICE_NAME}/instance_id" => VM_ID,
|
278
|
+
"#{CONTAINER_SERVICE_NAME}/cluster_name" => CLOUDFUNCTIONS_CLUSTER_NAME,
|
279
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
280
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
281
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
282
|
+
}
|
283
|
+
}
|
284
|
+
|
285
|
+
CUSTOM_PARAMS = {
|
286
|
+
service_name: COMPUTE_SERVICE_NAME,
|
287
|
+
log_name: 'test',
|
288
|
+
project_id: CUSTOM_PROJECT_ID,
|
289
|
+
zone: CUSTOM_ZONE,
|
290
|
+
labels: {
|
291
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
292
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => CUSTOM_VM_ID,
|
293
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => CUSTOM_HOSTNAME
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
EC2_PARAMS = {
|
298
|
+
service_name: EC2_SERVICE_NAME,
|
299
|
+
log_name: 'test',
|
300
|
+
project_id: EC2_PROJECT_ID,
|
301
|
+
zone: EC2_PREFIXED_ZONE,
|
302
|
+
labels: {
|
303
|
+
"#{EC2_SERVICE_NAME}/resource_type" => 'instance',
|
304
|
+
"#{EC2_SERVICE_NAME}/resource_id" => EC2_VM_ID,
|
305
|
+
"#{EC2_SERVICE_NAME}/account_id" => EC2_ACCOUNT_ID,
|
306
|
+
"#{EC2_SERVICE_NAME}/resource_name" => HOSTNAME
|
307
|
+
}
|
308
|
+
}
|
309
|
+
|
310
|
+
# Shared tests.
|
311
|
+
|
312
|
+
def test_configure_service_account_application_default
|
313
|
+
setup_gce_metadata_stubs
|
314
|
+
d = create_driver
|
315
|
+
assert_equal HOSTNAME, d.instance.vm_name
|
316
|
+
end
|
317
|
+
|
318
|
+
def test_configure_service_account_private_key
|
319
|
+
# Using out-of-date config method.
|
320
|
+
exception_count = 0
|
321
|
+
begin
|
322
|
+
create_driver(PRIVATE_KEY_CONFIG)
|
323
|
+
rescue Fluent::ConfigError => error
|
324
|
+
assert error.message.include? 'Please remove configuration parameters'
|
325
|
+
exception_count += 1
|
326
|
+
end
|
327
|
+
assert_equal 1, exception_count
|
328
|
+
end
|
329
|
+
|
330
|
+
def test_configure_custom_metadata
|
331
|
+
setup_no_metadata_service_stubs
|
332
|
+
d = create_driver(CUSTOM_METADATA_CONFIG)
|
333
|
+
assert_equal CUSTOM_PROJECT_ID, d.instance.project_id
|
334
|
+
assert_equal CUSTOM_ZONE, d.instance.zone
|
335
|
+
assert_equal CUSTOM_VM_ID, d.instance.vm_id
|
336
|
+
end
|
337
|
+
|
338
|
+
def test_configure_invalid_metadata_missing_parts
|
339
|
+
setup_no_metadata_service_stubs
|
340
|
+
Fluent::GoogleCloudOutput::CredentialsInfo.stubs(:project_id).returns(nil)
|
341
|
+
{ CONFIG_MISSING_METADATA_PROJECT_ID => ['project_id'],
|
342
|
+
CONFIG_MISSING_METADATA_ZONE => ['zone'],
|
343
|
+
CONFIG_MISSING_METADATA_VM_ID => ['vm_id'],
|
344
|
+
CONFIG_MISSING_METADATA_ALL => %w(project_id zone vm_id)
|
345
|
+
}.each_with_index do |(config, parts), index|
|
346
|
+
exception_count = 0
|
347
|
+
begin
|
348
|
+
create_driver(config)
|
349
|
+
rescue Fluent::ConfigError => error
|
350
|
+
assert error.message.include?('Unable to obtain metadata parameters:'),
|
351
|
+
"Index #{index} failed."
|
352
|
+
parts.each do |part|
|
353
|
+
assert error.message.include?(part), "Index #{index} failed."
|
354
|
+
end
|
355
|
+
exception_count += 1
|
356
|
+
end
|
357
|
+
assert_equal 1, exception_count, "Index #{index} failed."
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def test_metadata_loading
|
362
|
+
setup_gce_metadata_stubs
|
363
|
+
d = create_driver
|
364
|
+
d.run
|
365
|
+
assert_equal PROJECT_ID, d.instance.project_id
|
366
|
+
assert_equal ZONE, d.instance.zone
|
367
|
+
assert_equal VM_ID, d.instance.vm_id
|
368
|
+
assert_equal false, d.instance.running_on_managed_vm
|
369
|
+
end
|
370
|
+
|
371
|
+
def test_managed_vm_metadata_loading
|
372
|
+
setup_gce_metadata_stubs
|
373
|
+
setup_managed_vm_metadata_stubs
|
374
|
+
d = create_driver
|
375
|
+
d.run
|
376
|
+
assert_equal PROJECT_ID, d.instance.project_id
|
377
|
+
assert_equal ZONE, d.instance.zone
|
378
|
+
assert_equal VM_ID, d.instance.vm_id
|
379
|
+
assert_equal true, d.instance.running_on_managed_vm
|
380
|
+
assert_equal MANAGED_VM_BACKEND_NAME, d.instance.gae_backend_name
|
381
|
+
assert_equal MANAGED_VM_BACKEND_VERSION, d.instance.gae_backend_version
|
382
|
+
end
|
383
|
+
|
384
|
+
def test_gce_metadata_does_not_load_when_use_metadata_service_is_false
|
385
|
+
Fluent::GoogleCloudOutput.any_instance.expects(:fetch_metadata).never
|
386
|
+
d = create_driver(NO_METADATA_SERVICE_CONFIG + CUSTOM_METADATA_CONFIG)
|
387
|
+
d.run
|
388
|
+
assert_equal CUSTOM_PROJECT_ID, d.instance.project_id
|
389
|
+
assert_equal CUSTOM_ZONE, d.instance.zone
|
390
|
+
assert_equal CUSTOM_VM_ID, d.instance.vm_id
|
391
|
+
assert_equal false, d.instance.running_on_managed_vm
|
392
|
+
end
|
393
|
+
|
394
|
+
def test_gce_used_when_detect_subservice_is_false
|
395
|
+
setup_gce_metadata_stubs
|
396
|
+
# This would cause the service to be container.googleapis.com if not for the
|
397
|
+
# detect_subservice=false config.
|
398
|
+
setup_container_metadata_stubs
|
399
|
+
d = create_driver(NO_DETECT_SUBSERVICE_CONFIG)
|
400
|
+
d.run
|
401
|
+
assert_equal COMPUTE_SERVICE_NAME, d.instance.service_name
|
402
|
+
end
|
403
|
+
|
404
|
+
def test_metadata_overrides
|
405
|
+
{
|
406
|
+
# In this case we are overriding all configured parameters so we should
|
407
|
+
# see all "custom" values rather than the ones from the metadata server.
|
408
|
+
CUSTOM_METADATA_CONFIG =>
|
409
|
+
['gce', CUSTOM_PROJECT_ID, CUSTOM_ZONE, CUSTOM_VM_ID],
|
410
|
+
# Similar to above, but we are not overriding project_id in this config so
|
411
|
+
# we should see the metadata value for project_id and "custom" otherwise.
|
412
|
+
CONFIG_MISSING_METADATA_PROJECT_ID =>
|
413
|
+
['gce', PROJECT_ID, CUSTOM_ZONE, CUSTOM_VM_ID],
|
414
|
+
CONFIG_EC2_PROJECT_ID =>
|
415
|
+
['ec2', EC2_PROJECT_ID, EC2_PREFIXED_ZONE, EC2_VM_ID],
|
416
|
+
CONFIG_EC2_PROJECT_ID_AND_CUSTOM_VM_ID =>
|
417
|
+
['ec2', EC2_PROJECT_ID, EC2_PREFIXED_ZONE, CUSTOM_VM_ID]
|
418
|
+
}.each_with_index do |(config, parts), index|
|
419
|
+
send("setup_#{parts[0]}_metadata_stubs")
|
420
|
+
d = create_driver(config)
|
421
|
+
d.run
|
422
|
+
assert_equal parts[1], d.instance.project_id, "Index #{index} failed."
|
423
|
+
assert_equal parts[2], d.instance.zone, "Index #{index} failed."
|
424
|
+
assert_equal parts[3], d.instance.vm_id, "Index #{index} failed."
|
425
|
+
assert_equal false, d.instance.running_on_managed_vm,
|
426
|
+
"Index #{index} failed."
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def test_ec2_metadata_requires_project_id
|
431
|
+
setup_ec2_metadata_stubs
|
432
|
+
exception_count = 0
|
433
|
+
Fluent::GoogleCloudOutput::CredentialsInfo.stubs(:project_id).returns(nil)
|
434
|
+
begin
|
435
|
+
create_driver
|
436
|
+
rescue Fluent::ConfigError => error
|
437
|
+
assert error.message.include? 'Unable to obtain metadata parameters:'
|
438
|
+
assert error.message.include? 'project_id'
|
439
|
+
exception_count += 1
|
440
|
+
end
|
441
|
+
assert_equal 1, exception_count
|
442
|
+
end
|
443
|
+
|
444
|
+
def test_ec2_metadata_project_id_from_credentials
|
445
|
+
setup_ec2_metadata_stubs
|
446
|
+
[IAM_CREDENTIALS, LEGACY_CREDENTIALS].each do |creds|
|
447
|
+
ENV['GOOGLE_APPLICATION_CREDENTIALS'] = creds[:path]
|
448
|
+
d = create_driver
|
449
|
+
d.run
|
450
|
+
assert_equal creds[:project_id], d.instance.project_id
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def test_one_log
|
455
|
+
setup_gce_metadata_stubs
|
456
|
+
setup_logging_stubs do
|
457
|
+
d = create_driver
|
458
|
+
d.emit('message' => log_entry(0))
|
459
|
+
d.run
|
460
|
+
end
|
461
|
+
verify_log_entries(1, COMPUTE_PARAMS)
|
462
|
+
end
|
463
|
+
|
464
|
+
def test_one_log_with_json_credentials
|
465
|
+
setup_gce_metadata_stubs
|
466
|
+
ENV['GOOGLE_APPLICATION_CREDENTIALS'] = IAM_CREDENTIALS[:path]
|
467
|
+
setup_logging_stubs do
|
468
|
+
d = create_driver
|
469
|
+
d.emit('message' => log_entry(0))
|
470
|
+
d.run
|
471
|
+
end
|
472
|
+
verify_log_entries(1, COMPUTE_PARAMS)
|
473
|
+
end
|
474
|
+
|
475
|
+
def test_one_log_with_invalid_json_credentials
|
476
|
+
setup_gce_metadata_stubs
|
477
|
+
ENV['GOOGLE_APPLICATION_CREDENTIALS'] = INVALID_CREDENTIALS[:path]
|
478
|
+
setup_logging_stubs do
|
479
|
+
d = create_driver
|
480
|
+
d.emit('message' => log_entry(0))
|
481
|
+
exception_count = 0
|
482
|
+
begin
|
483
|
+
d.run
|
484
|
+
rescue RuntimeError => error
|
485
|
+
assert error.message.include? 'Unable to read the credential file'
|
486
|
+
exception_count += 1
|
487
|
+
end
|
488
|
+
assert_equal 1, exception_count
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
def test_one_log_custom_metadata
|
493
|
+
# don't set up any metadata stubs, so the test will fail if we try to
|
494
|
+
# fetch metadata (and explicitly check this as well).
|
495
|
+
Fluent::GoogleCloudOutput.any_instance.expects(:fetch_metadata).never
|
496
|
+
ENV['GOOGLE_APPLICATION_CREDENTIALS'] = IAM_CREDENTIALS[:path]
|
497
|
+
setup_logging_stubs do
|
498
|
+
d = create_driver(NO_METADATA_SERVICE_CONFIG + CUSTOM_METADATA_CONFIG)
|
499
|
+
d.emit('message' => log_entry(0))
|
500
|
+
d.run
|
501
|
+
end
|
502
|
+
verify_log_entries(1, CUSTOM_PARAMS)
|
503
|
+
end
|
504
|
+
|
505
|
+
def test_one_log_ec2
|
506
|
+
ENV['GOOGLE_APPLICATION_CREDENTIALS'] = IAM_CREDENTIALS[:path]
|
507
|
+
setup_ec2_metadata_stubs
|
508
|
+
setup_logging_stubs do
|
509
|
+
d = create_driver(CONFIG_EC2_PROJECT_ID)
|
510
|
+
d.emit('message' => log_entry(0))
|
511
|
+
d.run
|
512
|
+
end
|
513
|
+
verify_log_entries(1, EC2_PARAMS)
|
514
|
+
end
|
515
|
+
|
516
|
+
def test_struct_payload_log
|
517
|
+
setup_gce_metadata_stubs
|
518
|
+
setup_logging_stubs do
|
519
|
+
d = create_driver
|
520
|
+
d.emit('msg' => log_entry(0), 'tag2' => 'test', 'data' => 5000)
|
521
|
+
d.run
|
522
|
+
end
|
523
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'structPayload') do |entry|
|
524
|
+
fields = get_fields(entry['structPayload'])
|
525
|
+
assert_equal 3, fields.size, entry
|
526
|
+
assert_equal 'test log entry 0', get_string(fields['msg']), entry
|
527
|
+
assert_equal 'test', get_string(fields['tag2']), entry
|
528
|
+
assert_equal 5000, get_number(fields['data']), entry
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
def test_struct_payload_json_log
|
533
|
+
setup_gce_metadata_stubs
|
534
|
+
setup_logging_stubs do
|
535
|
+
d = create_driver
|
536
|
+
json_string = '{"msg": "test log entry 0", "tag2": "test", "data": 5000}'
|
537
|
+
d.emit('message' => 'notJSON ' + json_string)
|
538
|
+
d.emit('message' => json_string)
|
539
|
+
d.emit('message' => "\t" + json_string)
|
540
|
+
d.emit('message' => ' ' + json_string)
|
541
|
+
d.run
|
542
|
+
end
|
543
|
+
verify_log_entries(4, COMPUTE_PARAMS, '') do |entry|
|
544
|
+
assert entry.key?('textPayload'), 'Entry did not have textPayload'
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
def test_struct_payload_json_container_log
|
549
|
+
setup_gce_metadata_stubs
|
550
|
+
setup_container_metadata_stubs
|
551
|
+
setup_logging_stubs do
|
552
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
553
|
+
json_string = '{"msg": "test log entry 0", "tag2": "test", "data": 5000}'
|
554
|
+
d.emit(container_log_entry_with_metadata('notJSON' + json_string))
|
555
|
+
d.emit(container_log_entry_with_metadata(json_string))
|
556
|
+
d.emit(container_log_entry_with_metadata(" \r\n \t" + json_string))
|
557
|
+
d.run
|
558
|
+
end
|
559
|
+
log_index = 0
|
560
|
+
verify_log_entries(
|
561
|
+
3, CONTAINER_FROM_METADATA_PARAMS, '') do |entry|
|
562
|
+
log_index += 1
|
563
|
+
if log_index == 1
|
564
|
+
assert entry.key?('textPayload'), 'Entry did not have textPayload'
|
565
|
+
else
|
566
|
+
assert entry.key?('structPayload'), 'Entry did not have structPayload'
|
567
|
+
fields = get_fields(entry['structPayload'])
|
568
|
+
assert_equal 3, fields.size, entry
|
569
|
+
assert_equal 'test log entry 0', get_string(fields['msg']), entry
|
570
|
+
assert_equal 'test', get_string(fields['tag2']), entry
|
571
|
+
assert_equal 5000, get_number(fields['data']), entry
|
572
|
+
end
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
def test_timestamps
|
577
|
+
setup_gce_metadata_stubs
|
578
|
+
expected_ts = []
|
579
|
+
emit_index = 0
|
580
|
+
setup_logging_stubs do
|
581
|
+
[Time.at(123_456.789), Time.at(0), Time.now].each do |ts|
|
582
|
+
d = create_driver
|
583
|
+
# Test the "native" fluentd timestamp as well as our nanosecond tags.
|
584
|
+
d.emit({ 'message' => log_entry(emit_index) }, ts.to_f)
|
585
|
+
expected_ts.push(ts)
|
586
|
+
emit_index += 1
|
587
|
+
d.emit('message' => log_entry(emit_index),
|
588
|
+
'timeNanos' => ts.tv_sec * 1_000_000_000 + ts.tv_nsec)
|
589
|
+
expected_ts.push(ts)
|
590
|
+
emit_index += 1
|
591
|
+
d.emit('message' => log_entry(emit_index),
|
592
|
+
'timestamp' => { 'seconds' => ts.tv_sec, 'nanos' => ts.tv_nsec })
|
593
|
+
expected_ts.push(ts)
|
594
|
+
emit_index += 1
|
595
|
+
d.emit('message' => log_entry(emit_index),
|
596
|
+
'timestampSeconds' => ts.tv_sec, 'timestampNanos' => ts.tv_nsec)
|
597
|
+
expected_ts.push(ts)
|
598
|
+
emit_index += 1
|
599
|
+
d.run
|
600
|
+
end
|
601
|
+
end
|
602
|
+
verify_index = 0
|
603
|
+
verify_log_entries(emit_index, COMPUTE_PARAMS) do |entry|
|
604
|
+
assert_equal_with_default entry['metadata']['timestamp']['seconds'],
|
605
|
+
expected_ts[verify_index].tv_sec, 0, entry
|
606
|
+
assert_equal_with_default entry['metadata']['timestamp']['nanos'],
|
607
|
+
expected_ts[verify_index].tv_nsec, 0, entry do
|
608
|
+
# Fluentd v0.14 onwards supports nanosecond timestamp values.
|
609
|
+
# Added in 600 ns delta to avoid flaky tests introduced
|
610
|
+
# due to rounding error in double-precision floating-point numbers
|
611
|
+
# (to account for the missing 9 bits of precision ~ 512 ns).
|
612
|
+
# See http://wikipedia.org/wiki/Double-precision_floating-point_format
|
613
|
+
assert_in_delta expected_ts[verify_index].tv_nsec,
|
614
|
+
entry['metadata']['timestamp']['nanos'], 600, entry
|
615
|
+
end
|
616
|
+
verify_index += 1
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
def test_malformed_timestamp
|
621
|
+
setup_gce_metadata_stubs
|
622
|
+
setup_logging_stubs do
|
623
|
+
d = create_driver
|
624
|
+
# if timestamp is not a hash it is passed through to the struct payload.
|
625
|
+
d.emit('message' => log_entry(0), 'timestamp' => 'not-a-hash')
|
626
|
+
d.run
|
627
|
+
end
|
628
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'structPayload') do |entry|
|
629
|
+
fields = get_fields(entry['structPayload'])
|
630
|
+
assert_equal 2, fields.size, entry
|
631
|
+
assert_equal 'not-a-hash', get_string(fields['timestamp']), entry
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
# Make parse_severity public so we can test it.
|
636
|
+
class Fluent::GoogleCloudOutput # rubocop:disable Style/ClassAndModuleChildren
|
637
|
+
public :parse_severity
|
638
|
+
end
|
639
|
+
|
640
|
+
def test_label_map_without_field_present
|
641
|
+
setup_gce_metadata_stubs
|
642
|
+
setup_logging_stubs do
|
643
|
+
config = %(label_map { "label_field": "sent_label" })
|
644
|
+
d = create_driver(config)
|
645
|
+
d.emit('message' => log_entry(0))
|
646
|
+
d.run
|
647
|
+
# No additional labels should be present
|
648
|
+
end
|
649
|
+
verify_log_entries(1, COMPUTE_PARAMS)
|
650
|
+
end
|
651
|
+
|
652
|
+
def test_label_map_with_field_present
|
653
|
+
setup_gce_metadata_stubs
|
654
|
+
setup_logging_stubs do
|
655
|
+
config = %(label_map { "label_field": "sent_label" })
|
656
|
+
d = create_driver(config)
|
657
|
+
d.emit('message' => log_entry(0), 'label_field' => 'label_value')
|
658
|
+
d.run
|
659
|
+
end
|
660
|
+
# make a deep copy of COMPUTE_PARAMS and add the parsed label.
|
661
|
+
params = Marshal.load(Marshal.dump(COMPUTE_PARAMS))
|
662
|
+
params[:labels]['sent_label'] = 'label_value'
|
663
|
+
verify_log_entries(1, params)
|
664
|
+
end
|
665
|
+
|
666
|
+
def test_label_map_with_numeric_field
|
667
|
+
setup_gce_metadata_stubs
|
668
|
+
setup_logging_stubs do
|
669
|
+
config = %(label_map { "label_field": "sent_label" })
|
670
|
+
d = create_driver(config)
|
671
|
+
d.emit('message' => log_entry(0), 'label_field' => 123_456_789)
|
672
|
+
d.run
|
673
|
+
end
|
674
|
+
# make a deep copy of COMPUTE_PARAMS and add the parsed label.
|
675
|
+
params = Marshal.load(Marshal.dump(COMPUTE_PARAMS))
|
676
|
+
params[:labels]['sent_label'] = '123456789'
|
677
|
+
verify_log_entries(1, params)
|
678
|
+
end
|
679
|
+
|
680
|
+
def test_label_map_with_hash_field
|
681
|
+
setup_gce_metadata_stubs
|
682
|
+
setup_logging_stubs do
|
683
|
+
config = %(label_map { "label_field": "sent_label" })
|
684
|
+
d = create_driver(config)
|
685
|
+
# I'm not sure this actually makes sense for a user to do, but make
|
686
|
+
# sure that it works if they try it.
|
687
|
+
d.emit('message' => log_entry(0),
|
688
|
+
'label_field' => { 'k1' => 10, 'k2' => 'val' })
|
689
|
+
d.run
|
690
|
+
end
|
691
|
+
# make a deep copy of COMPUTE_PARAMS and add the parsed label.
|
692
|
+
params = Marshal.load(Marshal.dump(COMPUTE_PARAMS))
|
693
|
+
params[:labels]['sent_label'] = '{"k1"=>10, "k2"=>"val"}'
|
694
|
+
verify_log_entries(1, params)
|
695
|
+
end
|
696
|
+
|
697
|
+
def test_label_map_with_multiple_fields
|
698
|
+
setup_gce_metadata_stubs
|
699
|
+
setup_logging_stubs do
|
700
|
+
config = %(
|
701
|
+
label_map {
|
702
|
+
"label1": "sent_label_1",
|
703
|
+
"label_number_two": "foo.googleapis.com/bar",
|
704
|
+
"label3": "label3"
|
705
|
+
}
|
706
|
+
)
|
707
|
+
d = create_driver(config)
|
708
|
+
# not_a_label passes through to the struct payload
|
709
|
+
d.emit('message' => log_entry(0),
|
710
|
+
'label1' => 'value1',
|
711
|
+
'label_number_two' => 'value2',
|
712
|
+
'not_a_label' => 'value4',
|
713
|
+
'label3' => 'value3')
|
714
|
+
d.run
|
715
|
+
end
|
716
|
+
# make a deep copy of COMPUTE_PARAMS and add the parsed labels.
|
717
|
+
params = Marshal.load(Marshal.dump(COMPUTE_PARAMS))
|
718
|
+
params[:labels]['sent_label_1'] = 'value1'
|
719
|
+
params[:labels]['foo.googleapis.com/bar'] = 'value2'
|
720
|
+
params[:labels]['label3'] = 'value3'
|
721
|
+
verify_log_entries(1, params, 'structPayload') do |entry|
|
722
|
+
fields = get_fields(entry['structPayload'])
|
723
|
+
assert_equal 2, fields.size, entry
|
724
|
+
assert_equal 'test log entry 0', get_string(fields['message']), entry
|
725
|
+
assert_equal 'value4', get_string(fields['not_a_label']), entry
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
def test_multiple_logs
|
730
|
+
setup_gce_metadata_stubs
|
731
|
+
# Only test a few values because otherwise the test can take minutes.
|
732
|
+
[2, 3, 5, 11, 50].each do |n|
|
733
|
+
setup_logging_stubs do
|
734
|
+
d = create_driver
|
735
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
736
|
+
# do it manually here.
|
737
|
+
d.instance_variable_get('@entries').clear
|
738
|
+
@logs_sent = []
|
739
|
+
n.times { |i| d.emit('message' => log_entry(i)) }
|
740
|
+
d.run
|
741
|
+
end
|
742
|
+
verify_log_entries(n, COMPUTE_PARAMS)
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
746
|
+
def test_malformed_log
|
747
|
+
setup_gce_metadata_stubs
|
748
|
+
setup_logging_stubs do
|
749
|
+
d = create_driver
|
750
|
+
# if the entry is not a hash, the plugin should silently drop it.
|
751
|
+
d.emit('a string is not a valid message')
|
752
|
+
d.run
|
753
|
+
end
|
754
|
+
assert @logs_sent.empty?
|
755
|
+
end
|
756
|
+
|
757
|
+
def test_one_managed_vm_log
|
758
|
+
setup_gce_metadata_stubs
|
759
|
+
setup_managed_vm_metadata_stubs
|
760
|
+
setup_logging_stubs do
|
761
|
+
d = create_driver
|
762
|
+
d.emit('message' => log_entry(0))
|
763
|
+
d.run
|
764
|
+
end
|
765
|
+
verify_log_entries(1, VMENGINE_PARAMS)
|
766
|
+
end
|
767
|
+
|
768
|
+
def test_multiple_managed_vm_logs
|
769
|
+
setup_gce_metadata_stubs
|
770
|
+
setup_managed_vm_metadata_stubs
|
771
|
+
[2, 3, 5, 11, 50].each do |n|
|
772
|
+
setup_logging_stubs do
|
773
|
+
d = create_driver
|
774
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
775
|
+
# do it manually here.
|
776
|
+
d.instance_variable_get('@entries').clear
|
777
|
+
@logs_sent = []
|
778
|
+
n.times { |i| d.emit('message' => log_entry(i)) }
|
779
|
+
d.run
|
780
|
+
end
|
781
|
+
verify_log_entries(n, VMENGINE_PARAMS)
|
782
|
+
end
|
783
|
+
end
|
784
|
+
|
785
|
+
def test_one_container_log_metadata_from_plugin
|
786
|
+
setup_gce_metadata_stubs
|
787
|
+
setup_container_metadata_stubs
|
788
|
+
setup_logging_stubs do
|
789
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
790
|
+
d.emit(container_log_entry_with_metadata(log_entry(0)))
|
791
|
+
d.run
|
792
|
+
end
|
793
|
+
verify_log_entries(1, CONTAINER_FROM_METADATA_PARAMS) do |entry|
|
794
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
795
|
+
entry['metadata']['timestamp']['seconds'], entry
|
796
|
+
assert_equal CONTAINER_NANOS, \
|
797
|
+
entry['metadata']['timestamp']['nanos'], entry
|
798
|
+
assert_equal CONTAINER_SEVERITY, entry['metadata']['severity'], entry
|
799
|
+
end
|
800
|
+
end
|
801
|
+
|
802
|
+
def test_multiple_container_logs_metadata_from_plugin
|
803
|
+
setup_gce_metadata_stubs
|
804
|
+
setup_container_metadata_stubs
|
805
|
+
[2, 3, 5, 11, 50].each do |n|
|
806
|
+
@logs_sent = []
|
807
|
+
setup_logging_stubs do
|
808
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
809
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
810
|
+
# do it manually here.
|
811
|
+
d.instance_variable_get('@entries').clear
|
812
|
+
n.times { |i| d.emit(container_log_entry_with_metadata(log_entry(i))) }
|
813
|
+
d.run
|
814
|
+
end
|
815
|
+
verify_log_entries(n, CONTAINER_FROM_METADATA_PARAMS) do |entry|
|
816
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
817
|
+
entry['metadata']['timestamp']['seconds'], entry
|
818
|
+
assert_equal CONTAINER_NANOS, \
|
819
|
+
entry['metadata']['timestamp']['nanos'], entry
|
820
|
+
assert_equal CONTAINER_SEVERITY, entry['metadata']['severity'], entry
|
821
|
+
end
|
822
|
+
end
|
823
|
+
end
|
824
|
+
|
825
|
+
def test_multiple_container_logs_metadata_from_tag
|
826
|
+
setup_gce_metadata_stubs
|
827
|
+
setup_container_metadata_stubs
|
828
|
+
[2, 3, 5, 11, 50].each do |n|
|
829
|
+
@logs_sent = []
|
830
|
+
setup_logging_stubs do
|
831
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
832
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
833
|
+
# do it manually here.
|
834
|
+
d.instance_variable_get('@entries').clear
|
835
|
+
n.times { |i| d.emit(container_log_entry(log_entry(i))) }
|
836
|
+
d.run
|
837
|
+
end
|
838
|
+
verify_log_entries(n, CONTAINER_FROM_TAG_PARAMS) do |entry|
|
839
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
840
|
+
entry['metadata']['timestamp']['seconds'], entry
|
841
|
+
assert_equal CONTAINER_NANOS, \
|
842
|
+
entry['metadata']['timestamp']['nanos'], entry
|
843
|
+
assert_equal CONTAINER_SEVERITY, entry['metadata']['severity'], entry
|
844
|
+
end
|
845
|
+
end
|
846
|
+
end
|
847
|
+
|
848
|
+
def test_one_container_log_metadata_from_tag
|
849
|
+
setup_gce_metadata_stubs
|
850
|
+
setup_container_metadata_stubs
|
851
|
+
setup_logging_stubs do
|
852
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
853
|
+
d.emit(container_log_entry(log_entry(0)))
|
854
|
+
d.run
|
855
|
+
end
|
856
|
+
verify_log_entries(1, CONTAINER_FROM_TAG_PARAMS) do |entry|
|
857
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
858
|
+
entry['metadata']['timestamp']['seconds'], entry
|
859
|
+
assert_equal CONTAINER_NANOS, \
|
860
|
+
entry['metadata']['timestamp']['nanos'], entry
|
861
|
+
assert_equal CONTAINER_SEVERITY, entry['metadata']['severity'], entry
|
862
|
+
end
|
863
|
+
end
|
864
|
+
|
865
|
+
def test_one_container_log_from_tag_stderr
|
866
|
+
setup_gce_metadata_stubs
|
867
|
+
setup_container_metadata_stubs
|
868
|
+
setup_logging_stubs do
|
869
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
870
|
+
d.emit(container_log_entry(log_entry(0), 'stderr'))
|
871
|
+
d.run
|
872
|
+
end
|
873
|
+
expected_params = CONTAINER_FROM_TAG_PARAMS.merge(
|
874
|
+
labels: { "#{CONTAINER_SERVICE_NAME}/stream" => 'stderr' }
|
875
|
+
) { |_, oldval, newval| oldval.merge(newval) }
|
876
|
+
verify_log_entries(1, expected_params) do |entry|
|
877
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
878
|
+
entry['metadata']['timestamp']['seconds'], entry
|
879
|
+
assert_equal CONTAINER_NANOS, \
|
880
|
+
entry['metadata']['timestamp']['nanos'], entry
|
881
|
+
assert_equal 'ERROR', entry['metadata']['severity'], entry
|
882
|
+
end
|
883
|
+
end
|
884
|
+
|
885
|
+
def test_struct_container_log_metadata_from_plugin
|
886
|
+
setup_gce_metadata_stubs
|
887
|
+
setup_container_metadata_stubs
|
888
|
+
setup_logging_stubs do
|
889
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
890
|
+
d.emit(container_log_entry_with_metadata('{"msg": "test log entry 0", ' \
|
891
|
+
'"tag2": "test", "data": ' \
|
892
|
+
'5000, "severity": "WARNING"}'))
|
893
|
+
d.run
|
894
|
+
end
|
895
|
+
verify_log_entries(1, CONTAINER_FROM_METADATA_PARAMS,
|
896
|
+
'structPayload') do |entry|
|
897
|
+
fields = get_fields(entry['structPayload'])
|
898
|
+
assert_equal 3, fields.size, entry
|
899
|
+
assert_equal 'test log entry 0', get_string(fields['msg']), entry
|
900
|
+
assert_equal 'test', get_string(fields['tag2']), entry
|
901
|
+
assert_equal 5000, get_number(fields['data']), entry
|
902
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
903
|
+
entry['metadata']['timestamp']['seconds'], entry
|
904
|
+
assert_equal CONTAINER_NANOS, \
|
905
|
+
entry['metadata']['timestamp']['nanos'], entry
|
906
|
+
assert_equal 'WARNING', entry['metadata']['severity'], entry
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
910
|
+
def test_struct_container_log_metadata_from_tag
|
911
|
+
setup_gce_metadata_stubs
|
912
|
+
setup_container_metadata_stubs
|
913
|
+
setup_logging_stubs do
|
914
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
915
|
+
d.emit(container_log_entry('{"msg": "test log entry 0", ' \
|
916
|
+
'"tag2": "test", "data": 5000, ' \
|
917
|
+
'"severity": "W"}'))
|
918
|
+
d.run
|
919
|
+
end
|
920
|
+
verify_log_entries(1, CONTAINER_FROM_TAG_PARAMS,
|
921
|
+
'structPayload') do |entry|
|
922
|
+
fields = get_fields(entry['structPayload'])
|
923
|
+
assert_equal 3, fields.size, entry
|
924
|
+
assert_equal 'test log entry 0', get_string(fields['msg']), entry
|
925
|
+
assert_equal 'test', get_string(fields['tag2']), entry
|
926
|
+
assert_equal 5000, get_number(fields['data']), entry
|
927
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
928
|
+
entry['metadata']['timestamp']['seconds'], entry
|
929
|
+
assert_equal CONTAINER_NANOS, \
|
930
|
+
entry['metadata']['timestamp']['nanos'], entry
|
931
|
+
assert_equal 'WARNING', entry['metadata']['severity'], entry
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
def test_cloudfunctions_log
|
936
|
+
setup_gce_metadata_stubs
|
937
|
+
setup_cloudfunctions_metadata_stubs
|
938
|
+
[1, 2, 3, 5, 11, 50].each do |n|
|
939
|
+
setup_logging_stubs do
|
940
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CLOUDFUNCTIONS_TAG)
|
941
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
942
|
+
# do it manually here.
|
943
|
+
d.instance_variable_get('@entries').clear
|
944
|
+
@logs_sent = []
|
945
|
+
n.times { |i| d.emit(cloudfunctions_log_entry(i)) }
|
946
|
+
d.run
|
947
|
+
end
|
948
|
+
verify_log_entries(n, CLOUDFUNCTIONS_PARAMS) do |entry|
|
949
|
+
assert_equal 'DEBUG', entry['metadata']['severity'],
|
950
|
+
"Test with #{n} logs failed. \n#{entry}"
|
951
|
+
end
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
def test_cloudfunctions_logs_text_not_matched
|
956
|
+
setup_gce_metadata_stubs
|
957
|
+
setup_cloudfunctions_metadata_stubs
|
958
|
+
[1, 2, 3, 5, 11, 50].each do |n|
|
959
|
+
@logs_sent = []
|
960
|
+
setup_logging_stubs do
|
961
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CLOUDFUNCTIONS_TAG)
|
962
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
963
|
+
# do it manually here.
|
964
|
+
d.instance_variable_get('@entries').clear
|
965
|
+
n.times { |i| d.emit(cloudfunctions_log_entry_text_not_matched(i)) }
|
966
|
+
d.run
|
967
|
+
end
|
968
|
+
verify_log_entries(
|
969
|
+
n, CLOUDFUNCTIONS_TEXT_NOT_MATCHED_PARAMS) do |entry|
|
970
|
+
assert_equal 'INFO', entry['metadata']['severity'],
|
971
|
+
"Test with #{n} logs failed. \n#{entry}"
|
972
|
+
end
|
973
|
+
end
|
974
|
+
end
|
975
|
+
|
976
|
+
def test_multiple_cloudfunctions_logs_tag_not_matched
|
977
|
+
setup_gce_metadata_stubs
|
978
|
+
setup_cloudfunctions_metadata_stubs
|
979
|
+
[1, 2, 3, 5, 11, 50].each do |n|
|
980
|
+
@logs_sent = []
|
981
|
+
setup_logging_stubs do
|
982
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
983
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
984
|
+
# do it manually here.
|
985
|
+
d.instance_variable_get('@entries').clear
|
986
|
+
n.times { |i| d.emit(cloudfunctions_log_entry(i)) }
|
987
|
+
d.run
|
988
|
+
end
|
989
|
+
i = 0
|
990
|
+
verify_log_entries(n, CONTAINER_FROM_TAG_PARAMS, '') do |entry|
|
991
|
+
assert_equal '[D][2015-09-25T12:34:56.789Z][123-0] test log entry ' \
|
992
|
+
"#{i}", entry['textPayload'],
|
993
|
+
"Test with #{n} logs failed. \n#{entry}"
|
994
|
+
i += 1
|
995
|
+
end
|
996
|
+
end
|
997
|
+
end
|
998
|
+
|
999
|
+
def test_http_request_from_record
|
1000
|
+
setup_gce_metadata_stubs
|
1001
|
+
setup_logging_stubs do
|
1002
|
+
d = create_driver
|
1003
|
+
d.emit('httpRequest' => http_request_message)
|
1004
|
+
d.run
|
1005
|
+
end
|
1006
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'httpRequest') do |entry|
|
1007
|
+
assert_equal http_request_message, entry['httpRequest'], entry
|
1008
|
+
assert_nil get_fields(entry['structPayload'])['httpRequest'], entry
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
def test_http_request_partial_from_record
|
1013
|
+
setup_gce_metadata_stubs
|
1014
|
+
setup_logging_stubs do
|
1015
|
+
d = create_driver
|
1016
|
+
d.emit('httpRequest' => http_request_message.merge(
|
1017
|
+
'otherKey' => 'value'))
|
1018
|
+
d.run
|
1019
|
+
end
|
1020
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'httpRequest') do |entry|
|
1021
|
+
assert_equal http_request_message, entry['httpRequest'], entry
|
1022
|
+
fields = get_fields(entry['structPayload'])
|
1023
|
+
request = get_fields(get_struct(fields['httpRequest']))
|
1024
|
+
assert_equal 'value', get_string(request['otherKey']), entry
|
1025
|
+
end
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
def test_http_request_without_referer_from_record
|
1029
|
+
setup_gce_metadata_stubs
|
1030
|
+
setup_logging_stubs do
|
1031
|
+
d = create_driver
|
1032
|
+
d.emit('httpRequest' => http_request_message_without_referer)
|
1033
|
+
d.run
|
1034
|
+
end
|
1035
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'httpRequest') do |entry|
|
1036
|
+
assert_equal http_request_message_without_referer, entry['httpRequest'],
|
1037
|
+
entry
|
1038
|
+
assert_nil get_fields(entry['structPayload'])['httpRequest'], entry
|
1039
|
+
end
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
def test_http_request_when_not_hash
|
1043
|
+
setup_gce_metadata_stubs
|
1044
|
+
setup_logging_stubs do
|
1045
|
+
d = create_driver
|
1046
|
+
d.emit('httpRequest' => 'a_string')
|
1047
|
+
d.run
|
1048
|
+
end
|
1049
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'structPayload') do |entry|
|
1050
|
+
fields = get_fields(entry['structPayload'])
|
1051
|
+
assert_equal 'a_string', get_string(fields['httpRequest']), entry
|
1052
|
+
assert_nil entry['httpRequest'], entry
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
private
|
1057
|
+
|
1058
|
+
def uri_for_log(params)
|
1059
|
+
'https://logging.googleapis.com/v1beta3/projects/' + params[:project_id] +
|
1060
|
+
'/logs/' + params[:log_name] + '/entries:write'
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
def stub_metadata_request(metadata_path, response_body)
|
1064
|
+
stub_request(:get, 'http://169.254.169.254/computeMetadata/v1/' +
|
1065
|
+
metadata_path)
|
1066
|
+
.to_return(body: response_body, status: 200,
|
1067
|
+
headers: { 'Content-Length' => response_body.length })
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
def setup_no_metadata_service_stubs
|
1071
|
+
# Simulate a machine with no metadata service present
|
1072
|
+
stub_request(:any, %r{http://169.254.169.254/.*})
|
1073
|
+
.to_raise(Errno::EHOSTUNREACH)
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
def setup_gce_metadata_stubs
|
1077
|
+
# Stub the root, used for platform detection by the plugin and 'googleauth'.
|
1078
|
+
stub_request(:get, 'http://169.254.169.254')
|
1079
|
+
.to_return(status: 200, headers: { 'Metadata-Flavor' => 'Google' })
|
1080
|
+
|
1081
|
+
# Create stubs for all the GCE metadata lookups the agent needs to make.
|
1082
|
+
stub_metadata_request('project/project-id', PROJECT_ID)
|
1083
|
+
stub_metadata_request('instance/zone', FULLY_QUALIFIED_ZONE)
|
1084
|
+
stub_metadata_request('instance/id', VM_ID)
|
1085
|
+
stub_metadata_request('instance/attributes/',
|
1086
|
+
"attribute1\nattribute2\nattribute3")
|
1087
|
+
|
1088
|
+
# Used by 'googleauth' to fetch the default service account credentials.
|
1089
|
+
stub_request(:get, 'http://169.254.169.254/computeMetadata/v1/' \
|
1090
|
+
'instance/service-accounts/default/token')
|
1091
|
+
.to_return(body: %({"access_token": "#{FAKE_AUTH_TOKEN}"}),
|
1092
|
+
status: 200,
|
1093
|
+
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
1094
|
+
'Content-Type' => 'application/json' })
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
def setup_ec2_metadata_stubs
|
1098
|
+
# Stub the root, used for platform detection.
|
1099
|
+
stub_request(:get, 'http://169.254.169.254')
|
1100
|
+
.to_return(status: 200, headers: { 'Server' => 'EC2ws' })
|
1101
|
+
|
1102
|
+
# Stub the identity document lookup made by the agent.
|
1103
|
+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/' \
|
1104
|
+
'instance-identity/document')
|
1105
|
+
.to_return(body: EC2_IDENTITY_DOCUMENT, status: 200,
|
1106
|
+
headers: { 'Content-Length' => EC2_IDENTITY_DOCUMENT.length })
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def setup_auth_stubs
|
1110
|
+
# Used when loading credentials from a JSON file.
|
1111
|
+
stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token')
|
1112
|
+
.with(body: hash_including(grant_type: AUTH_GRANT_TYPE))
|
1113
|
+
.to_return(body: %({"access_token": "#{FAKE_AUTH_TOKEN}"}),
|
1114
|
+
status: 200,
|
1115
|
+
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
1116
|
+
'Content-Type' => 'application/json' })
|
1117
|
+
|
1118
|
+
stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token')
|
1119
|
+
.with(body: hash_including(grant_type: 'refresh_token'))
|
1120
|
+
.to_return(body: %({"access_token": "#{FAKE_AUTH_TOKEN}"}),
|
1121
|
+
status: 200,
|
1122
|
+
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
1123
|
+
'Content-Type' => 'application/json' })
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
def setup_managed_vm_metadata_stubs
|
1127
|
+
stub_metadata_request(
|
1128
|
+
'instance/attributes/',
|
1129
|
+
"attribute1\ngae_backend_name\ngae_backend_version\nlast_attribute")
|
1130
|
+
stub_metadata_request('instance/attributes/gae_backend_name',
|
1131
|
+
MANAGED_VM_BACKEND_NAME)
|
1132
|
+
stub_metadata_request('instance/attributes/gae_backend_version',
|
1133
|
+
MANAGED_VM_BACKEND_VERSION)
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
def setup_container_metadata_stubs
|
1137
|
+
stub_metadata_request(
|
1138
|
+
'instance/attributes/',
|
1139
|
+
"attribute1\nkube-env\nlast_attribute")
|
1140
|
+
stub_metadata_request('instance/attributes/kube-env',
|
1141
|
+
"ENABLE_NODE_LOGGING: \"true\"\n"\
|
1142
|
+
'INSTANCE_PREFIX: '\
|
1143
|
+
"gke-#{CONTAINER_CLUSTER_NAME}-740fdafa\n"\
|
1144
|
+
'KUBE_BEARER_TOKEN: AoQiMuwkNP2BMT0S')
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
def setup_cloudfunctions_metadata_stubs
|
1148
|
+
stub_metadata_request(
|
1149
|
+
'instance/attributes/',
|
1150
|
+
"attribute1\nkube-env\ngcf_region\nlast_attribute")
|
1151
|
+
stub_metadata_request('instance/attributes/kube-env',
|
1152
|
+
"ENABLE_NODE_LOGGING: \"true\"\n"\
|
1153
|
+
'INSTANCE_PREFIX: '\
|
1154
|
+
"gke-#{CLOUDFUNCTIONS_CLUSTER_NAME}-740fdafa\n"\
|
1155
|
+
'KUBE_BEARER_TOKEN: AoQiMuwkNP2BMT0S')
|
1156
|
+
stub_metadata_request('instance/attributes/gcf_region',
|
1157
|
+
CLOUDFUNCTIONS_REGION)
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
def container_log_entry_with_metadata(log)
|
1161
|
+
{
|
1162
|
+
log: log,
|
1163
|
+
stream: CONTAINER_STREAM,
|
1164
|
+
time: CONTAINER_TIMESTAMP,
|
1165
|
+
kubernetes: {
|
1166
|
+
namespace_id: CONTAINER_NAMESPACE_ID,
|
1167
|
+
namespace_name: CONTAINER_NAMESPACE_NAME,
|
1168
|
+
pod_id: CONTAINER_POD_ID,
|
1169
|
+
pod_name: CONTAINER_POD_NAME,
|
1170
|
+
container_name: CONTAINER_CONTAINER_NAME,
|
1171
|
+
labels: {
|
1172
|
+
CONTAINER_LABEL_KEY => CONTAINER_LABEL_VALUE
|
1173
|
+
}
|
1174
|
+
}
|
1175
|
+
}
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
def container_log_entry(log, stream = CONTAINER_STREAM)
|
1179
|
+
{
|
1180
|
+
log: log,
|
1181
|
+
stream: stream,
|
1182
|
+
time: CONTAINER_TIMESTAMP
|
1183
|
+
}
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
def cloudfunctions_log_entry(i)
|
1187
|
+
{
|
1188
|
+
stream: 'stdout',
|
1189
|
+
log: '[D][2015-09-25T12:34:56.789Z][123-0] ' + log_entry(i)
|
1190
|
+
}
|
1191
|
+
end
|
1192
|
+
|
1193
|
+
def cloudfunctions_log_entry_text_not_matched(i)
|
1194
|
+
{
|
1195
|
+
stream: 'stdout',
|
1196
|
+
log: log_entry(i)
|
1197
|
+
}
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
def log_entry(i)
|
1201
|
+
'test log entry ' + i.to_s
|
1202
|
+
end
|
1203
|
+
|
1204
|
+
def check_labels(entry, common_labels, expected_labels)
|
1205
|
+
# TODO(salty) test/handle overlap between common_labels and entry labels
|
1206
|
+
all_labels ||= common_labels
|
1207
|
+
all_labels.merge!(entry['metadata']['labels'] || {})
|
1208
|
+
all_labels.each do |key, value|
|
1209
|
+
assert value.is_a?(String), "Value #{value} for label #{key} " \
|
1210
|
+
'is not a string: ' + value.class.name
|
1211
|
+
assert expected_labels.key?(key), "Unexpected label #{key} => #{value}"
|
1212
|
+
assert_equal expected_labels[key], value, 'Value mismatch - expected ' \
|
1213
|
+
"#{expected_labels[key]} in #{key} => #{value}"
|
1214
|
+
end
|
1215
|
+
assert_equal expected_labels.length, all_labels.length, 'Expected ' \
|
1216
|
+
"#{expected_labels.length} labels, got #{all_labels.length}"
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
# The caller can optionally provide a block which is called for each entry.
|
1220
|
+
def verify_json_log_entries(n, params, payload_type = 'textPayload')
|
1221
|
+
i = 0
|
1222
|
+
@logs_sent.each do |batch|
|
1223
|
+
batch['entries'].each do |entry|
|
1224
|
+
unless payload_type.empty?
|
1225
|
+
assert entry.key?(payload_type), 'Entry did not contain expected ' \
|
1226
|
+
"#{payload_type} key: " + entry.to_s
|
1227
|
+
# Check the payload for textPayload, otherwise it's up to the caller.
|
1228
|
+
if payload_type == 'textPayload'
|
1229
|
+
assert_equal "test log entry #{i}", entry['textPayload'], batch
|
1230
|
+
end
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
assert_equal params[:zone], entry['metadata']['zone']
|
1234
|
+
assert_equal params[:service_name], entry['metadata']['serviceName']
|
1235
|
+
check_labels entry, batch['commonLabels'], params[:labels]
|
1236
|
+
yield(entry) if block_given?
|
1237
|
+
i += 1
|
1238
|
+
assert i <= n, "Number of entries #{i} exceeds expected number #{n}"
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
assert i == n, "Number of entries #{i} does not match expected number #{n}"
|
1242
|
+
end
|
1243
|
+
|
1244
|
+
# This module expects the methods below to be overridden.
|
1245
|
+
|
1246
|
+
# Create a Fluentd output test driver with the Google Cloud Output plugin.
|
1247
|
+
def create_driver(_conf = APPLICATION_DEFAULT_CONFIG, _tag = 'test')
|
1248
|
+
_undefined
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
# Set up http or grpc stubs to mock the external calls.
|
1252
|
+
def setup_logging_stubs
|
1253
|
+
_undefined
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
# Verify the number and the content of the log entries match the expectation.
|
1257
|
+
# The caller can optionally provide a block which is called for each entry.
|
1258
|
+
def verify_log_entries(_n, _params, _payload_type = 'textPayload', &_block)
|
1259
|
+
_undefined
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
# For an optional field with default values, Protobuf omits the field when it
|
1263
|
+
# is deserialized to json. So we need to add an extra check for gRPC which
|
1264
|
+
# uses Protobuf.
|
1265
|
+
#
|
1266
|
+
# An optional block can be passed in if we need to assert something other than
|
1267
|
+
# a plain equal. e.g. assert_in_delta.
|
1268
|
+
def assert_equal_with_default(_field, _expected_value, _default_value, _entry)
|
1269
|
+
_undefined
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
# A wrapper around the constant HTTP_REQUEST_MESSAGE, so the definition can be
|
1273
|
+
# skipped in the shared module here and defined in the test class later.
|
1274
|
+
def http_request_message
|
1275
|
+
_undefined
|
1276
|
+
end
|
1277
|
+
|
1278
|
+
# A wrapper around the constant HTTP_REQUEST_MESSAGE_WITHOUT_REFERER, so the
|
1279
|
+
# definition can be skipped in the shared module and defined in the test
|
1280
|
+
# classes later.
|
1281
|
+
def http_request_message_without_referer
|
1282
|
+
_undefined
|
1283
|
+
end
|
1284
|
+
|
1285
|
+
# Get the fields of the struct payload.
|
1286
|
+
def get_fields(_struct_payload)
|
1287
|
+
_undefined
|
1288
|
+
end
|
1289
|
+
|
1290
|
+
# Get the value of a struct field.
|
1291
|
+
def get_struct(_field)
|
1292
|
+
_undefined
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
# Get the value of a string field.
|
1296
|
+
def get_string(_field)
|
1297
|
+
_undefined
|
1298
|
+
end
|
1299
|
+
|
1300
|
+
# Get the value of a number field.
|
1301
|
+
def get_number(_field)
|
1302
|
+
_undefined
|
1303
|
+
end
|
1304
|
+
|
1305
|
+
def _undefined
|
1306
|
+
fail "Method #{__callee__} is unimplemented and needs to be overridden."
|
1307
|
+
end
|
1308
|
+
end
|